ゼロから作る Deep Learning 2 斎藤康毅著 — 第3章・第4章 のメモです。

コーパス:you say goodbye and i say hello.

第3章:シンプルな CBOW モデル(多値分類)

概要

CBOW( Continuous Bag of Words )は、周囲の単語(コンテキスト)から中心単語を予測するモデルです。

単語 ID の対応表

単語 ID
you 0
say 1
goodbye 2
and 3
i 4
hello 5
. 6

単語サイズは 7 です。

モデルの構造(コンテキストサイズ=1、前後1単語)

入力層                中間層(隠れ層)         出力層
[you]   → W_in →  |               |
                   | 平均ベクトル  | → W_out → Softmax → 全単語の確率分布
[goodbye] → W_in → |               |

学習データの例

コンテキスト [you, goodbye] → 正解:say

入力:you     (one-hot: [1,0,0,0,0,0,0])
入力:goodbye (one-hot: [0,0,1,0,0,0,0])

出力(Softmax後):
  you     → 0.05
  say     → 0.70  ← 正解!ここが高くなるよう学習
  goodbye → 0.05
  and     → 0.05
  i       → 0.05
  hello   → 0.05
  .       → 0.05

問題点:コーパスが巨大になると破綻する

語彙数が 100万語の場合:
  W_in  のサイズ:1,000,000 × 隠れ層次元
  W_out のサイズ:隠れ層次元 × 1,000,000
  Softmax の計算:全100万語について exp を計算する必要があり非常に重い

第4章:Embedding レイヤ + Negative Sampling(2値分類)

アイデアの転換

【3章】多値問題
  「you と goodbye が与えられたとき、次の単語は何か?」
  → 全単語の確率分布を出力(語彙数が大きいと計算コスト爆発)

【4章】2値問題
  「you と goodbye が与えられたとき、say は正しい単語か?」
  → YES / NO の1値だけ出力(Sigmoid)

Embedding レイヤとは

one-hot ベクトルと重み行列の積は、重み行列の該当行を取り出すだけです。

# 3章:非効率(one-hot との行列積)
one_hot = [0, 0, 1, 0, 0, 0, 0]  # goodbye の one-hot
result  = one_hot @ W_in           # 実質 W_in[2] を取り出しているだけ( @ は行列のドット積)

# 4章:Embedding レイヤ(直接インデックスで取り出す)
word_id = 2                        # goodbye の ID
result  = W_in[word_id]            # W_in[2] を直接参照 → 高速・省メモリ

Negative Sampling の仕組み

コーパス中の全コンテキストに対して、正例1つ + 少数の負例を組み合わせて学習します。

コーパス:you say goodbye and i say hello.

─────────────────────────────────────────────────
コンテキスト=[you, goodbye]  ← 中心語 say の前後
─────────────────────────────────────────────────
  正例:(コンテキスト=[you, goodbye], 単語=say)   → ラベル=1(正しい)
  負例:(コンテキスト=[you, goodbye], 単語=and)   → ラベル=0(誤り)
  負例:(コンテキスト=[you, goodbye], 単語=i)     → ラベル=0(誤り)

─────────────────────────────────────────────────
コンテキスト=[say, and]  ← 中心語 goodbye の前後
─────────────────────────────────────────────────
  正例:(コンテキスト=[say, and],   単語=goodbye) → ラベル=1
  負例:(コンテキスト=[say, and],   単語=you)     → ラベル=0
  負例:(コンテキスト=[say, and],   単語=hello)   → ラベル=0

─────────────────────────────────────────────────
コンテキスト=[goodbye, i]  ← 中心語 and の前後
─────────────────────────────────────────────────
  正例:(コンテキスト=[goodbye, i], 単語=and)     → ラベル=1
  負例:(コンテキスト=[goodbye, i], 単語=say)     → ラベル=0  ← say も負例になりうる
  負例:(コンテキスト=[goodbye, i], 単語=hello)   → ラベル=0

─────────────────────────────────────────────────
コンテキスト=[and, say]  ← 中心語 i の前後
─────────────────────────────────────────────────
  正例:(コンテキスト=[and, say],   単語=i)       → ラベル=1
  負例:(コンテキスト=[and, say],   単語=you)     → ラベル=0
  負例:(コンテキスト=[and, say],   単語=goodbye) → ラベル=0

─────────────────────────────────────────────────
コンテキスト=[i, hello]  ← 中心語 say の前後
─────────────────────────────────────────────────
  正例:(コンテキスト=[i, hello],   単語=say)     → ラベル=1
  負例:(コンテキスト=[i, hello],   単語=and)     → ラベル=0
  負例:(コンテキスト=[i, hello],   単語=you)     → ラベル=0

なぜ say が正例とわかるのか? 正例は「予測」ではなく、コーパスのテキストから機械的に取り出した既知の答えです。 you ? goodbye? に何が入るかはテキストを見れば自明です。

モデルの計算(1ペアあたり)

コンテキストベクトル = Embedding(you) + Embedding(goodbye)
                     ↓
スコア = コンテキストベクトル ・ Embedding(say)  (内積)
                     ↓
確率   = Sigmoid(スコア)  → 0〜1 の1値
                     ↓
損失   = CrossEntropyLoss(確率, ラベル)

負例のサンプリング方法

負例はコーパス中の単語出現頻度に基づいて選びます。

# 単語の出現頻度(コーパス例)
頻度say=2, you=1, goodbye=1, and=1, i=1, hello=1

# 0.75乗でスムージング(高頻度語を少し抑える)
P(w) = (freq(w) ** 0.75) / Σ(freq(w') ** 0.75)

# say は頻度が高いので負例に選ばれやすい
# ただし 0.75乗により偏りすぎを防ぐ

第3章 vs 第4章 の比較まとめ

項目 第3章(多値) 第4章(2値)
問いの形 「次の単語は何か?」 「この単語は正しいか?」
出力 全語彙の確率分布 0〜1 の1値(Sigmoid)
計算コスト 語彙数に比例して爆発 負例数に比例(少数でよい)
重みの参照 one-hot との行列積 Embedding レイヤで直接インデックス参照
語彙数100万での対応 ❌ 困難 ✅ 対応可能

学習で何が育つのか

学習前:各単語のベクトルはランダム

学習後:
  ・say   のベクトル ≈ [you, goodbye] のコンテキストベクトルに近い
  ・and   のベクトル ≈ [goodbye, i]   のコンテキストベクトルに近い
  ・意味的に似た単語(i と you など)は近いベクトルを持つようになる

これが単語の分散表現(Word Embedding) です。 Negative Sampling は「正解と間違いを繰り返し判定させること」で、 副産物として意味を捉えたベクトルを生み出します。