これまでの章で、Tensorの操作と自動微分(autograd)の仕組みを学びました。この章では、いよいよそれらを土台にしてニューラルネットワーク(Neural Network)を構築します。nn.Moduleを使ったモデル定義から、損失関数・最適化手法の選び方、学習ループの実装、DataLoaderによるミニバッチ処理、そして学習したモデルの保存・読み込みまで、実際にコードを動かしながら一通りの流れを身につけましょう。
学習目標
- ✅ nn.Moduleを使ってモデルを定義できる
- ✅ 適切な損失関数と最適化手法を選択できる
- ✅ 学習ループを実装できる
- ✅ DataLoaderでバッチ処理ができる
- ✅ モデルの保存・読み込みができる
1. nn.Moduleの基本
PyTorchでニューラルネットワークを構築する際の基本単位がtorch.nn.Moduleです。nn.Moduleは、パラメータ(重みやバイアス)の管理、GPUへの転送、モデルの保存・読み込みなど、ニューラルネットワークに必要な機能をまとめて提供してくれるクラスです。自分のモデルを定義するときは、このクラスを継承して作ります。
nn.Moduleを継承したクラスの作り方
独自のモデルを定義するには、次の2つのメソッドを実装します。
__init__(): モデルで使うレイヤー(層)を定義するforward(): 入力データがレイヤーをどのような順序で通過するか(順伝播、forward propagation)を定義する
import torch
import torch.nn as nn
class SimpleNet(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super().__init__()
self.fc1 = nn.Linear(input_size, hidden_size)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(hidden_size, output_size)
def forward(self, x):
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x)
return x
# モデルのインスタンス化: 入力10次元 → 隠れ層32次元 → 出力3次元
model = SimpleNet(input_size=10, hidden_size=32, output_size=3)
print(model)
出力:
SimpleNet(
(fc1): Linear(in_features=10, out_features=32, bias=True)
(relu): ReLU()
(fc2): Linear(in_features=32, out_features=3, bias=True)
)
💡 super().__init__()を忘れずに
__init__()の中で最初にsuper().__init__()を呼ぶ必要があります。これによってnn.Module内部のパラメータ管理の仕組みが初期化され、self.fc1やself.fc2のような属性が自動的にモデルのパラメータとして登録されます。
モデルの実行とパラメータの確認
定義したモデルは、関数のように呼び出すだけで順伝播が実行されます。内部的にはmodel(x)がmodel.forward(x)を呼び出す仕組みになっています。
import torch
# バッチサイズ5、入力次元10のダミーデータ
x = torch.randn(5, 10)
# 順伝播(forward)の実行
output = model(x)
print(f"入力の形状: {x.shape}")
print(f"出力の形状: {output.shape}")
# モデルが持つパラメータを確認
for name, param in model.named_parameters():
print(f"{name}: {param.shape}")
出力:
入力の形状: torch.Size([5, 10])
出力の形状: torch.Size([5, 3])
fc1.weight: torch.Size([32, 10])
fc1.bias: torch.Size([32])
fc2.weight: torch.Size([3, 32])
fc2.bias: torch.Size([3])
named_parameters()で確認できるように、self.fc1 = nn.Linear(...)のようにnn.Moduleのサブクラス(nn.Linearなど)を属性として代入すると、そのパラメータは自動的に親モデルの学習対象パラメータとして登録されます。これがnn.Moduleを使う大きな利点の1つです。
2. レイヤーの定義と組み合わせ
先ほどのモデルで使ったnn.Linearとnn.ReLUは、それぞれ全結合層(Fully Connected Layer)と活性化関数(Activation Function)を表すレイヤーです。PyTorchにはこの他にも、目的に応じた多様なレイヤーが用意されています。
よく使うレイヤーと活性化関数
| クラス | 役割 | 主な用途 |
|---|---|---|
nn.Linear(in, out) |
全結合層。線形変換 $y = xW^T + b$ を行う | MLP、分類器の出力層など |
nn.ReLU() |
活性化関数。負の値を0にする | 隠れ層で最も一般的な選択 |
nn.Sigmoid() |
出力を0〜1に押し込む活性化関数 | 二値分類の出力層 |
nn.Softmax(dim=-1) |
出力を合計1の確率分布に変換 | 多クラス分類の出力(通常は損失関数側に内蔵) |
nn.Dropout(p) |
学習時に確率pでニューロンを無効化 | 過学習の抑制(正則化) |
nn.BatchNorm1d(num_features) |
ミニバッチ単位で正規化 | 学習の安定化・高速化 |
畳み込み層(nn.Conv2d)や再帰層(nn.LSTMなど)といった画像・系列データ向けのレイヤーは、それぞれ専用のシリーズで詳しく扱います。この章では、まず全結合層を中心としたモデル構築に集中しましょう。
nn.Sequentialで手早く組み立てる
forward()の中でレイヤーを順番に適用するだけのシンプルなモデルであれば、nn.Sequentialを使うとより簡潔に書けます。
import torch.nn as nn
model_seq = nn.Sequential(
nn.Linear(10, 32),
nn.ReLU(),
nn.Linear(32, 16),
nn.ReLU(),
nn.Linear(16, 3)
)
print(model_seq)
出力:
Sequential(
(0): Linear(in_features=10, out_features=32, bias=True)
(1): ReLU()
(2): Linear(in_features=32, out_features=16, bias=True)
(3): ReLU()
(4): Linear(in_features=16, out_features=3, bias=True)
)
柔軟なレイヤー数に対応するMLPクラス
実務では、隠れ層の数やサイズを引数として自由に変えられるモデルを定義したい場面がよくあります。nn.Moduleとnn.Sequentialを組み合わせることで、次のような汎用的な多層パーセプトロン(Multi-Layer Perceptron、MLP)クラスを作れます。
import torch.nn as nn
class MLP(nn.Module):
def __init__(self, input_size, hidden_sizes, output_size):
super().__init__()
layers = []
prev_size = input_size
for hidden_size in hidden_sizes:
layers.append(nn.Linear(prev_size, hidden_size))
layers.append(nn.ReLU())
prev_size = hidden_size
layers.append(nn.Linear(prev_size, output_size))
self.network = nn.Sequential(*layers)
def forward(self, x):
return self.network(x)
# 入力20次元 → 隠れ層[64, 32] → 出力4次元
mlp = MLP(input_size=20, hidden_sizes=[64, 32], output_size=4)
print(mlp)
出力:
MLP(
(network): Sequential(
(0): Linear(in_features=20, out_features=64, bias=True)
(1): ReLU()
(2): Linear(in_features=64, out_features=32, bias=True)
(3): ReLU()
(4): Linear(in_features=32, out_features=4, bias=True)
)
)
このように、リストを受け取って動的にレイヤーを組み立てる設計にしておくと、隠れ層の構成を変えて実験するときにモデルクラス自体を書き換える必要がなくなります。この章の後半では、このMLPクラスを使って実際に学習を行います。
3. 損失関数と最適化手法
モデルを定義しただけでは、まだ何も学習していません。学習には、モデルの予測がどれだけ間違っているかを数値化する損失関数(Loss Function)と、その損失を小さくする方向にパラメータを更新する最適化手法(Optimizer)の2つが必要です。
代表的な損失関数
| クラス | 用途 | 備考 |
|---|---|---|
nn.CrossEntropyLoss() |
多クラス分類 | 内部でSoftmaxを適用するため、モデルの出力層にSoftmaxを追加してはいけない |
nn.BCEWithLogitsLoss() |
二値分類 | 内部でSigmoidを適用する。数値的に安定 |
nn.MSELoss() |
回帰 | 平均二乗誤差(Mean Squared Error) |
nn.L1Loss() |
回帰 | 平均絶対誤差。外れ値に頑健 |
⚠️ CrossEntropyLossの入力形式に注意
nn.CrossEntropyLoss()には、Softmaxをかける前の生の出力であるロジット(Logits)をそのまま渡します。正解ラベルはワンホットベクトルではなく、クラス番号を表す整数(dtype=torch.long)のTensorで渡す点にも注意しましょう。
代表的な最適化手法
| クラス | 特徴 |
|---|---|
optim.SGD(params, lr=0.01) |
確率的勾配降下法(Stochastic Gradient Descent)。シンプルで理論的にも扱いやすい |
optim.SGD(params, lr=0.01, momentum=0.9) |
momentum付きSGD。振動を抑えて収束を早める |
optim.Adam(params, lr=0.001) |
学習率を勾配の統計量に応じて自動調整する適応的手法。多くの場面での既定選択 |
迷ったら、まずnn.CrossEntropyLoss()(分類)またはnn.MSELoss()(回帰)とoptim.Adam(model.parameters(), lr=0.001)から始めるとよいでしょう。学習が安定しない場合に学習率を調整したり、他の手法を試したりするのが一般的な進め方です。
import torch
import torch.nn as nn
import torch.optim as optim
# 損失関数と最適化手法を用意する
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(mlp.parameters(), lr=0.001)
# ダミーデータで損失を1回だけ計算してみる
x = torch.randn(8, 20) # バッチサイズ8、入力20次元
y = torch.randint(0, 4, (8,)) # 0〜3のクラスラベル(整数)
logits = mlp(x) # ロジット(Softmax適用前)
loss = criterion(logits, y)
print(f"出力の形状: {logits.shape}")
print(f"損失: {loss.item():.4f}")
出力例:
出力の形状: torch.Size([8, 4])
損失: 1.4290
optim.Adam(mlp.parameters(), lr=0.001)のように、最適化手法にはモデルの全パラメータ(model.parameters())と学習率(learning rate)を渡します。これで、次の節で実装する学習ループの中でoptimizer.step()を呼ぶだけで、これらのパラメータが自動的に更新されるようになります。
4. 学習ループの実装
モデル・損失関数・最適化手法がそろったら、いよいよ学習ループ(Training Loop)です。PyTorchの学習ループは、どんなモデルであってもおおむね次の5ステップの繰り返しになります。
- optimizer.zero_grad(): 前回のイテレーションで蓄積された勾配をリセットする
- 順伝播(forward): モデルに入力を渡して予測を得る
- 損失計算: 予測と正解ラベルから損失を計算する
- 逆伝播(loss.backward()): 損失に対する各パラメータの勾配を自動微分で計算する
- optimizer.step(): 計算された勾配を使ってパラメータを更新する
まずは全データを1回のバッチとして扱う、最小構成の学習ループを見てみましょう。次の節で登場するmake_classificationで生成した合成データセットを使います。
import torch
import torch.nn as nn
import torch.optim as optim
from sklearn.datasets import make_classification
torch.manual_seed(42)
# 合成データ: 4クラス分類、特徴量20次元、1000サンプル
X, y = make_classification(
n_samples=1000,
n_features=20,
n_informative=15,
n_redundant=5,
n_classes=4,
random_state=42,
)
X_train = torch.tensor(X, dtype=torch.float32)
y_train = torch.tensor(y, dtype=torch.long)
model = MLP(input_size=20, hidden_sizes=[64, 32], output_size=4)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 最小構成の学習ループ(全データを毎回まとめて使う)
for epoch in range(5):
optimizer.zero_grad() # 1. 勾配のリセット
outputs = model(X_train) # 2. 順伝播
loss = criterion(outputs, y_train) # 3. 損失計算
loss.backward() # 4. 逆伝播
optimizer.step() # 5. パラメータ更新
print(f"Epoch {epoch+1}: loss = {loss.item():.4f}")
出力例:
Epoch 1: loss = 1.3877
Epoch 2: loss = 1.3762
Epoch 3: loss = 1.3651
Epoch 4: loss = 1.3542
Epoch 5: loss = 1.3435
この方法は動きますが、データセット全体を毎回1つの巨大なバッチとしてGPU/CPUのメモリに載せる必要があり、データ量が増えると現実的ではありません。そこで登場するのが、次の節で扱うDataLoaderによるミニバッチ学習(Mini-batch Learning)です。
5. DataLoaderの使い方
実際の学習では、データセットを小さな塊(ミニバッチ)に分割し、1バッチずつ学習ループを回すのが一般的です。PyTorchではtorch.utils.data.Datasetとtorch.utils.data.DataLoaderという2つのクラスがこの役割を担います。
- Dataset: 「何番目のデータか(インデックス)」を渡すと、対応する1サンプルを返すオブジェクト
- DataLoader: Datasetからサンプルを取り出し、指定したバッチサイズにまとめ、必要ならシャッフルして順番に供給するイテレータ
TensorDatasetで手早く作る
入力とラベルがすでにTensorとして揃っている場合は、TensorDatasetを使うのが最も手軽です。
import torch
from torch.utils.data import TensorDataset, DataLoader
# 学習用データとテスト用データに分割
from sklearn.model_selection import train_test_split
X_train_np, X_test_np, y_train_np, y_test_np = train_test_split(
X, y, test_size=0.2, random_state=42
)
X_train = torch.tensor(X_train_np, dtype=torch.float32)
y_train = torch.tensor(y_train_np, dtype=torch.long)
X_test = torch.tensor(X_test_np, dtype=torch.float32)
y_test = torch.tensor(y_test_np, dtype=torch.long)
train_dataset = TensorDataset(X_train, y_train)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
# 1バッチだけ取り出して形状を確認
batch_X, batch_y = next(iter(train_loader))
print(f"バッチ内の入力形状: {batch_X.shape}")
print(f"バッチ内のラベル形状: {batch_y.shape}")
print(f"データセット全体: {len(train_dataset)}件, バッチ数: {len(train_loader)}")
出力:
バッチ内の入力形状: torch.Size([32, 20])
バッチ内のラベル形状: torch.Size([32])
データセット全体: 800件, バッチ数: 25
shuffle=Trueを指定すると、エポックごとにサンプルの順序がシャッフルされます。学習データの並び順にモデルが依存してしまうのを防ぐため、学習用のDataLoaderでは基本的にshuffle=Trueにします(評価・テスト用では通常shuffle=Falseで構いません)。
独自のDatasetクラスを作る
画像ファイルの読み込みや前処理など、単純なTensorの切り出し以上の処理が必要な場合は、Datasetを継承して独自クラスを定義します。最低限、次の3つのメソッドを実装します。
import torch
from torch.utils.data import Dataset, DataLoader
class MyDataset(Dataset):
def __init__(self, X, y):
self.X = X
self.y = y
def __len__(self):
# データセット全体のサンプル数を返す
return len(self.X)
def __getitem__(self, idx):
# idx番目のサンプルを (入力, ラベル) のペアで返す
return self.X[idx], self.y[idx]
custom_dataset = MyDataset(X_train, y_train)
custom_loader = DataLoader(custom_dataset, batch_size=16, shuffle=True)
batch_X, batch_y = next(iter(custom_loader))
print(f"カスタムDatasetのバッチ形状: {batch_X.shape}, {batch_y.shape}")
出力:
カスタムDatasetのバッチ形状: torch.Size([16, 20]), torch.Size([16])
DataLoaderを使った本格的な学習ループ
それでは、DataLoaderを使ってミニバッチ単位で学習する、より実践的な学習ループを実装しましょう。エポックごとに訓練データ全体を1周し、精度も合わせて記録します。
import torch.nn as nn
import torch.optim as optim
model = MLP(input_size=20, hidden_sizes=[64, 32], output_size=4)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
n_epochs = 30
for epoch in range(n_epochs):
model.train() # 学習モードに設定(Dropout等が有効になる)
total_loss = 0.0
correct = 0
total = 0
for batch_X, batch_y in train_loader:
optimizer.zero_grad()
outputs = model(batch_X)
loss = criterion(outputs, batch_y)
loss.backward()
optimizer.step()
total_loss += loss.item() * batch_X.size(0)
preds = outputs.argmax(dim=1)
correct += (preds == batch_y).sum().item()
total += batch_y.size(0)
if (epoch + 1) % 5 == 0:
avg_loss = total_loss / total
train_acc = correct / total
print(f"Epoch {epoch+1:3d}/{n_epochs}: loss={avg_loss:.4f}, train_acc={train_acc:.4f}")
# テストデータで評価
model.eval() # 評価モードに設定
with torch.no_grad():
test_outputs = model(X_test)
test_preds = test_outputs.argmax(dim=1)
test_acc = (test_preds == y_test).float().mean().item()
print(f"テスト精度: {test_acc:.4f}")
出力例(実行結果):
Epoch 5/30: loss=0.7789, train_acc=0.7113
Epoch 10/30: loss=0.4871, train_acc=0.8287
Epoch 15/30: loss=0.3546, train_acc=0.8788
Epoch 20/30: loss=0.2659, train_acc=0.9187
Epoch 25/30: loss=0.1975, train_acc=0.9475
Epoch 30/30: loss=0.1468, train_acc=0.9650
テスト精度: 0.7350
💡 train()とeval()の違い
model.train()とmodel.eval()は、DropoutやBatchNormのように学習時と推論時で挙動を変えるレイヤーの動作を切り替えるためのスイッチです。今回のモデルには含まれていませんが、これらを含むモデルを扱うときのために、評価前には必ずmodel.eval()を呼ぶ習慣をつけておきましょう。あわせて、評価時はtorch.no_grad()で勾配計算を無効化すると、メモリ消費を抑えられます。
訓練精度(train_acc)が96.5%まで上がっている一方、テスト精度は73.5%にとどまっています。これは、モデルが訓練データの細部まで学習しすぎてしまう過学習(Overfitting)の典型的な兆候です。過学習への対策(Dropout、正則化、Early Stoppingなど)は今後のシリーズで詳しく扱いますが、「訓練精度とテスト精度を両方観察する」こと自体が、学習ループを実装するうえで欠かせない習慣だと覚えておいてください。
6. モデルの保存と読み込み
時間をかけて学習したモデルは、プロセスを終了しても再利用できるように保存しておく必要があります。PyTorchではtorch.save()とload_state_dict()を組み合わせるのが標準的な方法です。
state_dictとは何か
state_dictは、モデルが持つ全パラメータ(重み・バイアス)の名前と値を対応付けた辞書(OrderedDict)です。モデルの構造そのものではなく、学習済みの数値だけを保存・復元できる点が特徴です。
import torch
# モデルの重みだけを保存する
torch.save(model.state_dict(), "mlp_model.pth")
print("モデルを保存しました: mlp_model.pth")
# 同じ構造のモデルを新規作成し、保存した重みを読み込む
loaded_model = MLP(input_size=20, hidden_sizes=[64, 32], output_size=4)
loaded_model.load_state_dict(torch.load("mlp_model.pth"))
loaded_model.eval()
# 元のモデルと読み込んだモデルの出力が一致することを確認
with torch.no_grad():
original_output = model(X_test)
loaded_output = loaded_model(X_test)
print("出力が一致:", torch.allclose(original_output, loaded_output))
出力:
モデルを保存しました: mlp_model.pth
出力が一致: True
⚠️ モデルの構造は自分で再現する必要がある
state_dictには重みの数値しか入っていません。読み込み側では、保存時と同じ構造(同じhidden_sizesなど)のモデルを先に作成してからload_state_dict()を呼ぶ必要があります。構造が一致していないと、エラーになるか、意図しない結果になります。
学習を再開できるチェックポイントの保存
学習を途中から再開したい場合は、モデルの重みだけでなく、オプティマイザの状態(optimizer state_dict)やエポック数、損失なども一緒に保存しておくと便利です。これはチェックポイント(Checkpoint)と呼ばれる保存方法です。
import torch
import torch.optim as optim
# チェックポイントとして複数の情報をまとめて保存
checkpoint = {
'epoch': n_epochs,
'model_state_dict': model.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'loss': avg_loss,
}
torch.save(checkpoint, "checkpoint.pth")
print(f"チェックポイントを保存しました(epoch={n_epochs}, loss={avg_loss:.4f})")
# チェックポイントから学習を再開する準備
checkpoint_loaded = torch.load("checkpoint.pth")
resumed_model = MLP(input_size=20, hidden_sizes=[64, 32], output_size=4)
resumed_model.load_state_dict(checkpoint_loaded['model_state_dict'])
resumed_optimizer = optim.Adam(resumed_model.parameters(), lr=0.001)
resumed_optimizer.load_state_dict(checkpoint_loaded['optimizer_state_dict'])
start_epoch = checkpoint_loaded['epoch']
print(f"エポック{start_epoch}から学習を再開できます(前回の損失: {checkpoint_loaded['loss']:.4f})")
出力例:
チェックポイントを保存しました(epoch=30, loss=0.1468)
エポック30から学習を再開できます(前回の損失: 0.1468)
オプティマイザの状態まで復元することで、Adamが内部で保持している「勾配の移動平均」のような統計量も引き継がれ、学習を中断した地点から違和感なく再開できます。単にモデルを配布・推論に使うだけならstate_dictの保存だけで十分ですが、長時間の学習を扱う実務では、チェックポイント方式に慣れておくと安心です。
演習問題
演習1:3層のMLPを定義する
nn.Moduleを継承したクラスThreeLayerNetを定義してください。入力次元は8、隠れ層は2つ(それぞれ16、8次元)で活性化関数はReLU、出力次元は2としてください。定義後、バッチサイズ4のダミー入力を渡して出力の形状を確認してください。
解答例:
import torch
import torch.nn as nn
class ThreeLayerNet(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(8, 16)
self.relu1 = nn.ReLU()
self.fc2 = nn.Linear(16, 8)
self.relu2 = nn.ReLU()
self.fc3 = nn.Linear(8, 2)
def forward(self, x):
x = self.relu1(self.fc1(x))
x = self.relu2(self.fc2(x))
x = self.fc3(x)
return x
model = ThreeLayerNet()
x = torch.randn(4, 8)
output = model(x)
print(f"出力の形状: {output.shape}") # torch.Size([4, 2])
演習2:損失関数とoptimizerの選択
次の2つの問題設定それぞれについて、適切な損失関数と最適化手法の組み合わせをコードで示してください。
- 住宅価格を予測する回帰問題
- 手書き数字(0〜9の10クラス)を分類する問題
解答例:
import torch.nn as nn
import torch.optim as optim
# 1. 回帰問題: 出力は連続値なのでMSELossが適切
regression_model = nn.Linear(13, 1)
regression_criterion = nn.MSELoss()
regression_optimizer = optim.Adam(regression_model.parameters(), lr=0.001)
# 2. 10クラス分類: CrossEntropyLossが適切(出力層はSoftmaxを付けない)
classification_model = nn.Linear(784, 10)
classification_criterion = nn.CrossEntropyLoss()
classification_optimizer = optim.Adam(classification_model.parameters(), lr=0.001)
print("回帰: MSELoss + Adam")
print("分類: CrossEntropyLoss + Adam")
演習3:DataLoaderを使った学習と評価
sklearn.datasets.make_classificationで2クラス分類用の合成データ(サンプル数300、特徴量5次元)を作成し、TensorDatasetとDataLoader(batch_size=16)を使って、入力5次元・隠れ層[16]・出力2次元のMLPを10エポック学習させてください。最後にテストデータでの精度を表示してください。
解答例:
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import TensorDataset, DataLoader
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
torch.manual_seed(0)
X, y = make_classification(n_samples=300, n_features=5, n_classes=2, random_state=0)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
X_train = torch.tensor(X_train, dtype=torch.float32)
y_train = torch.tensor(y_train, dtype=torch.long)
X_test = torch.tensor(X_test, dtype=torch.float32)
y_test = torch.tensor(y_test, dtype=torch.long)
train_loader = DataLoader(TensorDataset(X_train, y_train), batch_size=16, shuffle=True)
class MLP(nn.Module):
def __init__(self, input_size, hidden_sizes, output_size):
super().__init__()
layers = []
prev_size = input_size
for h in hidden_sizes:
layers += [nn.Linear(prev_size, h), nn.ReLU()]
prev_size = h
layers.append(nn.Linear(prev_size, output_size))
self.network = nn.Sequential(*layers)
def forward(self, x):
return self.network(x)
model = MLP(5, [16], 2)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.01)
for epoch in range(10):
model.train()
for batch_X, batch_y in train_loader:
optimizer.zero_grad()
loss = criterion(model(batch_X), batch_y)
loss.backward()
optimizer.step()
model.eval()
with torch.no_grad():
test_acc = (model(X_test).argmax(dim=1) == y_test).float().mean().item()
print(f"テスト精度: {test_acc:.4f}")
まとめ
この章では、PyTorchで実際にニューラルネットワークを構築し、学習させる一連の流れを学びました。
- ✅
nn.Moduleを継承し、__init__()でレイヤーを定義、forward()で順伝播の流れを定義してモデルを作れる - ✅
nn.Linearやnn.ReLUなどのレイヤーをnn.Sequentialで組み合わせ、柔軟なMLPを構築できる - ✅ 問題の種類(分類・回帰)に応じて
nn.CrossEntropyLossやnn.MSELossなどの損失関数と、optim.Adamなどの最適化手法を選択できる - ✅ zero_grad→forward→loss→backward→stepの5ステップからなる学習ループを実装できる
- ✅
DatasetとDataLoaderを使ってミニバッチ単位でデータを供給し、効率的に学習できる - ✅
state_dictによるモデルの保存・読み込みと、オプティマイザの状態を含むチェックポイントの扱いを理解した
🎉 次のステップ
ニューラルネットワークを一から構築し、学習・保存・再利用する一通りの流れを体験できました。続く第5章では、GPUの活用や学習の可視化、過学習対策など、より実践的なモデル開発のテクニックへと進んでいく予定です。
参考リソース