余弦相似度
代码原文地址 https://blog.csdn.net/liu136313/article/details/47190231 为Java 现改为C# 方便以后使用
余弦相似度的灵感来自于数学中的余弦定理,这里对数学内容不做过多解释,直接上公式:
其中,A、B分别是文本一、文本二对应的n维向量,取值方式用语言比较难描述,直接看例子吧:
例2.5.1、文本一是“一个雨伞”,文本二是“下雨了开雨伞”,计算它们的余弦相似度。
它们的并集是{一,个,雨,伞,下,了,开},共7个字。
若并集中的第1个字符在文本一中出现了n次,则A1=n(n=0,1,2……)。
若并集中的第2个字符在文本一中出现了n次,则A2=n(n=0,1,2……)。
依此类推,算出A3、A4、……、A7,B1、B2、……、B7,最终得到:
A=(1,1,1,1,0,0,0)。
B=(0,0,2,1,1,1,1)。
将A、B代入计算公式,得到
适用场景
余弦相似度和杰卡德相似度虽然计算方式差异较大,但性质上很类似(与文本的交集高度相关),所以适用场景也非常类似。
余弦相似度相比杰卡德相似度最大的不同在于它考虑到了文本的频次,比如上面例子出现了2次“雨”,和只出现1次“雨”,相似度是不同的;再比如“这是是是是是是一个文本”和“这是一个文文文文文文本”,余弦相似度是39%,整体上符合“相同的内容少于一半,但超过1/3”的观感(仅从文本来看,不考虑语义)。
不适用场景
向量之间方向相同,但大小不同的情况(这种情况下余弦相似度是100%)。
比如“太棒了”和“太棒了太棒了太棒了”,
向量分别是(1,1,1)和(3,3,3),计算出的相似度是100%。这时候要根据业务场景进行取舍,有些场景下我们认为它们意思差不多,只是语气程度不一样,这时候余弦相似度是很给力的;有些场景下我们认为它们差异很大,哪怕意思差不多,但从文本的角度来看相似度并不高(最直白的,一个3个字,一个9个字),这时候余弦相似度就爱莫能助了。
//余弦相似度
public class CosineSimilarity
{
Dictionary<char, int[]> vectorMap = new Dictionary<char, int[]>();
int[] tempArray = null;
public CosineSimilarity(string string1, string string2)
{
foreach (char character1 in string1.ToCharArray())
{
if (vectorMap.ContainsKey(character1))
{
vectorMap[character1][0]++;
}
else
{
tempArray = new int[2];
tempArray[0] = 1;
tempArray[1] = 0;
vectorMap.Add(character1, tempArray);
}
}
foreach (char character2 in string2.ToCharArray())
{
if (vectorMap.ContainsKey(character2))
{
vectorMap[character2][1]++;
}
else
{
tempArray = new int[2];
tempArray[0] = 0;
tempArray[1] = 1;
vectorMap.Add(character2, tempArray);
}
}
}
// 求余弦相似度
public double Sim()
{
double result = 0;
result = PointMulti(vectorMap) / SqrtMulti(vectorMap);
return result;
}
private double SqrtMulti(Dictionary<char, int[]> paramMap)
{
double result = 0;
result = Squares(paramMap);
result = Math.Sqrt(result);
return result;
}
// 求平方和
private double Squares(Dictionary<char, int[]> paramMap)
{
double result1 = 0;
double result2 = 0;
HashSet<char> keySet = paramMap.Keys.ToHashSet();
foreach (char character in keySet)
{
int[] temp = paramMap[character];
result1 += (temp[0] * temp[0]);
result2 += (temp[1] * temp[1]);
}
return result1 * result2;
}
// 点乘法
private double PointMulti(Dictionary<char, int[]> paramMap)
{
double result = 0;
HashSet<char> keySet = paramMap.Keys.ToHashSet();
foreach (char character in keySet)
{
int[] temp = paramMap[character];
result += (temp[0] * temp[1]);
}
return result;
}
}
测试
string s1 = "我是一个帅哥";
string s2 = "帅哥是我";
Console.WriteLine(new CosineSimilarity(s1, s2).Sim());//0.8164.....