余弦类似度,又称为余弦类似性,是经过计算两个向量的夹角余弦值来评估他们的类似度。余弦类似度将向量根据坐标值,绘制到向量空间中。用向量空间中两个向量夹角的余弦值做为衡量两个个体间差别的大小。余弦值越接近1,就代表夹角越接近0度,也就是两个向量越类似,反之越接近0就表示两个向量类似度越低,这就叫"余弦类似性"。java
先简单的重温一下高中数学知识,余弦定理git
这个公式你们不知道还有没有印象呢?没有的话咱们看下下面的图程序员
此时a=(xa,ya)
,b=(xb,0)
,那么怎么计算各边长的长度呢?github
此时将各边长代入上图的公式当中,最后能够得出最终的计算公式bash
那么在咱们的文本类似度计算中,都有哪些步骤呢?markdown
你好,我是小王,我是个程序员”
,将会分割成你好/我/是/小王/我/是/个/程序员
。第二句:你好,我是设计师
,将会分红你好/我/是/设计师
你好1,我2,是2,小王1,个1,程序员1,设计师0
,第二句你好1,我1,是1,小王0,个0,程序员0,设计师1
(1,2,2,1,1,1,0)
,第二句(1,1,1,0,0,0,1)
。这里使用ikanalyzer来实现一个简单的分词功能maven
<dependency>
<groupId>com.janeluo</groupId>
<artifactId>ikanalyzer</artifactId>
<version>2012_u6</version>
</dependency>
复制代码
IKUtils分词工具类,代码比简单,惟一一个方法返回的是语句分词的List对象ide
/** * 分词相关工具类 * @author wangzh */ public class IKUtils { /** * 以List的格式返回文本分词的结果 * @param text * @return */ public static List<String> divideText(String text){ if(null == text || "".equals(text.trim())){ return null; } List<String> resultList = new ArrayList<>(); StringReader re = new StringReader(text); IKSegmenter ik = new IKSegmenter(re, true); Lexeme lex = null; try { while ((lex = ik.next()) != null) { resultList.add(lex.getLexemeText()); } } catch (Exception e) { //TODO } return resultList; } } 复制代码
下面是主要的代码逻辑,相关步骤已注释在代码里面工具
public class Analysis { public static void main(String[] args) { Map<String,int[]> resultMap = new HashMap<>(); //测试文本 String text1 = "你好,我是小王,我是个程序员"; String text2 = "你好,我是设计师"; //统计 statistics(resultMap, IKUtils.divideText(text1),1); statistics(resultMap, IKUtils.divideText(text2),0); //计算类 final Calculation calculation = new Calculation(); resultMap.forEach((k,v)->{ int[] arr = resultMap.get(k); calculation.setNumerator(calculation.getNumerator() + arr[0] * arr[1]); calculation.setElementA(calculation.getElementA() + arr[0] * arr[0]); calculation.setElementB(calculation.getElementB() + arr[1] * arr[1]); }); System.out.println("文本类似度:" + calculation.result()); } /** * 组合词频向量 * @param words * @param direction * @return */ private static void statistics(Map<String,int[]> map,List<String> words ,int direction){ if(null == words || words.size() == 0){ return ; } int[] in = null; boolean flag = direction(direction); for (String word : words){ int[] wordD = map.get(word); if(null == wordD){ if(flag){ in = new int[]{1,0}; }else { in = new int[]{0,1}; } map.put(word,in); }else{ if(flag){ wordD[0]++; }else{ wordD[1]++; } } } } //判断不一样句子 private static boolean direction(int direction){ return direction == 1?true:false; } } 复制代码
用于计算余弦类似度的类oop
public class Calculation{ private double elementA; private double elementB; private double numerator; public double result(){ return numerator / Math.sqrt(elementA * elementB); } //省略get/set } 复制代码
输出结果:
文本类似度:0.7216878364870323
复制代码
从结果能够看出这两句话大体上仍是比较类似的。用通俗一点的话来讲就是有72%的类似度。
参考图例:
公众号博文同步Github仓库,有兴趣的朋友能够帮忙给个Star哦,码字不易,感谢支持。
《如何优化代码中大量的if/else,switch/case?》
《如何提升使用Java反射的效率?》
《Java日志正确使用姿式》
关注「深夜里的程序猿」,分享最干的干货