層を深くすると精度は上がる?CNNの深さによる影響を調べる

2025年6月8日日曜日

CNN Google Colab Keras 画像分類

X f B! P L
アイキャッチ画像 層を深くすると精度は上がる?CNNの深さによる影響を調べる

はじめに

畳み込みニューラルネットワーク(CNN)は、画像認識タスクにおいて非常に高い性能を誇ることで知られています。
では、CNNの層を深くすればするほど、精度はどんどん向上するのでしょうか?
この記事では、CNNの層の深さを段階的に変化させながら、モデルの精度や学習挙動の違いを観察してみます。

実験概要

  • データセット:MNIST(手書き数字画像)
  • モデル構成:畳み込み層とプーリング層を1層〜3層まで増やしたCNN
  • 比較項目:精度(accuracy)、損失(loss)、学習速度、過学習傾向

モデル構成

浅いモデル(1層構成)

Conv2D(32, kernel_size=3, activation='relu')
MaxPooling2D(pool_size=2)
Flatten()
Dense(64, activation='relu')
Dense(10, activation='softmax')

中間モデル(2層構成)

Conv2D(32, kernel_size=3, activation='relu')
MaxPooling2D(pool_size=2)
Conv2D(64, kernel_size=3, activation='relu')
MaxPooling2D(pool_size=2)
Flatten()
Dense(64, activation='relu')
Dense(10, activation='softmax')

深いモデル(3層構成)

Conv2D(32, kernel_size=3, activation='relu')
MaxPooling2D(pool_size=2)
Conv2D(64, kernel_size=3, activation='relu')
MaxPooling2D(pool_size=2)
Conv2D(128, kernel_size=3, activation='relu')
Flatten()
Dense(64, activation='relu')
Dense(10, activation='softmax')

実験

準備:MNISTデータの読み込み

from tensorflow import keras
from tensorflow.keras import layers

# MNISTデータの読み込み
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()

# 正規化とチャンネル次元追加
x_train = x_train.astype("float32") / 255.0
x_test = x_test.astype("float32") / 255.0
x_train = x_train[..., None]
x_test = x_test[..., None]

モデル構築関数

モデル構成の繰り返しを避けるため、関数としてまとめます。

def build_cnn_model(conv_filters, dense_units=64):
    model = keras.Sequential()
    model.add(layers.Input(shape=(28, 28, 1)))
    for filters in conv_filters:
        model.add(layers.Conv2D(filters, kernel_size=3, activation='relu'))
        model.add(layers.MaxPooling2D(pool_size=2))
    model.add(layers.Flatten())
    model.add(layers.Dense(dense_units, activation='relu'))
    model.add(layers.Dense(10, activation='softmax'))
    return model

各モデルの学習と評価

models = {
    "SimpleCNN": [32],
    "MediumCNN": [32, 64],
    "DeepCNN": [32, 64, 128]
}

results = {}
histories = {}

for name, model in models.items():
    print(f"Training with {name}...")
    model = build_cnn_model(model)
    model.compile(optimizer='adam',
                  loss='sparse_categorical_crossentropy',
                  metrics=['accuracy'])
    model.summary()

    histories[name] = model.fit(
        x_train, y_train,
        validation_split=0.2,
        epochs=5
    )
    test_loss, test_acc = model.evaluate(x_test, y_test, verbose=0)
    results[name] = {
        "val_acc": histories[name].history["val_accuracy"][-1],
        "test_acc": test_acc
    }

学習ログ・結果

各モデルの学習ログと最終結果です。

Training with SimpleCNN...
Model: "sequential"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ conv2d (Conv2D)                 │ (None, 26, 26, 32)     │           320 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d (MaxPooling2D)    │ (None, 13, 13, 32)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ flatten (Flatten)               │ (None, 5408)           │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense (Dense)                   │ (None, 64)             │       346,176 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_1 (Dense)                 │ (None, 10)             │           650 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
 Total params: 347,146 (1.32 MB)
 Trainable params: 347,146 (1.32 MB)
 Non-trainable params: 0 (0.00 B)
Epoch 1/5
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 8s 4ms/step - accuracy: 0.8881 - loss: 0.3706 - val_accuracy: 0.9740 - val_loss: 0.0869
Epoch 2/5
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 8s 3ms/step - accuracy: 0.9785 - loss: 0.0694 - val_accuracy: 0.9798 - val_loss: 0.0683
Epoch 3/5
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9866 - loss: 0.0444 - val_accuracy: 0.9816 - val_loss: 0.0661
Epoch 4/5
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9904 - loss: 0.0320 - val_accuracy: 0.9843 - val_loss: 0.0580
Epoch 5/5
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9932 - loss: 0.0212 - val_accuracy: 0.9852 - val_loss: 0.0575
Training with MediumCNN...
Model: "sequential_1"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ conv2d_1 (Conv2D)               │ (None, 26, 26, 32)     │           320 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_1 (MaxPooling2D)  │ (None, 13, 13, 32)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_2 (Conv2D)               │ (None, 11, 11, 64)     │        18,496 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_2 (MaxPooling2D)  │ (None, 5, 5, 64)       │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ flatten_1 (Flatten)             │ (None, 1600)           │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_2 (Dense)                 │ (None, 64)             │       102,464 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_3 (Dense)                 │ (None, 10)             │           650 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
 Total params: 121,930 (476.29 KB)
 Trainable params: 121,930 (476.29 KB)
 Non-trainable params: 0 (0.00 B)
Epoch 1/5
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 8s 4ms/step - accuracy: 0.8923 - loss: 0.3482 - val_accuracy: 0.9830 - val_loss: 0.0580
Epoch 2/5
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 9s 4ms/step - accuracy: 0.9837 - loss: 0.0524 - val_accuracy: 0.9862 - val_loss: 0.0446
Epoch 3/5
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 10s 3ms/step - accuracy: 0.9886 - loss: 0.0352 - val_accuracy: 0.9897 - val_loss: 0.0351
Epoch 4/5
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9930 - loss: 0.0222 - val_accuracy: 0.9893 - val_loss: 0.0362
Epoch 5/5
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 4ms/step - accuracy: 0.9944 - loss: 0.0175 - val_accuracy: 0.9889 - val_loss: 0.0421
Training with DeepCNN...
Model: "sequential_2"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ conv2d_3 (Conv2D)               │ (None, 26, 26, 32)     │           320 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_3 (MaxPooling2D)  │ (None, 13, 13, 32)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_4 (Conv2D)               │ (None, 11, 11, 64)     │        18,496 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_4 (MaxPooling2D)  │ (None, 5, 5, 64)       │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_5 (Conv2D)               │ (None, 3, 3, 128)      │        73,856 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_5 (MaxPooling2D)  │ (None, 1, 1, 128)      │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ flatten_2 (Flatten)             │ (None, 128)            │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_4 (Dense)                 │ (None, 64)             │         8,256 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_5 (Dense)                 │ (None, 10)             │           650 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
 Total params: 101,578 (396.79 KB)
 Trainable params: 101,578 (396.79 KB)
 Non-trainable params: 0 (0.00 B)
Epoch 1/5
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 8s 4ms/step - accuracy: 0.8384 - loss: 0.5066 - val_accuracy: 0.9763 - val_loss: 0.0803
Epoch 2/5
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9751 - loss: 0.0809 - val_accuracy: 0.9794 - val_loss: 0.0699
Epoch 3/5
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 4ms/step - accuracy: 0.9832 - loss: 0.0559 - val_accuracy: 0.9814 - val_loss: 0.0599
Epoch 4/5
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 7s 4ms/step - accuracy: 0.9874 - loss: 0.0397 - val_accuracy: 0.9820 - val_loss: 0.0575
Epoch 5/5
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 10s 4ms/step - accuracy: 0.9902 - loss: 0.0306 - val_accuracy: 0.9883 - val_loss: 0.0419

精度・損失の推移グラフ

import matplotlib.pyplot as plt

for name, model in models.items():
    print(f"{name}: {results[name]}")

for name, history in histories.items():
    plt.plot(history.history['val_accuracy'], label=f'{name} val_accuracy')

plt.title('Validation Accuracy per Epoch')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.legend()
plt.grid(True)
plt.show()

for name, history in histories.items():
    plt.plot(history.history['val_loss'], label=f'{name} val_loss')

plt.title('Validation Loss per Epoch')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.grid(True)
plt.show()

最終結果の比較

検証精度とテスト精度

SimpleCNN: {'val_acc': 0.9851666688919067, 'test_acc': 0.9835000038146973}
MediumCNN: {'val_acc': 0.9889166951179504, 'test_acc': 0.9897000193595886}
DeepCNN: {'val_acc': 0.9882500171661377, 'test_acc': 0.9876000285148621}

精度グラフ

精度グラフ

損失グラフ

損失グラフ

考察

モデル構造の比較

モデル名 Conv層数 パラメータ数 Flatten直前の特徴量 全体サイズ
SimpleCNN 1 347,146(最大) 13×13×32 → 5,408 1.32MB
MediumCNN 2 121,930 5×5×64 → 1,600 476KB
DeepCNN 3 101,578(最小) 1×1×128 → 128 396KB

Conv層が増えることで特徴マップが圧縮され、全体のパラメータ数が減少しています。特にSimpleCNNはFlatten後の次元が大きく、Dense層に多くのパラメータを必要としています。

学習結果の比較(5エポック後)

モデル名 Train精度 Val精度 Val Loss
SimpleCNN 99.32% 98.52% 0.0575
MediumCNN 99.44% 98.89% 0.0421
DeepCNN 99.02% 98.83% 0.0419

結論:層を深くすると精度は上がる?

  • Conv層を適度に増やす(2~3層)ことで、精度・汎化性ともに向上。
  • 過度な深さは、情報の圧縮により学習が難しくなる可能性がある。
  • 本実験では、MediumCNN が最も良好な性能を発揮。

結論として、CNNは「深ければ良い」というわけではなく、データやモデル設計とのバランスが重要だとわかりました。

まとめ

  • 1層でも十分高い精度が出る(98%以上)
  • 2層に増やすとさらに精度が向上し、学習も安定
  • 3層では過学習の兆候があり、汎化性能は若干低下

今後は、ドロップアウトやバッチ正規化などのテクニックも交えて、深いモデルの過学習対策や最適化を検討していきたいと思います。

このブログを検索

自己紹介

はじめまして、機械学習を独学中のSHOU TAKEと申します。本ブログでは、Python・Keras・Google Colabを活用した画像分類やニューラルネットワークの実験記事を中心に発信しています。初学者の方にも分かりやすく、学んだことをそのまま実験形式でまとめるスタイルです。これまで取り組んだテーマには、学習率やOptimizerの比較、Batch Sizeの検証、事前学習の活用などがあります。ご質問やご感想は、お問い合わせフォームからお気軽にどうぞ。

お問い合わせフォーム

名前

メール *

メッセージ *

プライバシーポリシー

QooQ