• 文本相似度计算——HanLP分词+余弦相似度算法


    一、余弦相似度简介

    余弦相似度(又称为余弦相似性):是通过测量两个向量的夹角的余弦值来度量它们之间的相似性。余弦值接近1,夹角趋于0,表明两个向量越相似;余弦值接近于0,夹角趋于90度,表明两个向量越不相似。

                    

    那么如何来计算余弦相似性呢?

    余弦定理是三角形中三边长度与一个角的余弦值(cos)的数学式。

    余弦定理的表达式如下:

    勾股定理则是余弦定理的特殊情况,当角为直角时,即:cos\gamma = 0时,公式简化为c^{2}=a^{2}+b^{2}

    根据余弦定理表达式,余弦的计算公式如下:

    而a,b,c 是三个边的长度。假定a向量是[x1, y1],b向量是[x2, y2],那么根据向量求长度公式(向量的长度又叫向量的模,使用双竖线来包裹向量表示向量的长度)

    \vec{\left | a \right |}=\sqrt{x_{1}^{2}+y_{1}^{2}},即a^{2}=x_{1}^{2}+y_{1}^{2};同理,\vec{\left | b \right |}=\sqrt{x_{2}^{2}+y_{2}^{2}},即b^{2}=x_{2}^{2}+y_{2}^{2} ,

    c=\sqrt{(x_{1}-x_{2})^{2}+(y_{1}-y_{2})^{2}}

     将此时的a,b,c带入余弦公式,即可推导出

    cos\theta =\frac{a\cdot b}{\left \| a \right \| *\left \| b \right \|} ,其中分子为向量a与向量b的点乘,分母为二者各自的L2相乘,即将所有维度值的平方相加后开方。 

    二、文本相似度计算思路

    句子A:我想养一头奶牛,这样就可以每天喝新鲜的牛奶。

    句子B:我想每天喝新鲜的牛奶,所以打算养一头奶牛。

    1. 分词

           A分词结果:[我, 想, 养, 一头, 奶牛, 这样, 就, 可以, 每天, 喝, 新鲜, 的, 牛奶]

           B分词结果:[我, 想, 每天, 喝, 新鲜, 的, 牛奶, 所以, 打算, 养, 一头, 奶牛]

    2. 取并集(将句子A、B分词后的结果取并集)

            [我, 想, 养, 一头, 奶牛, 这样, 就, 可以, 每天, 喝, 新鲜, 的, 牛奶, 所以, 打算]

    3. 写出词频向量

    根据句子A、B的分词结果去计算词频向量,其中词频向量的长度为第二步中的并集,而每一位代表单词的出现次数。

            句子A:(1,1,1,1,1,1,1,1,1,1,1,1,1,0,0)

            句子B:(1,1,1,1,1,0,0,0,1,1,1,1,1,1,1)

    到这里,问题就变成了如何计算这两个向量的相似程度。

    4. 计算余弦相似度

    将词频向量代入余弦相似度公式:

    cos(\theta )=\frac{1\times 1+1\times 1+1\times 1+1\times 1+1\times 1+1\times 0+1\times 0+1\times 0+1\times 1+1\times 1+1\times 1+1\times 1+1\times 1+0\times 1+0\times 1}{\sqrt{1^{2}+1^{2}+1^{2}+1^{2}+1^{2}+1^{2}+1^{2}+1^{2}+1^{2}+1^{2}+1^{2}+1^{2}+1^{2}+0^{2}+0^{2}}\times \sqrt{1^{2}+1^{2}+1^{2}+1^{2}+1^{2}+0^{2}+0^{2}+0^{2}+1^{2}+1^{2}+1^{2}+1^{2}+1^{2}+1^{2}+1^{2}}}

     =\frac{10}{\sqrt{13}\times \sqrt{12}}\approx 0.80064

    计算结果中夹角的余弦值为0.80064,非常接近于1,所以,上面的句子A和句子B是基本相似的。

    三、代码实现

    使用HanLP需要先导入maven依赖:

    1. <dependency>
    2. <groupId>com.hankcsgroupId>
    3. <artifactId>hanlpartifactId>
    4. <version>portable-1.7.2version>
    5. dependency>

    Java代码如下:

    1. package com.scb.dss.udf;
    2. import com.hankcs.hanlp.HanLP;
    3. import java.util.ArrayList;
    4. import java.util.Collections;
    5. import java.util.List;
    6. import java.util.stream.Collectors;
    7. public class CosineSimilarity {
    8. /**
    9. * 使用余弦相似度算法计算文本相似性
    10. *
    11. * @param sentence1
    12. * @param sentence2
    13. * @return
    14. */
    15. public static double getSimilarity(String sentence1, String sentence2) {
    16. System.out.println("Step1. 分词");
    17. List sent1Words = getSplitWords(sentence1);
    18. System.out.println(sentence1 + "\n分词结果:" + sent1Words);
    19. List sent2Words = getSplitWords(sentence2);
    20. System.out.println(sentence2 + "\n分词结果:" + sent2Words);
    21. System.out.println("Step2. 取并集");
    22. List allWords = mergeList(sent1Words, sent2Words);
    23. System.out.println(allWords);
    24. int[] statistic1 = statistic(allWords, sent1Words);
    25. int[] statistic2 = statistic(allWords, sent2Words);
    26. // 向量A与向量B的点乘
    27. double dividend = 0;
    28. // 向量A所有维度值的平方相加
    29. double divisor1 = 0;
    30. // 向量B所有维度值的平方相加
    31. double divisor2 = 0;
    32. // 余弦相似度 算法
    33. for (int i = 0; i < statistic1.length; i++) {
    34. dividend += statistic1[i] * statistic2[i];
    35. divisor1 += Math.pow(statistic1[i], 2);
    36. divisor2 += Math.pow(statistic2[i], 2);
    37. }
    38. System.out.println("Step3. 计算词频向量");
    39. for(int i : statistic1) {
    40. System.out.print(i+",");
    41. }
    42. System.out.println();
    43. for(int i : statistic2) {
    44. System.out.print(i+",");
    45. }
    46. System.out.println();
    47. // 向量A与向量B的点乘 / (向量A所有维度值的平方相加后开方 * 向量B所有维度值的平方相加后开方)
    48. return dividend / (Math.sqrt(divisor1) * Math.sqrt(divisor2));
    49. }
    50. // 3. 计算词频
    51. private static int[] statistic(List allWords, List sentWords) {
    52. int[] result = new int[allWords.size()];
    53. for (int i = 0; i < allWords.size(); i++) {
    54. // 返回指定集合中指定对象出现的次数
    55. result[i] = Collections.frequency(sentWords, allWords.get(i));
    56. }
    57. return result;
    58. }
    59. // 2. 取并集
    60. private static List mergeList(List list1, List list2) {
    61. List result = new ArrayList<>();
    62. result.addAll(list1);
    63. result.addAll(list2);
    64. return result.stream().distinct().collect(Collectors.toList());
    65. }
    66. // 1. 分词
    67. private static List getSplitWords(String sentence) {
    68. // 标点符号会被单独分为一个Term,去除之
    69. return HanLP.segment(sentence).stream().map(a -> a.word).filter(s -> !"`~!@#$^&*()=|{}':;',\\[\\].<>/?~!@#¥……&*()——|{}【】‘;:”“'。,、? ".contains(s)).collect(Collectors.toList());
    70. }
    71. }

    测试代码:

    1. package com.scb.dss.udf;
    2. import org.junit.Test;
    3. public class CosineSimilarityTest {
    4. @Test
    5. public void testGetSimilarity() throws Exception {
    6. String text3 = "我想养一头奶牛,这样就可以每天喝新鲜的牛奶。";
    7. String text4 = "我想每天喝新鲜的牛奶,所以打算养一头奶牛。";
    8. System.out.println("文本相似度为:"+CosineSimilarity.getSimilarity(text3, text4));
    9. }
    10. }

     

    四、参考

    相似度计算方法(三) 余弦相似度_潘永青的博客-CSDN博客_余弦相似度

    余弦相似度Cosine Similarity相关计算公式 - 蝈蝈俊 - 博客园

  • 相关阅读:
    搭建自己的OCR服务,第一步:选择合适的开源OCR项目
    Tomcat 启动闪退问题解决集(八大类详细)
    快速非支配排序 python版
    Flink SQL 自定义 Connector
    Altium Designer实用系列(四)----Ultra Librarian 下载芯片原理图库及封装并导入AD
    机器学习复习(待更新)
    数据结构与算法
    【力扣-每日一题】714. 买卖股票的最佳时机含手续费
    【七】SpringBoot为什么可以打成 jar包启动
    JavaEE初阶之IO流快速顿悟一(超详细)
  • 原文地址:https://blog.csdn.net/qq_37771475/article/details/126894519