餘弦相似性
餘弦的概念對我們來說並不陌生,中學數學就開始接觸餘弦的概念了,在三角形中,餘弦的公式是:
在向量表示的三角形中,假設向量
餘弦有這樣的性質:餘弦值越接近1,就表明夾角越接近0度,也就是兩個向量越相似,夾角等於0,即兩個向量相等,這就叫”餘弦相似性”。
那麼該怎麼利用餘弦來計算文本的相似性了?首先來看一個例子。
文本1:我們喜歡足球
文本2:我喜歡踢足球
大致思路是:我們認爲問用詞越相似,則文本的相似度越高,所以計算文本相似性主要按照以下步驟進行:
分詞
此處利用 ansj 分詞工具進行分詞,文本1和文本2的分詞結果爲:
文本1:我/喜歡/足球
文本2:我們/喜歡/踢/足球列出所有的詞
我, 喜歡, 踢, 我們, 足球計算詞頻
文本1: 我:1,喜歡:1,踢:0,我們:0,足球:1
文本2: 我:0,喜歡:1,踢:1,我們:1,足球:1轉化爲向量
文本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步驟進行計算。