代码改变世界

余弦相似度

2013-01-09 14:46  ggzwtj  阅读(140)  评论(0)    收藏  举报

首先,先来想一下怎样来定义两个文本之间的“距离”?当然,要先转换成数学的模型,比如有四段文本:

AAAA

AABBBBBB

AAAAABBBBBBBBBBB

AAAAAAAAAAABB

如果分别对‘A’、‘B’统计可以将每段文字都映射到平面空间中:

那么能不能像计算两点之间距离那样来反应他们之间的差距?

这样判断的结果是“AAAA”与“AAAAABBBBBBBBBBB”的相似度与“AABBBBBB”和“AAAAABBBBBBBBBBB”之间的相似度,显然这是不准确的,“AAAA”里面连‘B’都没有怎么能说是相似呢?

那是什么导致了不准确?

如果一个文本是400字的,把它拿过来DOUBLE一下变成了800字,那么这两个文本之间的距离也是非常大的。显然,我们在考虑文本相似度的时候需要有一个定的量,如果把文本的总的字数定下来,那么这种情况就可以缓解了。那么直观上可以看到字数固定之后,各个向量之间的夹角可以大致反映两个文本的相似程度(也就是余弦相似度):

具体的实现的代码如下:

	public static double cosineSimilar(String str1, String str2) {
		if (StringUtils.isBlank(str1) || StringUtils.isBlank(str2)) {
			return 0.0;
		}
		Map<Character, Integer> map = new HashMap<Character, Integer>();
		int[] m = new int[500];
		int[] n = new int[500];
		int sumIndex = 0;
		Integer index = null;
		for (int i = 0; i < str1.length(); i++) {
			char ch = str1.charAt(i);
			index = map.get(ch);
			if (index == null) {
				map.put(ch, sumIndex++);
				index = sumIndex;
			}
			if (index < 500)
				m[index]++;
		}
		for (int i = 0; i < str2.length(); i++) {
			char ch = str2.charAt(i);
			index = map.get(ch);
			if (index == null) {
				map.put(ch, sumIndex++);
				index = sumIndex;
			}
			if (index < 500)
				n[index]++;
		}
		double mn = 0.0, mm = 0.0, nn = 0.0;
		for (int i = 0; i < sumIndex; i++) {
			mn += m[i] * n[i];
			mm += m[i] * m[i];
			nn += n[i] * n[i];
		}
		mm /= str1.length() * str1.length();
		nn /= str2.length() * str2.length();
		mn /= str1.length() * str2.length();
		return Math.sqrt(mn / ((mm + nn) * 0.5));
	}