Code Leading | Deep Learning
ゼロから作るDeep Learning シリーズ 斎藤 康毅 (著) を読むためのメモ。
1. コードの全体構成
| カテゴリ | クラス/関数 | 役割 |
|---|---|---|
| レイヤ | MatMul |
行列積の順伝播・逆伝播 |
| レイヤ | SoftmaxWithLoss |
確率化 + 損失計算 |
| 関数 | Softmax() |
数値安定版ソフトマックス |
| 関数 | cross_entropy_error() |
交差エントロピー損失 |
| 最適化 | SGD / Momentum / Adam |
重みの更新手法(切替可能) |
| モデル | SimpleCBOW |
CBOWモデル本体 |
| 学習 | Trainer |
ミニバッチ学習ループ管理 |
| ユーティリティ | remove_duplicate() |
共有重みの勾配集約 |
2. 学習定数と回数の関係
window_size = 1
hidden_size = 5
batch_size = 3
max_epoch = 1000
data_size = 6 # コーパスから生成されるサンプル数
max_iters = 6 // 3 # = 2(1 epoch あたりの更新回数)
用語の定義
- batch(バッチ) : 1回の更新に使うデータ数(=
batch_size) - iter(イテレーション) : 1バッチで重みを1回更新すること
- epoch : 全データを1周走査し終えること(=
max_iters回の更新)
トータルの重み更新回数
max_epoch × max_iters = 1000 × 2 = 2000 回
ループ構造
for epoch in range(max_epoch): # 外ループ: 1000回
shuffle(data) # 毎 epoch シャッフル ← 重要
for iters in range(max_iters): # 内ループ: 2回
# 1バッチ取り出し → forward → backward → update
シャッフルのポイント
毎 epoch の先頭でデータをシャッフルすることで、バッチ境界が毎回変わり、特定の組み合わせへの過剰適合(過学習)を防ぎ、汎化性能が上がる。
3. 1回のイテレーションの全体フロー
batch_size=3 のとき、3サンプルを同時並列に処理する(行列演算で一括処理 → 高速)。
データの形状
batch_x(contexts) : shape (3, 2, 7) ← 3サンプル × 左右2単語 × 語彙数7
batch_t(target) : shape (3, 7) ← 3サンプル × 語彙数7(one-hot)
順伝播(forward)
contexts[:, 0] → in_layer0.forward(W_in) → h0 (3, 5)
contexts[:, 1] → in_layer1.forward(W_in) → h1 (3, 5)
↓
h = (h0 + h1) * 0.5 (3, 5)
↓
out_layer.forward(W_out) → score (3, 7)
↓
loss_layer.forward(score, target) → loss(スカラー)
in_layer0とin_layer1は 同じW_inを共有している。
→remove_duplicate()で勾配を合算してから更新する。
逆伝播(backward)&重み更新
loss → loss_layer.backward()
→ out_layer.backward()
→ in_layer0.backward() / in_layer1.backward()
→ optimizer.update(W_in, W_out)
4. 具体例:コーパスと予測
文章: 'You say goodbye and I say hello.'
word_to_id: {you:0, say:1, goodbye:2, and:3, i:4, hello:5, .:6}
コンテキスト → ターゲットの対応(window_size=1)
| コンテキスト左 | ターゲット | コンテキスト右 |
|---|---|---|
| you (0) | say (1) | goodbye (2) |
| say (1) | goodbye (2) | and (3) |
| goodbye (2) | and (3) | i (4) |
| and (3) | i (4) | say (1) |
| i (4) | say (1) | hello (5) |
| say (1) | hello (5) | . (6) |
予測の例
入力: [you → 1 0 0 0 0 0 0] と [and → 0 0 0 1 0 0 0]
予測: [goodbye → 0 0 1 0 0 0 0] ← 精度良く学習できていれば正解を返す
5. 損失が減少する仕組み
損失が下がるのは2つの力の組み合わせ。
- 勾配降下法 : 毎イテレーション、損失を下げる方向に
W_in・W_outが少しずつ更新される - シャッフルによる汎化 : 毎 epoch でバッチ境界が変わり、過剰適合を防ぐ
6. コーパス外データを与えたときの挙動
精度良く学習できたモデルでも、学習データの分布の外(Out of Distribution)には対応できない。
# 不正な one-hot(1が2つある)
[1 0 0 0 1 0 0] # ← one-hot ではない
[0 1 0 0 0 0 0]
# コーパスにない組み合わせ(正しい one-hot でも)
[1 0 0 0 0 0 0] # you
[0 0 0 0 0 1 0] # hello ← 隣り合う文はコーパスにない
- モデルは入力を検証せず、そのまま行列積を計算して何らかの値を出力する
- 意味のある予測にはならない(でたらめな値)
現代の大規模言語モデルが膨大なデータで学習する理由の一つがここ。
コーパスが大きいほど、未知の組み合わせにも意味のある予測ができるようになる。
7. 発見したバグ(Momentum クラス)
# 誤り:同じ行が2回記述されている
self.v[i] = self.momentum * self.v[i] - self.lr * grads[i]
self.v[i] = self.momentum * self.v[i] - self.lr * grads[i] # ← 削除すべき
# 正しくは1行のみ
self.v[i] = self.momentum * self.v[i] - self.lr * grads[i]
2行目により「更新済みの v にさらに momentum をかけ直す」状態になり、本来の Momentum SGD とは異なる収束挙動になる。
8. 本当の目的は W_in(分散表現)
このモデルの目的は予測精度そのものではなく、予測がうまくできるように鍛えられた過程で W_in に蓄積された単語の意味ベクトル(分散表現)を得ること。
model.word_vecs # = W_in, shape: (7, 5)
# 各単語が5次元ベクトルで表現される
# 意味が近い単語 → ベクトルが近い
これが Word2Vec / 分散表現 の核心。