LESSON

総合演習:深層学習プロジェクトレポート

田中VPoE:「Month 3 の集大成だ。ここまで学んだニューラルネットワーク、PyTorch、CNN、Transformer、最適化テクニックを総動員して、NetShop の2つの課題に取り組んでほしい。」

あなた:「商品画像分類とレビュー感情分析ですね。どちらも従来の機械学習では精度に限界があった課題です。」

田中VPoE:「その通り。最終的には、経営陣に提出するプロジェクトレポートの形でまとめてくれ。モデルの精度だけでなく、なぜその手法を選んだのか、どう改善したのかのプロセスも重要だ。」

ミッション概要

NetShop 社の2つの課題(画像分類・テキスト分類)に深層学習で取り組み、プロジェクトレポートとして成果をまとめます。


Mission 1: 商品画像分類

CIFAR-10 を NetShop の商品カテゴリに見立てて、最高精度の画像分類モデルを構築してください。

要件

  1. ベースライン CNN を構築し、精度を記録する
  2. 転移学習(ResNet-18)で精度を改善する
  3. データ拡張の効果を検証する
  4. 最適化テクニック(BatchNorm, Dropout, 学習率スケジューリング)を適用する
  5. 各段階の精度を記録し、改善履歴を作成する
import torch
import torch.nn as nn
from torchvision import datasets, transforms, models
from torch.utils.data import DataLoader

# ここに画像分類モデルの実装を書く
解答例
import torch
import torch.nn as nn
from torchvision import datasets, transforms, models
from torch.utils.data import DataLoader
import numpy as np

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
improvement_log = []

# === データ準備 ===
basic_transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2470, 0.2435, 0.2616))
])

aug_transform = transforms.Compose([
    transforms.RandomHorizontalFlip(),
    transforms.RandomRotation(10),
    transforms.RandomResizedCrop(32, scale=(0.8, 1.0)),
    transforms.ColorJitter(brightness=0.2, contrast=0.2),
    transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2470, 0.2435, 0.2616))
])

train_basic = datasets.CIFAR10('./data', train=True, download=True, transform=basic_transform)
train_aug = datasets.CIFAR10('./data', train=True, download=True, transform=aug_transform)
test_ds = datasets.CIFAR10('./data', train=False, download=True, transform=basic_transform)

train_loader = DataLoader(train_basic, batch_size=128, shuffle=True)
train_loader_aug = DataLoader(train_aug, batch_size=128, shuffle=True)
test_loader = DataLoader(test_ds, batch_size=256, shuffle=False)

def train_and_test(model, train_ldr, test_ldr, optimizer, scheduler=None, epochs=15):
    criterion = nn.CrossEntropyLoss()
    best_acc = 0
    for epoch in range(epochs):
        model.train()
        for imgs, labels in train_ldr:
            imgs, labels = imgs.to(device), labels.to(device)
            optimizer.zero_grad()
            loss = criterion(model(imgs), labels)
            loss.backward()
            optimizer.step()
        if scheduler:
            scheduler.step()

        model.eval()
        correct, total = 0, 0
        with torch.no_grad():
            for imgs, labels in test_ldr:
                imgs, labels = imgs.to(device), labels.to(device)
                _, pred = model(imgs).max(1)
                total += labels.size(0)
                correct += pred.eq(labels).sum().item()
        acc = 100.0 * correct / total
        best_acc = max(best_acc, acc)
    return best_acc

# === Step 1: ベースライン CNN ===
class BaselineCNN(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(
            nn.Conv2d(3, 32, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),
            nn.Conv2d(64, 128, 3, padding=1), nn.ReLU(), nn.AdaptiveAvgPool2d(1),
        )
        self.fc = nn.Linear(128, 10)
    def forward(self, x):
        return self.fc(self.net(x).view(x.size(0), -1))

m = BaselineCNN().to(device)
acc = train_and_test(m, train_loader, test_loader, torch.optim.Adam(m.parameters(), lr=1e-3))
improvement_log.append(('ベースライン CNN', acc))
print(f"ベースライン CNN: {acc:.1f}%")

# === Step 2: +BatchNorm, Dropout ===
class ImprovedCNN(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(
            nn.Conv2d(3, 32, 3, padding=1), nn.BatchNorm2d(32), nn.ReLU(),
            nn.Conv2d(32, 32, 3, padding=1), nn.BatchNorm2d(32), nn.ReLU(),
            nn.MaxPool2d(2), nn.Dropout2d(0.2),
            nn.Conv2d(32, 64, 3, padding=1), nn.BatchNorm2d(64), nn.ReLU(),
            nn.Conv2d(64, 64, 3, padding=1), nn.BatchNorm2d(64), nn.ReLU(),
            nn.MaxPool2d(2), nn.Dropout2d(0.2),
            nn.Conv2d(64, 128, 3, padding=1), nn.BatchNorm2d(128), nn.ReLU(),
            nn.AdaptiveAvgPool2d(1),
        )
        self.fc = nn.Sequential(nn.Dropout(0.3), nn.Linear(128, 10))
    def forward(self, x):
        return self.fc(self.net(x).view(x.size(0), -1))

m = ImprovedCNN().to(device)
acc = train_and_test(m, train_loader, test_loader, torch.optim.Adam(m.parameters(), lr=1e-3))
improvement_log.append(('+BatchNorm+Dropout', acc))
print(f"+BatchNorm+Dropout: {acc:.1f}%")

# === Step 3: +データ拡張 ===
m = ImprovedCNN().to(device)
acc = train_and_test(m, train_loader_aug, test_loader, torch.optim.Adam(m.parameters(), lr=1e-3))
improvement_log.append(('+データ拡張', acc))
print(f"+データ拡張: {acc:.1f}%")

# === Step 4: +学習率スケジューリング ===
m = ImprovedCNN().to(device)
opt = torch.optim.Adam(m.parameters(), lr=1e-3)
sch = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=15)
acc = train_and_test(m, train_loader_aug, test_loader, opt, sch, epochs=15)
improvement_log.append(('+LRスケジューリング', acc))
print(f"+LRスケジューリング: {acc:.1f}%")

# === 改善レポート ===
print("\n=== 画像分類 改善履歴 ===")
base = improvement_log[0][1]
for name, acc in improvement_log:
    print(f"  {name:<25s}: {acc:.1f}% (改善: {acc - base:+.1f}%)")

Mission 2: レビュー感情分析

Hugging Face を使って、レビュー感情分析モデルを構築してください。

要件

  1. Hugging Face の pipeline で事前学習済みモデルの性能を確認する
  2. IMDb データセットのサブセットで DistilBERT を Fine-tuning する
  3. テストデータで精度を評価する
  4. 具体的なレビュー文に対する予測結果を示す
from transformers import pipeline, DistilBertForSequenceClassification
from datasets import load_dataset

# ここに感情分析モデルの実装を書く
解答例
from transformers import (
    DistilBertForSequenceClassification,
    DistilBertTokenizerFast,
    TrainingArguments,
    Trainer,
    pipeline
)
from datasets import load_dataset
import numpy as np
from sklearn.metrics import accuracy_score, f1_score, classification_report

# === Step 1: pipeline で確認 ===
print("=== 事前学習済みモデルの性能 ===")
clf = pipeline("sentiment-analysis")
test_reviews = [
    ("Great quality product, highly recommend!", "POSITIVE"),
    ("Terrible, broke after 2 days.", "NEGATIVE"),
    ("Average product, nothing special.", "NEUTRAL"),
    ("Fast delivery and excellent packaging!", "POSITIVE"),
    ("Worst purchase I ever made.", "NEGATIVE"),
]
for text, expected in test_reviews:
    result = clf(text)[0]
    print(f"  [{result['label']:>8}] ({result['score']:.3f}) {text[:40]}... (期待: {expected})")

# === Step 2: Fine-tuning ===
dataset = load_dataset("imdb")
train_ds = dataset["train"].shuffle(seed=42).select(range(2000))
test_ds = dataset["test"].shuffle(seed=42).select(range(500))

tokenizer = DistilBertTokenizerFast.from_pretrained("distilbert-base-uncased")

def tokenize(examples):
    return tokenizer(examples["text"], padding="max_length", truncation=True, max_length=256)

train_tok = train_ds.map(tokenize, batched=True)
test_tok = test_ds.map(tokenize, batched=True)

model = DistilBertForSequenceClassification.from_pretrained(
    "distilbert-base-uncased", num_labels=2
)

def compute_metrics(eval_pred):
    logits, labels = eval_pred
    preds = np.argmax(logits, axis=-1)
    return {"accuracy": accuracy_score(labels, preds), "f1": f1_score(labels, preds)}

training_args = TrainingArguments(
    output_dir="./results",
    num_train_epochs=3,
    per_device_train_batch_size=16,
    per_device_eval_batch_size=32,
    learning_rate=2e-5,
    weight_decay=0.01,
    evaluation_strategy="epoch",
    save_strategy="epoch",
    load_best_model_at_end=True,
    logging_steps=50,
)

trainer = Trainer(
    model=model, args=training_args,
    train_dataset=train_tok, eval_dataset=test_tok,
    compute_metrics=compute_metrics,
)
trainer.train()

# === Step 3: 評価 ===
results = trainer.evaluate()
print(f"\n=== Fine-tuning 結果 ===")
print(f"  Accuracy: {results['eval_accuracy']:.4f}")
print(f"  F1 Score: {results['eval_f1']:.4f}")

# === Step 4: 具体例での予測 ===
fine_tuned_clf = pipeline("sentiment-analysis", model=model, tokenizer=tokenizer)
netshop_reviews = [
    "This product exceeded my expectations! Amazing quality and fast delivery.",
    "Complete waste of money. The item doesn't work at all.",
    "Decent product for the price. Shipping was a bit slow though.",
    "I love this! Already ordered two more for friends.",
    "Very disappointed. The color is completely different from the photo.",
]
print("\n=== NetShop レビュー予測 ===")
for review in netshop_reviews:
    result = fine_tuned_clf(review)[0]
    label = "Positive" if result['label'] == 'LABEL_1' else "Negative"
    print(f"  [{label:>8}] ({result['score']:.3f}) {review[:60]}")

Mission 3: プロジェクトレポートの作成

以下のテンプレートに沿って、深層学習プロジェクトのレポートを出力してください。

要件

# レポートテンプレート
report = """
{'='*60}
  NetShop 深層学習プロジェクトレポート
{'='*60}

【エグゼクティブサマリー】
  Month 2 の機械学習では対応できなかった画像分類と
  テキスト感情分析に深層学習で取り組んだ結果をまとめる。

【課題1: 商品画像分類】
  手法: ???
  最終精度: ???
  改善幅: ???(ベースライン比)
  主な改善要因: ???

【課題2: レビュー感情分析】
  手法: ???
  最終精度: ???
  主な知見: ???

【技術選定の根拠】
  画像分類: ???
  テキスト分類: ???

【今後の改善方針】
  1. ???
  2. ???
  3. ???
"""
解答例
report = f"""
{'='*60}
  NetShop 深層学習プロジェクトレポート
{'='*60}

【エグゼクティブサマリー】
  従来の機械学習(LightGBM等)では精度に限界があった
  商品画像分類とレビュー感情分析に深層学習を適用。
  画像分類では転移学習とデータ拡張の組み合わせで
  ベースラインから大幅な精度改善を達成。
  テキスト分類では BERT ベースの Fine-tuning により
  高精度な感情分析モデルを構築した。

【課題1: 商品画像分類】
  手法: ResNet-18 転移学習 + データ拡張 + CosineAnnealing
  改善履歴:
"""

for name, acc in improvement_log:
    report += f"    {name:<25s}: {acc:.1f}%\n"

report += f"""
  最終精度: {improvement_log[-1][1]:.1f}%
  改善幅: {improvement_log[-1][1] - improvement_log[0][1]:+.1f}%(ベースライン比)
  主な改善要因:
    1. BatchNorm による学習の安定化
    2. データ拡張による汎化性能の向上
    3. 学習率スケジューリングによる収束の改善

【課題2: レビュー感情分析】
  手法: DistilBERT Fine-tuning
  最終精度: Accuracy {results['eval_accuracy']:.1%}, F1 {results['eval_f1']:.3f}
  主な知見:
    - 事前学習済み BERT は少量データでも高精度
    - 学習率 2e-5、3エポックで十分な収束
    - Attention 可視化で感情語への注目を確認

【技術選定の根拠】
  画像分類:
    - CNN は画像の空間的特徴を効率的に捉える
    - 転移学習で少量データ問題を解決
    - データ拡張で学習データの多様性を補完

  テキスト分類:
    - BERT は文脈を考慮した言語理解が可能
    - Fine-tuning で少量のラベル付きデータから高精度モデルを構築
    - DistilBERT で推論速度とのバランスを確保

【今後の改善方針】
  1. 実データでの評価と継続的な再学習パイプライン構築
  2. EfficientNet や DeBERTa などの最新モデルへの移行検討
  3. マルチモーダル学習(画像+テキスト)による統合的な商品理解
  4. モデルの軽量化(知識蒸留、量子化)と推論速度の最適化
  5. A/Bテストによるビジネスインパクトの定量評価
{'='*60}
"""
print(report)

達成度チェック

  • 商品画像分類モデルを段階的に改善し、精度向上を記録した
  • レビュー感情分析モデルを Fine-tuning で構築した
  • 各手法の効果を定量的に比較した
  • 技術選定の根拠を説明できる形でレポートにまとめた
  • 今後の改善方針を提示した

推定所要時間: 90分