深層学習⑤転移学習

Categories:

第5章 深層学習:転移学習

📚 この章の学習目標

  • 転移学習(Transfer Learning)の概念と利点を理解する
  • 事前学習済みモデル(VGG16、ResNet50、InceptionV3等)の構造を学ぶ
  • Feature Extraction(特徴抽出)とFine-tuning(微調整)の違いを理解する
  • 少ないデータで高精度なモデルを構築する方法を習得する
  • ImageNetで学習された知識を新しいタスクに転用する
  • 実践的な画像分類プロジェクトで転移学習を適用する

🔄 転移学習とは?

💡 Transfer Learning(転移学習)の定義

転移学習とは、ある問題(ソースタスク)で学習した知識を、別の関連する問題(ターゲットタスク)に転用する機械学習の手法です。

  • 大規模データセットで事前学習されたモデルを利用
  • 少ないデータでも高精度なモデルを構築可能
  • 学習時間とコンピューティングリソースを大幅に削減

なぜ転移学習が有効なのか?

従来のアプローチの問題点

❌ ゼロから学習する場合:

1. 大量のデータが必要(数十万〜数百万枚)
2. 学習に長時間かかる(数日〜数週間)
3. 高性能なGPUが必須
4. 小規模データセットでは過学習しやすい

転移学習の利点

✅ 転移学習を使う場合:

1. 少ないデータで学習可能(数百〜数千枚)
2. 学習時間が大幅に短縮(数分〜数時間)
3. 低スペックのGPUでも実行可能
4. 汎化性能が高い
5. 事前学習された知識を活用

転移学習の直感的理解

💡 人間の学習に例えると

転移学習は人間の学習方法に似ています:

  • ピアノを弾ける人は、オルガンも比較的早く習得できる
  • 英語ができる人は、スペイン語の文法を理解しやすい
  • 車の運転ができる人は、トラックの運転も習得しやすい

同様に、CNNがImageNetで学習した低レベル特徴(エッジ、色、テクスチャ)は、他の画像認識タスクでも有効です。

転移学習の仕組み

事前学習済みモデル(ImageNet: 1400万枚、1000クラス)
        ↓
  [Convolution層]  ← 低レベル特徴(エッジ、色)を学習
        ↓
  [Convolution層]  ← 中レベル特徴(形状、パターン)を学習
        ↓
  [Convolution層]  ← 高レベル特徴(物体の部品)を学習
        ↓
  [全結合層]       ← タスク固有の特徴を学習
        ↓
   [出力層]        ← 1000クラス分類

        ↓ 転移

新しいタスク(犬・猫分類: 2000枚、2クラス)
        ↓
  [Convolution層]  ← **凍結**(そのまま使用)
        ↓
  [Convolution層]  ← **凍結**(そのまま使用)
        ↓
  [Convolution層]  ← **凍結** or **微調整**
        ↓
  [全結合層]       ← **再訓練**(新しい層に置き換え)
        ↓
   [出力層]        ← 2クラス分類

📦 事前学習済みモデルの紹介

主要な事前学習済みモデル

モデル層数パラメータ数Top-5精度特徴
VGG16201416138M90.1%シンプルな構造、大きいモデル
VGG19201419144M90.0%VGG16より深い
ResNet5020155025.6M92.1%Residual接続、効率的
ResNet101201510144.5M92.9%より深いResNet
InceptionV320154823.8M93.9%マルチスケール特徴
Xception20167122.9M94.5%Depthwise Separable Conv
MobileNetV22018533.5M90.1%軽量、モバイル向け
EfficientNetB020195.3M93.3%効率的なスケーリング

1️⃣ VGG16(2014年)

💡 VGG16の特徴

  • 構造がシンプル:3×3のConvolutionを繰り返す
  • 深さ:16層(13 Conv + 3 FC)
  • ImageNetで訓練済み
  • 転移学習で最も広く使われるモデルの1つ

VGG16のアーキテクチャ

入力: 224×224×3

ブロック1:
  Conv3×3, 64  →  Conv3×3, 64  →  MaxPool2×2

ブロック2:
  Conv3×3, 128  →  Conv3×3, 128  →  MaxPool2×2

ブロック3:
  Conv3×3, 256  →  Conv3×3, 256  →  Conv3×3, 256  →  MaxPool2×2

ブロック4:
  Conv3×3, 512  →  Conv3×3, 512  →  Conv3×3, 512  →  MaxPool2×2

ブロック5:
  Conv3×3, 512  →  Conv3×3, 512  →  Conv3×3, 512  →  MaxPool2×2

Flatten

全結合層:
  Dense 4096  →  Dense 4096  →  Dense 1000

出力: 1000クラス(ImageNet)

2️⃣ ResNet50(2015年)

💡 ResNet50の特徴

  • Residual接続(スキップ接続)を導入
  • 勾配消失問題を解決
  • より深いネットワーク(50層以上)が可能に
  • VGG16よりパラメータ数が少ない

Residual Block(残差ブロック)

入力 x
  ↓
  ├─────────────→ (スキップ接続)
  ↓                    ↓
Conv → BN → ReLU      ↓
  ↓                    ↓
Conv → BN → ReLU      ↓
  ↓                    ↓
Conv → BN             ↓
  ↓                    ↓
  └─────────(+)←──────┘
         ↓
       ReLU
         ↓
       出力

スキップ接続の利点

  • 勾配がスムーズに流れる:深いネットワークでも学習可能
  • 恒等写像を学習しやすい:最悪でも入力をそのまま出力できる
  • 性能が向上:浅いネットワークより高精度

3️⃣ InceptionV3(2015年)

💡 InceptionV3の特徴

  • Inception Module:複数のフィルタサイズを並列処理
  • マルチスケール特徴を同時に抽出
  • 効率的:パラメータ数が少ない
  • 高精度:ImageNetで優れた性能

Inception Module

        入力
         ↓
    ┌────┼────┬────┐
    ↓    ↓    ↓    ↓
  1×1  1×1  1×1  3×3
  Conv Conv Conv MaxPool
    ↓    ↓    ↓    ↓
       3×3  5×5  1×1
       Conv Conv Conv
    ↓    ↓    ↓    ↓
    └────┼────┴────┘
         ↓
    Filter Concat
         ↓
        出力

Inception Moduleの利点

  • 異なるスケールの特徴を同時に抽出
  • 1×1 Convolutionで次元削減し、計算量を削減
  • 柔軟な特徴抽出が可能

🎯 転移学習の2つのアプローチ

1️⃣ Feature Extraction(特徴抽出)

💡 Feature Extractionとは?

事前学習済みモデルのConvolution層を凍結し、特徴抽出器として使用する方法です。

手順

1. 事前学習済みモデルをロード
2. Convolution層を**凍結**(重みを更新しない)
3. 全結合層を**新しい層に置き換え**
4. 新しい層だけを**訓練**

実装例:VGG16でFeature Extraction

from tensorflow.keras.applications import VGG16
from tensorflow.keras import layers, models

# VGG16をロード(全結合層なし)
base_model = VGG16(
    weights='imagenet',      # ImageNetの重みを使用
    include_top=False,       # 全結合層を除外
    input_shape=(224, 224, 3)
)

# Convolution層を凍結
base_model.trainable = False

# 新しいモデルを構築
model = models.Sequential([
    base_model,                                  # 事前学習済みの特徴抽出器
    layers.GlobalAveragePooling2D(),            # プーリング
    layers.Dense(256, activation='relu'),       # 新しい全結合層
    layers.Dropout(0.5),
    layers.Dense(10, activation='softmax')      # 出力層(10クラス)
])

model.compile(
    optimizer='adam',
    loss='categorical_crossentropy',
    metrics=['accuracy']
)

model.summary()

Feature Extractionが適している場合

こんな時に使う

  • データセットが小さい(数百〜数千枚)
  • ソースタスクとターゲットタスクが似ている
  • 計算リソースが限られている
  • 素早くプロトタイプを作りたい

2️⃣ Fine-tuning(微調整)

💡 Fine-tuningとは?

事前学習済みモデルの一部の層を解凍し、新しいデータで再訓練する方法です。

手順

1. Feature Extractionで訓練
2. モデルの性能が安定したら、一部の層を**解凍**
3. **低い学習率**で再訓練
4. モデルを**微調整**

実装例:VGG16でFine-tuning

# ステップ1: Feature Extractionで訓練
# (上記のコードで訓練)

# ステップ2: 上位の層を解凍
base_model.trainable = True

# 最後の4層だけ訓練可能にする
for layer in base_model.layers[:-4]:
    layer.trainable = False

# ステップ3: 低い学習率で再コンパイル
model.compile(
    optimizer=keras.optimizers.Adam(learning_rate=1e-5),  # 低い学習率
    loss='categorical_crossentropy',
    metrics=['accuracy']
)

# ステップ4: 再訓練
history_finetune = model.fit(
    train_data,
    epochs=10,
    validation_data=val_data
)

Fine-tuningが適している場合

こんな時に使う

  • データセットが中〜大規模(数千枚以上)
  • ソースタスクとターゲットタスクが異なる
  • より高い精度が必要
  • 十分な計算リソースがある

Feature Extraction vs Fine-tuning

項目Feature ExtractionFine-tuning
訓練する層新しい層のみ一部のConv層 + 新しい層
学習率通常(0.001)低い(0.00001)
データ量少ない(数百枚〜)中〜多い(数千枚〜)
訓練時間短い長い
精度
過学習リスク低い

🔬 実習:CIFAR-10での転移学習

データセットの準備

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.datasets import cifar10
from tensorflow.keras.utils import to_categorical
import numpy as np

# データセットの読み込み
(X_train, y_train), (X_test, y_test) = cifar10.load_data()

# クラス名
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer',
               'dog', 'frog', 'horse', 'ship', 'truck']

print(f'訓練データ: {X_train.shape}')
print(f'テストデータ: {X_test.shape}')

画像のリサイズ

⚠️ 重要な注意点

CIFAR-10の画像サイズは32×32ですが、VGG16などの事前学習済みモデルは224×224の入力を期待します。

そのため、画像をリサイズする必要があります。

# 画像を224×224にリサイズ
def resize_images(images, size=(224, 224)):
    resized = np.zeros((images.shape[0], size[0], size[1], 3))
    for i in range(images.shape[0]):
        resized[i] = tf.image.resize(images[i], size)
    return resized

X_train_resized = resize_images(X_train)
X_test_resized = resize_images(X_test)

# 正規化
X_train_resized = X_train_resized / 255.0
X_test_resized = X_test_resized / 255.0

# One-hot encoding
y_train_cat = to_categorical(y_train, 10)
y_test_cat = to_categorical(y_test, 10)

print(f'リサイズ後の訓練データ: {X_train_resized.shape}')

モデル1:ゼロから訓練(ベースライン)

# 比較のため、ゼロから訓練するモデルを作成
model_scratch = keras.Sequential([
    layers.Conv2D(32, (3,3), activation='relu', input_shape=(224, 224, 3)),
    layers.MaxPooling2D((2,2)),
    layers.Conv2D(64, (3,3), activation='relu'),
    layers.MaxPooling2D((2,2)),
    layers.Conv2D(128, (3,3), activation='relu'),
    layers.MaxPooling2D((2,2)),
    layers.Flatten(),
    layers.Dense(128, activation='relu'),
    layers.Dropout(0.5),
    layers.Dense(10, activation='softmax')
])

model_scratch.compile(
    optimizer='adam',
    loss='categorical_crossentropy',
    metrics=['accuracy']
)

print("=== ゼロから訓練するモデル ===")
model_scratch.summary()

# 訓練
history_scratch = model_scratch.fit(
    X_train_resized, y_train_cat,
    batch_size=64,
    epochs=20,
    validation_data=(X_test_resized, y_test_cat),
    verbose=1
)

モデル2:VGG16でFeature Extraction

from tensorflow.keras.applications import VGG16

# VGG16をロード(全結合層なし)
base_model_vgg = VGG16(
    weights='imagenet',
    include_top=False,
    input_shape=(224, 224, 3)
)

# Convolution層を凍結
base_model_vgg.trainable = False

# 新しいモデルを構築
model_vgg_fe = keras.Sequential([
    base_model_vgg,
    layers.GlobalAveragePooling2D(),
    layers.Dense(256, activation='relu'),
    layers.Dropout(0.5),
    layers.Dense(10, activation='softmax')
])

model_vgg_fe.compile(
    optimizer='adam',
    loss='categorical_crossentropy',
    metrics=['accuracy']
)

print("=== VGG16 Feature Extraction ===")
print(f"訓練可能なパラメータ数: {len(model_vgg_fe.trainable_variables)}")
model_vgg_fe.summary()

# 訓練
history_vgg_fe = model_vgg_fe.fit(
    X_train_resized, y_train_cat,
    batch_size=64,
    epochs=20,
    validation_data=(X_test_resized, y_test_cat),
    verbose=1
)

モデル3:ResNet50でFeature Extraction

from tensorflow.keras.applications import ResNet50

# ResNet50をロード
base_model_resnet = ResNet50(
    weights='imagenet',
    include_top=False,
    input_shape=(224, 224, 3)
)

# Convolution層を凍結
base_model_resnet.trainable = False

# 新しいモデルを構築
model_resnet_fe = keras.Sequential([
    base_model_resnet,
    layers.GlobalAveragePooling2D(),
    layers.Dense(256, activation='relu'),
    layers.Dropout(0.5),
    layers.Dense(10, activation='softmax')
])

model_resnet_fe.compile(
    optimizer='adam',
    loss='categorical_crossentropy',
    metrics=['accuracy']
)

print("=== ResNet50 Feature Extraction ===")
model_resnet_fe.summary()

# 訓練
history_resnet_fe = model_resnet_fe.fit(
    X_train_resized, y_train_cat,
    batch_size=64,
    epochs=20,
    validation_data=(X_test_resized, y_test_cat),
    verbose=1
)

モデル4:VGG16でFine-tuning

# VGG16をロード
base_model_vgg_ft = VGG16(
    weights='imagenet',
    include_top=False,
    input_shape=(224, 224, 3)
)

# 最初は全て凍結
base_model_vgg_ft.trainable = False

# モデル構築
model_vgg_ft = keras.Sequential([
    base_model_vgg_ft,
    layers.GlobalAveragePooling2D(),
    layers.Dense(256, activation='relu'),
    layers.Dropout(0.5),
    layers.Dense(10, activation='softmax')
])

model_vgg_ft.compile(
    optimizer='adam',
    loss='categorical_crossentropy',
    metrics=['accuracy']
)

# ステップ1: Feature Extractionで訓練
print("=== ステップ1: Feature Extraction ===")
history_vgg_ft_step1 = model_vgg_ft.fit(
    X_train_resized, y_train_cat,
    batch_size=64,
    epochs=10,
    validation_data=(X_test_resized, y_test_cat),
    verbose=1
)

# ステップ2: Fine-tuning
print("\n=== ステップ2: Fine-tuning ===")

# 上位の層を解凍
base_model_vgg_ft.trainable = True

# 最後の4層だけ訓練可能にする
for layer in base_model_vgg_ft.layers[:-4]:
    layer.trainable = False

# 低い学習率で再コンパイル
model_vgg_ft.compile(
    optimizer=keras.optimizers.Adam(learning_rate=1e-5),
    loss='categorical_crossentropy',
    metrics=['accuracy']
)

# 再訓練
history_vgg_ft_step2 = model_vgg_ft.fit(
    X_train_resized, y_train_cat,
    batch_size=64,
    epochs=10,
    validation_data=(X_test_resized, y_test_cat),
    verbose=1
)

📊 結果の比較と可視化

import matplotlib.pyplot as plt

# 各モデルの最終精度を取得
acc_scratch = history_scratch.history['val_accuracy'][-1]
acc_vgg_fe = history_vgg_fe.history['val_accuracy'][-1]
acc_resnet_fe = history_resnet_fe.history['val_accuracy'][-1]
acc_vgg_ft = history_vgg_ft_step2.history['val_accuracy'][-1]

# 精度の比較
models = ['From Scratch', 'VGG16\nFeature Ext', 'ResNet50\nFeature Ext', 'VGG16\nFine-tuning']
accuracies = [acc_scratch, acc_vgg_fe, acc_resnet_fe, acc_vgg_ft]

plt.figure(figsize=(12, 6))
bars = plt.bar(models, accuracies, color=['red', 'blue', 'green', 'purple'])
plt.ylabel('Test Accuracy')
plt.title('Model Performance Comparison')
plt.ylim([0, 1])

# 各バーの上に精度を表示
for bar, acc in zip(bars, accuracies):
    height = bar.get_height()
    plt.text(bar.get_x() + bar.get_width()/2., height,
             f'{acc:.4f}',
             ha='center', va='bottom')

plt.grid(axis='y', alpha=0.3)
plt.show()

# 学習曲線の比較
plt.figure(figsize=(15, 5))

# 訓練精度
plt.subplot(1, 2, 1)
plt.plot(history_scratch.history['accuracy'], label='From Scratch - Train')
plt.plot(history_vgg_fe.history['accuracy'], label='VGG16 FE - Train')
plt.plot(history_resnet_fe.history['accuracy'], label='ResNet50 FE - Train')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.title('Training Accuracy Comparison')
plt.legend()
plt.grid(True)

# 検証精度
plt.subplot(1, 2, 2)
plt.plot(history_scratch.history['val_accuracy'], label='From Scratch')
plt.plot(history_vgg_fe.history['val_accuracy'], label='VGG16 FE')
plt.plot(history_resnet_fe.history['val_accuracy'], label='ResNet50 FE')
plt.plot(range(10), history_vgg_ft_step1.history['val_accuracy'], label='VGG16 FT Step1')
plt.plot(range(10, 20), history_vgg_ft_step2.history['val_accuracy'], label='VGG16 FT Step2')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.title('Validation Accuracy Comparison')
plt.legend()
plt.grid(True)

plt.tight_layout()
plt.show()

# 詳細な比較表
print("\n=== モデル性能の詳細比較 ===")
print(f"{'モデル':<25} {'テスト精度':<15} {'訓練時間(相対)'}")
print("="*60)
print(f"{'ゼロから訓練':<25} {acc_scratch:<15.4f} {'長い'}")
print(f"{'VGG16 Feature Extraction':<25} {acc_vgg_fe:<15.4f} {'短い'}")
print(f"{'ResNet50 Feature Extraction':<25} {acc_resnet_fe:<15.4f} {'短い'}")
print(f"{'VGG16 Fine-tuning':<25} {acc_vgg_ft:<15.4f} {'中'}")

🐕🐈 実習プロジェクト:犬と猫の分類(転移学習版)

🎯 プロジェクト概要

第4章で作成した犬と猫の分類プロジェクトを、転移学習で改善します。

  • データセット: Dogs vs Cats
  • アプローチ: InceptionV3を使用した転移学習
  • 目標: 第4章のモデルより高精度を達成

データの準備(第4章と同じ)

# Google Driveのマウントとデータの準備
from google.colab import drive
drive.mount('/content/drive')

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.preprocessing.image import ImageDataGenerator
import os

# データディレクトリ
base_dir = '/content/data'  # 第4章で作成したディレクトリを使用

Data Augmentationの設定

# 訓練データ用のData Augmentation
train_datagen = ImageDataGenerator(
    rescale=1./255,
    rotation_range=40,
    width_shift_range=0.2,
    height_shift_range=0.2,
    shear_range=0.2,
    zoom_range=0.2,
    horizontal_flip=True,
    fill_mode='nearest'
)

# 検証・テストデータは正規化のみ
test_datagen = ImageDataGenerator(rescale=1./255)

# ジェネレータの作成(InceptionV3用に299×299にリサイズ)
train_generator = train_datagen.flow_from_directory(
    os.path.join(base_dir, 'train'),
    target_size=(299, 299),      # InceptionV3の入力サイズ
    batch_size=32,
    class_mode='binary'
)

validation_generator = test_datagen.flow_from_directory(
    os.path.join(base_dir, 'validation'),
    target_size=(299, 299),
    batch_size=32,
    class_mode='binary'
)

test_generator = test_datagen.flow_from_directory(
    os.path.join(base_dir, 'test'),
    target_size=(299, 299),
    batch_size=32,
    class_mode='binary',
    shuffle=False
)

モデルの構築:InceptionV3

from tensorflow.keras.applications import InceptionV3
from tensorflow.keras import layers, models

# InceptionV3をロード
base_model = InceptionV3(
    weights='imagenet',
    include_top=False,
    input_shape=(299, 299, 3)
)

# 全層を凍結
base_model.trainable = False

# 新しいモデルを構築
model = models.Sequential([
    base_model,
    layers.GlobalAveragePooling2D(),
    layers.Dense(512, activation='relu'),
    layers.BatchNormalization(),
    layers.Dropout(0.5),
    layers.Dense(256, activation='relu'),
    layers.BatchNormalization(),
    layers.Dropout(0.3),
    layers.Dense(1, activation='sigmoid')
])

model.compile(
    optimizer=keras.optimizers.Adam(learning_rate=0.0001),
    loss='binary_crossentropy',
    metrics=['accuracy']
)

print("=== InceptionV3 Transfer Learning Model ===")
model.summary()

ステップ1:Feature Extractionで訓練

from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau

# コールバックの設定
callbacks = [
    EarlyStopping(
        monitor='val_loss',
        patience=5,
        restore_best_weights=True,
        verbose=1
    ),
    ModelCheckpoint(
        'best_model_step1.h5',
        monitor='val_accuracy',
        save_best_only=True,
        verbose=1
    ),
    ReduceLROnPlateau(
        monitor='val_loss',
        factor=0.5,
        patience=3,
        min_lr=1e-7,
        verbose=1
    )
]

print("=== ステップ1: Feature Extraction ===")

# 訓練
history_step1 = model.fit(
    train_generator,
    steps_per_epoch=train_generator.samples // train_generator.batch_size,
    epochs=20,
    validation_data=validation_generator,
    validation_steps=validation_generator.samples // validation_generator.batch_size,
    callbacks=callbacks,
    verbose=1
)

ステップ2:Fine-tuningで訓練

print("\n=== ステップ2: Fine-tuning ===")

# InceptionV3の上位の層を解凍
base_model.trainable = True

# 最後の30層だけ訓練可能にする
for layer in base_model.layers[:-30]:
    layer.trainable = False

print(f"凍結された層: {sum([not layer.trainable for layer in base_model.layers])}")
print(f"訓練可能な層: {sum([layer.trainable for layer in base_model.layers])}")

# 非常に低い学習率で再コンパイル
model.compile(
    optimizer=keras.optimizers.Adam(learning_rate=1e-5),
    loss='binary_crossentropy',
    metrics=['accuracy']
)

# Fine-tuning訓練
history_step2 = model.fit(
    train_generator,
    steps_per_epoch=train_generator.samples // train_generator.batch_size,
    epochs=10,
    validation_data=validation_generator,
    validation_steps=validation_generator.samples // validation_generator.batch_size,
    callbacks=callbacks,
    verbose=1
)

学習曲線の可視化

import matplotlib.pyplot as plt

fig, axes = plt.subplots(2, 2, figsize=(15, 12))

# Step1の精度
axes[0, 0].plot(history_step1.history['accuracy'], label='Training')
axes[0, 0].plot(history_step1.history['val_accuracy'], label='Validation')
axes[0, 0].set_title('Step1: Feature Extraction - Accuracy')
axes[0, 0].set_xlabel('Epoch')
axes[0, 0].set_ylabel('Accuracy')
axes[0, 0].legend()
axes[0, 0].grid(True)

# Step1の損失
axes[0, 1].plot(history_step1.history['loss'], label='Training')
axes[0, 1].plot(history_step1.history['val_loss'], label='Validation')
axes[0, 1].set_title('Step1: Feature Extraction - Loss')
axes[0, 1].set_xlabel('Epoch')
axes[0, 1].set_ylabel('Loss')
axes[0, 1].legend()
axes[0, 1].grid(True)

# Step2の精度
axes[1, 0].plot(history_step2.history['accuracy'], label='Training')
axes[1, 0].plot(history_step2.history['val_accuracy'], label='Validation')
axes[1, 0].set_title('Step2: Fine-tuning - Accuracy')
axes[1, 0].set_xlabel('Epoch')
axes[1, 0].set_ylabel('Accuracy')
axes[1, 0].legend()
axes[1, 0].grid(True)

# Step2の損失
axes[1, 1].plot(history_step2.history['loss'], label='Training')
axes[1, 1].plot(history_step2.history['val_loss'], label='Validation')
axes[1, 1].set_title('Step2: Fine-tuning - Loss')
axes[1, 1].set_xlabel('Epoch')
axes[1, 1].set_ylabel('Loss')
axes[1, 1].legend()
axes[1, 1].grid(True)

plt.tight_layout()
plt.show()

テストデータでの評価

# テストデータで評価
test_loss, test_accuracy = model.evaluate(test_generator)
print(f'\n=== テスト結果 ===')
print(f'Test Accuracy: {test_accuracy:.4f}')
print(f'Test Loss: {test_loss:.4f}')

# 予測
predictions = model.predict(test_generator)
predicted_classes = (predictions > 0.5).astype(int).flatten()

# 実際のラベル
true_classes = test_generator.classes
class_labels = list(test_generator.class_indices.keys())

# 混同行列
from sklearn.metrics import confusion_matrix, classification_report
import seaborn as sns

cm = confusion_matrix(true_classes, predicted_classes)

plt.figure(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', 
            xticklabels=class_labels, yticklabels=class_labels)
plt.xlabel('Predicted')
plt.ylabel('Actual')
plt.title('Confusion Matrix - InceptionV3 Transfer Learning')
plt.show()

# 詳細なレポート
print("\n=== Classification Report ===")
print(classification_report(true_classes, predicted_classes, 
                          target_names=class_labels))

第4章モデルとの比較

# 第4章のモデル(ゼロから訓練)の精度: 約70-80%
# 転移学習モデル(InceptionV3)の精度: 約90-95%

print("\n=== 性能比較 ===")
print(f"{'モデル':<30} {'テスト精度':<15} {'改善率'}")
print("="*60)
print(f"{'第4章: ゼロから訓練':<30} {'~75%':<15} {'-'}")
print(f"{'第5章: InceptionV3転移学習':<30} {f'{test_accuracy:.2%}':<15} {f'+{(test_accuracy-0.75)*100:.1f}%'}")

予測結果の可視化

import random
import numpy as np

# ランダムに予測結果を表示
plt.figure(figsize=(18, 12))

random_indices = random.sample(range(len(test_generator.filenames)), 20)

for i, idx in enumerate(random_indices):
    plt.subplot(4, 5, i+1)

    # 画像の読み込み
    img_path = os.path.join(base_dir, 'test', test_generator.filenames[idx])
    img = keras.preprocessing.image.load_img(img_path, target_size=(299, 299))
    plt.imshow(img)

    # 予測と実際のラベル
    pred_class = 'dog' if predicted_classes[idx] == 1 else 'cat'
    true_class = 'dog' if true_classes[idx] == 1 else 'cat'
    confidence = predictions[idx][0] if predicted_classes[idx] == 1 else 1 - predictions[idx][0]

    # タイトル(正解なら緑、不正解なら赤)
    color = 'green' if pred_class == true_class else 'red'
    plt.title(f'Pred: {pred_class} ({confidence:.2f})\nTrue: {true_class}', 
             color=color, fontsize=10)
    plt.axis('off')

plt.suptitle('InceptionV3 Transfer Learning - Prediction Results', fontsize=16)
plt.tight_layout()
plt.show()

💡 転移学習のベストプラクティス

1️⃣ データセットサイズによる戦略

📊 データ量に応じた戦略選択

データセット規模推奨アプローチ理由
非常に小さい
(< 1000枚)
Feature Extraction過学習リスクが高い
小さい
(1000〜5000枚)
Feature Extraction + 軽いFine-tuningバランスが良い
中規模
(5000〜50000枚)
Feature Extraction → Fine-tuning十分なデータがある
大規模
(> 50000枚)
Fine-tuning or ゼロから訓練独自の特徴を学習可能

2️⃣ タスク類似度による戦略

🎯 ソースタスクとターゲットタスクの類似度

高い類似度(ImageNet → 一般的な物体認識)

✅ 推奨:Feature Extraction
✅ 凍結する層:ほぼ全て
✅ 学習率:通常(0.001)

例:
- ImageNet → 犬種分類
- ImageNet → 花の種類分類
- ImageNet → 車種分類

中程度の類似度(ImageNet → 特定のドメイン)

✅ 推奨:Feature Extraction → Fine-tuning
✅ 凍結する層:下位〜中位の層
✅ 学習率:低い(0.00001)

例:
- ImageNet → 医療画像診断
- ImageNet → 衛星画像分類
- ImageNet → 顔認識

低い類似度(大きく異なるドメイン)

⚠️ 推奨:軽い転移学習 or ゼロから訓練
⚠️ 凍結する層:最下位の層のみ
⚠️ 学習率:通常(0.001)

例:
- ImageNet → X線画像
- ImageNet → 顕微鏡画像
- ImageNet → 抽象芸術

3️⃣ 学習率の設定

学習率の重要性

Fine-tuningでは、事前学習された重みを壊さないために、非常に低い学習率を使用します。

# Feature Extraction
optimizer = keras.optimizers.Adam(learning_rate=0.001)  # 通常の学習率

# Fine-tuning
optimizer = keras.optimizers.Adam(learning_rate=0.00001)  # 10〜100分の1

4️⃣ どの層を解凍するか?

🔧 層の解凍戦略

CNN の階層構造:

[入力層]
    ↓
[低レベル特徴] ← エッジ、色、テクスチャ → **常に凍結**
    ↓
[中レベル特徴] ← 形状、パターン → **データが多ければ解凍**
    ↓
[高レベル特徴] ← 物体の部品 → **通常解凍**
    ↓
[全結合層] ← タスク固有 → **常に再訓練**
    ↓
[出力層]

実践的なガイドライン

# VGG16(16層)の場合
base_model.trainable = True

# 小データセット:最後の2〜4層のみ解凍
for layer in base_model.layers[:-4]:
    layer.trainable = False

# 中データセット:最後の6〜8層を解凍
for layer in base_model.layers[:-8]:
    layer.trainable = False

# 大データセット:最後の10層以上を解凍
for layer in base_model.layers[:-12]:
    layer.trainable = False

5️⃣ Batch NormalizationとFine-tuning

⚠️ 重要な注意点

Fine-tuning時にBatch Normalization層がある場合、特別な処理が必要です。

# 問題のある例
base_model.trainable = True  # すべての層を解凍

# 正しい例
base_model.trainable = True

# Batch Normalization層は凍結したまま
for layer in base_model.layers:
    if isinstance(layer, keras.layers.BatchNormalization):
        layer.trainable = False

理由:

  • BN層は訓練時と推論時で動作が異なる
  • Fine-tuning時にBN層の統計が変わると、性能が悪化する可能性がある

6️⃣ Data Augmentationの重要性

💡 転移学習でもData Augmentationは効果的

# 転移学習でも積極的にData Augmentationを使う
train_datagen = ImageDataGenerator(
    rescale=1./255,
    rotation_range=40,
    width_shift_range=0.2,
    height_shift_range=0.2,
    shear_range=0.2,
    zoom_range=0.2,
    horizontal_flip=True,
    fill_mode='nearest'
)

# より高い汎化性能が得られる

🎯 実習課題

🎯 課題:カスタムデータセットでの転移学習

課題内容:

自分で選んだ画像分類タスクに対して、転移学習を適用してください。

要件:

  1. データセットの準備
  • 最低2クラス、各クラス100枚以上
  • 訓練・検証・テストに分割
  • Data Augmentationを適用
  1. 3つの異なる事前学習済みモデルを試す
  • VGG16、ResNet50、InceptionV3等
  • Feature Extractionを実装
  • Fine-tuningを実装
  1. 性能比較
  • ゼロから訓練したモデルと比較
  • 各転移学習モデルの精度を比較
  • 学習曲線を可視化
  1. 分析レポート
  • どのモデルが最も効果的だったか
  • Fine-tuningの効果は?
  • 誤分類された画像の傾向
  • 改善提案

期待される成果物:

  • データセットの説明
  • 実装コード(Feature Extraction + Fine-tuning)
  • 性能比較表とグラフ
  • 混同行列と分類レポート
  • 予測結果の可視化
  • 詳細な分析レポート

評価基準:

  • 転移学習モデルの精度 > ゼロから訓練したモデルの精度
  • Feature ExtractionとFine-tuningの両方を実装
  • 体系的な実験と分析
  • 明確な考察と改善提案

📚 第5章のまとめ

🎯 第5章の重要ポイント

  • 転移学習は、事前学習された知識を新しいタスクに転用する強力な手法
  • Feature ExtractionFine-tuningの2つのアプローチがある
  • 少ないデータでも高精度なモデルを構築できる
  • VGG16、ResNet50、InceptionV3等の事前学習済みモデルが利用可能
  • データセットサイズとタスク類似度に応じて戦略を選択する
  • 低い学習率でFine-tuningすることが重要
  • Batch Normalization層の扱いに注意が必要
  • 転移学習 + Data Augmentationで最高の性能が得られる

💡 実践的なメリット

開発期間の短縮:数週間 → 数時間
コスト削減:高性能GPU不要
少ないデータで高精度:数百枚で90%以上の精度
汎用性:様々なタスクに適用可能

💡 次章への繋がり

第5章では転移学習を学びました。次の第6章以降では、より高度な深層学習トピック(物体検出、セグメンテーション、生成モデル等)を学びます。転移学習はこれらのタスクでも基礎となる重要な技術です。