第4章 深層学習:CNN Ⅱ
📚 この章の学習目標
- Convolution層のフィルタとカーネルの関係を深く理解する
- CNNのパラメータ数を正確に計算する方法を習得する
- モデルの性能を向上させる様々な技法を学ぶ
- Data Augmentationによるデータ拡張の重要性を理解する
- Batch NormalizationとDropoutの原理と実装を習得する
- 実践的な犬・猫分類プロジェクトを完成させる
🔄 第3章の復習:Convolutionの基礎
💡 フィルタ(Filter)とカーネル(Kernel)の関係
第3章で学んだ内容を復習しましょう。CNNでは「フィルタ」と「カーネル」という用語がよく混用されますが、厳密には:
- カーネル(Kernel): 重み行列そのもの
- フィルタ(Filter): 1つまたは複数のカーネルで構成される特徴抽出器
📐 Conv2Dのパラメータ
Conv2D(filters=32, kernel_size=3)
⚡ パラメータの意味
- filters=32: 32個の異なるフィルタを使用
- kernel_size=3: 3×3のカーネル(重み行列)
- 各フィルタは入力のチャンネル数と同じ深さを持つ
多チャンネル入力への適用例
例1:RGB画像への適用
入力: 32×32×3 (RGB 3チャンネル)
フィルタ: 3×3×3×16 (16個のフィルタ、各フィルタは3チャンネル)
↓
出力: 30×30×16 (padding='valid'の場合)
例2:特徴マップへの適用
入力: 14×14×32 (前層の出力32チャンネル)
フィルタ: 3×3×32×64 (64個のフィルタ、各フィルタは32チャンネル)
↓
出力: 12×12×64 (padding='valid'の場合)
⚠️ 重要な原則
- Convolutionフィルタのチャンネル数は入力Feature Mapのチャンネル数と同じでなければならない
- 適用するフィルタの個数が出力Feature Mapのチャンネル数になる
🔢 パラメータ数の詳細計算
基本的なパラメータ計算式
💡 Conv2D層のパラメータ数
パラメータ数 = (カーネル高さ × カーネル幅 × 入力チャンネル数 × フィルタ数) + バイアス数 = (kernel_h × kernel_w × input_channels × filters) + filters
📊 計算例:段階的な理解
例1:最初のConv2D層
Conv2D(filters=16, kernel_size=3, input_shape=(28, 28, 1))
計算過程:
入力: 28×28×1 (グレースケール画像)
カーネル: 3×3×1 (入力チャンネル数1)
フィルタ数: 16個
パラメータ数 = (3 × 3 × 1) × 16 + 16
= 9 × 16 + 16
= 144 + 16
= 160
⚡ 詳細な内訳
- 重み: 3×3×1×16 = 144個
- バイアス: 16個(フィルタごとに1個)
- 合計: 160個
例2:2番目のConv2D層
Conv2D(filters=32, kernel_size=3) # 前層の出力は13×13×16
計算過程:
入力: 13×13×16 (前層の出力)
カーネル: 3×3×16 (入力チャンネル数16)
フィルタ数: 32個
パラメータ数 = (3 × 3 × 16) × 32 + 32
= 144 × 32 + 32
= 4,608 + 32
= 4,640
例3:Dense(全結合)層
Flatten() # 5×5×32 = 800
Dense(32)
計算過程:
入力: 800 (Flattenされた特徴)
出力: 32
パラメータ数 = 800 × 32 + 32
= 25,600 + 32
= 25,632
⚡ Dense層の特徴
- すべての入力がすべての出力に接続される(全結合)
- パラメータ数が非常に多くなりやすい
- Conv層とPooling層で特徴を圧縮してからDense層に渡すのが一般的
🏗️ 完全なモデルのパラメータ計算例
model = Sequential([
Conv2D(16, (3,3), activation='relu', input_shape=(28, 28, 1)), # 160
MaxPooling2D((2,2)), # 0
Conv2D(32, (3,3), activation='relu'), # 4,640
MaxPooling2D((2,2)), # 0
Flatten(), # 0
Dense(32, activation='relu'), # 25,632
Dense(10, activation='softmax') # 330
])
# 総パラメータ数 = 160 + 4,640 + 25,632 + 330 = 30,762
💡 Pooling層のパラメータ
MaxPooling2D層はパラメータを持ちません。
単に各領域の最大値を選択するだけで、学習可能な重みがないためです。
📈 出力サイズの計算
💡 Convolution後の出力サイズ
出力サイズ = (入力サイズ - カーネルサイズ + 2×Padding) / Stride + 1
例:
入力: 28×28
カーネル: 3×3
Padding: 0 (valid)
Stride: 1
出力サイズ = (28 - 3 + 0) / 1 + 1 = 26
→ 出力: 26×26
💡 Pooling後の出力サイズ
出力サイズ = 入力サイズ / プールサイズ
例:
入力: 26×26
プールサイズ: 2×2
出力サイズ = 26 / 2 = 13
→ 出力: 13×13
🔍 実習:パラメータ数の検証
from tensorflow import keras
from tensorflow.keras import layers
# モデルの構築
model = keras.Sequential([
layers.Conv2D(16, (3,3), activation='relu', input_shape=(28, 28, 1)),
layers.MaxPooling2D((2,2)),
layers.Conv2D(32, (3,3), activation='relu'),
layers.MaxPooling2D((2,2)),
layers.Flatten(),
layers.Dense(32, activation='relu'),
layers.Dense(10, activation='softmax')
])
# モデルのサマリー表示
model.summary()
# 出力結果
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 26, 26, 16) 160
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 13, 13, 16) 0
_________________________________________________________________
conv2d_1 (Conv2D) (None, 11, 11, 32) 4640
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 5, 5, 32) 0
_________________________________________________________________
flatten (Flatten) (None, 800) 0
_________________________________________________________________
dense (Dense) (None, 32) 25632
_________________________________________________________________
dense_1 (Dense) (None, 10) 330
=================================================================
Total params: 30,762
Trainable params: 30,762
Non-trainable params: 0
_________________________________________________________________
🚀 CNNの性能向上技法
💡 モデル性能を向上させる4つの主要技法
- 層の追加(Deeper Network)
- Data Augmentation(データ拡張)
- Batch Normalization(バッチ正規化)
- Dropout(ドロップアウト)
1️⃣ 層の追加(Deeper Network)
💡 深いネットワークの利点
- より複雑な特徴を階層的に学習できる
- 低レベル特徴(エッジ、テクスチャ)から高レベル特徴(形状、物体)へ
- 表現力が向上する
浅いネットワーク vs 深いネットワーク
浅いネットワーク(2層):
model = Sequential([
Conv2D(32, (3,3), activation='relu', input_shape=(32, 32, 3)),
MaxPooling2D((2,2)),
Flatten(),
Dense(10, activation='softmax')
])
深いネットワーク(6層):
model = Sequential([
# ブロック1
Conv2D(32, (3,3), activation='relu', input_shape=(32, 32, 3)),
Conv2D(32, (3,3), activation='relu'),
MaxPooling2D((2,2)),
# ブロック2
Conv2D(64, (3,3), activation='relu'),
Conv2D(64, (3,3), activation='relu'),
MaxPooling2D((2,2)),
# ブロック3
Conv2D(128, (3,3), activation='relu'),
MaxPooling2D((2,2)),
# 分類器
Flatten(),
Dense(128, activation='relu'),
Dense(10, activation='softmax')
])
⚠️ 深いネットワークの注意点
- 計算コスト増加: 学習時間が長くなる
- 過学習のリスク: 正則化技法が必要
- 勾配消失問題: 活性化関数やBatch Normalizationで対処
2️⃣ Data Augmentation(データ拡張)
💡 Data Augmentationとは?
既存の訓練データに対して様々な変換を適用し、人工的にデータセットを拡大する技法です。
- 過学習を防止
- モデルの汎化性能を向上
- 少ないデータでも効果的に学習
主要なData Augmentation技法
| 技法 | 説明 | 効果 |
|---|---|---|
| Rotation | 画像を回転 | 向きの変化に頑健 |
| Width/Height Shift | 水平・垂直移動 | 位置の変化に頑健 |
| Zoom | 拡大・縮小 | スケールの変化に頑健 |
| Horizontal Flip | 左右反転 | 左右対称性を学習 |
| Brightness Adjustment | 明るさ調整 | 照明条件の変化に頑健 |
| Shear | せん断変換 | 変形に頑健 |
KerasのImageDataGenerator
from tensorflow.keras.preprocessing.image import ImageDataGenerator
# Data Augmentationの設定
datagen = ImageDataGenerator(
rotation_range=20, # ±20度回転
width_shift_range=0.2, # 水平方向に±20%移動
height_shift_range=0.2, # 垂直方向に±20%移動
horizontal_flip=True, # 左右反転
zoom_range=0.2, # ±20%ズーム
shear_range=0.2, # せん断変換
fill_mode='nearest' # 空白領域の埋め方
)
# 訓練データに適用
datagen.fit(X_train)
Data Augmentationの可視化
import matplotlib.pyplot as plt
import numpy as np
# 1枚の画像を選択
sample_image = X_train[0:1] # shape: (1, 32, 32, 3)
# 拡張された画像を生成
plt.figure(figsize=(15, 5))
plt.suptitle('Data Augmentation Examples', fontsize=16)
# オリジナル画像
plt.subplot(2, 5, 1)
plt.imshow(sample_image[0])
plt.title('Original')
plt.axis('off')
# 拡張された画像を9枚生成
aug_iter = datagen.flow(sample_image, batch_size=1)
for i in range(9):
plt.subplot(2, 5, i+2)
aug_image = next(aug_iter)[0].astype('uint8')
plt.imshow(aug_image)
plt.title(f'Augmented {i+1}')
plt.axis('off')
plt.tight_layout()
plt.show()
モデル訓練でのData Augmentation使用
# Data Augmentationを使った訓練
history = model.fit(
datagen.flow(X_train, y_train, batch_size=32),
steps_per_epoch=len(X_train) // 32,
epochs=50,
validation_data=(X_test, y_test)
)
⚡ steps_per_epochの計算
steps_per_epoch = 訓練データ数 / バッチサイズData Augmentationを使う場合、各エポックで異なる変換が適用されます。
3️⃣ Batch Normalization(バッチ正規化)
💡 Batch Normalizationとは?
各ミニバッチごとに、層の入力を平均0、分散1に正規化する技法です。
- 学習の安定化: 学習率を大きくできる
- 高速な収束: 学習が速くなる
- 正則化効果: 過学習を軽減
- 勾配消失問題の緩和
Batch Normalizationの動作原理
入力バッチ → [平均と分散を計算] → [正規化] → [スケールとシフト] → 出力
数式:
1. バッチの平均: μ = (1/m) Σ xᵢ
2. バッチの分散: σ² = (1/m) Σ (xᵢ - μ)²
3. 正規化: x̂ᵢ = (xᵢ - μ) / √(σ² + ε)
4. スケールとシフト: yᵢ = γ·x̂ᵢ + β
⚡ 学習可能なパラメータ
- γ(ガンマ): スケールパラメータ
- β(ベータ): シフトパラメータ
これらはバックプロパゲーションで学習されます。
Batch Normalizationの実装
from tensorflow.keras.layers import BatchNormalization
model = Sequential([
Conv2D(32, (3,3), input_shape=(32, 32, 3)),
BatchNormalization(), # Conv2Dの後に追加
Activation('relu'),
MaxPooling2D((2,2)),
Conv2D(64, (3,3)),
BatchNormalization(), # Conv2Dの後に追加
Activation('relu'),
MaxPooling2D((2,2)),
Flatten(),
Dense(128),
BatchNormalization(), # Denseの後に追加
Activation('relu'),
Dense(10, activation='softmax')
])
⚠️ 配置の順序
一般的には:
Conv2D → BatchNormalization → Activation → Poolingまたは:
Conv2D → Activation → BatchNormalization → Poolingどちらも使われますが、前者が最近では一般的です。
Batch Normalizationの効果比較
# Batch Normalizationなし
model_without_bn = Sequential([
Conv2D(64, (3,3), activation='relu', input_shape=(32, 32, 3)),
MaxPooling2D((2,2)),
Flatten(),
Dense(10, activation='softmax')
])
# Batch Normalizationあり
model_with_bn = Sequential([
Conv2D(64, (3,3), input_shape=(32, 32, 3)),
BatchNormalization(),
Activation('relu'),
MaxPooling2D((2,2)),
Flatten(),
Dense(128),
BatchNormalization(),
Activation('relu'),
Dense(10, activation='softmax')
])
4️⃣ Dropout(ドロップアウト)
💡 Dropoutとは?
訓練時にランダムにニューロンの一部を一時的に無効化する正則化技法です。
- 過学習を防止
- アンサンブル効果:複数のサブネットワークを学習
- 頑健なモデル:特定のニューロンへの依存を減らす
Dropoutの動作
通常の層:
[●][●][●][●][●][●][●][●]
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
すべてのニューロンが活性
Dropout適用(rate=0.5):
[●][○][●][○][●][●][○][●]
↓ ↓ ↓ ↓ ↓
50%がランダムにドロップ
⚡ 訓練時と推論時の違い
- 訓練時: ランダムにニューロンをドロップ
- 推論時: すべてのニューロンを使用(自動調整)
Dropoutの実装
from tensorflow.keras.layers import Dropout
model = Sequential([
Conv2D(32, (3,3), activation='relu', input_shape=(32, 32, 3)),
MaxPooling2D((2,2)),
Conv2D(64, (3,3), activation='relu'),
MaxPooling2D((2,2)),
Flatten(),
Dense(128, activation='relu'),
Dropout(0.5), # 50%のニューロンをドロップ
Dense(64, activation='relu'),
Dropout(0.3), # 30%のニューロンをドロップ
Dense(10, activation='softmax')
])
⚡ Dropout rateの選択
- 0.2-0.3: 軽度の正則化
- 0.5: 標準的な設定(最も一般的)
- 0.7-0.8: 強い正則化(過学習が深刻な場合)
すべての技法を組み合わせた最適モデル
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Dense
from tensorflow.keras.layers import Flatten, Dropout, BatchNormalization, Activation
model = Sequential([
# ブロック1
Conv2D(32, (3,3), padding='same', input_shape=(32, 32, 3)),
BatchNormalization(),
Activation('relu'),
Conv2D(32, (3,3), padding='same'),
BatchNormalization(),
Activation('relu'),
MaxPooling2D((2,2)),
Dropout(0.2),
# ブロック2
Conv2D(64, (3,3), padding='same'),
BatchNormalization(),
Activation('relu'),
Conv2D(64, (3,3), padding='same'),
BatchNormalization(),
Activation('relu'),
MaxPooling2D((2,2)),
Dropout(0.3),
# ブロック3
Conv2D(128, (3,3), padding='same'),
BatchNormalization(),
Activation('relu'),
MaxPooling2D((2,2)),
Dropout(0.4),
# 分類器
Flatten(),
Dense(128),
BatchNormalization(),
Activation('relu'),
Dropout(0.5),
Dense(10, activation='softmax')
])
model.summary()
🔬 実習:CIFAR-10での性能比較
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()
print(f'訓練データ: {X_train.shape}')
print(f'テストデータ: {X_test.shape}')
# クラス名
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer',
'dog', 'frog', 'horse', 'ship', 'truck']
# データの正規化
X_train = X_train.astype('float32') / 255.0
X_test = X_test.astype('float32') / 255.0
# ラベルのOne-hot encoding
y_train = to_categorical(y_train, 10)
y_test = to_categorical(y_test, 10)
モデル1:基本CNN(ベースライン)
model_basic = Sequential([
Conv2D(32, (3,3), activation='relu', input_shape=(32, 32, 3)),
MaxPooling2D((2,2)),
Conv2D(64, (3,3), activation='relu'),
MaxPooling2D((2,2)),
Flatten(),
Dense(64, activation='relu'),
Dense(10, activation='softmax')
])
model_basic.compile(
optimizer='adam',
loss='categorical_crossentropy',
metrics=['accuracy']
)
print("=== 基本CNNモデル ===")
model_basic.summary()
# 訓練
history_basic = model_basic.fit(
X_train, y_train,
batch_size=64,
epochs=30,
validation_data=(X_test, y_test),
verbose=1
)
モデル2:Data Augmentation追加
from tensorflow.keras.preprocessing.image import ImageDataGenerator
# Data Augmentationの設定
datagen = ImageDataGenerator(
rotation_range=15,
width_shift_range=0.1,
height_shift_range=0.1,
horizontal_flip=True
)
datagen.fit(X_train)
model_augmented = Sequential([
Conv2D(32, (3,3), activation='relu', input_shape=(32, 32, 3)),
MaxPooling2D((2,2)),
Conv2D(64, (3,3), activation='relu'),
MaxPooling2D((2,2)),
Flatten(),
Dense(64, activation='relu'),
Dense(10, activation='softmax')
])
model_augmented.compile(
optimizer='adam',
loss='categorical_crossentropy',
metrics=['accuracy']
)
print("=== Data Augmentation適用モデル ===")
# 訓練
history_augmented = model_augmented.fit(
datagen.flow(X_train, y_train, batch_size=64),
steps_per_epoch=len(X_train) // 64,
epochs=30,
validation_data=(X_test, y_test),
verbose=1
)
モデル3:すべての技法を適用
model_optimized = Sequential([
# ブロック1
Conv2D(32, (3,3), padding='same', input_shape=(32, 32, 3)),
BatchNormalization(),
Activation('relu'),
Conv2D(32, (3,3), padding='same'),
BatchNormalization(),
Activation('relu'),
MaxPooling2D((2,2)),
Dropout(0.2),
# ブロック2
Conv2D(64, (3,3), padding='same'),
BatchNormalization(),
Activation('relu'),
Conv2D(64, (3,3), padding='same'),
BatchNormalization(),
Activation('relu'),
MaxPooling2D((2,2)),
Dropout(0.3),
# ブロック3
Conv2D(128, (3,3), padding='same'),
BatchNormalization(),
Activation('relu'),
MaxPooling2D((2,2)),
Dropout(0.4),
# 分類器
Flatten(),
Dense(128),
BatchNormalization(),
Activation('relu'),
Dropout(0.5),
Dense(10, activation='softmax')
])
model_optimized.compile(
optimizer='adam',
loss='categorical_crossentropy',
metrics=['accuracy']
)
print("=== 最適化モデル(すべての技法適用) ===")
model_optimized.summary()
# 訓練
history_optimized = model_optimized.fit(
datagen.flow(X_train, y_train, batch_size=64),
steps_per_epoch=len(X_train) // 64,
epochs=50,
validation_data=(X_test, y_test),
verbose=1
)
📊 結果の比較と可視化
import matplotlib.pyplot as plt
# 訓練履歴の可視化
fig, axes = plt.subplots(1, 2, figsize=(15, 5))
# Accuracy
axes[0].plot(history_basic.history['accuracy'], label='Basic - Train')
axes[0].plot(history_basic.history['val_accuracy'], label='Basic - Val')
axes[0].plot(history_augmented.history['val_accuracy'], label='Augmented - Val')
axes[0].plot(history_optimized.history['val_accuracy'], label='Optimized - Val')
axes[0].set_xlabel('Epoch')
axes[0].set_ylabel('Accuracy')
axes[0].set_title('Model Accuracy Comparison')
axes[0].legend()
axes[0].grid(True)
# Loss
axes[1].plot(history_basic.history['loss'], label='Basic - Train')
axes[1].plot(history_basic.history['val_loss'], label='Basic - Val')
axes[1].plot(history_augmented.history['val_loss'], label='Augmented - Val')
axes[1].plot(history_optimized.history['val_loss'], label='Optimized - Val')
axes[1].set_xlabel('Epoch')
axes[1].set_ylabel('Loss')
axes[1].set_title('Model Loss Comparison')
axes[1].legend()
axes[1].grid(True)
plt.tight_layout()
plt.show()
# 最終結果の比較
print("\n=== 最終テスト精度の比較 ===")
print(f"基本CNN: {history_basic.history['val_accuracy'][-1]:.4f}")
print(f"Data Augmentation: {history_augmented.history['val_accuracy'][-1]:.4f}")
print(f"最適化モデル: {history_optimized.history['val_accuracy'][-1]:.4f}")
予測結果の確認
# 予測
predictions = model_optimized.predict(X_test)
predicted_labels = np.argmax(predictions, axis=1)
true_labels = np.argmax(y_test, axis=1)
# ランダムに9枚表示
plt.figure(figsize=(12, 12))
for i in range(9):
idx = np.random.randint(0, len(X_test))
plt.subplot(3, 3, i+1)
plt.imshow(X_test[idx])
pred_label = class_names[predicted_labels[idx]]
true_label = class_names[true_labels[idx]]
color = 'green' if pred_label == true_label else 'red'
plt.title(f'Pred: {pred_label}\nTrue: {true_label}', color=color)
plt.axis('off')
plt.tight_layout()
plt.show()
🐕🐈 実習プロジェクト:犬と猫の分類
🎯 プロジェクト概要
実世界のデータを使って、犬と猫の画像を分類するCNNモデルを構築します。
- データセット: Dogs vs Cats(Kaggle)
- クラス数: 2(犬、猫)
- 画像サイズ: 様々(リサイズが必要)
- 環境: Google Colab推奨
Google Colabでの環境設定
# 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
from tensorflow.keras.layers import *
from tensorflow.keras.models import Sequential
import matplotlib.pyplot as plt
import numpy as np
import os
データセットの準備
# データセットのダウンロード(Kaggle API使用)
!pip install kaggle
# Kaggle APIキーの設定(kaggle.jsonをアップロード)
!mkdir -p ~/.kaggle
!cp /content/drive/MyDrive/kaggle.json ~/.kaggle/
!chmod 600 ~/.kaggle/kaggle.json
# データセットのダウンロード
!kaggle competitions download -c dogs-vs-cats-redux-kernels-edition
!unzip -q dogs-vs-cats-redux-kernels-edition.zip
!unzip -q train.zip
⚡ データ構造
train/ ├── cat.0.jpg ├── cat.1.jpg ├── ... ├── dog.0.jpg ├── dog.1.jpg └── ...
データの整理
import shutil
import os
# ディレクトリ構造の作成
base_dir = '/content/data'
os.makedirs(base_dir, exist_ok=True)
# 訓練・検証・テスト用ディレクトリ
for split in ['train', 'validation', 'test']:
for category in ['cats', 'dogs']:
dir_path = os.path.join(base_dir, split, category)
os.makedirs(dir_path, exist_ok=True)
# データの分割(猫:12500枚、犬:12500枚)
# 訓練:各8000枚、検証:各2000枚、テスト:各2500枚
# 猫の画像を移動
fnames = [f'cat.{i}.jpg' for i in range(10000)]
for i, fname in enumerate(fnames):
src = os.path.join('/content/train', fname)
if i < 8000:
dst = os.path.join(base_dir, 'train/cats', fname)
elif i < 10000:
dst = os.path.join(base_dir, 'validation/cats', fname)
shutil.copyfile(src, dst)
fnames = [f'cat.{i}.jpg' for i in range(10000, 12500)]
for fname in fnames:
src = os.path.join('/content/train', fname)
dst = os.path.join(base_dir, 'test/cats', fname)
shutil.copyfile(src, dst)
# 犬の画像を移動
fnames = [f'dog.{i}.jpg' for i in range(10000)]
for i, fname in enumerate(fnames):
src = os.path.join('/content/train', fname)
if i < 8000:
dst = os.path.join(base_dir, 'train/dogs', fname)
elif i < 10000:
dst = os.path.join(base_dir, 'validation/dogs', fname)
shutil.copyfile(src, dst)
fnames = [f'dog.{i}.jpg' for i in range(10000, 12500)]
for fname in fnames:
src = os.path.join('/content/train', fname)
dst = os.path.join(base_dir, 'test/dogs', fname)
shutil.copyfile(src, dst)
print("データの整理完了!")
print(f"訓練データ(猫): {len(os.listdir(os.path.join(base_dir, 'train/cats')))}枚")
print(f"訓練データ(犬): {len(os.listdir(os.path.join(base_dir, 'train/dogs')))}枚")
print(f"検証データ(猫): {len(os.listdir(os.path.join(base_dir, 'validation/cats')))}枚")
print(f"検証データ(犬): {len(os.listdir(os.path.join(base_dir, 'validation/dogs')))}枚")
ImageDataGeneratorの設定
# Data Augmentationを含む訓練データジェネレータ
train_datagen = ImageDataGenerator(
rescale=1./255, # 正規化
rotation_range=40, # ±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)
# ジェネレータの作成
train_generator = train_datagen.flow_from_directory(
os.path.join(base_dir, 'train'),
target_size=(150, 150), # すべて150×150にリサイズ
batch_size=32,
class_mode='binary' # 二値分類
)
validation_generator = test_datagen.flow_from_directory(
os.path.join(base_dir, 'validation'),
target_size=(150, 150),
batch_size=32,
class_mode='binary'
)
test_generator = test_datagen.flow_from_directory(
os.path.join(base_dir, 'test'),
target_size=(150, 150),
batch_size=32,
class_mode='binary',
shuffle=False # テストデータは順序を保持
)
モデルの構築
model = Sequential([
# ブロック1
Conv2D(32, (3,3), padding='same', input_shape=(150, 150, 3)),
BatchNormalization(),
Activation('relu'),
Conv2D(32, (3,3), padding='same'),
BatchNormalization(),
Activation('relu'),
MaxPooling2D((2,2)),
Dropout(0.25),
# ブロック2
Conv2D(64, (3,3), padding='same'),
BatchNormalization(),
Activation('relu'),
Conv2D(64, (3,3), padding='same'),
BatchNormalization(),
Activation('relu'),
MaxPooling2D((2,2)),
Dropout(0.25),
# ブロック3
Conv2D(128, (3,3), padding='same'),
BatchNormalization(),
Activation('relu'),
Conv2D(128, (3,3), padding='same'),
BatchNormalization(),
Activation('relu'),
MaxPooling2D((2,2)),
Dropout(0.25),
# ブロック4
Conv2D(256, (3,3), padding='same'),
BatchNormalization(),
Activation('relu'),
MaxPooling2D((2,2)),
Dropout(0.25),
# 分類器
Flatten(),
Dense(512),
BatchNormalization(),
Activation('relu'),
Dropout(0.5),
Dense(1, activation='sigmoid') # 二値分類
])
model.compile(
optimizer=keras.optimizers.Adam(learning_rate=0.0001),
loss='binary_crossentropy',
metrics=['accuracy']
)
model.summary()
モデルの訓練
# Early Stoppingとモデルチェックポイント
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
callbacks = [
EarlyStopping(
monitor='val_loss',
patience=10,
restore_best_weights=True
),
ModelCheckpoint(
'best_model.h5',
monitor='val_accuracy',
save_best_only=True
)
]
# 訓練
history = model.fit(
train_generator,
steps_per_epoch=train_generator.samples // train_generator.batch_size,
epochs=50,
validation_data=validation_generator,
validation_steps=validation_generator.samples // validation_generator.batch_size,
callbacks=callbacks,
verbose=1
)
学習曲線の可視化
# Accuracy
plt.figure(figsize=(15, 5))
plt.subplot(1, 2, 1)
plt.plot(history.history['accuracy'], label='Training Accuracy')
plt.plot(history.history['val_accuracy'], label='Validation Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.title('Training and Validation Accuracy')
plt.legend()
plt.grid(True)
# Loss
plt.subplot(1, 2, 2)
plt.plot(history.history['loss'], label='Training Loss')
plt.plot(history.history['val_loss'], label='Validation Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training and Validation Loss')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
テストデータでの評価
# テストデータでの評価
test_loss, test_accuracy = model.evaluate(test_generator)
print(f'\nTest 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')
plt.show()
# 詳細なレポート
print("\n=== Classification Report ===")
print(classification_report(true_classes, predicted_classes,
target_names=class_labels))
予測結果の可視化
# ランダムに予測結果を表示
import random
plt.figure(figsize=(15, 10))
# ランダムに20枚選択
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=(150, 150))
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.tight_layout()
plt.show()
誤分類の分析
# 誤分類されたサンプルの抽出
misclassified_indices = np.where(predicted_classes != true_classes)[0]
print(f"\n誤分類されたサンプル数: {len(misclassified_indices)}")
print(f"全体の精度: {test_accuracy:.4f}")
# 誤分類されたサンプルの中で、特に確信度が高かったものを表示
confidences = np.abs(predictions.flatten() - 0.5)
misclassified_confidences = confidences[misclassified_indices]
top_misclassified = misclassified_indices[np.argsort(misclassified_confidences)[-12:]]
plt.figure(figsize=(15, 8))
for i, idx in enumerate(top_misclassified):
plt.subplot(3, 4, i+1)
img_path = os.path.join(base_dir, 'test', test_generator.filenames[idx])
img = keras.preprocessing.image.load_img(img_path, target_size=(150, 150))
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]
plt.title(f'Pred: {pred_class} ({confidence:.2f})\nTrue: {true_class}',
color='red', fontsize=10)
plt.axis('off')
plt.suptitle('High Confidence Misclassifications', fontsize=16)
plt.tight_layout()
plt.show()
🎯 実習課題
🎯 課題:犬と猫の分類モデルの改善
課題内容:
犬と猫の分類プロジェクトをベースに、さらに性能を向上させてください。
要件:
- 異なるモデルアーキテクチャを最低3つ試す
- 層の数を変える
- フィルタ数を調整する
- 異なる正則化技法を試す
- Data Augmentationのパラメータを最適化する
- 異なる変換の組み合わせを試す
- 過度な変換が悪影響を与えないか確認する
- ハイパーパラメータの調整
- 学習率の調整
- バッチサイズの変更
- Dropoutレートの最適化
- 結果の詳細な分析
- 各モデルの学習曲線を比較
- 混同行列で誤分類のパターンを分析
- 誤分類された画像の特徴を考察
期待される成果物:
- 3つ以上のモデルの実装コード
- 各モデルの性能比較表
- 学習曲線のグラフ
- 混同行列と分類レポート
- 誤分類の分析レポート
- 最終的な考察と改善提案
評価基準:
- テスト精度85%以上を目標
- 過学習を適切に抑制できているか
- 実験の体系的な記録と分析
📚 第4章のまとめ
🎯 第4章の重要ポイント
- パラメータ数の計算は、モデル設計と最適化に不可欠
- 深いネットワークは複雑な特徴を階層的に学習できる
- Data Augmentationはデータを人工的に拡張し、過学習を防ぐ
- Batch Normalizationは学習を安定化し、高速化する
- Dropoutはランダムにニューロンを無効化し、汎化性能を向上させる
- 複数の技法を組み合わせることで、最高の性能が得られる
- 実践的なプロジェクトを通じて、実世界のデータに対応する力を身につける
💡 次章への繋がり
第4章ではCNNの性能向上技法を学びました。次の第5章では、転移学習(Transfer Learning)を学びます。事前学習された強力なモデル(VGG、ResNet、Inception等)を活用することで、少ないデータでも高精度なモデルを構築できる技術を習得します。
