モデル定義
田中VPoE:「データの準備ができたら、次はモデルの定義だ。PyTorch では nn.Module を継承してモデルを作る。NumPy で書いた順伝播を、PyTorch の流儀で書き直してみよう。」
あなた:「nn.Module がすべてのモデルの基底クラスなんですね。」
田中VPoE:「そうだ。レイヤーの定義と forward メソッドさえ書けば、あとは PyTorch が逆伝播を自動で処理してくれる。」
nn.Module の基本
import torch
import torch.nn as nn
class SimpleClassifier(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super().__init__()
# レイヤーの定義
self.fc1 = nn.Linear(input_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, output_dim)
self.relu = nn.ReLU()
self.sigmoid = nn.Sigmoid()
def forward(self, x):
# 順伝播の定義
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x)
x = self.sigmoid(x)
return x
# モデルのインスタンス化
model = SimpleClassifier(input_dim=10, hidden_dim=64, output_dim=1)
print(model)
nn.Module のルール
| ルール | 説明 |
|---|---|
super().__init__() | 必ずコンストラクタで呼ぶ |
self.xxx = nn.xxx | レイヤーをインスタンス変数として定義 |
forward() | 順伝播のロジックを記述 |
| 逆伝播 | PyTorch が自動で処理(手動不要) |
主要なレイヤー
全結合層(Linear)
# nn.Linear(入力次元, 出力次元)
fc = nn.Linear(128, 64)
x = torch.randn(32, 128) # バッチサイズ32, 128次元
out = fc(x) # (32, 64)
print(f"パラメータ: weights={fc.weight.shape}, bias={fc.bias.shape}")
正規化層
# BatchNorm: バッチ方向に正規化(学習の安定化)
bn = nn.BatchNorm1d(64)
# LayerNorm: 特徴量方向に正規化(Transformer で使用)
ln = nn.LayerNorm(64)
Dropout
# 学習時にランダムにニューロンを無効化(過学習防止)
dropout = nn.Dropout(p=0.3) # 30% のニューロンを無効化
nn.Sequential でモデルを定義
単純な直列構造なら nn.Sequential が便利です。
model = nn.Sequential(
nn.Linear(10, 128),
nn.BatchNorm1d(128),
nn.ReLU(),
nn.Dropout(0.3),
nn.Linear(128, 64),
nn.BatchNorm1d(64),
nn.ReLU(),
nn.Dropout(0.3),
nn.Linear(64, 1),
nn.Sigmoid()
)
print(model)
nn.Sequential vs nn.Module クラス
| 方法 | メリット | デメリット |
|---|---|---|
nn.Sequential | コードが短い、シンプル | 分岐や複雑な処理が書けない |
nn.Module クラス | 柔軟な構造が可能 | コードがやや長い |
実践的なモデル設計
NetShop の顧客離反予測モデルを PyTorch で構築してみましょう。
class ChurnPredictor(nn.Module):
"""NetShop 離反予測モデル"""
def __init__(self, input_dim, hidden_dims=[128, 64, 32], dropout_rate=0.3):
super().__init__()
layers = []
prev_dim = input_dim
for hidden_dim in hidden_dims:
layers.extend([
nn.Linear(prev_dim, hidden_dim),
nn.BatchNorm1d(hidden_dim),
nn.ReLU(),
nn.Dropout(dropout_rate),
])
prev_dim = hidden_dim
# 出力層(Sigmoid は損失関数側で処理する場合もある)
layers.append(nn.Linear(prev_dim, 1))
self.network = nn.Sequential(*layers)
def forward(self, x):
return self.network(x)
# モデルのインスタンス化
model = ChurnPredictor(input_dim=15, hidden_dims=[128, 64, 32])
# パラメータ数の確認
total_params = sum(p.numel() for p in model.parameters())
trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f"総パラメータ数: {total_params:,}")
print(f"学習可能パラメータ数: {trainable_params:,}")
パラメータの確認と初期化
# パラメータの一覧
for name, param in model.named_parameters():
print(f"{name:30s} | shape={str(param.shape):15s} | requires_grad={param.requires_grad}")
# 重みの初期化(Xavier / Kaiming)
def init_weights(m):
if isinstance(m, nn.Linear):
nn.init.kaiming_normal_(m.weight, mode='fan_in', nonlinearity='relu')
if m.bias is not None:
nn.init.zeros_(m.bias)
model.apply(init_weights)
| 初期化手法 | 活性化関数 | 説明 |
|---|---|---|
| Xavier (Glorot) | Sigmoid, Tanh | 入出力の分散を均一に保つ |
| Kaiming (He) | ReLU | ReLU の特性に合わせた初期化 |
モデルの保存と読み込み
# モデルの保存(パラメータのみ - 推奨)
torch.save(model.state_dict(), 'churn_model.pth')
# モデルの読み込み
loaded_model = ChurnPredictor(input_dim=15)
loaded_model.load_state_dict(torch.load('churn_model.pth'))
loaded_model.eval() # 評価モードに切り替え
まとめ
nn.Moduleを継承し、__init__でレイヤー定義、forwardで順伝播を記述するnn.Sequentialはシンプルな直列構造に適し、複雑な構造はnn.Moduleクラスで定義する- BatchNorm と Dropout は学習の安定化と過学習防止に重要
- 重みの初期化は活性化関数に合わせて選択する(ReLU → Kaiming)
state_dict()でモデルの保存と読み込みを行う
チェックリスト
-
nn.Moduleを継承したモデルクラスを作成できる -
nn.Sequentialとnn.Moduleクラスの使い分けを理解した - 主要なレイヤー(Linear, BatchNorm, Dropout)の役割を説明できる
- モデルの保存と読み込みができる
推定読了時間: 30分