ความคล้ายคลึงกันของโคไซน์ระหว่าง 2 รายการหมายเลข


119

ฉันจำเป็นต้องใช้ในการคำนวณความคล้ายคลึงกันโคไซน์ระหว่างสองรายการสมมติว่าตัวอย่างเช่นรายการที่ 1 ซึ่งเป็นdataSetIรายการที่ 2 dataSetIIซึ่งเป็น ฉันไม่สามารถใช้สิ่งใด ๆ เช่นnumpyหรือโมดูลสถิติ ฉันต้องใช้โมดูลทั่วไป (คณิตศาสตร์ ฯลฯ ) (และโมดูลที่น้อยที่สุดเท่าที่จะทำได้เพื่อลดเวลาที่ใช้)

สมมติว่าdataSetIเป็น[3, 45, 7, 2]และเป็นdataSetII [2, 54, 13, 15]ความยาวของรายการจะเท่ากันเสมอ

แน่นอนความคล้ายคลึงกันโคไซน์อยู่ระหว่าง0 และ 1format(round(cosine, 3))และเพื่อประโยชน์ของมันก็จะกลมกับทศนิยมที่สามหรือสี่ด้วย

ขอบคุณมากล่วงหน้าสำหรับความช่วยเหลือ


29
ฉันชอบวิธีที่ SO บดขยี้วิญญาณออกจากคำถามการบ้านนี้เพื่อให้เป็นข้อมูลอ้างอิงทั่วไปที่ดี OP บอกว่า " ฉันใช้numpyไม่ได้ฉันต้องไปทางคณิตศาสตร์สำหรับคนเดินเท้า" และคำตอบอันดับต้น ๆ คือ "คุณควรลอง scipy มันใช้ numpy" กลศาสตร์ SO มอบป้ายทองให้กับคำถามยอดนิยม
Nikana Reklawyks

1
Nikana Reklawyks นั่นคือจุดที่ยอดเยี่ยม ฉันมีปัญหานั้นบ่อยขึ้นเรื่อย ๆ กับ StackOverflow และฉันมีคำถามหลายข้อที่ทำเครื่องหมายว่า "ซ้ำ" ของคำถามก่อนหน้านี้เนื่องจากผู้ดูแลไม่ได้ใช้เวลาทำความเข้าใจว่าอะไรทำให้คำถามของฉันไม่เหมือนใคร
LRK9

@NikanaReklawyks นี้ดีมาก ดูโปรไฟล์ของเขามันบอกเล่าเรื่องราวของผู้ร่วมให้ข้อมูลอันดับหนึ่งของ SO. 01% คุณรู้หรือไม่?
Nathan Chappell

คำตอบ:


175

คุณควรพยายาม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)

125

เวอร์ชันอื่นที่ใช้numpyเฉพาะ

from numpy import dot
from numpy.linalg import norm

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

3
ชัดเจนมากตามคำจำกัดความ แต่อาจnp.inner(a, b) / (norm(a) * norm(b))จะดีกว่าที่จะเข้าใจ dotจะได้ผลลัพธ์เช่นเดียวinnerกับเวกเตอร์
Belter

15
FYI โซลูชันนี้เร็วกว่าระบบของฉันscipy.spatial.distance.cosineอย่างมาก
Ozzah

ความคล้ายคลึงกันของโคไซน์ของ @ZhengfangXin มีตั้งแต่ -1 ถึง 1 ตามความหมาย
dontloo

2
ยิ่งสั้นลง:cos_sim = (a @ b.T) / (norm(a)*norm(b))
สถิติการเรียนรู้ตามตัวอย่าง

นี่เป็นแนวทางที่เร็วที่สุดเมื่อเทียบกับวิธีอื่น ๆ
Jason Youn

73

คุณสามารถใช้เอกสาร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]])

21
ขอเตือนว่าการส่งอาร์เรย์มิติเดียวเป็นข้อมูลอินพุตเลิกใช้แล้วใน sklearn เวอร์ชัน 0.17 และจะเพิ่มค่า ValueError ใน 0.19
Chong Tang

4
อะไรคือวิธีที่ถูกต้องในการดำเนินการกับ sklearn ที่ได้รับคำเตือนการเลิกใช้งานนี้
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 เดาว่าคุณหมายถึง? แต่ผลลัพธ์นั้นหมายความว่าอย่างไร? มันเป็นอาร์เรย์ 2d ใหม่ไม่ใช่ความคล้ายคลึงกันของโคไซน์
Isbister

10
แนบด้วยอีกหนึ่งวงเล็บcosine_similarity([[1, 0, -1]], [[-1,-1, 0]])
Ayush

34

ฉันไม่คิดว่าประสิทธิภาพจะสำคัญมากที่นี่ แต่ฉันไม่สามารถต้านทานได้ ฟังก์ชั่น 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 ของการแยกองค์ประกอบทีละรายการ แต่ไม่มีการคัดลอกอาร์เรย์จำนวนมากและทำให้ทุกอย่างที่สำคัญเสร็จสิ้นในครั้งเดียวสำหรับลูปและใช้สแควร์รูทเดียว

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

ดังนั้นวิธี unpythonic จะเร็วขึ้นประมาณ 3.6 เท่าในกรณีนี้


2
คืออะไรcosine_measureในกรณีนี้หรือไม่?
MERose

1
@MERose: cosine_measureและcosine_similarityเป็นการนำการคำนวณเดียวกันไปใช้งานที่แตกต่างกัน เทียบเท่ากับการปรับขนาดอาร์เรย์อินพุตทั้งสองเป็น "เวกเตอร์หน่วย" และนำผลิตภัณฑ์ดอท
Mike Housky

3
ฉันคงเดาได้เหมือนกัน แต่มันไม่เป็นประโยชน์ คุณนำเสนอการเปรียบเทียบเวลาของสองอัลกอริทึม แต่นำเสนอเพียงหนึ่งในนั้น
เมโรเซ่

@MERose โอ้ขอโทษ cosine_measureคือรหัสที่โพสต์ก่อนหน้านี้โดย pkacprzak รหัสนี้เป็นอีกทางเลือกหนึ่งของโซลูชัน Python มาตรฐานทั้งหมด "อื่น ๆ "
ไมค์ Housky

ขอบคุณนี่ยอดเยี่ยมมากเนื่องจากไม่ได้ใช้ไลบรารีใด ๆ และเป็นที่ชัดเจนที่จะเข้าใจคณิตศาสตร์ที่อยู่เบื้องหลัง
grepit

18

โดยไม่ต้องใช้การนำเข้าใด ๆ

math.sqrt (x)

สามารถแทนที่ด้วย

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

15

ฉันทำการเปรียบเทียบจากคำตอบหลายข้อในคำถามและเชื่อว่าตัวอย่างต่อไปนี้เป็นตัวเลือกที่ดีที่สุด:

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 ไปยังอาร์เรย์จำนวนนับ

ใส่คำอธิบายภาพที่นี่


คุณแน่ใจได้อย่างไรว่านี่เร็วที่สุด?
Jeru Luke

@JeruLuke ฉันได้วางลิงค์ของผลลัพธ์เกณฑ์มาตรฐานของฉันไว้ที่จุดเริ่มต้นของคำตอบ: 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)

คุณสามารถปัดเศษได้หลังจากคำนวณ:

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

ฉันลองใช้รหัสนี้แล้วดูเหมือนจะไม่ได้ผล ฉันพยายามมันมีความเป็นอยู่ v1 [2,3,2,5]และ v2 [3,2,2,0]เป็นอยู่ มันกลับมาพร้อมกับ1.0ราวกับว่ามันเหมือนกันทุกประการ มีความคิดอะไรผิด?
Rob Alsod

การแก้ไขใช้งานได้ที่นี่ งานที่ดี! ดูวิธีการที่น่าเกลียดกว่า แต่เร็วกว่าด้านล่าง
Mike Housky

เป็นไปได้อย่างไรที่จะปรับรหัสนี้หากต้องคำนวณความคล้ายคลึงกันภายในเมทริกซ์ไม่ใช่สำหรับเวกเตอร์สองตัว ฉันคิดว่าฉันใช้เมทริกซ์และเมทริกซ์ทรานสโพสิตแทนเวกเตอร์ที่สองบิตมันดูเหมือนจะไม่ทำงาน
นักเรียน

คุณสามารถใช้ np.dot (x, yT) เพื่อทำให้ง่ายขึ้น
user702846

3

คุณสามารถทำได้ใน 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)

3
นี่คือการใช้ข้อความของโคไซน์ มันจะให้เอาต์พุตที่ไม่ถูกต้องสำหรับอินพุตตัวเลข
alvas

คุณช่วยอธิบายได้ไหมว่าทำไมคุณถึงใช้ set ในบรรทัด "จุดตัด = set (vec1.keys ()) & set (vec2.keys ())"
Ghos3t

นอกจากนี้ฟังก์ชั่นของคุณดูเหมือนจะคาดหวังแผนที่ แต่คุณกำลังส่งรายการจำนวนเต็ม
Ghos3t

3

การใช้ 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]

1

คุณสามารถใช้ฟังก์ชันง่ายๆนี้เพื่อคำนวณความคล้ายคลึงกันของโคไซน์:

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
ทำไมต้องคิดค้นล้อใหม่?
Jeru Luke

@JeruLuke อาจจะให้คำตอบแบบ "โดดๆ" คำตอบที่ไม่ต้องการการนำเข้าเพิ่มเติม (และอาจจะแปลงจากรายการเป็น numpy.array หรืออะไรทำนองนั้น)
Marco Ottina

0

หากคุณเกิดขึ้นจะใช้PyTorchแล้วคุณควรไปกับพวกเขาดำเนินการ CosineSimilarity

สมมติว่าคุณมีสองnมิติnumpy.ndarrays, v1และเช่นรูปร่างของพวกเขาทั้งสองv2 (n,)นี่คือวิธีที่คุณได้รับความคล้ายคลึงกันของโคไซน์:

import torch
import torch.nn as nn

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

หรือสมมติว่าคุณมีสองnumpy.ndarrays w1และที่มีรูปทรงที่มีทั้งw2 (m, n)สิ่งต่อไปนี้ทำให้คุณได้รับรายการความคล้ายคลึงกันของโคไซน์โดยแต่ละค่าคือความคล้ายคลึงกันระหว่างแถวในw1และแถวที่สอดคล้องกันในw2:

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

-1

คำตอบทั้งหมดเหมาะสำหรับสถานการณ์ที่คุณไม่สามารถใช้ 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จะต้องรักษาความปลอดภัยให้กับฝ่ายนั้นด้วย

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.