【python】コサイン類似度は高校数学の知識で理解できます!|自然言語処理

自然言語処理の分野でよく使われる、「コサイン類似度」について解説しようと思います。

タイトルに書いてあるとおり、高校数学のベクトルの知識があればスムーズに理解できると思います。

ブックマーク推奨です!

Cos類似度(cosign similarity)

コサイン類似度とは、「2つのベクトルがどれだけ近い(似ている)のかを示す指標」です。

もう少し詳細に説明するとコサイン類似度は、ベクトル空間モデルにおいて、2つのベクトルの類似度を測るための指標です。

つまり、2つの文書や単語のベクトルがどれだけ方向が近いかを測定するための方法です。

コサイン類似度は、-1から1の範囲で値をとり、1に近いほど類似していることを示し、-1に近いほど異なっていることを示します。また、0に近い場合は類似性が低いことを示します。

$$\frac{z\sum (A_{i}・B_{i})}{\sqrt{\sum A{i}^2} \sqrt{\sum B{i}^2}}$$

計算式は以上の通りです。各ベクトルの平方和の積に根号をとったものが分母です。分子は各ベクトルの積の和です。

2つのベクトルが重なっていれば、分母と分子で値が同じになり、コサイン類似度は1を取ります。逆に、正反対を向いていれば、コサイン類似度は-1を取ります。

\(cosθは 90<θ<π\) であれば、負の値を取るというのは、高校数学で習いましたね。

2つのベクトルの間が90度を超えるとコサイン類似度は負の値を取るということです。

ちょっと相関係数のような形をしていますね。

2次元を例に挙げてみてみましょう。

ベクトルが3つあります。
それぞれを比べた時に、コサイン類似度が最も高いのはどの組み合わせでしょうか。

A(2,8)
B(5,6)
C(6,3)
例:2次元の話

上のような図が書けました。

では、AとB/BとC/AとCそれぞれのコサイン類似度を求めてみましょう。

AとBのコサイン類似度

上の式に当てはめると、このようになります。

$$\frac{2×5+8×6}{\sqrt{2^2+8^2} \sqrt{5^2+6^2}}$$

結果は、電卓等で計算してみてください。約0.90です。

やり方は同じなのでBとC/AとCは結果のみ書きます。

BとCのコサイン類似度:0.92

AとCのコサイン類似度:0.65

つまり、3つのベクトルの中ではBとCが一番似ている組み合わせです。

これは別に3次元でも同じやり方ができます。

自然言語処理

さて、実際ベクトル同士の類似度がわかると、何が嬉しいのでしょうか。

まず自然言語処理の解説をします。自然言語処理とは、「人間が使う言葉をプログラムが理解するための処理」です。

例えば、チャットボットを思い浮かべてほしいです。

人間「なんか、静かだけど、結構イケてる感じのレストランがいいです」

チャットボット「?」

機械は自然言語をそのまま理解することはできません。

以下の手順で、類似文書検索タスクを行います。

1:形態素解析

文章を文節や単語に区切ります。

入力文章「なんか | 静か | だけ | ど | 、| 結構 | イケてる | 感じ | の | レストラン | が | 良い | です | 。」

形態素解析について詳しく知りたい方は、【自然言語処理】形態素解析で文章を単語に分けてみましょう。をご覧ください。

2:Bag of Words

各文書のうち、特定の単語が何回出たか(頻度)をベクトルで表す。

3:コサイン類似度を測る

先ほどの、「なんか、静かだけど、結構イケてる感じのレストランが良いです。」を入力文書ベクトルとしたとき、下の図では口コミ1ベクトルとコサイン類似度が高いことがわかりました。そこで、口コミ1に掲載されているレストランをレコメンドしてあげよう、ということになります。

単語の出現頻度だけでなく、意味や文脈まで考慮すると、かなり正確なレコメンドができそうです。

コサイン類似度は、元のデータ数がそこまでなくても、類似文書検索ができる便利な指標です。

大規模言語モデル|LLMでも使われる?

コサイン類似度は、GPTやBert、Llamaを代表とする言語モデルにも使われています。

言語モデルを簡単に解説しておくと、トークンの生成確率\(p(x_1,x_2,…)\)を連鎖律で分解し、条件付き確率の積の形で表したモデルのことです。

以下のように、一つ前までのトークンを条件とした時にどのトークンの確率が高いかを予測し、最も高いものを生成しています。(これを繰り返す)

$$arg max p(x_L|x_1,x_2,….,X_{L-1})$$

特に、Retrieval-basedモデルだと、学習データだけではなく外部のトークンを利用するので、我々が入力したテキスト「2023年9月1日の天気はなんですか?」と、外部から引っ張ってきたテキストとの類似度を測ります。

→この時に、埋め込み化(エンべディング)してベクトルとなったトークン同士の類似度を測り、入力テキストと最も類似度が高い外部テキストを回答の参考にします。

この類似度の測り方は、tf-idfのような古典的な方法もありますが、最近の手法では深層学習ベースの埋め込みが主流となっています。

tf-idfについては以下で語っています。

【N-gram】テキストをベクトルで表現するには | 自然言語処理

その他の例

例えば、ユーザーの好みにぴったりのクーポンを出したいとします。

事前に、「あっさり系?」→「Yes or No」などの選択肢を複数選んでもらいます。

Yesなら1、Noなら0という2値をとるベクトルを下のように作ってみます。

あっさり系?値段は安め?静かな店内?中華?洋風?和風?
110100
Aさんの今日の好み

ここで、A(1,1,0,1,0,0)というベクトルが出来ました。

そして事前に用意してあるクーポンの特徴をベクトルで表します。

あっさり系?値段は安め?静かな店内?中華?洋風?和風?
101100
クーポンXの特徴

このクーポンのベクトルは、\(X(1,0,1,1,0,0)\)です。

他にも、クーポンのベクトルを作り、ベクトルAとのコサイン類似度が高い上位5番目くらいまでのクーポンをレコメンドすれば、「データ量が少なく、アイテムの特徴量のみで」簡単なレコメンド機能が作れます。

*料理のジャンルは重要度が高いので、「ジャンルが違っていればコサイン類似度は2倍下がる」などの工夫次第では精度を上げることも可能です。

このように情報検索、自然言語処理、機械学習などの分野で幅広く使用されています。

まとめると、以下のような応用例があります。

情報検索:検索クエリと文書の類似度を計算し、検索結果をランキングする。

文書分類:文書をベクトル化し、各カテゴリーとの類似度を計算して、文書を分類する。

類似商品検索:商品の特徴をベクトル化し、類似商品を検索する。

類語検索:単語のベクトルを計算し、類似する単語を検索する。

クラスタリング:文書のベクトルを計算し、類似する文書をグループ化する。

教師なし学習

今回のコサイン類似度は、機械学習の中でも、「強化学習」や「教師あり学習」と同列の概念である「教師なし学習」に分類されます。

教師なし学習とは、入力データのみを与えて、データの中に内在するパターンを導出するアルゴリズムを特徴としています。正しい出力が紐づいていないので、「教師データがない」ということになります。ユーザーのクラスタリングやアイテムのレコメンドなどの施策に利用されます。

入力データとしては、ユーザーの行動特徴などが入ります。

教師データが用意できない場面では、非常に有効な方法ですが、正解がないためモデルの「精度」を決めることはできません。

そのグループの分類が妥当かどうかは、人間の知見に委ねられることになります。

学習がうまくいっているかどうかを評価することが難しく、発展途上の技術といえます。

代表的なアルゴリズムは、k-means法です。

【非階層型】K-means法でクラスタリングをしてみましょう。

CODE|python

では、コサイン類似度をpython ライブラリのnumpyを使って計算してみます。

from numpy import dot
from numpy.linalg import norm
nyuryoku = [0, 1, 3, 3, 5]
kutikomi = [3, 2, 0, 1, 2]
result = dot(nyuryoku, kutikomi)/(norm(nyuryoku)*norm(kutikomi))
print(result)

コードで言うと、nyuryoku = [0, 1, 3, 3, 5]が、人間が入力した文書から作ったクエリベクトルです。

それに対して、kutikomi = [3, 2, 0, 1, 2]が、もともとあった口コミ(店舗情報と結びついている)をクエリベクトルにしたものです。

出力結果は、0.5330017908890262となりました。

さて、もう少し応用としてgensimを使った自然言語処理をしてみます。

答えは0.5となりました。

from sklearn.metrics.pairwise import cosine_similarity
import gensim.corpora as corpora

# 2つの文章を定義
text1 = "I love playing soccer"
text2 = "I enjoy playing football"

# 文章を単語に分割
text1 = text1.lower().split()
text2 = text2.lower().split()

# 辞書を作成
dictionary = corpora.Dictionary([text1, text2])

# 文章をベクトルに変換
vec1 = dictionary.doc2bow(text1)
vec2 = dictionary.doc2bow(text2)

# コサイン類似度を計算
vec1 = gensim.matutils.sparse2full(vec1, len(dictionary))
vec2 = gensim.matutils.sparse2full(vec2, len(dictionary))
similarity = cosine_similarity([vec1], [vec2])[0][0]
print(similarity)

このコードでは、sklearnのcosine_similarity関数を使用して、vec1とvec2のコサイン類似度を計算しています。

結果が出力され、その結果は-1から1の範囲で、1に近いほど文章の類似性が高いという意味になります。

gensimについては、こちらでも解説をしています。

【自然言語処理】gensimを使った単語の分散表現|python

まとめ

コサイン類似度(Cosine Similarity)は、2つのベクトル間の類似性を計算するために使用される手法です。

コサイン類似度は、2つのベクトルがどの程度似ているかを示す数値を返します。

値の範囲は-1から1で、1に近ければ似ていると判断され、-1に近ければ似ていないと判断されます。

コサイン類似度は、2つのベクトルのなす角度を計算し、それを元に類似性を算出します。

なす角度が小さいほど、2つのベクトルは似ていると判断されます。

コサイン類似度は、自然言語処理、推薦システムなどで使用されます。

特に文章のセマンティック類似性を計算するために、文章をベクトルに変換し、それらのベクトル間のコサイン類似度を計算することができます。

具体的には、それぞれの文章を単語のベクトルに変換し、それらを平均したものを文章のベクトルとして扱うことで、文章間の類似性を計算することができます。

補足:コサイン類似度の代替案はあるのか

コサイン類似度は、スケール不変性があるため、多くの場合有効に使用できます。

しかし、ベクトルの方向が全く異なる場合、類似度が低くなることがあるため、代替手段が必要になる場合があります。

例えば、以下のような方法があります。

ピアソンの積率相関係数:ベクトルの相関係数を計算することによって、類似度を測定する方法。

こちらは、想像しやすいと思います。以下のコンテンツで実装しております。

【外れ値に対処】順位相関係数と相関係数の違いについて | python

【SHAP】特徴量重要度や寄与度、限界効果を意思決定者にうまく伝えたい話|python

Jaccard係数:2つの集合の共通部分を全体で割ることによって、類似度を測定する方法。

Dice係数:2つの集合の共通部分を各集合のサイズの和で割ることによって、類似度を測定する方法。

これらの手法は、コサイン類似度よりも異なる特徴を捉えることができます。

そのために、コサイン類似度よりも有効な場合があります。しかし、テキストの場合は、一般的にコサイン類似度が最も効果的な手法とされています。

FOLLOW ME !