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

コサイン類似度(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 \thetaは 90<\theta<\pi \) であれば、負の値を取るというのは、高校数学で習いましたね。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次元でも同じやり方ができます。


補足|教師なし学習とは

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

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

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

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

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

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

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

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


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

基本的にテキストデータの分析においては、一般的にコサイン類似度が最も効果的な手法の一つとされています。テキストデータがしばしば高次元で疎なベクトルとして表現され、コサイン類似度がこのような特性に適しているためです。

しかし、いくつかの制限や欠点も存在します。

注意点
  1. 次元の問題: 高次元空間では、すべてのベクトルが互いにほぼ直交する傾向があり、コサイン類似度が常に小さくなる「次元の呪い」が発生することがあります。
  2. 負の相関の扱い: コサイン類似度は${-1}$から${1}$の範囲をとりますが、負の相関を適切に扱えない場合があります。
  3. 絶対的な大きさの無視: ベクトルの方向のみを考慮し、絶対的な大きさを無視するため、場合によっては重要な情報を見逃す可能性があります。
  4. 非線形関係の捕捉: 線形の関係性しか捉えられないため、データ間の複雑な非線形関係を見逃す可能性があります。

上記の制限により、ベクトルの方向が全く異なる場合や、より複雑な関係性を捉える必要がある場合には、以下のような代替手段が必要になることがあります。

ピアソンの積率相関係数: ベクトルの線形相関を計算することによって、類似度を測定する方法。コサイン類似度と異なり、データの平均からの偏差を考慮します。

$${r = \frac{\sum_{i=1}^{n} (x_i – \bar{x})(y_i – \bar{y})}{\sqrt{\sum_{i=1}^{n} (x_i – \bar{x})^2} \sqrt{\sum_{i=1}^{n} (y_i – \bar{y})^2}}}$$

Jaccard係数: 2つの集合の共通部分を全体で割ることによって、類似度を測定する方法。特に二値データや集合データに適しています。

$${J(A,B) = \frac{|A \cap B|}{|A \cup B|}}$$

Dice係数: 2つの集合の共通部分を各集合のサイズの和で割ることによって、類似度を測定する方法。Jaccard係数の変形で、共通要素により重きを置きます。

$${D(A,B) = \frac{2|A \cap B|}{|A| + |B|}}$$


自然言語処理での応用例

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

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

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

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

チャットボット「?」

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

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

1:形態素解析

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

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

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

2:Bag of Words

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

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

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

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

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

具体例

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

事前に、「あっさり系?」→「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倍下がる」などの工夫次第では精度を上げることも可能です。

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

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

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

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

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

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

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


コラム|大規模言語モデル(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】テキストをベクトルで表現するには | 自然言語処理


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つのベクトルは似ていると判断されます。

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

特に文章のセマンティック類似性を計算するために、文章をベクトルに変換し、それらのベクトル間のコサイン類似度を計算することができます。具体的には、それぞれの文章を単語のベクトルに変換し、それらを平均したものを文章のベクトルとして扱うことで、文章間の類似性を計算することができます。


FOLLOW ME !