機械学習・自然言語処理の勉強メモ

学んだことのメモやまとめ

gensimに依存しない単語の類似度計算

はじめに



前にgensimによる単語の類似度について書きました。
kento1109.hatenablog.com
この手の記事はググればいっぱい出てくるので、gensimでモデルを作って単語の類似度を計算するのは難しくないと思います。

ただ、LSTMなどで学習した後の単語の分散表現の類似度を測定したい場合に、そのためだけにわざわざgensimのモデルを構築するのは面倒ですし、無駄かなと思います。

ある単語と別の単語の類似度を測定するだけの場合、そのベクトル同士で測定すれば良いですが、gensimmost_similar関数のような「ある単語のベクトルに近いベクトルの単語をN個取ってくる」機能を実現する場合は少し実装が必要です。
1単語ずつループで回してコサイン類似度を計算する、なんてしていては計算速度が遅くなります。

そこで、scipyとnumpyのライブラリを活用した関数を考えました。

僕が考えたというより、stack overflowで書いている人の内容を整理しただけですが、、
stackoverflow.com

実装

scipyには、cdistという便利な関数があります。
scipy.spatial.distance.cdist — SciPy v1.2.1 Reference Guide

これは、入力のペアの各組み合わせの距離を計算してくれます。

早速見てみます。

import numpy as np
from scipy.spatial import distance

x = np.random.uniform(low=-1.0, high=1.0, size=[5, 5])
"""
array([[-0.63429221, -0.34524962,  0.33029203, -0.47351972, -0.53888627],
       [ 0.53165424, -0.70032725, -0.91572034, -0.71147304, -0.75343722],
       [ 0.41431408, -0.67125034,  0.59739731,  0.48297114,  0.78140535],
       [-0.62946089, -0.74813722,  0.65404081,  0.08913251,  0.33907134],
       [ 0.00564964, -0.79616936,  0.17679241,  0.89239204, -0.76633555]])
"""

distances = distance.cdist(x, x, "cosine")
"""
array([[0.        , 0.80326347, 1.33463697, 0.50618473, 0.79103524],
       [0.80326347, 0.        , 1.35661241, 1.36218033, 0.85455544],
       [1.33463697, 1.35661241, 0.        , 0.43272935, 0.75444092],
       [0.50618473, 1.36218033, 0.43272935, 0.        , 0.69980619],
       [0.79103524, 0.85455544, 0.75444092, 0.69980619, 0.        ]])
"""

この関数は「距離」を計算する関数であり、「類似度」を計算する場合は「1-距離」とする必要があります。

0番目のベクトルとそれぞれのベクトルとの類似度を計算する場合は次のように書きます。

distances = distance.cdist([x[0]], x, "cosine")[0]
similarity = 1 - distances
# array([ 1.        ,  0.19673653, -0.33463697,  0.49381527,  0.20896476])

類似度計算は後で行うとして、とりあえず距離のままでいきます。
最も近い距離を計算するためには、np.argmin()を使います。

min_distance = np.argmin(distances)
# 0

最も距離が近いのは自分自身なので、「0」が返ってきます。
これでは意味がないので、以下のように工夫します。

n = 3
target_index = distances.argsort()[1:n+1]
# array([3, 4, 1])

距離が近い1番目~N+1番目までのベクトルを取ってきます。
これにより、自身を除くN個のベクトルが取得可能となりました。

最後に取得したインデックスのベクトルとの類似度を計算します。

target_distance = distances[target_index]
target_similarity = 1 - target_distance
# [0.49381527 0.20896476 0.19673653]

最後にまとめて関数化します。

def most_similar(idx, X, n=10):
    distances = distance.cdist([X[idx]], X, "cosine")[0]
    target_index = distances.argsort()[1:n+1]
    target_distance = distances[target_index]
    target_similarity = 1 - target_distance
    print(target_similarity)

most_similar(idx=0, X=x, n=3)
# [0.49381527 0.20896476 0.19673653]

このままでは対応するインデックスなどは取れませんが、そこは簡単に修正できれば対応できると思います。