摘要:Numpy
,Python
余弦相似度公式
余弦相似度是衡量向量夹角的余弦值作为相似度度量指标,夹角越小相似度越高
公式为两个向量的点乘除以向量的模长的乘积
计算向量之间余弦相似度
使用Python的Numpy框架可以直接计算向量的点乘(np.dot)
,以及向量的模长(np.linalg.norm
),余弦相似度在[-1, 1]
之间,为了能更直观地和相似度等价,通常转化为[0, 1]
之间,如下代码实现计算两个一维向量之间的余弦相似度
def get_cos_similar(v1: list, v2: list):
num = float(np.dot(v1, v2)) # 向量点乘
denom = np.linalg.norm(v1) * np.linalg.norm(v2) # 求模长的乘积
return 0.5 + 0.5 * (num / denom) if denom != 0 else 0
计算向量和矩阵的余弦相似度
如果要计算一个向量和候选所有向量的余弦相似度,使用如下代码将一维向量和多维向量直接点乘
def get_cos_similar_multi(v1: list, v2: list):
num = np.dot([v1], np.array(v2).T) # 向量点乘
denom = np.linalg.norm(v1) * np.linalg.norm(v2, axis=1) # 求模长的乘积
res = num / denom
res[np.isneginf(res)] = 0
return 0.5 + 0.5 * res
计算矩阵和矩阵的余弦相似度
如果要计算两个向量库内,两两向量的余弦相似度,对之前代码稍作修改即可
def get_cos_similar_matrix(v1, v2):
num = np.dot(v1, np.array(v2).T) # 向量点乘
denom = np.linalg.norm(v1, axis=1).reshape(-1, 1) * np.linalg.norm(v2, axis=1) # 求模长的乘积
res = num / denom
res[np.isneginf(res)] = 0
return 0.5 + 0.5 * res
效率对比
先对比计算结果是否一致,没啥问题,三个方法计算的结果一致
print(get_cos_similar([1, 2, 3], [2, 3, -1])) # 0.6785714285714286
print(get_cos_similar([1, 2, 3], [2, -1, -1])) # 0.3363365823230057
print(get_cos_similar([2, 5, -1], [2, 3, -1])) # 0.9879500364742666
print(get_cos_similar([2, 5, -1], [2, -1, -1])) # 0.5
print(get_cos_similar_multi([1, 2, 3], [[2, 3, -1], [2, -1, -1]])) # [[0.67857143 0.33633658]]
print(get_cos_similar_matrix([[1, 2, 3], [2, 5, -1]], [[2, 3, -1], [2, -1, -1]]))
# [[0.67857143 0.33633658]
# [0.98795004 0.5 ]]
再对比一下计算一个向量和向量库内其他所有向量的余弦相似度,使用方法一,和使用方法二的效率差异,可以遍历所有向量分别计算一次和直接点乘向量和矩阵效率相差10倍,具体差异需要看向量长度和向量数目
import time
v1 = [1, 2, 3, 5, 7, 6, 2, 5, 9, 10]
v2 = [[2, 5, 1, 8, 4, 1, 1, 3, 1, -5]] * 10000
t1 = time.time()
[get_cos_similar(v1, x) for x in v2]
t2 = time.time()
print(t2- t1) # 0.25322484970092773
t3 = time.time()
get_cos_similar_multi(v1, v2)
t4 = time.time()
print(t4 - t3) # 0.025495529174804688
最后对比一下两个向量库两两计算余弦相似度分别使用三个方法的效率差异,可见矩阵一次点乘的效率最高,遍历之后多次点乘的效率最低
v3 = [v1] * 100
v4 = [[2, 5, 1, 8, 4, 1, 1, 3, 1, -5]] * 100
t5 = time.time()
for vv1 in v3:
for vv2 in v4:
get_cos_similar(vv1, vv2)
t6 = time.time()
print(t6 - t5) # 0.2532625198364258
t7 = time.time()
for vv1 in v3:
get_cos_similar_multi(vv1, v4)
t8 = time.time()
print(t8 - t7) # 0.03303122520446777
t9 = time.time()
get_cos_similar_matrix(v3, v4)
t10 = time.time()
print(t10 - t9) # 0.0014848709106445312