パーセプトロンから多層パーセプトロンへ:
単純なパーセプトロンは線形分離面しか生成できないため、XORのような非線形問題を解決できません。
この制限を克服するには、ネットワークに1つまたは複数の隠れ層を追加して多層パーセプトロン(MLP)を作成します。しかし、単に隠れ層を追加しただけでは、まだ線形モデルに等しいため、非線形の活性化関数を導入することで、多層アーキテクチャの潜在能力を引き出す必要があります。
理論的には、1つの隠れ層を持つネットワークでも任意の関数を学習できますが、実際には浅い幅広いネットワークは訓練が難しく、過学習しやすいです。一方、深く狭いネットワークは訓練が容易で、各層が少しずつ学習します。
活性化関数:
活性化関数の選択はそれほど重要ではなく、特に考えがない場合はReLUを使用するのが一般的です。他のハイパーパラメータ(例えば、隠れ層の数や各層のサイズ)の方がより重要です。
- ReLU:
- \(ReLU(x) = \max(x, 0)\)
- 非常に単純な非線形変換(指数計算なし、高速)
- 微分が簡単で、最適化に有利(\(x < 0\) では0、\(x > 0\) では1)
- Sigmoid関数の勾配消失問題を解決
- Sigmoid関数:
- \(sigmoid(x) = \frac{1}{1 + \exp(-x)}\)
- 形状がソフトバージョンのステップ関数に似ているが、両端での勾配消失により、活性化関数として使用される頻度が減少している(RNNでは時系列情報フローの制御に使用される)
- tanh関数:
- \(tanh(x) = \frac{\exp(2x) - 1}{\exp(2x) + 1}\)
- Sigmoid関数の範囲を(-1, 1)に拡張し、原点を中心に対称
コード実装:
import torch
from torch import nn
net = nn.Sequential(
nn.Flatten(),
nn.Linear(784, 256),
nn.ReLU(),
nn.Linear(256, 10)
)
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01)
net.apply(init_weights)
学習誤差と汎化誤差:
学習誤差は訓練データセット上で計算された誤差であり、汎化誤差はモデルが未知のデータに対してどの程度の性能を発揮するかの期待値です。汎化誤差は正確に計算することは難しいので、独立したテストセットまたは検証セットでの誤差で推定します。
学習不足と過学習:
学習不足はモデルが訓練データのパターンを十分に捉えられない状態で、訓練誤差と検証誤差が共に大きい場合に発生します。一方、過学習はモデルが訓練データの詳細に過剰に適合し、検証データでの性能が悪くなる状態です。
モデル選択:
訓練セットはモデルのパラメータを訓練し、検証セットはハイパーパラメータを選択するために使用されます。データセットが小さい場合は、k-分割交差検証を使用すると良いです。
正則化方法:重み減衰、ドロップアウト
過学習を防ぐために、モデルの容量を制限する必要があります。これには、パラメータの数を減らすか、パラメータの値の範囲を縮小する方法があります。
重み減衰:
重み減衰は、L2正則化項を損失関数に追加することで実現します。
def train_concise(wd):
net = nn.Sequential(nn.Linear(num_inputs, 1))
for param in net.parameters():
param.data.normal_()
loss = nn.MSELoss(reduction='none')
num_epochs, lr = 100, 0.003
trainer = torch.optim.SGD([
{"params": net[0].weight, 'weight_decay': wd},
{"params": net[0].bias}], lr=lr)
animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='log',
xlim=[5, num_epochs], legend=['train', 'test'])
for epoch in range(num_epochs):
for X, y in train_iter:
trainer.zero_grad()
l = loss(net(X), y)
l.mean().backward()
trainer.step()
if (epoch + 1) % 5 == 0:
animator.add(epoch + 1,
(d2l.evaluate_loss(net, train_iter, loss),
d2l.evaluate_loss(net, test_iter, loss)))
print('wのL2ノルム:', net[0].weight.norm().item())
ドロップアウト:
ドロップアウトは、訓練中に一部のニューロンの出力をランダムに0に設定することで、モデルの複雑さを制限します。
net = nn.Sequential(
nn.Flatten(),
nn.Linear(784, 256),
nn.ReLU(),
nn.Dropout(dropout1),
nn.Linear(256, 256),
nn.ReLU(),
nn.Dropout(dropout2),
nn.Linear(256, 10)
)
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01)
net.apply(init_weights)
数値安定性:
深層ネットワークでは、勾配消失や勾配爆発といった数値不安定性の問題が発生します。これらの問題を解決するには、適切な重み初期化、ReLUなどの安定性の高い活性化関数の使用、勾配クリッピングなどが有効です。