两个数字列表之间的余弦相似度

2025-02-28 08:23:00
admin
原创
64
摘要:问题描述:我想计算两个列表之间的余弦相似度,例如列表 1 是,列表 2 是。dataSetI`dataSetII`假设dataSetI是[3, 45, 7, 2]且dataSetII是[2, 54, 13, 15]。列表的长度始终相等。我想将余弦相似度报告为 0 到 1 之间的数字。dataSetI = [3...

问题描述:

我想计算两个列表之间的余弦相似度,例如列表 1 是,列表 2 是。dataSetI`dataSetII`

假设dataSetI[3, 45, 7, 2]dataSetII[2, 54, 13, 15]。列表的长度始终相等。我想将余弦相似度报告为 0 到 1 之间的数字。

dataSetI = [3, 45, 7, 2]
dataSetII = [2, 54, 13, 15]

def cosine_similarity(list1, list2):
  # How to?
  pass

print(cosine_similarity(dataSetI, dataSetII))

解决方案 1:

另一个版本numpy仅基于

from numpy import dot
from numpy.linalg import norm

cos_sim = dot(a, b)/(norm(a)*norm(b))

解决方案 2:

您应该尝试SciPy。它有许多有用的科学例程,例如“用于数值计算积分、求解微分方程、优化和稀疏矩阵的例程”。它使用超快优化的 NumPy 进行数字运算。请参阅此处以了解如何安装。

请注意,spatial.distance.cosine 计算的是距离,而不是相似度。因此,您必须从 1 中减去该值才能得到相似度

from scipy import spatial

dataSetI = [3, 45, 7, 2]
dataSetII = [2, 54, 13, 15]
result = 1 - spatial.distance.cosine(dataSetI, dataSetII)

解决方案 3:

您可以使用cosine_similarity函数形式sklearn.metrics.pairwise 文档

In [23]: from sklearn.metrics.pairwise import cosine_similarity

In [24]: cosine_similarity([[1, 0, -1]], [[-1,-1, 0]])
Out[24]: array([[-0.5]])

解决方案 4:

我认为性能在这里并不重要,但我无法抗拒。zip() 函数完全重新复制了两个向量(实际上更像是矩阵转置),只是为了以“Pythonic”顺序获取数据。计算具体实现的时间会很有趣:

import math
def cosine_similarity(v1,v2):
    "compute cosine similarity of v1 to v2: (v1 dot v2)/{||v1||*||v2||)"
    sumxx, sumxy, sumyy = 0, 0, 0
    for i in range(len(v1)):
        x = v1[i]; y = v2[i]
        sumxx += x*x
        sumyy += y*y
        sumxy += x*y
    return sumxy/math.sqrt(sumxx*sumyy)

v1,v2 = [3, 45, 7, 2], [2, 54, 13, 15]
print(v1, v2, cosine_similarity(v1,v2))

Output: [3, 45, 7, 2] [2, 54, 13, 15] 0.972284251712

它经历了类似 C 的一次提取元素的噪音,但没有进行批量数组复制,而是在单个 for 循环中完成所有重要操作,并使用单个平方根。

ETA:更新了打印调用,使其成为一个函数。(原始版本是 Python 2.7,而不是 3.3。当前版本在 Python 2.7 下使用from __future__ import print_function语句运行。)无论哪种方式,输出都是相同的。

CPYthon 2.7.3 在 3.0GHz Core 2 Duo 上运行:

>>> timeit.timeit("cosine_similarity(v1,v2)",setup="from __main__ import cosine_similarity, v1, v2")
2.4261788514654654
>>> timeit.timeit("cosine_measure(v1,v2)",setup="from __main__ import cosine_measure, v1, v2")
8.794677709375264

因此,在这种情况下,非 Python 方式的速度大约快 3.6 倍。

解决方案 5:

无需使用任何导入

数学.sqrt(x)

可以替换为

十**.5

如果不使用 numpy.dot(),则必须使用列表推导创建自己的点函数:

def dot(A,B): 
    return (sum(a*b for a,b in zip(A,B)))

然后只需简单应用余弦相似度公式即可:

def cosine_similarity(a,b):
    return dot(a,b) / ( (dot(a,a) **.5) * (dot(b,b) ** .5) )

解决方案 6:

Python代码计算:

  • 余弦距离

  • 余弦相似度

  • 角距离

  • 角度相似性


import math

from scipy import spatial


def calculate_cosine_distance(a, b):
    cosine_distance = float(spatial.distance.cosine(a, b))
    return cosine_distance


def calculate_cosine_similarity(a, b):
    cosine_similarity = 1 - calculate_cosine_distance(a, b)
    return cosine_similarity


def calculate_angular_distance(a, b):
    cosine_similarity = calculate_cosine_similarity(a, b)
    angular_distance = math.acos(cosine_similarity) / math.pi
    return angular_distance


def calculate_angular_similarity(a, b):
    angular_similarity = 1 - calculate_angular_distance(a, b)
    return angular_similarity

相似性搜索

如果您想在嵌入数组中找到最接近的余弦相似度,您可以使用Tensorflow,如下面的代码。

在我的测试中,在不到 1 秒的时间内(使用)在 1M 嵌入(1'000'000 x 512)中找到最接近形状为 1x512 的嵌入的值GPU

import time

import numpy as np  # np.__version__ == '1.23.5'
import tensorflow as tf  # tf.__version__ == '2.11.0'

EMBEDDINGS_LENGTH = 512
NUMBER_OF_EMBEDDINGS = 1000 * 1000


def calculate_cosine_similarities(x, embeddings):
    cosine_similarities = -1 * tf.keras.losses.cosine_similarity(x, embeddings)
    return cosine_similarities.numpy()


def find_closest_embeddings(x, embeddings, top_k=1):
    cosine_similarities = calculate_cosine_similarities(x, embeddings)
    values, indices = tf.math.top_k(cosine_similarities, k=top_k)
    return values.numpy(), indices.numpy()


def main():
    # x shape: (512)
    # Embeddings shape: (1000000, 512)
    x = np.random.rand(EMBEDDINGS_LENGTH).astype(np.float32)
    embeddings = np.random.rand(NUMBER_OF_EMBEDDINGS, EMBEDDINGS_LENGTH).astype(np.float32)

    print('Embeddings shape: ', embeddings.shape)

    n = 100
    sum_duration = 0
    for i in range(n):
        start = time.time()
        best_values, best_indices = find_closest_embeddings(x, embeddings, top_k=1)
        end = time.time()

        duration = end - start
        sum_duration += duration

        print('Duration (seconds): {}, Best value: {}, Best index: {}'.format(duration, best_values[0], best_indices[0]))

    # Average duration (seconds): 1.707 for Intel(R) Core(TM) i7-10700 CPU @ 2.90GHz
    # Average duration (seconds): 0.961 for NVIDIA 1080 ti
    print('Average duration (seconds): ', sum_duration / n)


if __name__ == '__main__':
    main()

对于更高级的相似性搜索,您可以使用Milvus、Weaviate或Faiss。


解决方案 7:

我根据问题中的几个答案进行了基准测试,并且认为以下代码片段是最佳选择:

def dot_product2(v1, v2):
    return sum(map(operator.mul, v1, v2))


def vector_cos5(v1, v2):
    prod = dot_product2(v1, v2)
    len1 = math.sqrt(dot_product2(v1, v1))
    len2 = math.sqrt(dot_product2(v2, v2))
    return prod / (len1 * len2)

结果让我很惊讶,基于的实现scipy并不是最快的。我分析发现,scipy 中的余弦函数需要花费大量时间将一个向量从 python 列表转换为 numpy 数组。

在此处输入图片描述

解决方案 8:

import math
from itertools import izip

def dot_product(v1, v2):
    return sum(map(lambda x: x[0] * x[1], izip(v1, v2)))

def cosine_measure(v1, v2):
    prod = dot_product(v1, v2)
    len1 = math.sqrt(dot_product(v1, v1))
    len2 = math.sqrt(dot_product(v2, v2))
    return prod / (len1 * len2)

计算后即可四舍五入:

cosine = format(round(cosine_measure(v1, v2), 3))

如果你想要它非常简短,你可以使用这个单行:

from math import sqrt
from itertools import izip

def cosine_measure(v1, v2):
    return (lambda (x, y, z): x / sqrt(y * z))(reduce(lambda x, y: (x[0] + y[0] * y[1], x[1] + y[0]**2, x[2] + y[1]**2), izip(v1, v2), (0, 0, 0)))

解决方案 9:

您可以使用这个简单的函数来计算余弦相似度:

def cosine_similarity(a, b):
  return sum([i*j for i,j in zip(a, b)])/(math.sqrt(sum([i*i for i in a]))* math.sqrt(sum([i*i for i in b])))

解决方案 10:

您可以使用简单的函数在 Python 中完成此操作:

def get_cosine(text1, text2):
  vec1 = text1
  vec2 = text2
  intersection = set(vec1.keys()) & set(vec2.keys())
  numerator = sum([vec1[x] * vec2[x] for x in intersection])
  sum1 = sum([vec1[x]**2 for x in vec1.keys()])
  sum2 = sum([vec2[x]**2 for x in vec2.keys()])
  denominator = math.sqrt(sum1) * math.sqrt(sum2)
  if not denominator:
     return 0.0
  else:
     return round(float(numerator) / denominator, 3)
dataSet1 = [3, 45, 7, 2]
dataSet2 = [2, 54, 13, 15]
get_cosine(dataSet1, dataSet2)

解决方案 11:

使用 numpy 将一个数字列表与多个列表(矩阵)进行比较:

def cosine_similarity(vector,matrix):
   return ( np.sum(vector*matrix,axis=1) / ( np.sqrt(np.sum(matrix**2,axis=1)) * np.sqrt(np.sum(vector**2)) ) )[::-1]

解决方案 12:

另一个版本,如果您有一个场景,其中您有向量列表和一个查询向量,并且您想要计算查询向量与列表中所有向量的余弦相似度,您可以按照以下方式一次性完成:

>>> import numpy as np

>>> A      # list of vectors, shape -> m x n
array([[ 3, 45,  7,  2],
       [ 1, 23,  3,  4]])

>>> B      # query vector, shape -> 1 x n
array([ 2, 54, 13, 15])

>>> similarity_scores = A.dot(B)/ (np.linalg.norm(A, axis=1) * np.linalg.norm(B))

>>> similarity_scores
array([0.97228425, 0.99026919])

解决方案 13:

如果您恰好已经在使用PyTorch,那么您应该采用它们的CosineSimilarity 实现。

假设有n二维的numpy.ndarraysv1v2,即它们的形状都是(n,)。下面是如何获得它们的余弦相似度:

import torch
import torch.nn as nn

cos = nn.CosineSimilarity()
cos(torch.tensor([v1]), torch.tensor([v2])).item()

或者假设有两个numpy.ndarrayw1w2它们的形状都是(m, n)。以下将获得一个余弦相似度列表,每个列表都是 中的一行w1与 中的对应行之间的余弦相似度w2

cos(torch.tensor(w1), torch.tensor(w2)).tolist()

解决方案 14:

您可以使用SciPy(最简单的方法):

from scipy import spatial

dataSetI = [3, 45, 7, 2]
dataSetII = [2, 54, 13, 15]
print(1 - spatial.distance.cosine(dataSetI, dataSetII))

请注意,这spatial.distance.cosine()会给您一个差异(距离)值,因此要获得相似度,您需要从 1 中减去该值。

获得解决方案的另一种方法是自己编写函数,甚至考虑不同长度列表的可能性:

def cosineSimilarity(v1, v2):
  scalarProduct = moduloV1 = moduloV2 = 0

  if len(v1) > len(v2):
    v2.extend(0 for _ in range(len(v1) - len(v2)))
  else:
    v2.extend(0 for _ in range(len(v2) - len(v1)))

  for i in range(len(v1)):
    scalarProduct += v1[i] * v2[i]
    moduloV1 += v1[i] * v1[i]
    moduloV2 += v2[i] * v2[i]

  return round(scalarProduct/(math.sqrt(moduloV1) * math.sqrt(moduloV2)), 3)

dataSetI = [3, 45, 7, 2]
dataSetII = [2, 54, 13, 15]
print(cosineSimilarity(dataSetI, dataSetII))

解决方案 15:

以下是纯列表的余弦相似度计算:

def dot_product(v1, v2):
    return sum(x * y for x, y in zip(v1, v2))

def magnitude(v):
    return (dot_product(v, v))**0.5

def cosine_similarity(list1, list2):
    return dot_product(list1, list2) / (magnitude(list1) * magnitude(list2))

您可以像这样使用此功能:

dataSetI = [1, 2, 3]
dataSetII = [4, 5, 6]

similarity = cosine_similarity(dataSetI, dataSetII)
print(similarity)  # Output will be between -1 and 1

解决方案 16:

我们可以用简单的数学方程式轻松计算余弦相似度。余弦相似度 = 1-(向量点积/(向量范数乘积))。我们可以定义两个函数,分别用于计算点积和范数。

def dprod(a,b):
    sum=0
    for i in range(len(a)):
        sum+=a[i]*b[i]
    return sum

def norm(a):

    norm=0
    for i in range(len(a)):
    norm+=a[i]**2
    return norm**0.5

    cosine_a_b = 1-(dprod(a,b)/(norm(a)*norm(b)))

解决方案 17:

应该是找到两个数字矩阵之间余弦相似度的最有效方法 - 无需 for/循环,全部在 numpy 中。

def cos_sim(a: np.ndarray, b: np.ndarray):
    
    out_dim = 2        
    if len(b.shape) == 1:
        b = b.reshape([1,-1])
        out_dim -=1
    if len(a.shape) == 1:
        a = a.reshape([1,-1])
        out_dim -=1
        
    norm1 = norm(a.astype(float), axis=1, keepdims=True)
    norm2 = norm(b.astype(float), axis=1, keepdims=True)
    similarity = 1 - a.dot(b.T) / norm1.dot(norm2.T)
    
    ## by default outputs 2 x 2 matrix
    if out_dim == 0:
        return similarity[0,0]
    elif out_dim == 1:
        return similarity[:,0]

    return similarity

解决方案 18:

这是一个同样适用于矩阵的实现。其行为与 sklearn 余弦相似度完全相同:

def cosine_similarity(a, b):    
    return np.divide(
        np.dot(a, b.T),
        np.linalg.norm(
            a,
            axis=1,
            keepdims=True
        ) 
        @ # matrix multiplication
        np.linalg.norm(
            b,
            axis=1,
            keepdims=True
        ).T
    )

@ 符号代表矩阵乘法。请参阅
Python 中的“at”(@) 符号有什么作用?

解决方案 19:

以下对我有用:

import numpy as np
from typing import List

def cosine_similarity(
        a: List[float] | List[List[float]],
        b: List[float] | List[List[float]]
) -> float | List[float]:
    a = np.array(a)
    b = np.array(b)
    if a.ndim == 1 and b.ndim == 1:
        return a @ b / (np.linalg.norm(a) * np.linalg.norm(b))
    if a.ndim == 2 and b.ndim == 1:
        return [i @ b / (np.linalg.norm(i) * np.linalg.norm(b)) for i in a]
    if a.ndim == 1 and b.ndim == 2:
        return [a @ i / (np.linalg.norm(a) * np.linalg.norm(i)) for i in b]
    if a.ndim == 2 and b.ndim == 2:
        return (a @ b.T) / (np.linalg.norm(a) * np.linalg.norm(b)) * np.sqrt(len(a) * len(b))
    raise ValueError('!')

解决方案 20:

对于无法使用 NumPy 的情况,所有答案都非常有用。如果可以,以下是另一种方法:

def cosine(x, y):
    dot_products = np.dot(x, y.T)
    norm_products = np.linalg.norm(x) * np.linalg.norm(y)
    return dot_products / (norm_products + EPSILON)

还要记住EPSILON = 1e-07确保部门的安全。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2941  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1803  
  PLM(产品生命周期管理)系统在企业的产品研发、生产与管理过程中扮演着至关重要的角色。然而,在实际运行中,资源冲突是经常会遇到的难题。资源冲突可能导致项目进度延迟、成本增加以及产品质量下降等一系列问题,严重影响企业的效益与竞争力。因此,如何有效应对PLM系统中的资源冲突,成为众多企业关注的焦点。接下来,我们将详细探讨5...
plm项目管理系统   31  
  敏捷项目管理与产品生命周期管理(PLM)的融合,正成为企业在复杂多变的市场环境中提升研发效率、增强竞争力的关键举措。随着技术的飞速发展和市场需求的快速更迭,传统的研发流程面临着诸多挑战,而将敏捷项目管理理念融入PLM,有望在2025年实现研发流程的深度优化,为企业创造更大的价值。理解敏捷项目管理与PLM的核心概念敏捷项...
plm项目   31  
  模块化设计在现代产品开发中扮演着至关重要的角色,它能够提升产品开发效率、降低成本、增强产品的可维护性与可扩展性。而产品生命周期管理(PLM)系统作为整合产品全生命周期信息的关键平台,对模块化设计有着强大的支持能力。随着技术的不断发展,到 2025 年,PLM 系统在支持模块化设计方面将有一系列令人瞩目的技术实践。数字化...
plm软件   28  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用