LESSON

モデル定義

田中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)ReLUReLU の特性に合わせた初期化

モデルの保存と読み込み

# モデルの保存(パラメータのみ - 推奨)
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.Sequentialnn.Module クラスの使い分けを理解した
  • 主要なレイヤー(Linear, BatchNorm, Dropout)の役割を説明できる
  • モデルの保存と読み込みができる

推定読了時間: 30分