Word2Vecは、2013年にGoogleの Tomas Mikolov らが提案した、単語を固定長の実数ベクトルに変換する手法です。大量のテキストコーパスから「単語の意味」を数百次元のベクトル空間に埋め込むことで、単語間の意味的な類似度を計算したり、有名な “king – man + woman = queen” のようなベクトル演算で意味の関係を捉えたりできます。
Word2Vecは自然言語処理(NLP)の歴史において最も影響力のある手法の一つです。これ以降、GloVe、fastText、ELMo、そしてBERT・GPTへと続く単語表現の進化の出発点となりました。現在のTransformerベースのモデルを理解するうえでも、Word2Vecの考え方は欠かせない基礎です。
本記事の内容
- 分布仮説とOne-hot表現の限界
- CBOW(Continuous Bag-of-Words)の目的関数
- Skip-gramの目的関数
- softmaxの計算コストとNegative Samplingによる効率化
- Subsampling of Frequent Words
- 学習されるベクトルの性質(加法構成性)
- gensimによるPython実装とt-SNEでの可視化
前提知識
この記事を読む前に、以下の記事を読んでおくと理解が深まります。
分布仮説 — 単語の意味は周囲の単語で決まる
Word2Vecの理論的背景には分布仮説(distributional hypothesis)があります。これは言語学者 J.R. Firth の有名な言葉に集約されます。
“You shall know a word by the company it keeps.”(単語の意味は、その周囲の単語によって決まる)
たとえば、「犬は公園でを追いかけた」と「猫は庭でを追いかけた」という文を考えてみましょう。空欄に入る単語(ボール、蝶など)は似ており、「犬」と「猫」が似た文脈に現れることから、これらが意味的に近いことがわかります。
Word2Vecは、この分布仮説を具体的なアルゴリズムに落とし込み、共起パターンからベクトル表現を学習します。
One-hot表現の限界
自然言語処理で単語をコンピュータに入力するには、何らかの数値表現が必要です。最も素朴な方法がOne-hot表現です。語彙サイズを $V$ とすると、各単語を $V$ 次元のベクトルで表し、対応する位置だけを1、残りを0にします。
$$ \text{“king”} = \begin{pmatrix} 0 \\ 0 \\ 1 \\ 0 \\ \vdots \\ 0 \end{pmatrix}, \quad \text{“queen”} = \begin{pmatrix} 0 \\ 0 \\ 0 \\ 1 \\ \vdots \\ 0 \end{pmatrix} $$
One-hot表現には2つの致命的な問題があります。
1. 次元の呪い: 語彙サイズが $V = 100{,}000$ なら、各単語は10万次元のスパースベクトルになります。計算効率が極めて悪くなります。
2. 意味的な類似度が計算できない: 任意の2つの異なるOne-hotベクトル $\bm{x}_i, \bm{x}_j$ について、内積は常にゼロです。
$$ \bm{x}_i^\top \bm{x}_j = 0 \quad (i \neq j) $$
つまり「king」と「queen」の類似度も、「king」と「apple」の類似度も、どちらもゼロです。意味の近さを全く反映できません。
Word2Vecは、このOne-hot表現を低次元の密なベクトル(例えば300次元)に圧縮し、意味的に近い単語が近いベクトルになるよう学習します。
CBOW — 周辺語から中心語を予測する
Word2Vecには2つのアーキテクチャがあります。まずCBOW(Continuous Bag-of-Words)から解説します。
CBOWの基本的なアイデアは「周辺語(コンテキスト)から中心語(ターゲット)を予測する」ことです。
たとえば、ウィンドウサイズ $m = 2$ で「the cat sat on the」という文を考えると、中心語が “sat” のとき、周辺語は {“the”, “cat”, “on”, “the”} です。
モデルの構造
語彙サイズを $V$、埋め込み次元を $d$ とします。CBOWは2つの重み行列を持ちます。
- 入力側の重み行列: $\bm{W} \in \mathbb{R}^{V \times d}$ — 各行が単語の入力ベクトル $\bm{v}_w$
- 出力側の重み行列: $\bm{W’} \in \mathbb{R}^{d \times V}$ — 各列が単語の出力ベクトル $\bm{u}_w$
CBOWでは、まず周辺語のベクトルの平均を計算します。
$$ \hat{\bm{v}} = \frac{1}{2m} \sum_{-m \le j \le m, \, j \neq 0} \bm{v}_{w_{t+j}} $$
ここで $w_{t+j}$ は位置 $t$ の中心語から $j$ だけ離れた周辺語です。
目的関数
中心語 $w_t$ が生成される確率をsoftmaxで定義します。
$$ P(w_t \mid w_{t-m}, \dots, w_{t+m}) = \frac{\exp(\bm{u}_{w_t}^\top \hat{\bm{v}})}{\displaystyle\sum_{w=1}^{V} \exp(\bm{u}_w^\top \hat{\bm{v}})} $$
コーパス全体にわたる対数尤度を最大化することが目標です。
$$ J_{\text{CBOW}} = \frac{1}{T} \sum_{t=1}^{T} \log P(w_t \mid w_{t-m}, \dots, w_{t+m}) $$
ここで $T$ はコーパス中の総単語数です。これを展開すると、
$$ \begin{align} J_{\text{CBOW}} &= \frac{1}{T} \sum_{t=1}^{T} \log \frac{\exp(\bm{u}_{w_t}^\top \hat{\bm{v}})}{\displaystyle\sum_{w=1}^{V} \exp(\bm{u}_w^\top \hat{\bm{v}})} \\ &= \frac{1}{T} \sum_{t=1}^{T} \left[ \bm{u}_{w_t}^\top \hat{\bm{v}} – \log \sum_{w=1}^{V} \exp(\bm{u}_w^\top \hat{\bm{v}}) \right] \end{align} $$
第1項はターゲット単語のスコア、第2項は全単語にわたる正規化定数の対数です。
CBOWは周辺語を平均して使うため、語順の情報は失われます(”bag of words” の名の通り)。一方で、小規模コーパスでも比較的安定して学習できるという利点があります。
Skip-gram — 中心語から周辺語を予測する
Skip-gramはCBOWの逆で、「中心語からその周辺語を予測する」モデルです。Mikolovの原論文では、Skip-gramのほうが特に低頻度語の表現学習に優れるとされています。
モデルの構造
中心語 $w_t$ が与えられたとき、ウィンドウ内の各周辺語 $w_{t+j}$ が独立に生成されると仮定します。
$$ P(w_{t+j} \mid w_t) = \frac{\exp(\bm{u}_{w_{t+j}}^\top \bm{v}_{w_t})}{\displaystyle\sum_{w=1}^{V} \exp(\bm{u}_w^\top \bm{v}_{w_t})} $$
ここで $\bm{v}_{w_t}$ は中心語の入力ベクトル、$\bm{u}_{w_{t+j}}$ は周辺語の出力ベクトルです。
目的関数
Skip-gramの目的関数は、コーパス全体にわたる対数尤度の最大化です。
$$ J_{\text{Skip-gram}} = \frac{1}{T} \sum_{t=1}^{T} \sum_{\substack{-m \le j \le m \\ j \neq 0}} \log P(w_{t+j} \mid w_t) $$
これを展開すると、
$$ \begin{align} J_{\text{Skip-gram}} &= \frac{1}{T} \sum_{t=1}^{T} \sum_{\substack{-m \le j \le m \\ j \neq 0}} \log \frac{\exp(\bm{u}_{w_{t+j}}^\top \bm{v}_{w_t})}{\displaystyle\sum_{w=1}^{V} \exp(\bm{u}_w^\top \bm{v}_{w_t})} \\ &= \frac{1}{T} \sum_{t=1}^{T} \sum_{\substack{-m \le j \le m \\ j \neq 0}} \left[ \bm{u}_{w_{t+j}}^\top \bm{v}_{w_t} – \log \sum_{w=1}^{V} \exp(\bm{u}_w^\top \bm{v}_{w_t}) \right] \end{align} $$
Skip-gramでは各周辺語を個別に予測するため、1つの中心語から $2m$ 個の学習サンプルが生成されます。これにより、低頻度語でも十分な学習機会が得られるのです。
softmaxの計算コスト問題
CBOWとSkip-gramの目的関数には共通して、正規化定数(分母)の計算があります。
$$ \sum_{w=1}^{V} \exp(\bm{u}_w^\top \bm{v}) $$
この和は語彙中の全単語 $V$ について計算する必要があります。典型的な語彙サイズは $V = 10^5 \sim 10^6$ であり、これを全学習ステップで毎回計算するのは現実的ではありません。
パラメータの勾配を考えてみましょう。Skip-gramの場合、$\bm{v}_{w_t}$ に関する勾配は、
$$ \frac{\partial \log P(w_O \mid w_I)}{\partial \bm{v}_{w_I}} = \bm{u}_{w_O} – \sum_{w=1}^{V} P(w \mid w_I) \bm{u}_w $$
となりますが、第2項で全語彙についての期待値を計算しなければなりません。これは計算量 $O(V \cdot d)$ であり、ボトルネックになります。
この問題を解決するために提案されたのが、Hierarchical SoftmaxとNegative Samplingです。本記事では、より広く使われているNegative Samplingを詳しく解説します。
Negative Sampling — 効率的な学習の鍵
基本的なアイデア
Negative Samplingの直感的なアイデアは非常にシンプルです。「全語彙にわたるsoftmaxを計算する代わりに、正例1つと少数の負例だけを使って二値分類問題に帰着させる」というものです。
つまり、「(center, context) ペアが実際にコーパスに現れたか否か」を判定するロジスティック回帰に問題を置き換えます。
目的関数の導出
まず、シグモイド関数を定義します。
$$ \sigma(x) = \frac{1}{1 + \exp(-x)} $$
Skip-gram with Negative Sampling(SGNS)の目的関数は以下の通りです。
$$ J_{\text{NEG}} = \log \sigma(\bm{u}_{w_O}^\top \bm{v}_{w_I}) + \sum_{k=1}^{K} \mathbb{E}_{w_k \sim P_n(w)} \left[ \log \sigma(-\bm{u}_{w_k}^\top \bm{v}_{w_I}) \right] $$
各項の意味を確認しましょう。
第1項: 正例(実際に共起する単語ペア)に対して、内積 $\bm{u}_{w_O}^\top \bm{v}_{w_I}$ を大きくします。$\sigma$ が1に近づくので、$\log \sigma$ が0に近づき、目的関数が最大化されます。
第2項: 負例(ランダムにサンプリングした単語 $w_k$)に対して、内積 $\bm{u}_{w_k}^\top \bm{v}_{w_I}$ を小さく(負に)します。$\sigma(-\bm{u}_{w_k}^\top \bm{v}_{w_I})$ が1に近づくので、同様に目的関数が最大化されます。
ここで $K$ は負例の数(原論文では小規模コーパスで $K = 5 \sim 20$、大規模コーパスで $K = 2 \sim 5$ を推奨)、$P_n(w)$ はノイズ分布です。
ノイズ分布
負例のサンプリングには、単語の出現頻度 $f(w)$ の $3/4$ 乗に比例する分布が用いられます。
$$ P_n(w) = \frac{f(w)^{3/4}}{\displaystyle\sum_{w’} f(w’)^{3/4}} $$
なぜ $3/4$ 乗なのでしょうか。生の頻度 $f(w)$ をそのまま使うと、”the”、”a”、”is” のような高頻度語ばかりが負例として選ばれてしまい、学習が偏ります。一方、一様分布にすると低頻度語が負例として多く選ばれすぎます。$3/4$ 乗は、この両極端の間をうまくバランスする経験的に優れた値です。
具体例で確認しましょう。
| 単語 | $f(w)$ | $f(w)^{3/4}$ | 相対的な変化 |
|---|---|---|---|
| “the” | 0.10 | 0.056 | 減少 |
| “cat” | 0.001 | 0.0018 | 増加 |
| “axolotl” | 0.00001 | 0.000056 | 増加 |
高頻度語のサンプリング確率が下がり、低頻度語のサンプリング確率が相対的に上がることで、より多様な負例が得られます。
計算量の改善
Negative Samplingにより、各学習ステップの計算量は $O(V \cdot d)$ から $O(K \cdot d)$ に削減されます。$K \ll V$ なので、これは劇的な効率化です。例えば $V = 100{,}000$, $K = 5$ とすると、計算量は約2万分の1になります。
Subsampling of Frequent Words
英語のコーパスでは “the”、”a”、”in” のような高頻度語が非常に多く出現します。これらの単語は、
- すでに十分な学習サンプルがあり、追加のサンプルで大きく改善しない
- 他の単語との共起にあまり情報を持たない(”the” はほぼ全ての名詞と共起する)
そこで、Mikolov et al. (2013) ではsubsamplingを導入しました。学習コーパス中の各単語 $w_i$ を確率 $P_{\text{discard}}(w_i)$ で破棄します。
$$ P_{\text{discard}}(w_i) = 1 – \sqrt{\frac{t}{f(w_i)}} $$
ここで $f(w_i)$ は単語 $w_i$ の出現頻度(全単語に対する割合)、$t$ は閾値パラメータ(典型的には $t = 10^{-5}$)です。
この式の振る舞いを確認しましょう。
- $f(w_i) \gg t$ のとき(”the” のような超高頻度語): $\sqrt{t / f(w_i)} \ll 1$ なので、$P_{\text{discard}} \approx 1$。ほとんど破棄されます。
- $f(w_i) \approx t$ のとき: $P_{\text{discard}} \approx 0$。ほぼ保持されます。
- $f(w_i) \ll t$ のとき: $\sqrt{t / f(w_i)} > 1$ なので、$P_{\text{discard}} < 0$(= 0とみなす)。必ず保持されます。
これにより、高頻度語によるノイズが削減され、低頻度語の相対的な重要度が上がり、学習の質と速度が向上します。
Word2Vecで学習されるベクトルの性質
Word2Vecで学習された単語ベクトルには、興味深い加法構成性(additive compositionality)が現れます。
king – man + woman = queen
最も有名な例がこの関係です。学習済みのベクトル空間において、以下の演算が成り立ちます。
$$ \bm{v}_{\text{king}} – \bm{v}_{\text{man}} + \bm{v}_{\text{woman}} \approx \bm{v}_{\text{queen}} $$
直感的には、$\bm{v}_{\text{king}} – \bm{v}_{\text{man}}$ で「男性性」を除去し、$+ \bm{v}_{\text{woman}}$ で「女性性」を加えると、「女性の王族」= queen になるということです。
これは単なる偶然ではなく、様々な意味関係で成り立ちます。
| 関係 | 例 |
|---|---|
| 性別 | $\bm{v}_{\text{brother}} – \bm{v}_{\text{man}} + \bm{v}_{\text{woman}} \approx \bm{v}_{\text{sister}}$ |
| 国と首都 | $\bm{v}_{\text{Paris}} – \bm{v}_{\text{France}} + \bm{v}_{\text{Japan}} \approx \bm{v}_{\text{Tokyo}}$ |
| 比較級 | $\bm{v}_{\text{bigger}} – \bm{v}_{\text{big}} + \bm{v}_{\text{small}} \approx \bm{v}_{\text{smaller}}$ |
| 時制 | $\bm{v}_{\text{walking}} – \bm{v}_{\text{walk}} + \bm{v}_{\text{swim}} \approx \bm{v}_{\text{swimming}}$ |
なぜ加法構成性が生まれるのか
Levy & Goldberg (2014) は、Skip-gram with Negative Sampling が暗黙的に単語の共起行列のPMI(Pointwise Mutual Information)を分解していることを示しました。
$$ \bm{v}_{w}^\top \bm{u}_{c} \approx \text{PMI}(w, c) – \log K $$
ここで PMI は以下のように定義されます。
$$ \text{PMI}(w, c) = \log \frac{P(w, c)}{P(w) P(c)} $$
PMI行列は加法的な構造を持つため、その分解で得られるベクトルにも加法構成性が自然に現れます。
コサイン類似度
2つの単語ベクトル間の意味的な類似度は、コサイン類似度で測定します。
$$ \text{sim}(\bm{v}_a, \bm{v}_b) = \frac{\bm{v}_a^\top \bm{v}_b}{\|\bm{v}_a\| \, \|\bm{v}_b\|} $$
値は $[-1, 1]$ の範囲をとり、1に近いほど意味が似ていることを示します。
Python実装 — gensimで学習しt-SNEで可視化
gensimによるWord2Vecの学習
実際にWord2Vecを学習してみましょう。Pythonの gensim ライブラリを使えば、数行で学習できます。
import gensim.downloader as api
from gensim.models import Word2Vec
# 学習済みコーパスのダウンロード(text8: Wikipedia英語の最初の1億文字)
dataset = api.load("text8")
# Word2Vecモデルの学習(Skip-gram)
model = Word2Vec(
sentences=dataset,
vector_size=200, # 埋め込み次元
window=5, # ウィンドウサイズ
min_count=5, # 最低出現回数
sg=1, # 1: Skip-gram, 0: CBOW
negative=5, # 負例の数
sample=1e-5, # subsampling閾値
epochs=5, # エポック数
workers=4 # 並列ワーカー数
)
# モデルの保存
model.save("word2vec_text8.model")
類似単語の検索
# "king" に最も近い単語を検索
similar_words = model.wv.most_similar("king", topn=10)
for word, score in similar_words:
print(f"{word}: {score:.4f}")
アナロジーテスト
# king - man + woman = ?
result = model.wv.most_similar(
positive=["king", "woman"],
negative=["man"],
topn=5
)
print("king - man + woman =")
for word, score in result:
print(f" {word}: {score:.4f}")
# Paris - France + Japan = ?
result = model.wv.most_similar(
positive=["paris", "japan"],
negative=["france"],
topn=5
)
print("\nparis - france + japan =")
for word, score in result:
print(f" {word}: {score:.4f}")
t-SNEによる可視化
高次元の単語ベクトルを2次元に圧縮し、可視化してみましょう。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE
# 可視化する単語リスト
words = [
"king", "queen", "prince", "princess",
"man", "woman", "boy", "girl",
"dog", "cat", "horse", "bird",
"car", "bus", "train", "airplane",
"japan", "china", "france", "germany",
"tokyo", "beijing", "paris", "berlin",
"one", "two", "three", "four",
]
# 単語ベクトルの取得
word_vectors = np.array([model.wv[w] for w in words])
# t-SNEで2次元に圧縮
tsne = TSNE(n_components=2, random_state=42, perplexity=10)
vectors_2d = tsne.fit_transform(word_vectors)
# 可視化
plt.figure(figsize=(14, 10))
plt.scatter(vectors_2d[:, 0], vectors_2d[:, 1], c="steelblue", s=50, alpha=0.7)
for i, word in enumerate(words):
plt.annotate(
word,
xy=(vectors_2d[i, 0], vectors_2d[i, 1]),
fontsize=12,
ha="center",
va="bottom",
)
plt.title("Word2Vec Embeddings Visualized with t-SNE", fontsize=14)
plt.xlabel("t-SNE dim 1")
plt.ylabel("t-SNE dim 2")
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig("word2vec_tsne.png", dpi=150)
plt.show()
t-SNEの結果を見ると、意味的に近い単語がクラスターを形成していることが確認できます。王族に関する単語(king, queen, prince, princess)、動物(dog, cat, horse, bird)、乗り物(car, bus, train, airplane)、国と首都(japan-tokyo, france-paris など)がそれぞれ近くに配置されます。
CBOWでの学習
CBOWで学習する場合は、sg=0 に変更するだけです。
# CBOWで学習
model_cbow = Word2Vec(
sentences=dataset,
vector_size=200,
window=5,
min_count=5,
sg=0, # 0: CBOW
negative=5,
sample=1e-5,
epochs=5,
workers=4,
)
一般的に、Skip-gramは低頻度語の表現が優れ、CBOWは学習が高速で高頻度語の表現が安定するとされています。用途に応じて使い分けましょう。
まとめ
本記事では、Word2Vecの理論と実装について解説しました。
- 分布仮説: 単語の意味はその周囲の単語(文脈)によって決まるという考え方が基盤
- CBOW: 周辺語から中心語を予測するモデル。周辺語ベクトルの平均を入力とする
- Skip-gram: 中心語から周辺語を予測するモデル。低頻度語の学習に優れる
- Negative Sampling: 全語彙にわたるsoftmaxの代わりに、少数の負例との二値分類で近似する。計算量を $O(V \cdot d)$ から $O(K \cdot d)$ に削減
- Subsampling: 高頻度語を確率的に除外し、学習の質と速度を向上
- 加法構成性: 学習されたベクトルは “king – man + woman = queen” のような意味的な演算が可能
- PMIとの関係: SGNSは暗黙的にPMI行列を分解している
Word2Vecは2013年の提案以来、NLPの基盤技術として広く使われ続けています。しかし、Word2Vecには「各単語に固定のベクトルを割り当てる」という制約があり、文脈によって意味が変わる多義語(例: “bank” は「銀行」か「岸」か)を区別できません。
次のステップとして、以下の記事も参考にしてください。
- Self-Attention機構とは?理論と実装をわかりやすく解説 — Word2Vecの固定ベクトルの限界を超え、文脈に応じた表現を学習するTransformerの中核技術