転移学習で画像分類モデルを構築する
「まずは転移学習でベースラインを作ろう。ResNetとEfficientNet、どちらが我々のタスクに適しているか検証してくれ。」
田中VPoEがKaggleのデータセットページを開きながら指示する。
「最初から完璧を目指す必要はない。まずは動くものを作り、そこから改善していく。」
転移学習の基本戦略
Feature Extraction vs Fine-tuning
import torch
import torch.nn as nn
from torchvision import models
# 戦略1: Feature Extraction(特徴抽出)
# 事前学習済みの層を凍結し、最終層のみ学習
def create_feature_extractor(num_classes=2):
model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V2)
# 全パラメータを凍結
for param in model.parameters():
param.requires_grad = False
# 最終全結合層のみ置換
model.fc = nn.Linear(model.fc.in_features, num_classes)
return model
# 戦略2: Fine-tuning(微調整)
# 一部の層を解凍して学習
def create_finetuned_model(num_classes=2):
model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V2)
# 全パラメータを凍結
for param in model.parameters():
param.requires_grad = False
# 最後の2ブロック + 全結合層を解凍
for param in model.layer4.parameters():
param.requires_grad = True
model.fc = nn.Linear(model.fc.in_features, num_classes)
return model
戦略の使い分け
| 条件 | 推奨戦略 | 理由 |
|---|---|---|
| データ少量(数百枚) | Feature Extraction | 過学習リスクが低い |
| データ中量(数千枚) | Fine-tuning(後半の層) | 精度と汎化のバランス |
| データ大量(数万枚) | Full Fine-tuning | タスク固有の特徴を十分学習可能 |
ResNet vs EfficientNet
ResNet(Residual Network)
# ResNetの特徴: Skip Connection(残差接続)
# 層を深くしても勾配消失を防ぐ
model_resnet = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V2)
print(f"パラメータ数: {sum(p.numel() for p in model_resnet.parameters()):,}")
# 約25.6M パラメータ
EfficientNet
# EfficientNetの特徴: Compound Scaling
# 幅・深さ・解像度を統合的にスケーリング
model_effnet = models.efficientnet_b3(
weights=models.EfficientNet_B3_Weights.IMAGENET1K_V1
)
print(f"パラメータ数: {sum(p.numel() for p in model_effnet.parameters()):,}")
# 約12.2M パラメータ
| モデル | パラメータ数 | ImageNet Top-1 | 推論速度 | 特徴 |
|---|---|---|---|---|
| ResNet-50 | 25.6M | 80.9% | 速い | 安定、実績豊富 |
| EfficientNet-B3 | 12.2M | 82.0% | やや遅い | 高精度、軽量 |
| EfficientNet-B0 | 5.3M | 77.7% | 速い | 軽量、エッジ向き |
データの準備
from torchvision import transforms, datasets
from torch.utils.data import DataLoader
# 画像の前処理パイプライン
train_transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
),
])
val_transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
),
])
# Chest X-Rayデータセットの読み込み
train_dataset = datasets.ImageFolder(
root="chest_xray/train",
transform=train_transform
)
val_dataset = datasets.ImageFolder(
root="chest_xray/val",
transform=val_transform
)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False)
print(f"クラス: {train_dataset.classes}")
print(f"学習データ数: {len(train_dataset)}")
学習ループの実装
import torch.optim as optim
def train_model(model, train_loader, val_loader, epochs=10, lr=1e-3):
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(
filter(lambda p: p.requires_grad, model.parameters()),
lr=lr
)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.1)
best_val_acc = 0.0
for epoch in range(epochs):
# 学習フェーズ
model.train()
train_loss, train_correct = 0.0, 0
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
train_loss += loss.item() * images.size(0)
train_correct += (outputs.argmax(1) == labels).sum().item()
# 検証フェーズ
model.eval()
val_loss, val_correct = 0.0, 0
with torch.no_grad():
for images, labels in val_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
val_loss += loss.item() * images.size(0)
val_correct += (outputs.argmax(1) == labels).sum().item()
train_acc = train_correct / len(train_loader.dataset)
val_acc = val_correct / len(val_loader.dataset)
print(f"Epoch {epoch+1}/{epochs} - "
f"Train Loss: {train_loss/len(train_loader.dataset):.4f}, "
f"Train Acc: {train_acc:.4f}, "
f"Val Acc: {val_acc:.4f}")
if val_acc > best_val_acc:
best_val_acc = val_acc
torch.save(model.state_dict(), "best_model.pth")
scheduler.step()
return model
まとめ
| 項目 | ポイント |
|---|---|
| Feature Extraction | 少量データ向け、全パラメータ凍結 + 最終層のみ学習 |
| Fine-tuning | 中〜大量データ向け、後半の層を解凍して微調整 |
| ResNet-50 | 安定性が高い、実績豊富、25.6Mパラメータ |
| EfficientNet-B3 | 高精度かつ軽量、12.2Mパラメータ |
チェックリスト
- Feature ExtractionとFine-tuningの違いを説明できる
- ResNetとEfficientNetの特徴を比較できる
- PyTorchで転移学習のコードを書ける
- 学習ループの各要素(損失関数、最適化、スケジューラ)を理解した
次のステップへ
ベースラインモデルを構築したところで、次はデータ拡張でモデルの汎化性能を向上させよう。
推定読了時間: 30分