画像認識タスクにおいて、畳み込みニューラルネットワーク(CNN)は空間的特徴を効率的に抽出できるため、多層パーセプトロン(MLP)よりも優れた性能を発揮することが知られています。本稿では、猫と犬の二値分類というシンプルな課題を通じて、両者の構造的差異と学習挙動を実コードで比較・分析します。
環境設定とデータ準備
Google Colab上でTensorFlow 2.xを使用し、GPUアクセラレーションを有効にした環境で実験を行います。データセットにはcats_and_dogs_filteredを採用。訓練用2000枚、検証用1000枚の画像が含まれており、初学者向けの軽量データとして最適です。
# 必要ライブラリのインポート
import tensorflow as tf
from tensorflow.keras import layers, models
from tensorflow.keras.preprocessing.image import ImageDataGenerator
import matplotlib.pyplot as plt
# GPU利用確認
gpus = tf.config.list_physical_devices('GPU')
print(f"GPU detected: {len(gpus) > 0}")
# データセット取得
!wget --no-check-certificate \
https://storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered.zip \
-O dataset.zip
!unzip -q dataset.zip
データ前処理とジェネレータ構築
訓練データにはランダム回転や反転などの拡張を適用し、モデルの汎化性能を向上させます。検証データは正規化のみで、評価の一貫性を保ちます。
# 拡張付きデータジェネレータ
train_gen = ImageDataGenerator(
rescale=1./255,
rotation_range=25,
zoom_range=0.3,
horizontal_flip=True,
fill_mode='nearest'
)
val_gen = ImageDataGenerator(rescale=1./255)
# バッチ生成器
train_flow = train_gen.flow_from_directory(
'cats_and_dogs_filtered/train',
target_size=(150, 150),
batch_size=32,
class_mode='binary'
)
val_flow = val_gen.flow_from_directory(
'cats_and_dogs_filtered/validation',
target_size=(150, 150),
batch_size=32,
class_mode='binary',
shuffle=False
)
モデルアーキテクチャの定義
MLPは画像を単純なベクトルとして扱うため、空間的情報が失われます。一方、CNNは階層的な畳み込み演算により、局所的特徴からグローバルなパターンまで段階的に学習します。
# MLPモデル:空間情報を無視した全結合層のみ
def build_mlp():
model = models.Sequential([
layers.Flatten(input_shape=(150, 150, 3)),
layers.Dense(256, activation='relu'),
layers.Dropout(0.5),
layers.Dense(64, activation='relu'),
layers.Dropout(0.5),
layers.Dense(1, activation='sigmoid')
])
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
return model
# CNNモデル:空間的階層特徴を抽出
def build_cnn():
model = models.Sequential([
layers.Conv2D(32, 3, activation='relu', input_shape=(150, 150, 3)),
layers.MaxPooling2D(2),
layers.Conv2D(64, 3, activation='relu'),
layers.MaxPooling2D(2),
layers.Conv2D(128, 3, activation='relu'),
layers.MaxPooling2D(2),
layers.Flatten(),
layers.Dense(128, activation='relu'),
layers.Dropout(0.5),
layers.Dense(1, activation='sigmoid')
])
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
return model
mlp_model = build_mlp()
cnn_model = build_cnn()
学習プロセスと性能比較
同一条件下で両モデルを学習させ、検証精度の推移を可視化します。CNNは初期段階から安定して精度が上昇する傾向があります。
# 学習実行(短時間比較用)
mlp_hist = mlp_model.fit(train_flow, epochs=10, validation_data=val_flow, verbose=0)
cnn_hist = cnn_model.fit(train_flow, epochs=10, validation_data=val_flow, verbose=0)
# 精度比較グラフ
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(mlp_hist.history['val_accuracy'], label='MLP', linestyle='--')
plt.plot(cnn_hist.history['val_accuracy'], label='CNN', linestyle='-')
plt.title('Validation Accuracy')
plt.xlabel('Epoch'); plt.ylabel('Accuracy'); plt.legend()
plt.subplot(1, 2, 2)
plt.plot(mlp_hist.history['val_loss'], label='MLP', linestyle='--')
plt.plot(cnn_hist.history['val_loss'], label='CNN', linestyle='-')
plt.title('Validation Loss')
plt.xlabel('Epoch'); plt.ylabel('Loss'); plt.legend()
plt.tight_layout()
plt.show()
性能改善のための高度な戦略
初期精度が低い場合、以下の改良策が有効です:
- データ拡張の強化:垂直反転、せん断変換などを追加
- ネットワークの深化:追加の畳み込み層とバッチ正規化層を導入
- 学習制御:EarlyStoppingとReduceLROnPlateauコールバックを活用
# 改良版CNN:深層化と正則化を強化
def build_advanced_cnn():
model = models.Sequential([
layers.Conv2D(32, 3, activation='relu', input_shape=(150, 150, 3)),
layers.BatchNormalization(),
layers.MaxPooling2D(2),
layers.Conv2D(64, 3, activation='relu'),
layers.BatchNormalization(),
layers.MaxPooling2D(2),
layers.Conv2D(128, 3, activation='relu'),
layers.BatchNormalization(),
layers.MaxPooling2D(2),
layers.Conv2D(256, 3, activation='relu'),
layers.BatchNormalization(),
layers.MaxPooling2D(2),
layers.GlobalAveragePooling2D(), # Flattenの代わりに使用
layers.Dense(256, activation='relu', kernel_regularizer='l2'),
layers.Dropout(0.6),
layers.Dense(1, activation='sigmoid')
])
return model
# 学習コールバック
callbacks = [
tf.keras.callbacks.EarlyStopping(patience=5, restore_best_weights=True),
tf.keras.callbacks.ReduceLROnPlateau(factor=0.5, patience=3)
]
advanced_cnn = build_advanced_cnn()
advanced_cnn.compile(optimizer=tf.keras.optimizers.Adam(1e-4),
loss='binary_crossentropy', metrics=['accuracy'])
# 長期学習(最大50エポック)
hist_adv = advanced_cnn.fit(train_flow, epochs=50,
validation_data=val_flow,
callbacks=callbacks, verbose=1)
結果分析と誤分類の可視化
混同行列と誤分類サンプルの表示により、モデルの弱点を具体的に把握できます。CNNはMLPと比較して、明確な特徴を持つ画像の誤判別が少ない傾向があります。
import numpy as np
import seaborn as sns
from sklearn.metrics import confusion_matrix
# 混同行列の作成
def plot_confusion(model, generator, title):
preds = (model.predict(generator) > 0.5).flatten().astype(int)
true_labels = generator.classes
cm = confusion_matrix(true_labels, preds)
plt.figure(figsize=(6,5))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
xticklabels=['Cat','Dog'], yticklabels=['Cat','Dog'])
plt.title(f'{title} Confusion Matrix')
plt.ylabel('True Label'); plt.xlabel('Predicted Label')
plt.show()
plot_confusion(advanced_cnn, val_flow, "Advanced CNN")