2つの数値リスト間の余弦の類似性


118

2つのリスト間のコサイン類似度を計算する必要があります。たとえば、リスト1であるdataSetIリスト2であるとしdataSetIIます。numpyや統計モジュールなどは使用できません。私は共通のモジュール(数学など)を使用する必要があります(そして、費やす時間を削減するために、可能な限り最小のモジュールを使用します)。

dataSetIis [3, 45, 7, 2]dataSetIIis だとしましょう[2, 54, 13, 15]。リストの長さは常に同じです。

もちろん、コサイン類似度は0と1の間であり、そのために、で3番目または4番目の10進数に丸められformat(round(cosine, 3))ます。

よろしくお願いします。


29
この宿題の質問から魂を押しつぶして、それを素敵な一般的な参照の質問にする方法が大好きです。OPは「私はnumpyを使用できない、私は歩行者の数学の道を行かなければならない言っています、そしてトップの答えは「あなたはscipyを試すべきです、それはnumpyを使用します」です。SOメカニックは人気のある質問にゴールドバッジを付与します。
Nikana Reklawyks

1
Nikana Reklawyks、それは素晴らしい点です。StackOverflowでその問題がますます頻繁に発生しています。また、モデレーターが私の質問の独自性を理解するのに時間をかけなかったため、以前のいくつかの質問の「重複」としてマークされたいくつかの質問がありました。
LRK9 2016年

@NikanaReklawyks、これは素晴らしいです。彼のプロフィールを見てください。それは、SOの上位.01%貢献者の1人のストーリーを物語っています。
Nathan Chappell

回答:


174

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)

122

に基づく別のバージョン 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 2017

15
ちなみに、このソリューションは私のシステムではを使用するよりもはるかに高速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
1つの次元配列を入力データとして渡すことはsklearnバージョン0.17で廃止され、0.19でValueErrorが発生することを思い出させてください。
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もしかして?しかし、その結果はそれが戻るということをどういう意味ですか?その新しい2次元配列であり、コサイン類似性ではありません。
Isbister 2017年

10
それをもう1つのブラケットで囲みますcosine_similarity([[1, 0, -1]], [[-1,-1, 0]])
Ayush 2017

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

これは、要素を1つずつ抽出するCのようなノイズを通過しますが、配列の一括コピーを行わず、1つのforループで重要なすべての処理を実行し、1つの平方根を使用します。

ETA:print呼び出しを関数に更新しました。(オリジナルは3.3ではなくPython 2.7でした。現在のバージョンはPython 2.7で実行され、from __future__ import print_functionステートメントを実行しています。)出力はどちらの方法でも同じです。

3.0 GHz Core 2 Duo上のCPYthon 2.7.3:

>>> 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倍高速です。


2
cosine_measureこの場合は何ですか?
MERose 2018年

1
@MERose:cosine_measurecosine_similarity単純に同じ計算の異なる実装されています。両方の入力配列を「単位ベクトル」にスケーリングし、内積を取ることと同等です。
Mike Housky、2018年

3
私は同じことを推測したでしょう。しかし、それは役に立ちません。2つのアルゴリズムの時間比較を提示しますが、それらの1つのみを提示します。
MERose 2018年

@MERoseおお、ごめんなさい。 cosine_measure以前にpkacprzakによって投稿されたコードです。このコードは、「その他」のすべての標準Pythonソリューションの代替手段でした。
Mike Housky、2018年

ありがとう、これはライブラリを使用していないので素晴らしいです。その背後にある数学を理解することは明らかです
grepit

17

インポートを使用せずに

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リストからnumpy配列にベクトルをキャストするのに長い時間がかかることを発見しました。

ここに画像の説明を入力してください


これが最速であるとどうして確信していますか?
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まったく同じであるかのように、で戻ります。何が間違っているのでしょうか?
ロブアルド2013

修正はここで機能しました。良くやった!醜いがより速いアプローチについては、以下を参照してください。
Mike Housky 2013

2つのベクトルではなく行列内で類似度を計算する必要がある場合、このコードをどのように適応させることができますか?私は2番目のベクトルの代わりに行列と転置行列を取ると思っていましたが、それは機能していないようです。
学生、

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 2016年

「intersection = set(vec1.keys())&set(vec2.keys())」という行でsetを使用した理由を説明できますか。
Ghos3t

また、あなたの関数はマップを期待しているようですが、整数のリストを送信しています。
Ghos3t

3

numpyを使用して、数値の1つのリストを複数のリスト(マトリックス)と比較します。

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

1

既にPyTorchを使用している場合は、それらのCosineSimilarity実装を使用する必要があります。

2 n次元numpy.ndarrayの、v1およびv2、つまりそれらの形状が両方であるとし(n,)ます。ここでは、コサインの類似性を取得する方法を示します。

import torch
import torch.nn as nn

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

または、2つnumpy.ndarrayw1とがあり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.