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である。
まとめ
| 項目 | ポイント |
|---|---|
| 分散表現 | 単語を密なベクトルで表現、意味の近さを距離で表現 |
| Word2Vec | CBOW/Skip-gramの2手法、周囲の単語から学習 |
| 文書ベクトル | 平均プーリングまたはTF-IDF重み付き平均 |
| 限界 | 多義語、文脈依存性、OOV問題 |
チェックリスト
- One-hot表現と分散表現の違いを説明できる
- Word2Vecの学習方法(CBOW/Skip-gram)を理解した
- 事前学習済みモデルを活用できる
- 文書ベクトルの作成方法を実装できる
- Word2Vecの限界を3つ以上挙げられる
次のステップへ
Word2Vecの基礎を学んだところで、次はBERT以降の文埋め込み技術であるSentence Transformersを学ぼう。
推定読了時間: 30分