はじめに
畳み込みニューラルネットワーク(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層では過学習の兆候があり、汎化性能は若干低下
今後は、ドロップアウトやバッチ正規化などのテクニックも交えて、深いモデルの過学習対策や最適化を検討していきたいと思います。
0 件のコメント:
コメントを投稿