画像分類におけるMLPとCNNの性能比較:猫犬識別プロジェクトを通じた実践的検証

画像認識タスクにおいて、畳み込みニューラルネットワーク(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")

タグ: TensorFlow CNN 画像分類 機械学習 Python

6月25日 21:40 投稿