コサイン類似度によるテキストレコメンド

文をBag-of-Wordsベクトルで表し、コサイン類似度を計算して、最も似ている文を見つけます。

このケースを学ぶと、次のことが理解できます:検索・レコメンドシステムの数学的基盤——内積は「同じ向き」を測り、ノルムで正規化する。


日常生活からの導入

淘宝で「蓝牙耳机」を検索

「蓝牙耳机」の4文字を入力すると、淘宝は瞬時に百万の商品から最も関連性の高いものを見つけ出します。タイトルに完全一致する語があるかどうかで順位付けするのではなく、各商品タイトルを数字の並び(ベクトル)に変換し、検索語と各タイトルの「方向」が一致するかを比較するのです。

方向が近いほど、意味も近いことを示します。タイトルの長さは判断に影響しません——これがコサイン類似度の直感です。


直感的な理解

2本の弓から放たれた矢を想像してください。2本の矢の方向が一致するほど、なす角のコサインは1に近づきます。互いに垂直ならコサインは0、正反対の方向ならコサインは-1です。

~ 1
方向が同じ
2つの文の意味が同じ
~ 0
互いに垂直
2つの文は全く無関係
~ -1
方向が逆
Bag of Wordsモデルでは珍しい

重要な問題は、どうやってテキストを「矢印」にするかです。ここでBag of Wordsモデルを導入します。


数学的定義

コサイン類似度の公式

\[ \cos(\theta) = \frac{\mathbf{a} \cdot \mathbf{b}}{\|\mathbf{a}\| \cdot \|\mathbf{b}\|} = \frac{\sum_{i=1}^{n} a_i \cdot b_i}{\sqrt{\sum a_i^2} \cdot \sqrt{\sum b_i^2}} \]

分子は内積(2つのベクトルが「同じ向き」である度合いを測る)、分母は2つのノルムの積(ベクトルの長さの影響を除く)です。

Bag of Wordsモデル(BoW)

一文をベクトルに変換する方法:

  1. 語彙リストを決める(出現し得るすべての語)
  2. 各文について、その語が出現すれば1、出現しなければ0を立てる
  3. 語彙リストのサイズと同じ長さのベクトルを得る

ここでは最も簡略化した「出現の有無」(one-hot Bag of Words)を使っていますが、実際の応用ではTF-IDFやWord Embedding(単語埋め込み)がよく使われます。しかし核となる考え方は同じです。テキストを数学的な演算に参加できるベクトルに変換する、ということです。


Pythonで実践

5つの中国語の文でBag-of-Wordsベクトルを構築し、クエリ文とのコサイン類似度を計算して、最も似ている文を見つけます。

サンプル

import numpy as np

# 1. コーパスの準備——AI学習と日常生活の2種類の話題を含む中国語の文5つ
sentences = [
    "我喜欢用python学习机器学习",
    pythonは人工知能を学ぶのに良いツールです。,
    "今天天气很好适合出去散步",
    深層学習には大量のデータと計算能力が必要です,
    "散歩はとても良いリラックス方法です",
]

# 2. 語彙表の構築(教育用簡易版、出現する可能性のある単語を手動で定義)
vocab_words = ["我", "喜欢", "用", "python", "学习", "机器学习", "だ",
               "人工智能", "好", "工具", "今天", "天气", "很", "适合",
               "出去", "散步", "深度学习", "需要", "大量", "の", "数据",
               "算力", "一种", "放松", "方式"]

def to_vector(sentence, vocab):
    '''文をbag-of-wordsベクトルに変換する——出現した単語を1としてマークする'''
    vec = np.zeros(len(vocab))
    for i, w in enumerate(vocab):
        if w in sentence:
            vec[i] = 1  # 単語が出現したら 1 を記録する
    return vec

# 5 句话 → 5 个向量
vectors = np.array([to_vector(s, vocab_words) for s in sentences])
print(サンプル 各文のベクトル次元:, vectors.shape[1],
      (語彙リストのサイズ))

# 3. 手写余弦相似度函数(不调 sklearn)
def cosine_similarity(a, b):
    dot = np.dot(a, b)             # 点积
    norm_a = np.linalg.norm(a)     # a のノルム
    norm_b = np.linalg.norm(b)     # b のノルム
    if norm_a == 0 or norm_b == 0:
        return 0.0                 # 零向量没有方向
    return dot / (norm_a * norm_b)

# 4. 与えられたクエリ文から、最も類似した文を見つける
query = “私はPythonと人工知能を学習しています”
query_vec = to_vector(query, vocab_words)

sims = [cosine_similarity(query_vec, v) for v in vectors]

print(f"\nサンプル 查询句子: {query!r}\n")
for s, sim in sorted(zip(sentences, sims),
                     key=lambda x: -x[1]):
    print(f" 相似度 {sim:.3f} <- {s}")

# 5. 検証:無関係な文の類似度は0
print("\nサンプル 検証:2つの完全に関係のない文)
v1 = to_vector("python机器学习", vocab_words)
v2 = to_vector("散步放松", vocab_words)
print(f" 'python机器学习' vs '散步放松': "
      f"cos = {cosine_similarity(v1, v2):.3f}")
サンプル 每句话的向量维度: 25 (词表大小)

サンプル 查询句子: '我在学习python和人工智能'

  相似度 0.577  <-  python是学习人工智能的好工具
  相似度 0.500  <-  我喜欢用python学习机器学习
  相似度 0.000  <-  今天天气很好适合出去散步
  相似度 0.000  <-  深度学习需要大量的数据和算力
  相似度 0.000  <-  散步是一种很好的放松方式

サンプル 验证:两个完全不相关的句子
  'python机器学习' vs '散步放松': cos = 0.000

結果はとても直感的です。同じキーワードを含む文は類似度が高く、内容が全く無関係な文は類似度が0になります。


AIでの応用シーン

AIシーンコサイン類似度の使い方
検索エンジン検索語と文書をベクトルに変換し、コサイン類似度順に並べて結果を返す
レコメンドシステムユーザーの履歴行動ベクトルに最も似ている商品を見つける
セマンティック検索Word2Vec/BERTなどの単語埋め込みでベクトル化し、同義語を捉える——「蓝牙耳机」と「无线耳塞」もマッチする
顔認識顔画像をベクトル(Face Embedding)にエンコードし、2つの顔のコサイン類似度を比較して同一人物か判断する
RAG検索大規模モデル応用では、コサイン類似度を使って知識ベースから最も関連性の高い文書断片を検索する

GPTなどの大規模モデルで使われる「アテンション機構」では、QとKの内積(QK^T)も本質的に類似度を計算しています——コサイン類似度は分母のノルム正規化を省いたものです。

その他の発展