利用餘弦計算文本相似性

餘弦相似性

餘弦的概念對我們來說並不陌生,中學數學就開始接觸餘弦的概念了,在三角形中,餘弦的公式是:
這裏寫圖片描述

cosα=b2+c2a22bc(11)

在向量表示的三角形中,假設向量 a⃗ =(x1,y1) , b⃗ =(x2,y2) 則向量a⃗ ,b⃗  的夾角的餘弦爲:

cosa⃗ ,b⃗ =a⃗ b⃗ |a||b|(12)

餘弦有這樣的性質:餘弦值越接近1,就表明夾角越接近0度,也就是兩個向量越相似,夾角等於0,即兩個向量相等,這就叫”餘弦相似性”。
那麼該怎麼利用餘弦來計算文本的相似性了?首先來看一個例子。

文本1:我們喜歡足球
文本2:我喜歡踢足球

大致思路是:我們認爲問用詞越相似,則文本的相似度越高,所以計算文本相似性主要按照以下步驟進行:

  1. 分詞
    此處利用 ansj 分詞工具進行分詞,文本1和文本2的分詞結果爲:
    文本1:我/喜歡/足球
    文本2:我們/喜歡/踢/足球

  2. 列出所有的詞
    我, 喜歡, 踢, 我們, 足球

  3. 計算詞頻
    文本1: 我:1,喜歡:1,踢:0,我們:0,足球:1
    文本2: 我:0,喜歡:1,踢:1,我們:1,足球:1

  4. 轉化爲向量
    文本1:[1, 1, 0, 0, 1]
    文本2:[0, 1, 1, 1, 1]

代碼示例

package com.myapp.ml.nlp;

import lombok.Data;

/**
 * Created by lionel on 16/12/21.
 */
@Data
public class WordValue {
    private String word;
    private Integer frequecy;

    public WordValue() {
    }

    public WordValue(String word, Integer frequecy) {
        this.word = word;
        this.frequecy = frequecy;
    }

}
package com.myapp.ml.nlp;

import org.ansj.domain.Term;
import org.ansj.splitWord.analysis.ToAnalysis;
import org.ansj.util.FilterModifWord;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Created by lionel on 16/12/21.
 */
public class CosineSimilarity {
    /**
     * 向量餘弦
     *
     * @param text1 文本1
     * @param text2 文本2
     * @return 向量餘弦
     */
    public double cos(String text1, String text2) {
        if (StringUtils.isBlank(text2 + text1)) {
            return 0.0;
        }
        List<String> words = parse(text1);
        List<String> words1 = parse(text2);
        Set<String> set = mergeList(words, words1);
        List<WordValue> wordValueList = computeWordCount(words);
        List<WordValue> wordValueList1 = computeWordCount(words1);
        int[] vector = wordToVector(set, wordValueList);
        int[] vector1 = wordToVector(set, wordValueList1);
        int moduleA = 0;
        int moduleB = 0;
        int AMutiplyB = 0;
        for (int i = 0; i < vector.length; i++) {
            moduleA += vector[i] * vector[i];
            moduleB += vector1[i] * vector1[i];
            AMutiplyB += vector[i] * vector1[i];
        }
        return (double) AMutiplyB / (Math.sqrt(moduleA) * Math.sqrt(moduleB));

    }

    /**
     * 轉化爲向量
     *
     * @param set  所有的詞表
     * @param list 詞信息
     * @return 向量
     */

    private int[] wordToVector(Set<String> set, List<WordValue> list) {
        if (set == null || set.size() == 0 || list == null || list.size() == 0) {
            return null;
        }
        List<String> mergeList = new ArrayList<String>(set);
        int[] vector = new int[mergeList.size()];

        for (int i = 0; i < mergeList.size(); i++) {
            for (WordValue wordValue : list) {
                if (wordValue.getWord().equals(mergeList.get(i))) {
                    vector[i] = wordValue.getFrequecy();
                    break;
                } else {
                    vector[i] = 0;
                }
            }
        }
        return vector;
    }

    /**
     * 分詞
     *
     * @param text 文本
     * @return 分詞結果
     */

    private List<String> parse(String text) {
        if (StringUtils.isBlank(text)) {
            return null;
        }

        List<Term> terms = FilterModifWord.modifResult(ToAnalysis.parse(text));
        if (terms == null || terms.size() == 0) {
            return null;
        }
        List<String> words = new ArrayList<String>();
        for (Term term : terms) {
            if (StringUtils.isNotBlank(term.getName())) {
                words.add(term.getName());
            }
        }
        return words;
    }

    /**
     * 獲取所有的詞
     *
     * @param str1 文本1分詞結果
     * @param str2 文本2分詞結果
     * @return 返回所有的詞,不重複
     */

    private Set<String> mergeList(List<String> str1, List<String> str2) {
        if (str1 == null || str1.size() == 0
                || str2 == null || str2.size() == 0) {
            return null;
        }
        Set<String> set = new HashSet<String>();
        for (String ele : str1) {
            set.add(ele);
        }
        for (String ele : str2) {
            set.add(ele);
        }
        return set;
    }

    /**
     * 計算詞頻
     *
     * @param list 分詞結果
     * @return 詞頻
     */

    private List<WordValue> computeWordCount(List<String> list) {
        List<WordValue> wordValueList = new ArrayList<WordValue>();
        for (String element : list) {
            if (isInWordValueList(element, wordValueList)) {
                WordValue wordValue = wordValueList.get(getElementIndex(element, wordValueList));
                wordValue.setFrequecy(wordValue.getFrequecy() + 1);
            } else {
                WordValue wordValue = new WordValue(element, 1);
                wordValueList.add(wordValue);
            }
        }
        return wordValueList;
    }

    private int getElementIndex(String word, List<WordValue> wordValueList) {
        if (StringUtils.isBlank(word) || wordValueList == null || wordValueList.size() == 0) {
            return -1;
        }
        for (int i = 0; i < wordValueList.size(); i++) {
            if (wordValueList.get(i).getWord().equals(word)) {
                return i;
            }
        }
        return -1;
    }

    private boolean isInWordValueList(String str, List<WordValue> wordValueList) {
        if (StringUtils.isBlank(str) || wordValueList == null || wordValueList.size() == 0) {
            return false;
        }
        for (WordValue wordValue : wordValueList) {
            if (wordValue.getWord().equals(str)) {
                return true;
            }
        }
        return false;
    }
}

測試代碼

package com.myapp.ml.nlp;

import org.junit.Test;

/**
 * Created by lionel on 16/12/21.
 */
public class CosineSimilarityTest {
    @Test
    public void test() {
        String text = "我喜歡足球";
        String text1 = "我們喜歡踢足球";
        CosineSimilarity cosineSimilarity = new CosineSimilarity();
        System.out.println(cosineSimilarity.cos(text, text1));//0.5773502691896258
    }
}

以上程序結果是0.5773502691896258,經驗證結果是正確的。
注意:以上程序只符合簡單的應用情況,不適用複雜的情況。另外,餘弦相似性還可以用來判別文檔的相似性,大致思路是:首先求出文檔的關鍵詞(可以利用 TF-IDF 方法),以後可以按照以上2,3,4步驟進行計算。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章