演習:画像分類モデルを構築しよう
「ここからが本番だ。Chest X-Rayデータセットで肺炎判定モデルを構築し、Grad-CAMで判断根拠を可視化してくれ。」
田中VPoEが要件を示す。
「精度だけでなく、モデルが正しい根拠で判断しているかの確認まで含めて、一連のパイプラインを完成させてほしい。」
ミッション概要
Kaggle Chest X-Ray Images (Pneumonia) データセットを使い、肺炎判定の画像分類モデルを構築する。転移学習、データ拡張、Grad-CAMによる解釈性確保までを一貫して実装する。
Mission 1: データの準備と探索(20分)
タスク:
- Chest X-Ray データセットをダウンロードし、ディレクトリ構造を確認する
- 各クラス(NORMAL/PNEUMONIA)の画像数を集計する
- サンプル画像を表示し、視覚的な特徴を確認する
- クラス不均衡の度合いを計算する
# ヒント: データセットの読み込み
from torchvision import datasets
import matplotlib.pyplot as plt
train_dataset = datasets.ImageFolder("chest_xray/train")
print(f"クラス: {train_dataset.classes}")
print(f"クラス別画像数: {dict(zip(train_dataset.classes,
[sum(1 for _, l in train_dataset.samples if l == i)
for i in range(len(train_dataset.classes))]))}")
解答例
import os
from collections import Counter
# ディレクトリ構造の確認
for split in ["train", "val", "test"]:
for cls in ["NORMAL", "PNEUMONIA"]:
path = f"chest_xray/{split}/{cls}"
count = len(os.listdir(path))
print(f"{split}/{cls}: {count}枚")
# サンプル画像の表示
fig, axes = plt.subplots(2, 5, figsize=(20, 8))
for i, cls in enumerate(["NORMAL", "PNEUMONIA"]):
imgs = os.listdir(f"chest_xray/train/{cls}")[:5]
for j, img_name in enumerate(imgs):
img = Image.open(f"chest_xray/train/{cls}/{img_name}")
axes[i][j].imshow(img, cmap="gray")
axes[i][j].set_title(cls)
axes[i][j].axis("off")
plt.tight_layout()
plt.show()
Mission 2: ベースラインモデルの構築(25分)
タスク:
- ResNet-50の転移学習(Feature Extraction)でベースラインモデルを構築する
- 基本的なデータ拡張パイプラインを実装する
- クラス不均衡に対処する(WeightedRandomSampler or 重み付き損失)
- 学習を実行し、学習曲線(Loss/Accuracy)をプロットする
# ヒント: モデル構築
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, 2)
解答例
# データ拡張
train_transform = transforms.Compose([
transforms.Resize((256, 256)),
transforms.RandomCrop(224),
transforms.RandomHorizontalFlip(),
transforms.RandomRotation(10),
transforms.ColorJitter(brightness=0.2, contrast=0.2),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
])
# クラス不均衡対策
class_counts = [1341, 3875]
class_weights = torch.tensor([sum(class_counts)/c for c in class_counts])
criterion = nn.CrossEntropyLoss(weight=class_weights.to(device))
# 学習実行
model = train_model(model, train_loader, val_loader, epochs=10, lr=1e-3)
Mission 3: Fine-tuningで精度向上(20分)
タスク:
- ResNet-50のlayer4を解凍してFine-tuningする
- EfficientNet-B3でも同様のFine-tuningを実施する
- 両モデルのテストデータでの性能を比較する(Accuracy, Sensitivity, Specificity)
- 混同行列を可視化する
解答例
from sklearn.metrics import confusion_matrix, classification_report
import seaborn as sns
# Fine-tuning(layer4を解凍)
for param in model.layer4.parameters():
param.requires_grad = True
optimizer = optim.Adam([
{"params": model.layer4.parameters(), "lr": 1e-4},
{"params": model.fc.parameters(), "lr": 1e-3},
])
# テストデータでの評価
model.eval()
all_preds, all_labels = [], []
with torch.no_grad():
for images, labels in test_loader:
outputs = model(images.to(device))
preds = outputs.argmax(1).cpu()
all_preds.extend(preds.numpy())
all_labels.extend(labels.numpy())
print(classification_report(all_labels, all_preds,
target_names=["NORMAL", "PNEUMONIA"]))
# 混同行列
cm = confusion_matrix(all_labels, all_preds)
sns.heatmap(cm, annot=True, fmt="d",
xticklabels=["NORMAL", "PNEUMONIA"],
yticklabels=["NORMAL", "PNEUMONIA"])
plt.xlabel("予測")
plt.ylabel("実際")
plt.title("混同行列")
plt.show()
Mission 4: Grad-CAMによる解釈性確認(25分)
タスク:
- 最良モデルに対してGrad-CAMを実装する
- 正解/誤答それぞれ5枚ずつGrad-CAMを可視化する
- ショートカット学習の兆候がないか確認する
- モデルの注目領域が医学的に妥当か考察する
解答例
# Grad-CAMの生成と可視化
target_layer = model.layer4[-1] # ResNet-50の最終畳み込み層
grad_cam = GradCAM(model, target_layer)
# 正解ケースの可視化
correct_cases = [(img, label) for img, label, pred
in zip(test_images, test_labels, predictions)
if label == pred][:5]
# 誤答ケースの可視化
wrong_cases = [(img, label) for img, label, pred
in zip(test_images, test_labels, predictions)
if label != pred][:5]
for img, label in correct_cases + wrong_cases:
visualize_gradcam(img, model, target_layer,
class_names=["NORMAL", "PNEUMONIA"])
確認ポイント:
- 肺炎ケース: 浸潤影のある領域に注目しているか
- 正常ケース: 特定の領域に偏らず分散しているか
- ショートカット: 画像端や非肺野領域に注目していないか
提出物
- 学習済みモデルファイル(best_model.pth)
- モデル比較レポート(ResNet vs EfficientNet の性能比較表)
- Grad-CAM可視化結果(正解5枚、誤答5枚)
- 考察レポート(ショートカット学習の有無、改善提案)
推定所要時間: 90分