做文本挖掘时,我们可能需要分析不同文本之间的关联,最简单的就是分析两文本是否相似,是否在陈述相同的事情。文本属于非数值数据,通常可根据分词,把文本转化成数值向量,进一步根据相似度度量进行分析。
相似度度量(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
List
int size = vector1.size();
int size2 = vector2.size();
Map
// 计算并集
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
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
List
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);
}
}