概要
Word2Vecは、分散仮説に基づいて単語を低次元の密なベクトル(単語埋め込み)に変換するニューラルネットワーク手法である。2013年にGoogleのMikolovらが提案した。TF-IDFや共起行列が生み出す疎で高次元なベクトルとは異なり、100〜300次元の密なベクトルを学習し、意味的・構文的な類似性をベクトル空間の距離として表現できる。「王 − 男 + 女 ≈ 女王」という加算性が象徴的である。
直感・モチベーション
TF-IDFや共起行列には2つの根本的な問題がある。
- 高次元・疎: 語彙サイズ(数万〜数十万)の次元が必要で、ほとんどの要素がゼロ
- 類似語の区別なし: 「車」と「自動車」は全く別の行として扱われ、類似性は表現できない
Word2Vecは「単語の意味は周囲の単語で決まる(分散仮説)」という考えをニューラルネットの学習問題として定式化し直す。予測タスクを解く過程で、副産物として良質な単語ベクトルが得られる。
2つのアーキテクチャがある:
- CBOW(Continuous Bag of Words): 文脈語から中心語を予測する。学習が速い
- Skip-gram: 中心語から文脈語を予測する。低頻度語の表現に優れる
数学的定式化
Skip-gramモデルを例にとる。中心語 から文脈語 ()を予測する確率を最大化する:
条件付き確率はソフトマックスで定義する:
ここで は入力ベクトル(埋め込み行列の行)、 は出力ベクトル。
導出を見る
Negative Sampling による近似:
ソフトマックスの分母は全語彙 についての和であり、計算量が となる(数万〜数十万)。これを近似するのがNegative Samplingである。
1つの正例ペア と 個のノイズサンプル (コーパス頻度に比例してサンプリング)を使い、次を最大化する:
ここで はシグモイド関数、 はノイズ分布(頻度の3/4乗でスムーズ化)。
これにより計算量は (通常 )に削減できる。
加算性の説明:
学習後、意味的に近い単語は近いベクトルになる。「王」「男」「女」「女王」について:
これは「性別」という意味的方向がベクトル空間に線形に符号化されているためである。
重要な性質・注意点
- 多義語の非対応: 「bank」(銀行/川岸)に単一ベクトルを割り当てるため、文脈による意味の違いを表現できない(BERTなど文脈依存モデルで解決)
- 語彙外単語(OOV): 学習時に見ていない単語のベクトルは得られない(FastTextなどの文字N-gramベースで解決)
- 次元数の選択: 典型的には100〜300次元。大きすぎると過学習、小さすぎると表現力不足
まとめ
- 単語予測タスクを解かせることで、副産物として意味を捉えたベクトルが得られる
- CBOWは高速、Skip-gramは低頻度語に強い
- Negative Samplingで計算コストを大幅に削減
- 意味の加算性(「王 − 男 + 女 ≈ 女王」)が示す豊かな幾何学的構造を持つ
実装例
pythonimport math import random from collections import Counter def sigmoid(x: float) -> float: return 1.0 / (1.0 + math.exp(-x)) class Word2Vec: def __init__(self, vocab: list[str], dim: int = 10): self.w2i = {w: i for i, w in enumerate(vocab)} self.V = len(vocab) self.dim = dim # 入力・出力の2つの埋め込み行列をランダム初期化 self.W_in = [[random.gauss(0, 0.01) for _ in range(dim)] for _ in range(self.V)] self.W_out = [[random.gauss(0, 0.01) for _ in range(dim)] for _ in range(self.V)] def dot(self, a: list[float], b: list[float]) -> float: return sum(x * y for x, y in zip(a, b)) def train_pair(self, center: int, context: int, negatives: list[int], lr: float): # Positive pair score_pos = self.dot(self.W_in[center], self.W_out[context]) grad_pos = lr * (1 - sigmoid(score_pos)) for d in range(self.dim): self.W_in[center][d] += grad_pos * self.W_out[context][d] self.W_out[context][d] += grad_pos * self.W_in[center][d] # Negative pairs for neg in negatives: score_neg = self.dot(self.W_in[center], self.W_out[neg]) grad_neg = -lr * sigmoid(score_neg) for d in range(self.dim): self.W_in[center][d] += grad_neg * self.W_out[neg][d] self.W_out[neg][d] += grad_neg * self.W_in[center][d] def similarity(self, w1: str, w2: str) -> float: v1, v2 = self.W_in[self.w2i[w1]], self.W_in[self.w2i[w2]] norm1 = math.sqrt(sum(x**2 for x in v1)) norm2 = math.sqrt(sum(x**2 for x in v2)) return self.dot(v1, v2) / (norm1 * norm2 + 1e-8) # 使用例(簡略化) corpus = [["銀行", "預金", "融資", "口座"], ["川", "岸", "水", "流れ"]] vocab = list(set(w for sent in corpus for w in sent)) model = Word2Vec(vocab, dim=10)
関連記事
前提知識
- 分散仮説 — Word2Vecの理論的根拠
- ニューラルネットワーク基礎 — 重み更新・誤差逆伝播の基礎
- 行列演算と内積 — ベクトル演算の基礎
派生技術
- Doc2Vec — Word2Vecを文書単位に拡張した手法
- GloVe — 共起行列とWord2Vecを統合した手法
- FastText — 文字N-gramで未知語に対応した拡張
応用事例
- 類義語・関連語検索 — コサイン類似度によるベクトル近傍探索
- 転移学習 — 事前学習済みベクトルを下流タスクの初期値に利用
用語解説
| 用語 | 説明 |
|---|---|
| 単語埋め込み | 単語を実数値の密なベクトルに変換したもの |
| CBOW | 文脈語の平均から中心語を予測するWord2Vecの学習方式 |
| Skip-gram | 中心語から各文脈語を予測するWord2Vecの学習方式 |
| Negative Sampling | 全語彙ソフトマックスをランダム負例との2値分類で近似する手法 |
| OOV(Out-of-Vocabulary) | 学習時に見ていない未知語 |
| 加算性 | 単語ベクトルの差分・和が意味的な関係を表す性質 |
関連リンク
この記事への参照
- 比較対象Embedding