Chuyển đến nội dung chính

レッスン 3: 転移学習 — トレーニング済みモデルの使用

転移学習: 事前トレーニングされたモデル、特徴抽出、微調整。実践: ImageNet で事前トレーニングされた EfficientNet を使用した画像分類。小規模なデータセット向けのデータ拡張戦略。

🧠 AI と ML — レッスン 2 レッスン 3: 転移学習 — 最初にモデルを使用する トレーニング

深層学習によるコンピューター ビジョン: CNN から Vision Transformer まで

パート 1: コンピューター ビジョン プラットフォーム

xdev.asia

はじめに

あなたは犬と猫の 500 枚の写真を持っており、分類モデルを構築したいと考えています。ゼロからトレーニングしますか? 数百万の画像 + 強力な GPU + 数週間のトレーニングが必要です。または...転移学習を使用します。ImageNet (1,400 万枚の画像) でトレーニングされたモデルを取得し、問題に知識を「転移」します。わずか 500 枚の画像 + 10 分間のトレーニング → 精度 95%+!

🎯 転移学習は、実際の履歴書で最もよく使用されるテクニックです。プロジェクトの 90% は最初からトレーニングを行いません。


1.転移学習とは何ですか?

1.1 アイデア

ImageNet Model (train trên 14M ảnh, 1000 classes):
┌──────────────────────────┬───────────────┐
│   Feature Extraction     │  Classifier   │
│   (Conv layers)          │  (FC layers)  │
│                          │               │
│   Học: edges, textures,  │  Học: 1000    │
│   shapes, patterns,      │  ImageNet     │
│   parts, objects         │  classes      │
└──────────────────────────┴───────────────┘
         ↓ KEEP                  ↓ REPLACE
┌──────────────────────────┬───────────────┐
│   Feature Extraction     │  New Classifier│
│   (GIỮA NGUYÊN hoặc     │  (Train mới   │
│    fine-tune nhẹ)        │   cho task    │
│                          │   của bạn)    │
└──────────────────────────┴───────────────┘

1.2 2 つの主な戦略

戦略やり方いつ使用するか
特徴抽出バックボーンを凍結し、新しいトレイン分類器のみ小規模なデータセット(<1000画像)、ImageNetに類似したドメイン
微調整バックボーンの一部を解凍 + 新しいトレイン分類器中規模のデータセット (1000 ~ 10000 画像)、異なるドメイン ImageNet
Dataset size vs Strategy:
< 500 ảnh   → Feature Extraction (freeze toàn bộ)
500 - 5000  → Fine-tune top layers
5000+       → Fine-tune toàn bộ (lower learning rate)
50000+      → Có thể train from scratch

2. データ拡張 — データ拡張

2.1 なぜ必要なのでしょうか?

データセットが小さい → モデルは過剰適合しやすい。データ拡張により、画像のより多くの「バリエーション」が作成され、モデルの学習が向上します。

2.2 一般的なテクニック

"""Data Augmentation với torchvision transforms"""
import torchvision.transforms as T
from PIL import Image

# Training transforms (có augmentation)
train_transform = T.Compose([
    T.RandomResizedCrop(224, scale=(0.8, 1.0)),  # Random crop + resize
    T.RandomHorizontalFlip(p=0.5),                # Lật ngang 50%
    T.RandomVerticalFlip(p=0.1),                   # Lật dọc 10%
    T.RandomRotation(degrees=15),                  # Xoay ±15°
    T.ColorJitter(
        brightness=0.2,  # Thay đổi sáng ±20%
        contrast=0.2,    # Thay đổi contrast ±20%
        saturation=0.2,  # Thay đổi saturation ±20%
        hue=0.1,         # Thay đổi hue ±10%
    ),
    T.RandomGrayscale(p=0.1),                      # Đen trắng 10%
    T.GaussianBlur(kernel_size=3, sigma=(0.1, 2.0)), # Blur nhẹ
    T.ToTensor(),
    T.Normalize(mean=[0.485, 0.456, 0.406],
                std=[0.229, 0.224, 0.225]),
])

# Validation transforms (KHÔNG augment)
val_transform = T.Compose([
    T.Resize(256),
    T.CenterCrop(224),
    T.ToTensor(),
    T.Normalize(mean=[0.485, 0.456, 0.406],
                std=[0.229, 0.224, 0.225]),
])

2.3 Visualize Augmentation

"""Xem ảnh sau augmentation"""
import matplotlib.pyplot as plt

img = Image.open("dog.jpg")

# Augmentation nhẹ (không normalize)
aug_viz = T.Compose([
    T.RandomResizedCrop(224, scale=(0.8, 1.0)),
    T.RandomHorizontalFlip(p=0.5),
    T.RandomRotation(degrees=15),
    T.ColorJitter(brightness=0.3, contrast=0.3),
])

fig, axes = plt.subplots(2, 5, figsize=(20, 8))
axes[0][0].imshow(img)
axes[0][0].set_title("Original")
for i in range(1, 10):
    ax = axes[i // 5][i % 5]
    augmented = aug_viz(img)
    ax.imshow(augmented)
    ax.set_title(f"Aug #{i}")
for ax in axes.flat:
    ax.axis("off")
plt.suptitle("Data Augmentation Samples", fontsize=16)
plt.tight_layout()
plt.show()

2.4 Advanced: RandAugment & Mixup

"""Augmentation nâng cao — dùng cho SOTA results"""
from torchvision.transforms import v2

# RandAugment: tự động chọn augmentation tốt nhất
train_transform_v2 = T.Compose([
    T.RandomResizedCrop(224),
    T.RandomHorizontalFlip(),
    v2.RandAugment(num_ops=2, magnitude=9),  # Auto augmentation!
    T.ToTensor(),
    T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
])

# CutMix / MixUp: trộn 2 ảnh → model robust hơn
# (thường implement trong training loop)

3. 実践: EfficientNet を使用した転移学習

3.1 データセットの準備

"""Tải và chuẩn bị dataset — ví dụ: Cats vs Dogs"""
import torch
from torch.utils.data import DataLoader
from torchvision import datasets

# Cấu trúc folders:
# data/
#   train/
#     cat/   (200 ảnh)
#     dog/   (200 ảnh)
#   val/
#     cat/   (50 ảnh)
#     dog/   (50 ảnh)

# Load dataset từ folder structure
train_dataset = datasets.ImageFolder(
    root="data/train",
    transform=train_transform,  # Có augmentation
)

val_dataset = datasets.ImageFolder(
    root="data/val",
    transform=val_transform,    # Không augmentation
)

print(f"Training samples: {len(train_dataset)}")
print(f"Validation samples: {len(val_dataset)}")
print(f"Classes: {train_dataset.classes}")  # ['cat', 'dog']

# DataLoaders
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=4)
val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False, num_workers=4)

3.2 Strategy 1: Feature Extraction

"""Feature Extraction — freeze backbone, chỉ train classifier"""
import torch.nn as nn
import torchvision.models as models

# Load pretrained EfficientNet-B0
model = models.efficientnet_b0(weights="IMAGENET1K_V1")

# ❄️ FREEZE tất cả layers
for param in model.parameters():
    param.requires_grad = False

# 🔥 Thay classifier head mới (train cho 2 classes)
num_features = model.classifier[1].in_features  # 1280
model.classifier = nn.Sequential(
    nn.Dropout(p=0.3),
    nn.Linear(num_features, 2),  # 2 classes: cat, dog
)

# Chỉ classifier mới có requires_grad=True
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
total = sum(p.numel() for p in model.parameters())
print(f"Trainable: {trainable:,} / {total:,} ({trainable/total*100:.1f}%)")
# Trainable: 2,562 / 5,290,130 (0.05%)  ← CHỈ 0.05%!

3.3 Strategy 2: Fine-tuning

"""Fine-tuning — unfreeze top layers + train"""

# Load pretrained
model = models.efficientnet_b0(weights="IMAGENET1K_V1")

# ❄️ Freeze tất cả
for param in model.parameters():
    param.requires_grad = False

# 🔥 Unfreeze top 2 blocks + classifier
for param in model.features[-2:].parameters():
    param.requires_grad = True

# Thay classifier
model.classifier = nn.Sequential(
    nn.Dropout(p=0.3),
    nn.Linear(1280, 2),
)

trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
total = sum(p.numel() for p in model.parameters())
print(f"Trainable: {trainable:,} / {total:,} ({trainable/total*100:.1f}%)")

3.4 Training Loop

"""Training loop hoàn chỉnh cho Transfer Learning"""
import torch.optim as optim

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)

# Optimizer — learning rate THẤP cho fine-tuning!
optimizer = optim.AdamW(
    model.parameters(),
    lr=1e-4,          # Thấp hơn train from scratch (thường 1e-3)
    weight_decay=1e-4,
)

# Learning Rate Scheduler
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=10)

# Loss function
criterion = nn.CrossEntropyLoss()

# Training
num_epochs = 10
best_val_acc = 0.0

for epoch in range(num_epochs):
    # === TRAIN ===
    model.train()
    train_loss = 0
    train_correct = 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()
        train_correct += (outputs.argmax(1) == labels).sum().item()

    train_acc = train_correct / len(train_dataset)

    # === VALIDATE ===
    model.eval()
    val_correct = 0

    with torch.no_grad():
        for images, labels in val_loader:
            images, labels = images.to(device), labels.to(device)
            outputs = model(images)
            val_correct += (outputs.argmax(1) == labels).sum().item()

    val_acc = val_correct / len(val_dataset)

    # Scheduler step
    scheduler.step()

    # Save best model
    if val_acc > best_val_acc:
        best_val_acc = val_acc
        torch.save(model.state_dict(), "best_model.pth")

    print(f"エポック {epoch+1}/{num_epochs} | "
          f"列車損失: {train_loss/len(train_loader):.4f} | "
          f"列車アクセス: {train_acc:.4f} | "
          f"Val Acc: {val_acc:.4f} {'⭐' if val_acc == best_val_acc else ''}")

print(f"\n最高の検証精度: {best_val_acc:.4f}")

3.5 Inference

"""推論 — 新しい写真を予測します"""
PILインポート画像から

# 最適なモデルをロードする
model.load_state_dict(torch.load("best_model.pth"))
モデル.eval()

def 予測画像(画像パス、モデル、変換、クラス名):
    img = Image.open(image_path).convert("RGB")
    input_tensor = 変換(img).unsqueeze(0).to(デバイス)

    torch.no_grad() を使用:
        出力 = モデル(入力テンソル)
        確率 = torch.softmax(出力、dim=1)
        信頼度、予測 = probabilities.max(1)

    pred_class = class_names[predicted.item()]
    conf = 信頼性.item()

    print(f"🖼️ {image_path}")
    print(f"📌 予測: {pred_class} ({conf:.1%})")
    私にとって、enumerate(class_names) の名前:
        print(f" {名前}: {確率[0][i]:.1%}")

    pred_class、confを返す

# テスト
class_names = ["猫", "犬"]
detect_image("test_cat.jpg", モデル, val_transform, class_names)
detect_image("test_dog.jpg", モデル, val_transform, class_names)

4. 実践的なヒント

4.1 事前トレーニング済みモデルの選択

問題点 → 推奨機種
─────────────────────
大分類 → EfficientNet-B0/B2
高い精度が必要 → EfficientNetV2-M
モバイル/エッジ → MobileNetV3
検出バックボーン → ResNet-50 + FPN
医用画像処理 → ResNet-50 (多くの研究)
小規模なデータセット (<500)    → Feature Extraction + heavy augmentation

4.2 Common Mistakes

間違い結果修正方法
学習率が高すぎるモデルは学習した特徴を「忘れる」微調整には1e-4~1e-5を使用
拡張データなし高速オーバーフィット常に列車セットを増強する
検証セットを拡張する精度の誤った評価トレーニング セットのみを強化します
凍結が少なすぎる不安定なトレーニング最初は大量に凍結し、徐々に解凍します。
正規化するのを忘れました精度が非常に低いImageNet の平均値/標準値を使用する

4.3 Discriminative Learning Rates

"""Trick: dùng learning rate khác nhau cho từng phần model"""

# Backbone: lr rất nhỏ (fine-tune nhẹ)
# Classifier: lr lớn hơn (train nhiều hơn)
param_groups = [
    {"params": model.features.parameters(), "lr": 1e-5},     # Backbone
    {"params": model.classifier.parameters(), "lr": 1e-3},   # Classifier
]

optimizer = optim.AdamW(param_groups, weight_decay=1e-4)

## まとめ

コンセプト覚えておいてください
転移学習事前トレーニングされたモデルを使用して、知識を新しいタスクに「転送」します。
特徴抽出バックボーンをフリーズし、ヘッドのみをトレーニングする - 小規模なデータセットの場合
微調整バックボーンの一部をフリーズ解除します — 中規模のデータセットの場合
データ拡張データ拡張: 反転、回転、カラージッター
学習率微調整には低い LR (1e-4 ~ 1e-5) が必要です。
正規化常に ImageNet 統計を使用してください: [0.485, 0.456, 0.406]

一般的な演習

  1. 猫 vs 犬: Kaggle からデータセットをダウンロードし、特徴抽出と微調整を使用して EfficientNet-B0 をトレーニングします。精度を比較します。
  2. 3 クラス分類: さらに 1 クラスを追加します (例: 鳥)。またトレーニングします。精度はまだ大丈夫ですか?
  3. 増強実験: 増強の 3 つのレベルを比較します: 増強なし、軽度の増強、強力な増強。学習曲線を描きます。
  4. 小規模データの課題: クラスあたり 50 個の画像のみを使用します。微調整と特徴抽出 — どちらが勝つでしょうか?

次の記事: YOLO オブジェクト検出 — v3 から v11 まで、リアルタイムの写真/ビデオ内のあらゆるオブジェクトを検出します。