最近在工作中要处理好多文本文档,要求找出和每个文档的相识的文档。通过查找资料总结如下几个计算方法:

  1、余弦相似性

    我举一个例子来说明,什么是"余弦相似性"。

    为了简单起见,我们先从句子着手。


    文本相似性计算总结(余弦定理,simhash)及代码

    请问怎样才能计算上面两句话的相似程度?

    基本思路是:如果这两句话的用词越相似,它们的内容就应该越相似。因此,可以从词频入手,计算它们的相似程度。

    第一步,分词。

    文本相似性计算总结(余弦定理,simhash)及代码

    第二步,列出所有的词。

    文本相似性计算总结(余弦定理,simhash)及代码

    第三步,计算词频。

    文本相似性计算总结(余弦定理,simhash)及代码

    第四步,写出词频向量。

    文本相似性计算总结(余弦定理,simhash)及代码

    

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

    我们可以把它们想象成空间中的两条线段,都是从原点([0, 0, ...])出发,指向不同的方向。两条线段之间形成一个夹角,如果夹角为0度,意味着方向相同、线段重合;如果夹角为90度,

    意味着形成直角,方向完全不相似;如果夹角为180度,意味着方向正好相反。因此,我们可以通过夹角的大小,来判断向量的相似程度。夹角越小,就代表越相似。

    文本相似性计算总结(余弦定理,simhash)及代码

    以二维空间为例,上图的a和b是两个向量,我们要计算它们的夹角θ。余弦定理告诉我们,可以用下面的公式求得:

    文本相似性计算总结(余弦定理,simhash)及代码

    文本相似性计算总结(余弦定理,simhash)及代码

    假定a向量是[x1, y1],b向量是[x2, y2],那么可以将余弦定理改写成下面的形式:

    文本相似性计算总结(余弦定理,simhash)及代码

  文本相似性计算总结(余弦定理,simhash)及代码

    数学家已经证明,余弦的这种计算方法对n维向量也成立。假定A和B是两个n维向量,A是 [A1, A2, ..., An] ,B是 [B1, B2, ..., Bn] ,则A与B的夹角θ的余弦等于:

    文本相似性计算总结(余弦定理,simhash)及代码

    使用这个公式,我们就可以得到,句子A与句子B的夹角的余弦。

    文本相似性计算总结(余弦定理,simhash)及代码

 

     

    余弦值越接近1,就表明夹角越接近0度,也就是两个向量越相似,这就叫"余弦相似性"。所以,上面的句子A和句子B是很相似的,事实上它们的夹角大约为20.3度。

    由此,我们就得到了"找出相似文章"的一种算法:

    1、使用TF-IDF算法,找出两篇文章的关键词;

    2、每篇文章各取出若干个关键词(比如20个),合并成一个集合,计算每篇文章对于这个集合中的词的词频(为了避免文章长度的差异,可以使用相对词频);

    3、生成两篇文章各自的词频向量;

    4、计算两个向量的余弦相似度,值越大就表示越相似。

 

     "余弦相似度"是一种非常有用的算法,只要是计算两个向量的相似程度,都可以采用它。

    

    应用场景及优缺点

          本文目前将该算法应用于网页标题合并和标题聚类中,目前仍在尝试应用于其它场景中。

          优点:计算结果准确,适合对短文本进行处理。

          缺点:需要逐个进行向量化,并进行余弦计算,比较消耗CPU处理时间,因此不适合长文本,如网页正文、文档等。

     

   java实现代码

package main.java;
import com.hankcs.hanlp.HanLP;
import com.hankcs.hanlp.corpus.tag.Nature;
import com.hankcs.hanlp.seg.common.Term;

import java.util.*;
/**
 * @Author:sks
 * @Description:
 * @Date:Created in 16:04 2018/6/1
 * @Modified by:
 **/
public class CosineSimilarity {

    public static void main(String[] args) {

        ComputeTxtSimilar();

    }

    private static void ComputeTxtSimilar(){
        String txtLeft = CommonClass.getDocFileText("D:/work/Solr/ImportData-1/160926 进击的直播:细数各路媒体的入场“姿势”(完整版).doc");
        String txtRight = CommonClass.getDocFileText("D:/work/Solr/ImportData-1/160926 刘庆振 直播经济:全民网红时代的内容创业与流量变现.doc");
        //所有文档的总词库
        List<String> totalWordList = new ArrayList<String>();
        //计算文档的词频
        Map<String, Integer> leftWordCountMap = getWordCountMap(txtLeft, totalWordList);
        Map<String, Float> leftWordTfMap = calculateWordTf(leftWordCountMap);

        Map<String, Integer> rightWordCountMap = getWordCountMap(txtRight, totalWordList);
        Map<String, Float> rightWordTfMap = calculateWordTf(rightWordCountMap);


        //获取文档的特征值
        List<Float> leftFeature = getTxtFeature(totalWordList,leftWordTfMap);
        List<Float> rightFeature = getTxtFeature(totalWordList,rightWordTfMap);

        //计算文档对应特征值的平方和的平方根
        float leftVectorSqrt = calculateVectorSqrt(leftWordTfMap);
        float rightVectorSqrt = calculateVectorSqrt(rightWordTfMap);

        //根据余弦定理公司,计算余弦公式中的分子
        float fenzi = getCosValue(leftFeature,rightFeature);

        //根据余弦定理计算两个文档的余弦值
        double cosValue = 0;
        if (fenzi > 0) {
            cosValue = fenzi / (leftVectorSqrt * rightVectorSqrt);
        }
        cosValue = Double.parseDouble(String.format("%.4f",cosValue));
        System.out.println(cosValue);

    }

    /**
     * @Author:sks
     * @Description:获取词及词频键值对,并将词保存到词库中
     * @Date:
     */
    public static  Map<String,Integer> getWordCountMap(String text,List<String> totalWordList){
        Map<String,Integer> wordCountMap = new HashMap<String,Integer>();
        List<Term> words= HanLP.segment(text);
        int count = 0;
        for(Term tm:words){
            //取字数为两个字或两个字以上名词或动名词作为关键词
            if(tm.word.length()>1 && (tm.nature== Nature.n||tm.nature== Nature.vn)){
                count = 1;
                if(wordCountMap.containsKey(tm.word))
                {
                    count = wordCountMap.get(tm.word) + 1;
                    wordCountMap.remove(tm.word);
                }
                wordCountMap.put(tm.word,count);
                if(!totalWordList.contains(tm.word)){
                    totalWordList.add(tm.word);
                }
            }
        }
        return wordCountMap;
    }



    //计算关键词词频
    private static Map<String, Float> calculateWordTf(Map<String, Integer> wordCountMap) {
        Map<String, Float> wordTfMap =new HashMap<String, Float>();
        int totalWordsCount = 0;
        Collection<Integer> cv = wordCountMap.values();
        for (Integer count : cv) {
            totalWordsCount += count;
        }

        wordTfMap = new HashMap<String, Float>();
        Set<String> keys = wordCountMap.keySet();
        for (String key : keys) {
            wordTfMap.put(key, wordCountMap.get(key) / (float) totalWordsCount);
        }
        return wordTfMap;
    }

    //计算文档对应特征值的平方和的平方根
    private static float calculateVectorSqrt(Map<String, Float> wordTfMap) {
        float result = 0;
        Collection<Float> cols =  wordTfMap.values();
        for(Float temp : cols){
            if (temp > 0) {
                result += temp * temp;
            }
        }
        return (float) Math.sqrt(result);
    }



    private static List<Float> getTxtFeature(List<String> totalWordList,Map<String, Float> wordCountMap){
        List<Float> list =new ArrayList<Float>();
        for(String word :totalWordList){
            float tf = 0;
            if(wordCountMap.containsKey(word)){
                tf = wordCountMap.get(word);
            }
            list.add(tf);
        }
        return list;
    }

    /**
     * @Author:sks
     * @Description:根据两个向量计算余弦值
     * @Date:
     */
    private static float getCosValue(List<Float> leftFeature, List<Float> rightFeature) {
        float fenzi = 0;
        float tempX = 0;
        float tempY = 0;
        for (int i = 0; i < leftFeature.size(); i++) {
            tempX = leftFeature.get(i);
            tempY = rightFeature.get(i);
            if (tempX > 0 && tempY > 0) {
                fenzi += tempX * tempY;
            }
        }
        return fenzi;
    }




}
View Code

相关文章:

  • 2021-05-09
  • 2022-12-23
  • 2022-01-14
  • 2022-01-20
  • 2021-08-21
  • 2021-12-24
  • 2021-05-06
猜你喜欢
  • 2021-06-21
  • 2022-02-05
  • 2021-05-12
  • 2021-08-04
相关资源
相似解决方案