余弦相似度JAVA实现


余弦相似度,又称为余弦相似性,是通过计算两个向量的夹角余弦值来评估他们的相似度。余弦相似度将向量根据坐标值,绘制到向量空间中,如最常见的二维空间。

概念
将向量根据坐标值,绘制到向量空间中。如最常见的二维空间。

求得他们的夹角,并得出夹角对应的余弦值,此余弦值就可以用来表征,这两个向量的相似性。夹角越小,余弦值越接近于1,它们的方向更加吻合,则越相似。
计算
对于二维空间,根据向量点积公式[1]  ,显然可以得知:

假设向量a、b的坐标分别为(x1,y1)、(x2,y2) 。则:

设向量 A = (A1,A2,...,An),B = (B1,B2,...,Bn) 。推广到多维:

余弦值的范围在[-1,1]之间,值越趋近于1,代表两个向量的方向越接近;越趋近于-1,他们的方向越相反;接近于0,表示两个向量近乎于正交。
最常见的应用就是计算文本相似度。将两个文本根据他们词,建立两个向量,计算这两个向量的余弦值,就可以知道两个文本在统计学方法中他们的相似度情况。实践证明,这是一个非常有效的方法。
代码实现
实现想法,先使用分词器把目标文本进行分词处理,然后计算每个词出现次数的向量值,再根据公式得出结果
1、引入pom,分词器选用ik分词


<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-analyzers</artifactId>
    <version>3.6.2</version>
</dependency>
<dependency>
    <groupId>com.koolearn.b2b</groupId>
    <artifactId>ik-analyzer</artifactId>
    <version>1.0</version>
</dependency>

2、代码实现

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.wltea.analyzer.lucene.IKAnalyzer;
 
import java.io.IOException;
import java.io.StringReader;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
 
/**
 * 余弦相似度算法
 */
public class CosineUtil {
 
    /**
     * 获取两组字符串的词频向量
     * @param str1List
     * @param str2List
     * @return
     */
    public static int [][] getStringFrequency(List<String> str1List,List<String> str2List){
        Set<String> cnSet = new HashSet<String>();
        cnSet.addAll(str1List);
        cnSet.addAll(str2List);
        int [][] res = new int[2][cnSet.size()];
        Iterator it = cnSet.iterator();
        int i=0;
        while(it.hasNext()){
            String word = it.next().toString();
            int s1 = 0;
            int s2 = 0;
            System.out.println(word);
            for(String str : str1List){
                if(word.equals(str)){
                    s1++;
                }
            }
            System.out.println("s1="+s1);
            res[0][i] = s1;
            for(String str : str2List){
                if(word.equals(str)){
                    s2++;
                }
            }
            res[1][i] = s2;
            System.out.println("s2="+s2);
            i++;
        }
        return res;
    }
 
    /**
     * 中文分词
     * @return
     */
    public static List<String> stringParticiple(String text){
        List<String> list = new ArrayList<String>();
        Analyzer analyzer = new IKAnalyzer(true);
        try{
            TokenStream tokenStream = analyzer.tokenStream(text, new StringReader(text));
 
            CharTermAttribute cta = tokenStream.addAttribute(CharTermAttribute.class);
            tokenStream.reset();
            while(tokenStream.incrementToken()){
                list.add(cta.toString());
            }
            tokenStream.close();
        }catch (IOException e){
            e.printStackTrace();
        }
        return list;
    }
 
    public static void main(String[] args) {
        String text1 = "专四语法词汇单项精讲";
        String text2 = "专四语法词汇单项精讲";
        List<String> str1List = stringParticiple(text1);
        List<String> str2List = stringParticiple(text2);
        int [][] res = getStringFrequency(str1List,str2List);
        System.out.println(getDoubleStrForCosValue(res));
    }
 
    /**
     * 获取两组向量的余弦值
     * @param ints
     * @return
     */
    public static float getDoubleStrForCosValue(int [][] ints){
        BigDecimal fzSum = new BigDecimal(0);
        BigDecimal fmSum = new BigDecimal(0);
        int num = ints[0].length;
        for(int i=0;i<num;i++){
            BigDecimal adb = new BigDecimal(ints[0][i]).multiply(new BigDecimal(ints[1][i]));
            fzSum = fzSum.add(adb);
        }
 
        BigDecimal seq1SumBigDecimal = new BigDecimal(0);
        BigDecimal seq2SumBigDecimal = new BigDecimal(0);
        for(int i=0;i<num;i++){
            seq1SumBigDecimal = seq1SumBigDecimal.add(new BigDecimal(Math.pow(ints[0][i],2)));
            seq2SumBigDecimal = seq2SumBigDecimal.add(new BigDecimal(Math.pow(ints[1][i],2)));
        }
        double sqrt1 = Math.sqrt(seq1SumBigDecimal.doubleValue());
        double sqrt2 = Math.sqrt(seq2SumBigDecimal.doubleValue());
        fmSum = new BigDecimal(sqrt1).multiply(new BigDecimal(sqrt2));
 
        return fzSum.divide(fmSum,10,RoundingMode.HALF_UP).floatValue();
    }
}

3、测试结果

测试一
文本A:专四语法词汇单项精讲 
文本B:专四改错单项精讲

结果:0.6172134

测试二
文本A:专四语法词汇单项精讲 
文本B:专四语法单项精讲

结果:0.77151674

测试三
文本A:专四语法词汇单项精讲 
文本B:专四语法词汇精讲

结果:0.9258201

测试四
文本A:专四语法词汇单项精讲
文本B:专四语法词汇单项精讲

结果:1.0

2 个解决方案

#1


老宋大神

#2


智能推荐

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
© 2014-2019 ITdaan.com 粤ICP备14056181号  

赞助商广告