はじめに
畳み込みニューラルネットワーク(CNN)は、画像分類や物体検出など、多くの画像処理タスクで高い性能を発揮する深層学習モデルです。
一般的に、「ネットワークを深くするほど精度が上がる」と考えられがちですが、実際はどうなのでしょうか?
この記事では、手書き数字データセット「MNIST」を用いて、CNNの層の深さ(1層・2層・3層)によって、モデルの性能や学習挙動がどのように変化するかを検証します。
実験概要
今回の実験では、以下のような設定でCNNモデルを構築し、比較を行います。
- 使用データセット:MNIST(0〜9の手書き数字画像)
- モデル構成:畳み込み層とプーリング層を1層・2層・3層と段階的に増加
- 評価指標:検証精度(Validation Accuracy)、テスト精度、損失(Loss)、過学習の傾向
ネットワークを深くすると本当に精度が上がるのか、どの程度までが最適なのかを、具体的な学習ログとグラフで解説していきます。
モデル構成
本実験では、CNNの層の深さを変化させた3つのモデルを用意しました。
それぞれ、畳み込み層(Conv2D)とプーリング層(MaxPooling2D)の数が異なり、浅い構造から深い構造まで段階的に比較します。
1層構成:SimpleCNN
もっともシンプルな構造で、畳み込み層が1層だけのモデルです。
入力画像から直接特徴を抽出し、全結合層に渡して分類を行います。
Conv2D(32, kernel_size=3, activation='relu')
MaxPooling2D(pool_size=2)
Flatten()
Dense(64, activation='relu')
Dense(10, activation='softmax')
1層のみの構成でも、MNISTのような単純な画像データでは高い精度が期待されますが、表現力には限界があります。
2層構成:MediumCNN
中間構成として、畳み込み層を2層に増やしたモデルです。
1層目で抽出した特徴をさらに深く解析することで、より複雑なパターンの認識が可能になります。
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層構成:DeepCNN
最も深い構成で、畳み込み層が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データセットの読み込みと前処理
今回の実験では、手書き数字認識で広く使われている MNISTデータセット を使用します。
このデータは、28×28ピクセルのグレースケール画像と、それに対応する0〜9のラベルで構成されています。
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]
このように前処理を行うことで、畳み込みニューラルネットワーク(CNN)に適した形式でデータを準備できます。
CNNモデル構築関数の定義
異なる層構成を持つモデルを共通の関数で生成できるように、モデル構築処理を関数化します。
これにより、層の深さを変えるだけで再利用でき、実験効率も向上します。
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
この関数では、conv_filters
に指定されたフィルター数のリストに従って畳み込み層を追加し、dense_units
で全結合層のユニット数を制御します。
各モデルの学習と評価手順
次に、構築した各CNNモデル(1層・2層・3層)を訓練し、検証精度およびテスト精度を比較します。
最適化には Adam を用い、損失関数には SparseCategoricalCrossentropy を使用します。
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
}
検証用データとして訓練データの20%を分割し、エポック数は5に統一しています。
訓練のたびにログを出力し、最後にテストデータで汎化性能を確認しています。
学習ログと各モデルの評価結果
各モデルの学習ログと最終結果です。
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()
このグラフから、全モデルともエポック数の増加とともに精度が向上していることがわかります。
特にMediumCNNは、安定して高い検証精度を記録しており、層の深さと性能のバランスが最も良好であることが視覚的に確認できます。
損失のグラフでは、SimpleCNNはやや高めのval_lossを示し、DeepCNNは途中まで高い値を示していますが、後半で急激に改善しています。
MediumCNNは常に安定しており、損失の観点からも優れた学習挙動を示しています。
精度グラフ
損失グラフ
最終結果の比較:CNNの層の深さと精度の関係
検証精度(Validation Accuracy)とテスト精度(Test Accuracy)の比較
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}
この比較から、中間的な深さを持つMediumCNN(2層構成)が、最も高い検証精度およびテスト精度を記録しました。
深いモデル(DeepCNN)はFlatten前の特徴量が非常に少なくなりすぎて、学習がやや不安定になる傾向が見られます。
考察:層の深さと学習性能の関係性
モデル構造ごとの比較表
モデル名 | 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層の次元数が非常に大きいため、全結合層に多くのメモリと計算リソースを消費します。
DeepCNNは非常にコンパクトですが、特徴量の圧縮が極端で情報損失のリスクがあります。
学習結果の定量的な比較(5エポック後)
モデル名 | Train精度 | Val精度 | Val損失 |
---|---|---|---|
SimpleCNN | 99.32% | 98.52% | 0.0575 |
MediumCNN | 99.44% | 98.89% | 0.0421 |
DeepCNN | 99.02% | 98.83% | 0.0419 |
Train精度は全モデルとも非常に高く、過学習の兆候は限定的ですが、MediumCNNが最も安定した結果を示しました。
DeepCNNは損失が最も低いものの、精度面ではわずかに劣っており、バランスの取れた設計が重要であることがわかります。
結論:CNNは層を深くすれば精度が上がるのか?
- Conv層を2層に増やすことで、精度・損失ともに改善される。
- 3層構成(DeepCNN)では過学習や学習の不安定さが発生しやすい。
- 最適な層の深さは、データセットやモデル設計に依存。
本実験では、2層構成のMediumCNNが最も良好な性能を発揮しました。
深ければ良いというわけではなく、「適切な深さ」で構築することが重要です。
まとめ:CNNの深さと性能の最適バランスとは?
- 1層構成(SimpleCNN)でも98%以上の精度が得られる。
- 2層構成(MediumCNN)は、精度・損失・学習安定性のバランスが最も良い。
- 3層構成(DeepCNN)は、情報圧縮が過剰になり、汎化性能がやや低下。
今後は、ドロップアウトやバッチ正規化といった手法を取り入れることで、より深いモデルの安定性向上と過学習対策が可能です。
また、より大規模なデータセット(例:CIFAR-10やImageNet)でも、層の深さによる影響を再検証していきたいと考えています。
0 件のコメント:
コメントを投稿