利用余弦定理计算文本相似度

   做文本挖掘时,我们可能需要分析不同文本之间的关联,最简单的就是分析两文本是否相似,是否在陈述相同的事情。文本属于非数值数据,通常可根据分词,把文本转化成数值向量,进一步根据相似度度量进行分析。

   相似度度量(Similarity),即计算个体间的相似程度,相似度度量的值越小,说明个体间相似度越小,相似度的值越大说明个体差异越大。

   余弦相似度(Cosine Similarity),假定a和b是两个n维向量,a是(x1, x2, ..., xn),b是 (y1, y2, ..., yn),则a与b的夹角的余弦等于:

                ​

余弦值越接近1,就表明夹角越接近0度,也就是两个向量越相似,这就叫"余弦相似性"。所以计算出来的余弦值越大,说明两文本越相似。

   下面给出实例进行介绍。

需要判断的两文本:String str1 = "乌干达外交部就此事件可能对中国大使馆造成的负面影响感到抱歉。",

String str2 = "此事件对中国大使馆造成了一定的负面影响,乌干达外交部感到抱歉并公开道歉。"

1、IK分词后的结果:

str1: 乌干达/外交部/就此/此事/事件/可能/能对/对中/中国大使馆/造成/负面影响/感到/抱歉

str2:此事/事件/对中/中国大使馆/造成了/一定的/负面影响/乌干达/外交部/感到/抱歉/公开/开道/道歉

2、列出两个句子所有不重复的词:18个,(这里直接截图了)

3、得到str1,str的向量。所有词18个,每个句子分出的词在这18个词中,则为1,不在则为0,如“乌干达”,atr1中有,为1,str2中有,为1。而“一定的”str1中没有,为0,str2中有,为1。所以分词后的句子转化为如下对应的数值向量X,Y。

X = (1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,0,0)

Y = (0,1,1,0,1,1,1,0,1,1,0,1,1,1,1,1,1,1)

4、根据公式,计算两数值向量的余弦值为0.6671243。此时,我们可以根据一定的阈值判断两文本是否相似,如阈值为0.5,则0.6671243 大于0.5,两文本可视为相似。

算法用Java实现,如下:

import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import org.wltea.analyzer.IKSegmentation;
import org.wltea.analyzer.Lexeme;

 

public class CosineSimilarWord {
    /**
     * 阈值
     */
    public static double THRESHOLD = 0;

    /**
     * 计算两个字符串的夹角余弦值
     * 
     * @param str1
     *            字符串1
     * @param str2
     *            字符串2
     * @return 夹角余弦值
     */
    public static double getSimilarity(String str1, String str2) throws Exception {
        List vector1 = participle(str1);
        List vector2 = participle(str2);
        int size = vector1.size();
        int size2 = vector2.size();
        Map map = new HashMap();
        // 计算并集
        String index = null;
        for (int i = 0; i < size; i++) {
            index = vector1.get(i);
            if (index != null) {
                double[] c = map.get(index);
                c = new double[2];
                c[0] = 1; // vector1的语义分数
                c[1] = THRESHOLD;// vector2的语义分数
                map.put(index, c);
            }
        }

        for (int i = 0; i < size2; i++) {
            index = vector2.get(i);
            if (index != null) {
                double[] c = map.get(index);
                if (c != null && c.length == 2) {
                    c[1] = 1; // vector2中也存在,vector2的语义分数=1
                } else {
                    c = new double[2];
                    c[0] = THRESHOLD; // vector1的语义分数
                    c[1] = 1; // vector2的语义分数
                    map.put(index, c);
                }
            }
        }

        Iterator it = map.keySet().iterator();
        double s1 = 0, s2 = 0, sum = 0;
        while (it.hasNext()) {
            double[] c = map.get(it.next());
            sum += c[0] * c[1];
            s1 += c[0] * c[0];
            s2 += c[1] * c[1];
        }
        return sum / Math.sqrt(s1 * s2);
    }

    /**
     * 分词
     * 
     * @param str
     *            字符串
     * @return 分次后的字符集合
     * @throws IOException
     */
    public static List participle(String str) throws IOException {
        List vector = new Vector();// 对输入进行分词
        StringReader reader = new StringReader(str);
        IKSegmentation ik = new IKSegmentation(reader, true);// 当为true时,分词器进行最大词长切分
        Lexeme lexeme = null;
        while ((lexeme = ik.next()) != null) {
            vector.add(lexeme.getLexemeText());
        }
        return vector;
    }

    public static void main(String[] args) {
        double same = 0;
        try {
            same = CosineSimilarWord.getSimilarity("乌干达外交部就此事件可能对中国大使馆造成的负面影响感到抱歉。", "此事件对中国大使馆造成了一定的负面影响,乌干达外交部感到抱歉并公开道歉。");
        } catch (Exception e) {
            System.err.println(e.getMessage());
        }

        System.out.println("相似度:" + same);
    }
}
 

你可能感兴趣的:(机器学习,文本挖掘,算法)