ニューラルネットワーク入門 ── パーセプトロンから誤差逆伝播まで
LLM との関係:LLM は「巨大なニューラルネットワーク」である。この章で扱う順伝播・逆伝播・勾配降下法は、LLM の訓練そのものの基盤。

この章で何ができるようになるか:「なぜ重みが更新されるとモデルが賢くなるか」を数学的に理解し、PyTorch での基本的な訓練ループを書けるようになる。
パーセプトロン:1個のニューロン
$$y = f(w_1 x_1 + w_2 x_2 + \dots + w_n x_n + b)$$
import numpy as np
def perceptron(x, weights, bias, activation='sigmoid'):
z = np.dot(weights, x) + bias # 線形結合
if activation == 'sigmoid':
return 1 / (1 + np.exp(-z)) # 0〜1 に圧縮
elif activation == 'relu':
return max(0, z) # 0 以下を切り捨て
活性化関数がなぜ必要か:線形変換を何層重ねても結果は線形。非線形な活性化関数を挟むことで、非線形な関数(現実世界の複雑なパターン)を表現できるようになる。
主要な活性化関数
def sigmoid(z):
return 1 / (1 + np.exp(-z))
# 範囲: (0, 1)。確率の出力に使う。勾配消失しやすい。
def tanh(z):
return np.tanh(z)
# 範囲: (-1, 1)。sigmoid より中心が0で扱いやすい。
def relu(z):
return np.maximum(0, z)
# 範囲: [0, ∞)。最も広く使われる。計算が速い。
def gelu(z):
"""Gaussian Error Linear Unit — Transformer で標準的に使われる"""
return 0.5 * z * (1 + np.tanh(np.sqrt(2/np.pi) * (z + 0.044715 * z**3)))
# ReLU の滑らかな近似。GPT/BERT で使用。
多層ニューラルネットワーク:順伝播
class SimpleNN:
"""2層のニューラルネットワーク(NumPy 実装)"""
def __init__(self, input_dim, hidden_dim, output_dim):
# 重みをランダムに初期化(Xavier 初期化)
scale1 = np.sqrt(2.0 / input_dim)
scale2 = np.sqrt(2.0 / hidden_dim)
self.W1 = np.random.randn(hidden_dim, input_dim) * scale1
self.b1 = np.zeros(hidden_dim)
self.W2 = np.random.randn(output_dim, hidden_dim) * scale2
self.b2 = np.zeros(output_dim)
def forward(self, x):
"""順伝播:入力 → 隠れ層 → 出力"""
# 隠れ層
self.z1 = self.W1 @ x + self.b1
self.h = np.maximum(0, self.z1) # ReLU
# 出力層
self.z2 = self.W2 @ self.h + self.b2
self.output = softmax(self.z2) # 確率分布
return self.output
誤差逆伝播法(Backpropagation)
「出力の誤差を、各重みがどれだけ寄与したかに応じて逆方向に伝播し、重みを修正する」。
連鎖律(Chain Rule)
$$\frac{\partial L}{\partial w} = \frac{\partial L}{\partial y} \cdot \frac{\partial y}{\partial z} \cdot \frac{\partial z}{\partial w}$$
def backward(self, x, y_true):
"""逆伝播:勾配を計算する"""
batch_size = 1
# 出力層の勾配(ソフトマックス + 交差エントロピーの勾配は単純)
dz2 = self.output.copy()
dz2[y_true] -= 1 # ∂L/∂z2 = predicted - one_hot(true)
# W2, b2 の勾配
self.dW2 = np.outer(dz2, self.h) # ∂L/∂W2
self.db2 = dz2 # ∂L/∂b2
# 隠れ層への勾配
dh = self.W2.T @ dz2 # ∂L/∂h
# ReLU の勾配(z > 0 なら 1、z <= 0 なら 0)
dz1 = dh * (self.z1 > 0).astype(float)
# W1, b1 の勾配
self.dW1 = np.outer(dz1, x)
self.db1 = dz1
def update(self, learning_rate=0.01):
"""勾配降下法で重みを更新"""
self.W2 -= learning_rate * self.dW2
self.b2 -= learning_rate * self.db2
self.W1 -= learning_rate * self.dW1
self.b1 -= learning_rate * self.db1
勾配降下法のバリエーション
SGD(確率的勾配降下法)
# 全データでなく、ミニバッチごとに更新
for epoch in range(100):
for batch in dataloader:
loss = model.forward(batch)
loss.backward()
for param in model.parameters():
param.data -= learning_rate * param.grad
Adam(Adaptive Moment Estimation)
LLM の訓練で最も広く使われるオプティマイザ。
# Adam の直感:
# 1. 勾配の移動平均(momentum): 過去の方向を覚えて慣性をつける
# 2. 勾配の二乗の移動平均: パラメータごとに学習率を調整
# → 勾配が大きいパラメータは学習率を下げ、小さいパラメータは上げる
import torch
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4, betas=(0.9, 0.999))
for batch in dataloader:
optimizer.zero_grad() # 勾配をリセット
output = model(batch.input) # 順伝播
loss = criterion(output, batch.target) # 損失計算
loss.backward() # 逆伝播(自動微分)
optimizer.step() # パラメータ更新
PyTorch での実装
import torch
import torch.nn as nn
class SimpleLanguageModel(nn.Module):
"""最小の言語モデル(Transformer ではない簡易版)"""
def __init__(self, vocab_size, embed_dim, hidden_dim):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim)
self.fc1 = nn.Linear(embed_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, vocab_size)
self.gelu = nn.GELU()
def forward(self, x):
# x: (batch_size, seq_len) — トークンIDの列
emb = self.embedding(x) # (batch, seq, embed_dim)
emb = emb.mean(dim=1) # 全トークンの平均(簡易版)
h = self.gelu(self.fc1(emb)) # (batch, hidden_dim)
logits = self.fc2(h) # (batch, vocab_size)
return logits
# 訓練ループ
model = SimpleLanguageModel(vocab_size=10000, embed_dim=256, hidden_dim=512)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
criterion = nn.CrossEntropyLoss()
for epoch in range(10):
for input_ids, target_ids in dataloader:
logits = model(input_ids)
loss = criterion(logits, target_ids)
optimizer.zero_grad()
loss.backward() # PyTorch が自動で逆伝播を計算
optimizer.step()
print(f"Epoch {epoch}: Loss = {loss.item():.4f}")
PyTorch の loss.backward() は何をしているか:計算グラフ(forward で記録された全演算の有向グラフ)を逆方向にたどり、連鎖律で全パラメータの勾配を自動計算する。手動で微分を書く必要はない。
過学習と正則化
過学習(Overfitting):
訓練データには高い精度を出すが、未知のデータでは性能が低い
→ モデルが「訓練データを丸暗記」している
対策:
Dropout: ランダムにニューロンを無効化(共適応を防ぐ)
Weight Decay: 重みの大きさにペナルティ(L2正則化)
Early Stopping: 検証損失が悪化し始めたら訓練を止める
class ModelWithDropout(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(256, 512)
self.dropout = nn.Dropout(0.1) # 10% のニューロンをランダムに0にする
self.fc2 = nn.Linear(512, 10000)
def forward(self, x):
h = torch.relu(self.fc1(x))
h = self.dropout(h) # 訓練時のみ有効。推論時は無効化される
return self.fc2(h)
まとめ
| 概念 | 役割 | LLM への接続 |
|---|---|---|
| 順伝播 | 入力 → 出力の計算 | LLM の推論はまさにこれ |
| 逆伝播 | 勾配の計算(連鎖律) | LLM の訓練の核心 |
| 勾配降下法 | 重みの更新 | Adam が標準 |
| 活性化関数 | 非線形性の導入 | GELU が Transformer 標準 |
| Dropout | 過学習防止 | LLM の全層に入っている |
| 損失関数 | 予測の良さの計測 | 交差エントロピー |
次章では「単語をどうやってベクトルに変換するか」──Word2Vec の仕組みを見ていく。