答案 0 :(得分:419)
以下是两篇非常简短的文字:
Julie loves me more than Linda loves me
Jane likes me more than Julie loves me
我们想知道这些文本有多相似,纯粹是按字数统计(并忽略字序)。我们首先列出两个文本中的单词:
me Julie loves Linda than more likes Jane
现在我们计算每个单词出现在每个文本中的次数:
me 2 2
Jane 0 1
Julie 1 1
Linda 1 0
likes 0 1
loves 2 1
more 1 1
than 1 1
我们对这些词本身并不感兴趣。我们只对此感兴趣 那两个垂直向量的计数。例如,有两个实例 每个文本中都有“我”。我们将决定这两个文本与每个文本的接近程度 另外,通过计算这两个向量的一个函数,即余弦的余弦 他们之间的角度。
这两个载体又是:
a: [2, 0, 1, 1, 0, 2, 1, 1]
b: [2, 1, 1, 0, 1, 1, 1, 1]
它们之间的角度余弦约为0.822。
这些向量是8维的。显然使用余弦相似性的优点 它将一个超出人类能力的问题转化为一个问题 那可以。在这种情况下,您可以将其视为约35的角度 与零或完全一致的“距离”的度数。
答案 1 :(得分:108)
我猜你更有兴趣深入了解“为什么”余弦相似性的作用(为什么它提供了良好的相似性指示),而不是“如何“计算它(用于计算的具体操作)。如果您对后者感兴趣,请参阅本文中丹尼尔指出的参考文献以及a related SO Question。
为了解释原因如何以及更多原因,首先,简化问题并仅在二维中起作用是有用的。一旦你在2D中得到它,就更容易在三个维度中考虑它,当然在更多维度中更难想象,但到那时我们可以使用线性代数进行数值计算,并帮助我们用术语思考在n维中的线/矢量/“飞机”/“球体”,即使我们无法绘制这些。
所以... 二维:关于文本相似性,这意味着我们将关注两个不同的术语,比如“伦敦”和“巴黎”这两个词,我们会算在我们希望比较的两个文件的每一个中找到这些单词的每一次。这为我们提供了每个文档在xy平面上的一个点,例如,如果Doc1有一次巴黎,而伦敦有四次,则(1,4)点会出现这个文档(关于文档的这种小的评估) 。或者,就向量而言,这个Doc1文档将是从原点到点的箭头(1,4)。考虑到这个图像,让我们考虑两个文档的相似之处以及它与向量的关系。
非常类似的文件(同样关于这个有限的维度)将具有与巴黎相同数量的引用,以及与伦敦相同数量的引用,或者,它们可能具有相同的参考比例(比如文件Doc2,其中2个参考巴黎和8个参考到伦敦,也会非常相似,可能只是更长的文本或某种程度上更重复的城市名称,但同样的比例:也许这两个文件都是关于伦敦的指南,只是将参考文献传递给巴黎(以及那个城市有多酷;-)开个玩笑!!!)。 现在不太相似的文件,也可能包括对这两个城市的引用,但不同的比例,也许Doc2只会引用巴黎一次和伦敦7次。
回到我们的xy平面,如果我们绘制这些假设文档,我们会看到当它们非常相似时它们的向量重叠(尽管某些向量可能更长),并且随着它们开始变得更少通常,这些向量开始发散,它们之间的角度更大。
的Bam!通过测量矢量之间的角度,我们可以很好地了解它们的相似性,并且为了使事情更容易,通过采用这个角度的余弦,我们得到了一个很好的0到1(或-1到1,取决于我们考虑的内容和方式)指示这种相似性的值。角度越小,余弦值越大(越接近1),相似度越大。
极端情况下,如果Doc1仅引用巴黎而Doc2仅引用伦敦,那么这些文件绝对没有任何共同之处。 Doc1在x轴上有矢量,在y轴上有Doc2,角度为90度,余弦为0.(BTW 当我们说两个事物彼此正交时我们的意思 )
添加尺寸:
通过这种直观的相似性表达为小角度(或大余弦),我们现在可以想象三维中的事物,比如在混合中引入“阿姆斯特丹”这个词。并且很好地想象一个具有两个参考文件的文档如何在特定方向上有一个向量,我们可以看到这个方向如何与一个引用巴黎和伦敦三次但不是阿姆斯特丹等的文档进行比较。如上所述我们可以试着想象这个10或100个城市的奇特空间,很难画,但很容易概念化。
我将结束只是说关于公式本身的一些话。如所述其他参考文献提供关于计算的良好信息。
再次首先在2个维度。两个向量之间角度余弦的公式来自三角差(角度α和角度b之间)
cos(a - b) = (cos(a) * cos(b)) + (sin (a) * sin(b))
此公式与点积公式非常相似:
Vect1 . Vect2 = (x1 * x2) + (y1 * y2)
其中cos(a)匹配第一个向量的x值和sin(a)y值。唯一的问题是x,y等不完全是cos和sin值,因为这些值需要在单位圆上读取。这就是公式的分母所在的地方:通过除以这些向量的长度乘积,x和y坐标变得规范化。
答案 2 :(得分:21)
这是我在C#中的实现。
using System;
namespace CosineSimilarity
{
class Program
{
static void Main()
{
int[] vecA = {1, 2, 3, 4, 5};
int[] vecB = {6, 7, 7, 9, 10};
var cosSimilarity = CalculateCosineSimilarity(vecA, vecB);
Console.WriteLine(cosSimilarity);
Console.Read();
}
private static double CalculateCosineSimilarity(int[] vecA, int[] vecB)
{
var dotProduct = DotProduct(vecA, vecB);
var magnitudeOfA = Magnitude(vecA);
var magnitudeOfB = Magnitude(vecB);
return dotProduct/(magnitudeOfA*magnitudeOfB);
}
private static double DotProduct(int[] vecA, int[] vecB)
{
// I'm not validating inputs here for simplicity.
double dotProduct = 0;
for (var i = 0; i < vecA.Length; i++)
{
dotProduct += (vecA[i] * vecB[i]);
}
return dotProduct;
}
// Magnitude of the vector is the square root of the dot product of the vector with itself.
private static double Magnitude(int[] vector)
{
return Math.Sqrt(DotProduct(vector, vector));
}
}
}
答案 3 :(得分:19)
为简单起见,我正在减少向量a和b:
Let :
a : [1, 1, 0]
b : [1, 0, 1]
然后余弦相似度(Theta):
(Theta) = (1*1 + 1*0 + 0*1)/sqrt((1^2 + 1^2))* sqrt((1^2 + 1^2)) = 1/2 = 0.5
然后cos 0.5的倒数是60度。
答案 4 :(得分:13)
这个Python代码是我实现算法的快速而肮脏的尝试:
import math
from collections import Counter
def build_vector(iterable1, iterable2):
counter1 = Counter(iterable1)
counter2 = Counter(iterable2)
all_items = set(counter1.keys()).union(set(counter2.keys()))
vector1 = [counter1[k] for k in all_items]
vector2 = [counter2[k] for k in all_items]
return vector1, vector2
def cosim(v1, v2):
dot_product = sum(n1 * n2 for n1, n2 in zip(v1, v2) )
magnitude1 = math.sqrt(sum(n ** 2 for n in v1))
magnitude2 = math.sqrt(sum(n ** 2 for n in v2))
return dot_product / (magnitude1 * magnitude2)
l1 = "Julie loves me more than Linda loves me".split()
l2 = "Jane likes me more than Julie loves me or".split()
v1, v2 = build_vector(l1, l2)
print(cosim(v1, v2))
答案 5 :(得分:8)
使用@Bill Bell示例,在[R]
中有两种方法可以做到这一点a = c(2,1,0,2,0,1,1,1)
b = c(2,1,1,1,1,0,1,1)
d = (a %*% b) / (sqrt(sum(a^2)) * sqrt(sum(b^2)))
或利用crossprod()方法的表现......
e = crossprod(a, b) / (sqrt(crossprod(a, a)) * sqrt(crossprod(b, b)))
答案 6 :(得分:4)
这是一个实现余弦相似性的简单Python
代码。
from scipy import linalg, mat, dot
import numpy as np
In [12]: matrix = mat( [[2, 1, 0, 2, 0, 1, 1, 1],[2, 1, 1, 1, 1, 0, 1, 1]] )
In [13]: matrix
Out[13]:
matrix([[2, 1, 0, 2, 0, 1, 1, 1],
[2, 1, 1, 1, 1, 0, 1, 1]])
In [14]: dot(matrix[0],matrix[1].T)/np.linalg.norm(matrix[0])/np.linalg.norm(matrix[1])
Out[14]: matrix([[ 0.82158384]])
答案 7 :(得分:3)
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
*
* @author Xiao Ma
* mail : 409791952@qq.com
*
*/
public class SimilarityUtil {
public static double consineTextSimilarity(String[] left, String[] right) {
Map<String, Integer> leftWordCountMap = new HashMap<String, Integer>();
Map<String, Integer> rightWordCountMap = new HashMap<String, Integer>();
Set<String> uniqueSet = new HashSet<String>();
Integer temp = null;
for (String leftWord : left) {
temp = leftWordCountMap.get(leftWord);
if (temp == null) {
leftWordCountMap.put(leftWord, 1);
uniqueSet.add(leftWord);
} else {
leftWordCountMap.put(leftWord, temp + 1);
}
}
for (String rightWord : right) {
temp = rightWordCountMap.get(rightWord);
if (temp == null) {
rightWordCountMap.put(rightWord, 1);
uniqueSet.add(rightWord);
} else {
rightWordCountMap.put(rightWord, temp + 1);
}
}
int[] leftVector = new int[uniqueSet.size()];
int[] rightVector = new int[uniqueSet.size()];
int index = 0;
Integer tempCount = 0;
for (String uniqueWord : uniqueSet) {
tempCount = leftWordCountMap.get(uniqueWord);
leftVector[index] = tempCount == null ? 0 : tempCount;
tempCount = rightWordCountMap.get(uniqueWord);
rightVector[index] = tempCount == null ? 0 : tempCount;
index++;
}
return consineVectorSimilarity(leftVector, rightVector);
}
/**
* The resulting similarity ranges from −1 meaning exactly opposite, to 1
* meaning exactly the same, with 0 usually indicating independence, and
* in-between values indicating intermediate similarity or dissimilarity.
*
* For text matching, the attribute vectors A and B are usually the term
* frequency vectors of the documents. The cosine similarity can be seen as
* a method of normalizing document length during comparison.
*
* In the case of information retrieval, the cosine similarity of two
* documents will range from 0 to 1, since the term frequencies (tf-idf
* weights) cannot be negative. The angle between two term frequency vectors
* cannot be greater than 90°.
*
* @param leftVector
* @param rightVector
* @return
*/
private static double consineVectorSimilarity(int[] leftVector,
int[] rightVector) {
if (leftVector.length != rightVector.length)
return 1;
double dotProduct = 0;
double leftNorm = 0;
double rightNorm = 0;
for (int i = 0; i < leftVector.length; i++) {
dotProduct += leftVector[i] * rightVector[i];
leftNorm += leftVector[i] * leftVector[i];
rightNorm += rightVector[i] * rightVector[i];
}
double result = dotProduct
/ (Math.sqrt(leftNorm) * Math.sqrt(rightNorm));
return result;
}
public static void main(String[] args) {
String left[] = { "Julie", "loves", "me", "more", "than", "Linda",
"loves", "me" };
String right[] = { "Jane", "likes", "me", "more", "than", "Julie",
"loves", "me" };
System.out.println(consineTextSimilarity(left,right));
}
}
答案 8 :(得分:2)
用于计算余弦相似度的简单JAVA代码
/**
* Method to calculate cosine similarity of vectors
* 1 - exactly similar (angle between them is 0)
* 0 - orthogonal vectors (angle between them is 90)
* @param vector1 - vector in the form [a1, a2, a3, ..... an]
* @param vector2 - vector in the form [b1, b2, b3, ..... bn]
* @return - the cosine similarity of vectors (ranges from 0 to 1)
*/
private double cosineSimilarity(List<Double> vector1, List<Double> vector2) {
double dotProduct = 0.0;
double normA = 0.0;
double normB = 0.0;
for (int i = 0; i < vector1.size(); i++) {
dotProduct += vector1.get(i) * vector2.get(i);
normA += Math.pow(vector1.get(i), 2);
normB += Math.pow(vector2.get(i), 2);
}
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}
答案 9 :(得分:1)
让我试着用 Python 代码和一些图形数学公式来解释这一点。
假设我们的代码中有两个非常短的文本:
texts = ["I am a boy", "I am a girl"]
我们想比较以下查询文本,以查看查询与上述文本的接近程度,使用快速余弦相似度得分:
query = ["I am a boy scout"]
我们应该如何计算余弦相似度分数?首先,让我们用 Python 为这些文本构建一个 tfidf 矩阵:
from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer()
tfidf_matrix = vectorizer.fit_transform(texts)
接下来,让我们检查 tfidf 矩阵的值及其词汇:
print(tfidf_matrix.toarray())
# output
array([[0.57973867, 0.81480247, 0. ],
[0.57973867, 0. , 0.81480247]])
在这里,我们得到一个 tfidf 矩阵,其 tfidf 值为 2 x 3,或 2 个文档/文本 x 3 个术语。这是我们的 tfidf 文档-术语矩阵。让我们通过调用 vectorizer.vocabulary_
print(vectorizer.vocabulary_)
# output
{'am': 0, 'boy': 1, 'girl': 2}
这告诉我们 tfidf 矩阵中的 3 个术语是“am”、“boy”和“girl”。 “am”位于第 0 列,“boy”位于第 1 列,“girl”位于第 2 列。矢量化器已删除术语“I”和“a”,因为它们是停用词。
现在我们有了 tfidf 矩阵,我们想将查询文本与文本进行比较,看看我们的查询与文本的接近程度。为此,我们可以计算查询与文本的 tfidf 矩阵的余弦相似度分数。但首先,我们需要计算查询的 tfidf:
query = ["I am a boy scout"]
query_tfidf = vectorizer.transform([query])
print(query_tfidf.toarray())
#output
array([[0.57973867, 0.81480247, 0. ]])
在这里,我们计算了查询的 tfidf。我们的 query_tfidf 有一个 tfidf 值向量 [0.57973867, 0.81480247, 0. ]
,我们将使用它来计算我们的余弦相似度乘法分数。如果我没记错的话,query_tfidf 值或 vectorizer.transform([query])
值是通过从 tfidf_matrix 中选择与查询匹配最多的行或文档来导出的。例如,tfidf_matrix 的第 1 行或文档/文本 1 与包含“am”(0.57973867)和“boy”(0.81480247)的查询文本匹配最多,因此 [0.57973867, 0.81480247, 0. ]
的 tfidf_matrix 的第 1 行选择值作为 query_tfidf 的值。 (注意:如果有人可以帮助进一步解释这一点,那就太好了)
在计算我们的 query_tfidf 之后,我们现在可以将我们的 query_tfidf 向量与我们的文本 tfidf_matrix 进行矩阵乘法或点积,以获得余弦相似度分数。
回想一下余弦相似度得分或公式等于以下内容:
cosine similarity score = (A . B) / ||A|| ||B||
这里,A = 我们的 query_tfidf 向量,B = tfidf_matrix 的每一行
请注意:A . B = A * B^T,或 A 点积 B = A 乘以 B 转置。
知道公式后,让我们手动计算 query_tfidf 的余弦相似度分数,然后将我们的答案与 sklearn.metrics cosine_similarity 函数提供的值进行比较。让我们手动计算:
query_tfidf_arr = query_tfidf.toarray()
tfidf_matrix_arr = tfidf_matrix.toarray()
cosine_similarity_1 = np.dot(query_tfidf_arr, tfidf_matrix_arr[0].T) /
(np.linalg.norm(query_tfidf_arr) * np.linalg.norm(tfidf_matrix_arr[0]))
cosine_similarity_2 = np.dot(query_tfidf_arr, tfidf_matrix_arr[1].T) /
(np.linalg.norm(query_tfidf_arr) * np.linalg.norm(tfidf_matrix_arr[1]))
manual_cosine_similarities = [cosine_similarity_1[0], cosine_similarity_2[0]]
print(manual_cosine_similarities)
#output
[1.0, 0.33609692727625745]
我们手动计算的余弦相似度得分为 [1.0, 0.33609692727625745]
。让我们用 sklearn.metrics cosine_similarity 函数提供的答案值检查我们手动计算的余弦相似度得分:
from sklearn.metrics.pairwise import cosine_similarity
function_cosine_similarities = cosine_similarity(query_tfidf, tfidf_matrix)
print(function_cosine_similarities)
#output
array([[1.0 , 0.33609693]])
输出值都是一样的!人工计算的余弦相似度值与函数计算的余弦相似度值相同!
因此,这个简单的解释展示了如何计算余弦相似度值。希望这个解释对您有帮助。
答案 10 :(得分:0)
两个向量A和B存在于2D空间或3D空间中,这些向量之间的角度为cos相似度。
如果角度更大(可以达到最大180度),即Cos 180 = -1,最小角度为0度。 cos 0 = 1表示向量彼此对齐,因此向量相似。
cos 90 = 0(这足以得出向量A和B根本不相似,并且由于距离不能为负,因此余弦值将介于0到1之间。因此,更大的角度意味着降低相似性(可视化也很有意义)
答案 11 :(得分:0)
下面是一个简单的Python代码来计算余弦相似度:
import math
def dot_prod(v1, v2):
ret = 0
for i in range(len(v1)):
ret += v1[i] * v2[i]
return ret
def magnitude(v):
ret = 0
for i in v:
ret += i**2
return math.sqrt(ret)
def cos_sim(v1, v2):
return (dot_prod(v1, v2)) / (magnitude(v1) * magnitude(v2))