第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精度 | 特徴 |
|---|---|---|---|---|---|
| VGG16 | 2014 | 16 | 138M | 90.1% | シンプルな構造、大きいモデル |
| VGG19 | 2014 | 19 | 144M | 90.0% | VGG16より深い |
| ResNet50 | 2015 | 50 | 25.6M | 92.1% | Residual接続、効率的 |
| ResNet101 | 2015 | 101 | 44.5M | 92.9% | より深いResNet |
| InceptionV3 | 2015 | 48 | 23.8M | 93.9% | マルチスケール特徴 |
| Xception | 2016 | 71 | 22.9M | 94.5% | Depthwise Separable Conv |
| MobileNetV2 | 2018 | 53 | 3.5M | 90.1% | 軽量、モバイル向け |
| EfficientNetB0 | 2019 | – | 5.3M | 93.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 Extraction | Fine-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'
)
# より高い汎化性能が得られる
🎯 実習課題
🎯 課題:カスタムデータセットでの転移学習
課題内容:
自分で選んだ画像分類タスクに対して、転移学習を適用してください。
要件:
- データセットの準備
- 最低2クラス、各クラス100枚以上
- 訓練・検証・テストに分割
- Data Augmentationを適用
- 3つの異なる事前学習済みモデルを試す
- VGG16、ResNet50、InceptionV3等
- Feature Extractionを実装
- Fine-tuningを実装
- 性能比較
- ゼロから訓練したモデルと比較
- 各転移学習モデルの精度を比較
- 学習曲線を可視化
- 分析レポート
- どのモデルが最も効果的だったか
- Fine-tuningの効果は?
- 誤分類された画像の傾向
- 改善提案
期待される成果物:
- データセットの説明
- 実装コード(Feature Extraction + Fine-tuning)
- 性能比較表とグラフ
- 混同行列と分類レポート
- 予測結果の可視化
- 詳細な分析レポート
評価基準:
- 転移学習モデルの精度 > ゼロから訓練したモデルの精度
- Feature ExtractionとFine-tuningの両方を実装
- 体系的な実験と分析
- 明確な考察と改善提案
📚 第5章のまとめ
🎯 第5章の重要ポイント
- 転移学習は、事前学習された知識を新しいタスクに転用する強力な手法
- Feature ExtractionとFine-tuningの2つのアプローチがある
- 少ないデータでも高精度なモデルを構築できる
- VGG16、ResNet50、InceptionV3等の事前学習済みモデルが利用可能
- データセットサイズとタスク類似度に応じて戦略を選択する
- 低い学習率でFine-tuningすることが重要
- Batch Normalization層の扱いに注意が必要
- 転移学習 + Data Augmentationで最高の性能が得られる
💡 実践的なメリット
✅ 開発期間の短縮:数週間 → 数時間
✅ コスト削減:高性能GPU不要
✅ 少ないデータで高精度:数百枚で90%以上の精度
✅ 汎用性:様々なタスクに適用可能💡 次章への繋がり
第5章では転移学習を学びました。次の第6章以降では、より高度な深層学習トピック(物体検出、セグメンテーション、生成モデル等)を学びます。転移学習はこれらのタスクでも基礎となる重要な技術です。
