KNeighborsClassifierで距離の代わりにコサイン類似度を使用する

k近傍法により分類問題を解く際は、近傍点k個の多数決をとってラベルの判定を行います。この近傍点というのは、一般的には点間の距離を用いられることが多いです。この記事では、距離ではなくコサイン類似度を使ったk近傍法をpythonのサンプルコードとともに解説します。

以前の記事で、scikit learnのKNeighborsClassifierの引数について解説した記事もありますので、そちらも合わせてご覧ください。

コサイン類似度を使った近傍点の算出

簡単に実装するのであれば、以下のように実装することができます。

from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics.pairwise import cosine_similarity
from scipy.sparse import coo_matrix

def kneighbors_cos_ex():
    X = [[0, 10, 20], [15, 25, 35], [33, 66, 99], [44, 55, 66]]
    y = [0, 0, 1, 1]
    sample = [5, 10, 15]

    distance_function = lambda x, y: cosine_similarity([x], [y])[0, 0] + 1

    # metricに直接distance_fancutionを渡す場合
    neigh = KNeighborsClassifier(n_neighbors=3, metric=distance_function)
    neigh.fit(X, y)
    print(neigh.predict([sample]))
    print(neigh.kneighbors([sample]))

    # >>> [0]
    # >>> (array([[1.95618289, 1.97463185, 1.9974149 ]]), array([[0, 3, 1]]))

X, y, sampleはそれぞれデモ用に作成した訓練用座標、ラベル、推論用座標です。

また、コサイン類似度の計算にはscikit learnのcosine_similarityを使用しています。コサイン類似度を計算した後に1を足していますが、これはすべての値を正にするためです。k近傍法は距離を扱っているため負の値は受け付けません。このようにして作成したdistance_functionをKNeighborsClassifierのmetric引数に指定することにより、近傍点の算出にコサイン類似度を用いることができます。

以前の記事で距離を事前に計算しておく方法を解説しました。実際にk近傍法を使用する時は大量のデータを訓練データとして使用することが多いと思いますので、事前にコサイン類似度を計算しておき保存しておくと毎度毎度計算しなおす必要がなくなります。事前に計算する場合は以下のようなコードになります。

def kneighbors_cos_ex():
    X = [[0, 10, 20], [15, 25, 35], [33, 66, 99], [44, 55, 66]]
    y = [0, 0, 1, 1]
    sample = [5, 10, 15]

    distance_function = lambda x, y: cosine_similarity([x], [y])[0, 0] + 1

    # metric="precomputed"を使用する場合
    distances = []
    for i in range(len(X)):
        distance = []
        for j in range(len(X)):
            d = distance_function(X[i], X[j])
            distance.append(d)
        distances.append(distance)
    
    # 計算に時間がかかる場合は、適宜distancesを保存しておく.
    coo = coo_matrix(distances)
    
    neigh = KNeighborsClassifier(n_neighbors=3, metric="precomputed")
    neigh.fit(coo, y)

    sample_point = [sample]
    pred_distances = []
    for i in range(len(sample_point)):
        distance = []
        for j in range(len(X)):
            d = distance_function(sample_point[i], X[j])
            distance.append(d)
        pred_distances.append(distance)

    s = coo_matrix(pred_distances)
    print(neigh.predict(s))
    print(neigh.kneighbors(s))

    # >>> [0]
    # >>> (array([[1.95618289, 1.97463185, 1.9974149 ]]), array([[0, 3, 1]], dtype=int32))

k近傍法はモデルの適用範囲の判定にも使用することができます。本記事の内容もコサイン類似度によるモデルの適用範囲の判定に応用することができます。そちらの内容は今後執筆予定となります。