PyTorchによるCIFAR-10カラー画像分類モデルの実装
環境設定
本チュートリアルでは、PyTorchを使用してCIFAR-10データセットのカラー画像分類モデルを実装します。CIFAR-10は10クラスの物体を含む画像データセットで、機械学習の入門として広く利用されています。
開発環境:
- Python 3.8以上
- Jupyter Notebook
- PyTorch(GPU対応環境推奨)
GPU環境のセットアップ
計算リソースの効率的な利用のため、GPUが利用可能な場合はGPU、そうでない場合はCPUを使用するように設定します。
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
import torchvision
# デバイスの設定
compute_device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"使用デバイス: {compute_device}")
データセットの準備
データセットのダウンロードと前処理
CIFAR-10データセットをローカル環境にダウンロードし、訓練用とテスト用に分割します。データローダーを使用して、バッチ処理が可能な形式でデータを読み込みます。
# CIFAR-10データセットのダウンロード
train_data = torchvision.datasets.CIFAR10(
root='dataset',
train=True,
transform=torchvision.transforms.ToTensor(),
download=True
)
test_data = torchvision.datasets.CIFAR10(
root='dataset',
train=False,
transform=torchvision.transforms.ToTensor(),
download=True
)
# バッチサイズの定義
batch_size = 32
# データローダーの作成
train_loader = torch.utils.data.DataLoader(
train_data,
batch_size=batch_size,
shuffle=True
)
test_loader = torch.utils.data.DataLoader(
test_data,
batch_size=batch_size
)
# データの形状を確認
sample_images, sample_labels = next(iter(train_loader))
print(f"画像バッチの形状: {sample_images.shape}")
print(f"ラベルバッチの形状: {sample_labels.shape}")
データ可視化
訓練データのサンプルを視覚的に確認することで、データの内容を把握します。
import numpy as np
# 画像表示の設定
plt.figure(figsize=(20, 5))
for i, img in enumerate(sample_images[:20]):
# チャネル順序の変更 (C, H, W) -> (H, W, C)
img_np = img.numpy().transpose((1, 2, 0))
# 2行10列のグリッドに配置
plt.subplot(2, 10, i + 1)
plt.imshow(img_np)
plt.axis('off')
plt.show()
CNNモデルの構築
畳み込みニューラルネットワーク(CNN)は、特徴抽出部と分類部で構成されます。畳み込み層で画像の特徴を抽出し、全結合層で分類を行います。
主要レイヤー解説
nn.Conv2d(二次元畳み込み層)
入力特徴マップに対して二次元畳み込み演算を適用します。
torch.nn.Conv2d(
in_channels, # 入力画像のチャネル数
out_channels, # 出力チャネル数(フィルター数)
kernel_size, # フィルターサイズ
stride=1, # ストライド
padding=0, # パディング
dilation=1 # 拡張率
)
nn.MaxPool2d(二次元最大プーリング層)
入力特徴マップのローカル領域から最大値を取得し、空間サイズを縮小します。
torch.nn.MaxPool2d(
kernel_size, # -poolingウィンドウサイズ
stride=None, # ストライド(kernel_sizeと同じ場合はNoneでOK)
padding=0 # パディング
)
nn.Linear(全結合層)
入力特徴と出力特徴の間の線形変換を実行します。
torch.nn.Linear(
in_features, # 入力特徴数
out_features # 出力特徴数
)
ネットワーク構造
以下のコードは、CIFAR-10の32×32ピクセル画像を10クラスに分類するCNNモデルを定義しています。データフローの流れは、3×32×32の入力から始まり、畳み込みとプーリングを繰り返しながら特徴次元を圧縮し、最終的に10次元ベクトル(クラス確率)が出力されます。
import torch.nn.functional as F
# クラス数の定義
num_classes = 10
class ImageClassifier(nn.Module):
def __init__(self):
super().__init__()
# 特徴抽出ネットワーク
self.conv_block1 = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2)
)
self.conv_block2 = nn.Sequential(
nn.Conv2d(64, 128, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2)
)
self.conv_block3 = nn.Sequential(
nn.Conv2d(128, 256, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2)
)
# 分類ネットワーク
self.classifier = nn.Sequential(
nn.Flatten(),
nn.Linear(256 * 4 * 4, 512),
nn.ReLU(),
nn.Dropout(0.3),
nn.Linear(512, 128),
nn.ReLU(),
nn.Linear(128, num_classes)
)
def forward(self, x):
x = self.conv_block1(x)
x = self.conv_block2(x)
x = self.conv_block3(x)
x = self.classifier(x)
return x
from torchinfo import summary
# モデルのインスタンス化とデバイスへの転送
network = ImageClassifier().to(compute_device)
# モデル構造の詳細表示
summary(network, input_size=(batch_size, 3, 32, 32))
モデル訓練
損失関数と最適化アルゴリズム
# クロスエントロピー損失(多クラス分類用)
loss_function = nn.CrossEntropyLoss()
# 確率的勾配降下法(SGD)
learning_rate = 0.01
optimizer = torch.optim.SGD(network.parameters(), lr=learning_rate)
訓練ループの実装
訓練関数では、バッチごとにモデルへの入力Forward計算、損失の計算、逆伝播による勾配計算、パラメータ更新を行います。
def train_model(data_loader, model, criterion, optimizer):
dataset_size = len(data_loader.dataset)
batch_count = len(data_loader)
total_loss = 0.0
correct_predictions = 0
model.train()
for batch_x, batch_y in data_loader:
batch_x, batch_y = batch_x.to(compute_device), batch_y.to(compute_device)
# 順伝播
predictions = model(batch_x)
loss = criterion(predictions, batch_y)
# 逆伝播
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 統計情報の更新
total_loss += loss.item()
correct_predictions += (predictions.argmax(1) == batch_y).sum().item()
avg_loss = total_loss / batch_count
accuracy = correct_predictions / dataset_size
return accuracy, avg_loss
テスト関数の実装
テスト時はパラメータ更新を行わないため、勾配計算を無効化してメモリ効率を向上させます。
def evaluate_model(data_loader, model, criterion):
dataset_size = len(data_loader.dataset)
batch_count = len(data_loader)
total_loss = 0.0
correct_predictions = 0
model.eval()
with torch.no_grad():
for batch_x, batch_y in data_loader:
batch_x, batch_y = batch_x.to(compute_device), batch_y.to(compute_device)
predictions = model(batch_x)
loss = criterion(predictions, batch_y)
total_loss += loss.item()
correct_predictions += (predictions.argmax(1) == batch_y).sum().item()
avg_loss = total_loss / batch_count
accuracy = correct_predictions / dataset_size
return accuracy, avg_loss
訓練の実行
モデルを訓練モードと評価モード適切に切り替えながら、 指定したエポック数だけ訓練を繰り返します。
num_epochs = 10
history_train_acc = []
history_train_loss = []
history_test_acc = []
history_test_loss = []
for epoch in range(num_epochs):
# 訓練フェーズ
network.train()
train_acc, train_loss = train_model(train_loader, network, loss_function, optimizer)
# 評価フェーズ
network.eval()
test_acc, test_loss = evaluate_model(test_loader, network, loss_function)
# 履歴の保存
history_train_acc.append(train_acc)
history_train_loss.append(train_loss)
history_test_acc.append(test_acc)
history_test_loss.append(test_loss)
# 結果の表示
print(f"Epoch {epoch+1:2d}: "
f"Train Acc={train_acc*100:.1f}%, Train Loss={train_loss:.3f}, "
f"Test Acc={test_acc*100:.1f}%, Test Loss={test_loss:.3f}")
print("訓練完了")
出力例:
Epoch 1: Train Acc=15.1%, Train Loss=2.245, Test Acc=24.0%, Test Loss=2.044
Epoch 2: Train Acc=27.2%, Train Loss=1.976, Test Acc=31.9%, Test Loss=1.854
Epoch 3: Train Acc=35.9%, Train Loss=1.763, Test Acc=39.2%, Test Loss=1.652
Epoch 4: Train Acc=41.8%, Train Loss=1.604, Test Acc=41.2%, Test Loss=1.623
Epoch 5: Train Acc=45.3%, Train Loss=1.510, Test Acc=48.4%, Test Loss=1.437
Epoch 6: Train Acc=48.5%, Train Loss=1.428, Test Acc=49.4%, Test Loss=1.413
Epoch 7: Train Acc=51.7%, Train Loss=1.347, Test Acc=47.9%, Test Loss=1.420
Epoch 8: Train Acc=54.4%, Train Loss=1.275, Test Acc=55.6%, Test Loss=1.265
Epoch 9: Train Acc=57.1%, Train Loss=1.213, Test Acc=59.0%, Test Loss=1.176
Epoch 10: Train Acc=59.3%, Train Loss=1.154, Test Acc=54.5%, Test Loss=1.270
結果の可視化
訓練过程中的精度と損失の変化をグラフ化し、モデルの学習状況を評価します。
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings("ignore")
# グラフの設定
plt.figure(figsize=(12, 4))
# 精度の推移
plt.subplot(1, 2, 1)
plt.plot(range(1, num_epochs + 1), history_train_acc, 'b-', label='訓練精度')
plt.plot(range(1, num_epochs + 1), history_test_acc, 'orange', label='テスト精度')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.legend()
plt.title('精度の推移')
# 損失の推移
plt.subplot(1, 2, 2)
plt.plot(range(1, num_epochs + 1), history_train_loss, 'b-', label='訓練損失')
plt.plot(range(1, num_epochs + 1), history_test_loss, 'orange', label='テスト損失')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.title('損失の推移')
plt.tight_layout()
plt.show()
技術的解説
CNNの主要コンポーネント
畳み込み層
畳み込み層は、入力画像に複数のフィルターを適用することで、エッジ、テクスチャ、色などの局所的特徴を抽出します。学習可能なパラメータとしてフィルター重みが存在し、データから自動的に効果的な特徴表現を獲得します。
プーリング層
プーリング層は、特徴マップの空間サイズを縮小し、計算量を削減しながら位置変化に対する不変性を獲得します。最大プーリングはローカル領域の最大値を取得し、代表的特徴を維持しながら次元圧縮を行います。
全結合層
全結合層は、抽出された特徴を総合して最終的な分類判断を行います。出力層の次元は分類タスクのクラス数と一致し、ソフトマックス関数により確率分布に変換されます。
ドロップアウト
ドロップアウトは、訓練時にランダムに一部のニューロンを無効化することで、過学習を防止する正則化手法です。本モデルでは30%のドロップアウト率を設定しています。
Flatiningによる次元変換
三次元特徴マップ(256×4×4)を一次元ベクトル(256×4×4=4096次元)に変換することで、全結合層への入力として適合させています。この変換により、空間的に分布した特徴が分類器で処理可能な形式に変換されます。