Cosine tương tự giữa 2 danh sách số


118

Tôi cần tính toán sự giống nhau về cosin giữa hai danh sách , giả sử ví dụ danh sách 1 là dataSetIvà danh sách 2 là dataSetII. Tôi không thể sử dụng bất cứ thứ gì như numpy hoặc mô-đun thống kê. Tôi phải sử dụng các mô-đun phổ biến (toán học, v.v.) (và ít mô-đun nhất có thể, để giảm thời gian sử dụng).

Hãy nói dataSetI[3, 45, 7, 2]dataSetIIđang [2, 54, 13, 15]. Độ dài của các danh sách luôn bằng nhau.

Tất nhiên, sự giống nhau về cosine là giữa 0 và 1 , và vì lợi ích của nó, nó sẽ được làm tròn đến số thập phân thứ ba hoặc thứ tư với format(round(cosine, 3)).

Cảm ơn bạn rất nhiều trước vì đã giúp đỡ.


29
Tôi thích cách SO nghiền nát linh hồn của câu hỏi bài tập về nhà này để biến nó thành một tài liệu tham khảo chung tốt đẹp. OP nói " Tôi không thể sử dụng numpy , tôi phải đi theo con đường toán học dành cho người đi bộ", và câu trả lời hàng đầu là "bạn nên thử scipy, nó sử dụng numpy". SO cơ khí cấp huy hiệu vàng cho câu hỏi phổ biến.
Nikana Reklawyks 20/09/2016

1
Nikana Reklawyks, đó là một điểm tuyệt vời. Tôi đã gặp vấn đề đó nhiều hơn và thường xuyên hơn với StackOverflow. Và tôi đã có một số câu hỏi được đánh dấu là "bản sao" của một số câu hỏi trước đó, bởi vì người kiểm duyệt không dành thời gian để hiểu điều gì đã làm cho câu hỏi của tôi trở nên độc đáo.
LRK9

@NikanaReklawyks, điều này thật tuyệt. Nhìn vào hồ sơ của anh ấy, nó kể câu chuyện về một trong những người đóng góp 0,01% hàng đầu của SO, bạn biết không?
Nathan Chappell

Câu trả lời:


174

Bạn nên thử SciPy . Nó có một loạt các quy trình khoa học hữu ích, chẳng hạn như "quy trình tính toán tích phân số, giải phương trình vi phân, tối ưu hóa và ma trận thưa thớt." Nó sử dụng NumPy được tối ưu hóa siêu nhanh để xử lý số của nó. Xem tại đây để cài đặt.

Lưu ý rằng spatial.distance.cosine tính khoảng cách chứ không phải tính tương tự. Vì vậy, bạn phải trừ giá trị cho 1 để có được sự tương tự .

from scipy import spatial

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

122

một phiên bản khác numpychỉ dựa trên

from numpy import dot
from numpy.linalg import norm

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

3
Rất rõ ràng như định nghĩa, nhưng có thể np.inner(a, b) / (norm(a) * norm(b))hiểu rõ hơn. dotcó thể nhận được kết quả tương tự như innerđối với vectơ.
Belter

15
FYI giải pháp này trên hệ thống của tôi nhanh hơn đáng kể so với việc sử dụng scipy.spatial.distance.cosine.
Ozzah

@ZhengfangXin dãy cosin tương đồng từ -1 đến 1 theo định nghĩa
dontloo

2
Thậm chí ngắn hơn:cos_sim = (a @ b.T) / (norm(a)*norm(b))
Số liệu thống kê học tập bằng ví dụ

Đây là cách tiếp cận nhanh nhất so với những cách khác.
Jason Youn

73

Bạn có thể sử dụng tài liệucosine_similarity biểu mẫu hàmsklearn.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]])

21
Chỉ xin nhắc lại rằng Việc chuyển mảng một thứ nguyên làm dữ liệu đầu vào không được dùng nữa trong sklearn phiên bản 0.17 và sẽ tăng ValueError trong 0.19.
Chong Tang

4
Cách chính xác để thực hiện việc này với sklearn được đưa ra cảnh báo không dùng nữa này là gì?
Elliott

2
@Elliott one_dimension_array.reshape (-1,1)
bobo32

2
@ bobo32 cosine_similarity (np.array ([1, 0, -1]). reshape (-1,0), np.array ([- 1, -1, 0]). reshape (-1,0)) I đoán ý bạn là gì? Nhưng kết quả đó có nghĩa là nó trả về là gì? Nó là một mảng 2d mới, không phải là một sự tương đồng cosine.
Isbister

10
Kèm theo nó bằng một dấu ngoặc cosine_similarity([[1, 0, -1]], [[-1,-1, 0]])
nhọn

34

Tôi không cho rằng hiệu suất quan trọng ở đây, nhưng tôi không thể cưỡng lại. Hàm zip () hoàn toàn sao chép lại cả hai vectơ (thực ra là chuyển vị ma trận) chỉ để lấy dữ liệu theo thứ tự "Pythonic". Sẽ rất thú vị khi thời gian thực hiện các đai ốc:

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

Điều đó vượt qua tiếng ồn giống như C của việc trích xuất từng phần tử một, nhưng không sao chép mảng hàng loạt và hoàn thành mọi thứ quan trọng trong một vòng lặp for duy nhất và sử dụng một căn bậc hai.

ETA: Cuộc gọi in được cập nhật thành một hàm. (Bản gốc là Python 2.7, không phải 3.3. Hiện tại chạy dưới Python 2.7 vớifrom __future__ import print_function câu lệnh.) Đầu ra giống nhau.

CPYthon 2.7.3 trên 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

Vì vậy, cách unpythonic nhanh hơn khoảng 3,6 lần trong trường hợp này.


2
Là gì cosine_measuretrong trường hợp này?
MERose

1
@MERose: cosine_measurecosine_similaritychỉ đơn giản là các triển khai khác nhau của cùng một phép tính. Tương đương với việc chia tỷ lệ cả hai mảng đầu vào thành "vectơ đơn vị" và lấy tích số chấm.
Mike Housky

3
Tôi cũng đoán như vậy. Nhưng nó không hữu ích. Bạn trình bày so sánh thời gian của hai thuật toán nhưng chỉ trình bày một trong số chúng.
MERose

@MERose Ồ, xin lỗi. cosine_measurelà mã được pkacprzak đăng trước đó. Mã này là một giải pháp thay thế cho giải pháp Python tiêu chuẩn "khác".
Mike Housky

cảm ơn bạn, điều này là rất tốt vì nó không sử dụng bất kỳ thư viện và rõ ràng để hiểu được toán học đằng sau nó
grepit

17

mà không sử dụng bất kỳ hàng nhập khẩu nào

math.sqrt (x)

có thể được thay thế bằng

x ** .5

mà không sử dụng numpy.dot (), bạn phải tạo hàm chấm của riêng mình bằng cách sử dụng tính năng hiểu danh sách:

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

và sau đó nó chỉ là một vấn đề đơn giản của việc áp dụng công thức tương tự cosin:

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

15

Tôi đã làm điểm chuẩn dựa trên một số câu trả lời trong câu hỏi và đoạn mã sau được cho là lựa chọn tốt nhất:

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)

Kết quả làm cho tôi ngạc nhiên rằng việc thực hiện dựa trên scipykhông phải là nhanh nhất. Tôi đã lập hồ sơ và thấy rằng cosine trong scipy mất rất nhiều thời gian để truyền một vector từ danh sách python sang mảng numpy.

nhập mô tả hình ảnh ở đây


làm thế nào để bạn chắc chắn rằng điều này là nhanh nhất?
Jeru Luke

@JeruLuke Tôi đã dán liên kết kết quả điểm chuẩn của mình vào đầu câu trả lời: gist.github.com/mckelvin/…
McKelvin

10
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)

Bạn có thể làm tròn nó sau khi tính toán:

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

Nếu bạn muốn nó thực sự ngắn, bạn có thể sử dụng một lớp lót này:

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)))

Tôi đã thử mã này, và nó có vẻ không hoạt động. Tôi đã thử nó với v1 là [2,3,2,5]và v2 [3,2,2,0]. Nó trả về với 1.0, như thể chúng giống hệt nhau. Bất cứ ý tưởng gì là sai?
Rob Alsod

Bản sửa lỗi đã hoạt động ở đây. Công việc tốt! Xem bên dưới để biết cách tiếp cận xấu hơn nhưng nhanh hơn.
Mike Housky

Làm thế nào có thể điều chỉnh mã này nếu độ tương tự phải được tính trong một ma trận chứ không phải cho hai vectơ? Tôi nghĩ rằng tôi lấy một ma trận và ma trận chuyển vị thay vì vectơ thứ hai, bit dường như không hoạt động.
sinh viên

bạn có thể sử dụng np.dot (x, yT) để đơn giản hơn
user702846 Ngày

3

Bạn có thể làm điều này bằng Python bằng cách sử dụng hàm đơn giản:

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)

3
Đây là một triển khai văn bản của cosine. Nó sẽ cho đầu ra sai đối với đầu vào số.
alvas

Bạn có thể giải thích lý do tại sao bạn sử dụng thiết lập trong dòng "giao nhau = set (vec1.keys ()) & set (vec2.keys ())".
Ghos3t

Ngoài ra, chức năng của bạn dường như đang mong đợi bản đồ nhưng bạn đang gửi cho nó danh sách các số nguyên.
Ghos3t

3

Sử dụng numpy so sánh một danh sách các số với nhiều danh sách (ma trận):

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]

1

Bạn có thể sử dụng hàm đơn giản này để tính toán độ tương tự của cosin:

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])))

1
tại sao lại phát minh ra bánh xe?
Jeru Luke

@JeruLuke có thể đưa ra một câu trả lời "độc lập", những người mà không cần thêm nhập khẩu (s) (và có thể chuyển đổi từ danh sách để numpy.array hoặc một cái gì đó như thế)
Marco Ottina

1

Nếu bạn đã sử dụng PyTorch , bạn nên sử dụng triển khai CosineSimilarity của họ .

Giả sử bạn có hai nchiều numpy.ndarray, v1v2, tức là hình dạng của chúng là cả hai (n,). Đây là cách bạn có được sự giống nhau về cosine của chúng:

import torch
import torch.nn as nn

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

Hoặc giả sử bạn có hai chữ numpy.ndarrays w1w2, có hình dạng của cả hai (m, n). Phần sau sẽ cung cấp cho bạn danh sách các điểm giống nhau về cosin, mỗi điểm là độ tương đồng về cosin giữa một hàng trong w1và hàng tương ứng trong w2:

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

-1

Tất cả các câu trả lời đều tuyệt vời cho những trường hợp bạn không thể sử dụng NumPy. Nếu bạn có thể, đây là một cách tiếp cận khác:

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)

Cũng nên nhớ về EPSILON = 1e-07việc đảm bảo sự phân chia.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.