LESSON 30分

Word2Vecと単語の分散表現

「TF-IDFは強力だが、単語間の意味的な関係を捉えられない。『届かない』と『未着』が同じ意味だということを理解できないんだ。」

田中VPoEが2つの問い合わせを並べる。

「Word2Vecを使えば、意味的に近い単語をベクトル空間上で近くに配置できる。これがNLPの第二の革命だった。」

分散表現(Word Embedding)とは

単語を密なベクトル(通常100〜300次元)で表現する手法。意味的に近い単語は近いベクトルにマッピングされる。

One-hot表現 vs 分散表現

import numpy as np

# One-hot表現(語彙サイズ=5の場合)
vocab = ['配送', '届かない', '返品', '商品', '故障']
# "配送" → [1, 0, 0, 0, 0]
# "届かない" → [0, 1, 0, 0, 0]
# → すべての単語間の距離が等しい(意味の近さを表現できない)

# 分散表現(3次元に簡略化)
embeddings = {
    '配送': [0.8, 0.1, 0.3],
    '届かない': [0.7, 0.2, 0.4],   # 配送と近い
    '返品': [-0.3, 0.8, 0.1],
    '商品': [0.1, 0.5, 0.6],
    '故障': [-0.5, 0.7, 0.2],      # 返品と近い
}
# → 意味的に近い単語が近い位置にマッピングされる

Word2Vecの仕組み

2つのアーキテクチャがある。

CBOW(Continuous Bag of Words)

周囲の単語から中心の単語を予測する。

入力: [注文, した, __, まだ, 届かない]
予測: 商品が

Skip-gram

中心の単語から周囲の単語を予測する。

入力: 商品が
予測: [注文, した, まだ, 届かない]

Word2Vecの学習と利用

from gensim.models import Word2Vec

# 学習データ(トークン化済みの文のリスト)
sentences = [
    ['注文', 'した', '商品', 'が', '届かない'],
    ['配送', '状況', 'を', '確認', 'したい'],
    ['商品', 'を', '返品', 'したい'],
    ['返金', '手続き', 'について', '教えて'],
    ['商品', 'が', '壊れ', 'て', 'いた'],
    ['配送', 'が', '遅い', 'いつ', '届く'],
]

# Word2Vecモデルの学習
model = Word2Vec(
    sentences,
    vector_size=100,   # ベクトルの次元数
    window=5,          # コンテキストウィンドウのサイズ
    min_count=1,       # 最低出現回数
    workers=4,         # 並列処理数
    sg=1,              # 1=Skip-gram, 0=CBOW
)

# 単語ベクトルの取得
vector = model.wv['配送']
print(f"'配送'のベクトル(先頭10次元): {vector[:10]}")

# 類似単語の検索
similar = model.wv.most_similar('配送', topn=5)
print(f"'配送'に近い単語: {similar}")

事前学習済みWord2Vecの活用

実際のプロジェクトでは、大規模コーパスで学習済みのモデルを使う。

import gensim.downloader as api

# 事前学習済みモデルのロード(英語)
model = api.load('word2vec-google-news-300')

# 有名な単語のアナロジー
result = model.most_similar(
    positive=['king', 'woman'],
    negative=['man'],
    topn=3
)
print("king - man + woman =", result)
# [('queen', 0.7118), ...]

# 類似度の計算
similarity = model.similarity('delivery', 'shipping')
print(f"delivery と shipping の類似度: {similarity:.4f}")
# 出力: delivery と shipping の類似度: 0.6523

文書ベクトルの作成

Word2Vecは単語レベルの表現なので、文書レベルのベクトルを作るにはいくつかの方法がある。

平均プーリング

import numpy as np

def document_vector(model, tokens):
    """文書内の全単語ベクトルの平均を取る"""
    vectors = []
    for token in tokens:
        if token in model.wv:
            vectors.append(model.wv[token])
    if vectors:
        return np.mean(vectors, axis=0)
    else:
        return np.zeros(model.vector_size)

# 文書ベクトルの計算
doc1 = document_vector(model, ['order', 'not', 'delivered'])
doc2 = document_vector(model, ['package', 'not', 'arrived'])
doc3 = document_vector(model, ['return', 'refund', 'request'])

# コサイン類似度
from numpy.linalg import norm

def cosine_similarity(v1, v2):
    return np.dot(v1, v2) / (norm(v1) * norm(v2))

print(f"doc1 vs doc2(類似した内容): {cosine_similarity(doc1, doc2):.4f}")
print(f"doc1 vs doc3(異なる内容): {cosine_similarity(doc1, doc3):.4f}")
# doc1 vs doc2: 高い類似度が期待される
# doc1 vs doc3: 低い類似度が期待される

TF-IDFで重み付けした平均

def weighted_document_vector(model, tokens, tfidf_weights):
    """TF-IDFで重み付けした平均ベクトル"""
    vectors = []
    weights = []
    for token in tokens:
        if token in model.wv and token in tfidf_weights:
            vectors.append(model.wv[token])
            weights.append(tfidf_weights[token])
    if vectors:
        weights = np.array(weights)
        weights = weights / weights.sum()  # 正規化
        return np.average(vectors, axis=0, weights=weights)
    else:
        return np.zeros(model.vector_size)

Word2Vecの限界

限界説明
多義語「銀行」(金融機関 vs 川の岸)を区別できない
文脈文脈に応じた意味の変化を捉えられない
OOV語彙にない単語(Out of Vocabulary)は扱えない
文レベル文全体の意味を適切に表現する保証がない

これらの限界を克服するのが、次に学ぶSentence Transformersである。

まとめ

項目ポイント
分散表現単語を密なベクトルで表現、意味の近さを距離で表現
Word2VecCBOW/Skip-gramの2手法、周囲の単語から学習
文書ベクトル平均プーリングまたはTF-IDF重み付き平均
限界多義語、文脈依存性、OOV問題

チェックリスト

  • One-hot表現と分散表現の違いを説明できる
  • Word2Vecの学習方法(CBOW/Skip-gram)を理解した
  • 事前学習済みモデルを活用できる
  • 文書ベクトルの作成方法を実装できる
  • Word2Vecの限界を3つ以上挙げられる

次のステップへ

Word2Vecの基礎を学んだところで、次はBERT以降の文埋め込み技術であるSentence Transformersを学ぼう。

推定読了時間: 30分