Dense層のユニット数を変えると何が起きるか?CNNでの実験結果

投稿日:2026年2月27日金曜日 最終更新日:

Dense Google Colab Keras

X f B! P L
Dense層のユニット数を変えると何が起きるか?についてのサムネイル画像

CNNモデルを構築する際、Dense層のユニット数をどの程度に設定すべきかで悩むことは少なくありません。 ユニット数はモデルの表現力や過学習のしやすさに直結する一方で、 明確な正解が示されにくいハイパーパラメータのひとつです。

本記事では、畳み込み層(Conv層)の構成を固定したまま、 Dense層のユニット数のみを変更し、 分類精度や学習挙動にどのような違いが生じるのかを 実験的に検証します。

なぜDense層のユニット数が重要なのか

Dense層は、CNNによって抽出された特徴量を入力として、 最終的な分類判断を行う層です。 そのため、Dense層の設計はモデル全体の性能や汎化能力に大きな影響を与えます。

  • ユニット数が少なすぎる → 表現力が不足し、精度が伸びにくい
  • ユニット数が多すぎる → 過学習を招きやすく、計算コストも増大する

適切なユニット数はデータセットの規模や複雑さ、 およびモデル構造に依存するため、 実際に学習させて挙動を確認することが不可欠です。

実験条件

データセット

  • MNIST(手書き数字分類)

モデル構成(Dense層以外は固定)

model = tf.keras.Sequential([
    tf.keras.layers.Conv2D(32, (3,3), activation="relu", input_shape=(28,28,1)),
    tf.keras.layers.MaxPooling2D((2,2)),
    tf.keras.layers.Conv2D(64, (3,3), activation="relu"),
    tf.keras.layers.MaxPooling2D((2,2)),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(units, activation="relu"),
    tf.keras.layers.Dense(10, activation="softmax")
])

比較したDense層のユニット数

  • 64
  • 128
  • 256
  • 512

学習条件

  • Optimizer:Adam
  • Epoch:20
  • Batch size:32

実験コード

以下は、本記事で使用した実験用コードです。 TensorFlow(Keras)を用いて、Dense層のユニット数を変更したCNNモデルを構築・学習し、 分類精度への影響を比較します。 以降のコードでは、モデル定義・学習ループ・結果の集計までを順に示します。

import tensorflow as tf
from tensorflow.keras import layers, models
from tensorflow.keras.datasets import mnist
import matplotlib.pyplot as plt

# =========================
# Data preparation
# =========================
(x_train, y_train), (x_test, y_test) = 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]

# =========================
# Model construction function
# =========================
def build_model(dense_units):
    model = models.Sequential([
        layers.Input(shape=(28,28,1)),
        layers.Conv2D(32, (3,3), activation="relu"),
        layers.MaxPooling2D((2,2)),
        layers.Conv2D(64, (3,3), activation="relu"),
        layers.MaxPooling2D((2,2)),
        layers.Flatten(),
        layers.Dense(dense_units, activation="relu"),
        layers.Dense(10, activation="softmax")
    ])

    model.compile(
        optimizer="adam",
        loss="sparse_categorical_crossentropy",
        metrics=["accuracy"]
    )
    return model

# =========================
# Experiment configuration
# =========================
dense_units_list = [64, 128, 256, 512]
histories = {}
test_results = {}

# =========================
# Training loop
# =========================
for units in dense_units_list:
    print(f"\n=== Dense units: {units} ===")
    model = build_model(units)
    history = model.fit(
        x_train, y_train,
        validation_split=0.2,
        epochs=20,
        batch_size=32,
        verbose=1
    )
    histories[units] = history

    test_loss, test_acc = model.evaluate(x_test, y_test, verbose=0)
    test_results[units] = (test_loss, test_acc)

実行結果

学習結果をクリックして内容を開く
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11490434/11490434 ━━━━━━━━━━━━━━━━━━━━ 0s 0us/step

=== Dense units: 64 ===
Epoch 1/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 18s 8ms/step - accuracy: 0.8890 - loss: 0.3589 - val_accuracy: 0.9800 - val_loss: 0.0697
Epoch 2/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 11s 7ms/step - accuracy: 0.9832 - loss: 0.0546 - val_accuracy: 0.9833 - val_loss: 0.0555
Epoch 3/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 12s 8ms/step - accuracy: 0.9889 - loss: 0.0363 - val_accuracy: 0.9868 - val_loss: 0.0468
Epoch 4/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 10s 7ms/step - accuracy: 0.9921 - loss: 0.0254 - val_accuracy: 0.9875 - val_loss: 0.0429
Epoch 5/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 9s 6ms/step - accuracy: 0.9943 - loss: 0.0185 - val_accuracy: 0.9877 - val_loss: 0.0471
Epoch 6/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 12s 8ms/step - accuracy: 0.9948 - loss: 0.0155 - val_accuracy: 0.9864 - val_loss: 0.0513
Epoch 7/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 11s 7ms/step - accuracy: 0.9963 - loss: 0.0115 - val_accuracy: 0.9882 - val_loss: 0.0509
Epoch 8/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 9s 6ms/step - accuracy: 0.9968 - loss: 0.0102 - val_accuracy: 0.9888 - val_loss: 0.0440
Epoch 9/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 11s 7ms/step - accuracy: 0.9977 - loss: 0.0074 - val_accuracy: 0.9900 - val_loss: 0.0471
Epoch 10/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 18s 5ms/step - accuracy: 0.9981 - loss: 0.0058 - val_accuracy: 0.9881 - val_loss: 0.0565
Epoch 11/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 7s 3ms/step - accuracy: 0.9989 - loss: 0.0039 - val_accuracy: 0.9877 - val_loss: 0.0591
Epoch 12/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9981 - loss: 0.0060 - val_accuracy: 0.9897 - val_loss: 0.0480
Epoch 13/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 4ms/step - accuracy: 0.9985 - loss: 0.0043 - val_accuracy: 0.9883 - val_loss: 0.0616
Epoch 14/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9981 - loss: 0.0051 - val_accuracy: 0.9871 - val_loss: 0.0617
Epoch 15/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 4ms/step - accuracy: 0.9990 - loss: 0.0031 - val_accuracy: 0.9908 - val_loss: 0.0565
Epoch 16/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9988 - loss: 0.0033 - val_accuracy: 0.9893 - val_loss: 0.0690
Epoch 17/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9984 - loss: 0.0046 - val_accuracy: 0.9902 - val_loss: 0.0590
Epoch 18/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9985 - loss: 0.0039 - val_accuracy: 0.9900 - val_loss: 0.0670
Epoch 19/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 4ms/step - accuracy: 0.9988 - loss: 0.0036 - val_accuracy: 0.9874 - val_loss: 0.0774
Epoch 20/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9988 - loss: 0.0039 - val_accuracy: 0.9896 - val_loss: 0.0693

=== Dense units: 128 ===
Epoch 1/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 13s 7ms/step - accuracy: 0.8997 - loss: 0.3250 - val_accuracy: 0.9835 - val_loss: 0.0528
Epoch 2/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9858 - loss: 0.0478 - val_accuracy: 0.9887 - val_loss: 0.0405
Epoch 3/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9908 - loss: 0.0291 - val_accuracy: 0.9893 - val_loss: 0.0355
Epoch 4/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9931 - loss: 0.0222 - val_accuracy: 0.9883 - val_loss: 0.0426
Epoch 5/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9956 - loss: 0.0137 - val_accuracy: 0.9897 - val_loss: 0.0370
Epoch 6/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9956 - loss: 0.0125 - val_accuracy: 0.9921 - val_loss: 0.0351
Epoch 7/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9965 - loss: 0.0106 - val_accuracy: 0.9905 - val_loss: 0.0370
Epoch 8/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9974 - loss: 0.0070 - val_accuracy: 0.9883 - val_loss: 0.0531
Epoch 9/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9977 - loss: 0.0067 - val_accuracy: 0.9916 - val_loss: 0.0351
Epoch 10/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9990 - loss: 0.0036 - val_accuracy: 0.9892 - val_loss: 0.0486
Epoch 11/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9981 - loss: 0.0053 - val_accuracy: 0.9909 - val_loss: 0.0535
Epoch 12/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9986 - loss: 0.0048 - val_accuracy: 0.9906 - val_loss: 0.0544
Epoch 13/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9987 - loss: 0.0037 - val_accuracy: 0.9886 - val_loss: 0.0599
Epoch 14/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 4ms/step - accuracy: 0.9987 - loss: 0.0049 - val_accuracy: 0.9881 - val_loss: 0.0597
Epoch 15/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9988 - loss: 0.0038 - val_accuracy: 0.9914 - val_loss: 0.0479
Epoch 16/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9984 - loss: 0.0054 - val_accuracy: 0.9891 - val_loss: 0.0587
Epoch 17/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9995 - loss: 0.0015 - val_accuracy: 0.9895 - val_loss: 0.0646
Epoch 18/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9972 - loss: 0.0079 - val_accuracy: 0.9907 - val_loss: 0.0576
Epoch 19/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9991 - loss: 0.0023 - val_accuracy: 0.9893 - val_loss: 0.0735
Epoch 20/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9991 - loss: 0.0028 - val_accuracy: 0.9916 - val_loss: 0.0562

=== Dense units: 256 ===
Epoch 1/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 8s 4ms/step - accuracy: 0.9093 - loss: 0.2855 - val_accuracy: 0.9824 - val_loss: 0.0568
Epoch 2/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 4ms/step - accuracy: 0.9862 - loss: 0.0425 - val_accuracy: 0.9890 - val_loss: 0.0391
Epoch 3/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9914 - loss: 0.0268 - val_accuracy: 0.9843 - val_loss: 0.0626
Epoch 4/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9929 - loss: 0.0220 - val_accuracy: 0.9902 - val_loss: 0.0380
Epoch 5/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9959 - loss: 0.0122 - val_accuracy: 0.9898 - val_loss: 0.0371
Epoch 6/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9967 - loss: 0.0090 - val_accuracy: 0.9898 - val_loss: 0.0403
Epoch 7/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9977 - loss: 0.0070 - val_accuracy: 0.9898 - val_loss: 0.0418
Epoch 8/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9978 - loss: 0.0065 - val_accuracy: 0.9908 - val_loss: 0.0452
Epoch 9/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9974 - loss: 0.0082 - val_accuracy: 0.9908 - val_loss: 0.0464
Epoch 10/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9982 - loss: 0.0049 - val_accuracy: 0.9916 - val_loss: 0.0539
Epoch 11/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 4ms/step - accuracy: 0.9976 - loss: 0.0081 - val_accuracy: 0.9889 - val_loss: 0.0551
Epoch 12/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9986 - loss: 0.0043 - val_accuracy: 0.9898 - val_loss: 0.0589
Epoch 13/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 4ms/step - accuracy: 0.9985 - loss: 0.0048 - val_accuracy: 0.9911 - val_loss: 0.0526
Epoch 14/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9991 - loss: 0.0030 - val_accuracy: 0.9904 - val_loss: 0.0650
Epoch 15/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9984 - loss: 0.0059 - val_accuracy: 0.9908 - val_loss: 0.0576
Epoch 16/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9989 - loss: 0.0038 - val_accuracy: 0.9899 - val_loss: 0.0630
Epoch 17/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9985 - loss: 0.0053 - val_accuracy: 0.9918 - val_loss: 0.0512
Epoch 18/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9991 - loss: 0.0028 - val_accuracy: 0.9913 - val_loss: 0.0668
Epoch 19/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9994 - loss: 0.0018 - val_accuracy: 0.9911 - val_loss: 0.0616
Epoch 20/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.9987 - loss: 0.0036 - val_accuracy: 0.9911 - val_loss: 0.0673

=== Dense units: 512 ===
Epoch 1/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 9s 4ms/step - accuracy: 0.9121 - loss: 0.2877 - val_accuracy: 0.9830 - val_loss: 0.0554
Epoch 2/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9871 - loss: 0.0419 - val_accuracy: 0.9877 - val_loss: 0.0409
Epoch 3/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9928 - loss: 0.0241 - val_accuracy: 0.9895 - val_loss: 0.0369
Epoch 4/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 4ms/step - accuracy: 0.9942 - loss: 0.0170 - val_accuracy: 0.9878 - val_loss: 0.0422
Epoch 5/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9955 - loss: 0.0142 - val_accuracy: 0.9902 - val_loss: 0.0382
Epoch 6/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 4ms/step - accuracy: 0.9960 - loss: 0.0117 - val_accuracy: 0.9855 - val_loss: 0.0604
Epoch 7/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9967 - loss: 0.0096 - val_accuracy: 0.9912 - val_loss: 0.0348
Epoch 8/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 4ms/step - accuracy: 0.9985 - loss: 0.0043 - val_accuracy: 0.9893 - val_loss: 0.0462
Epoch 9/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9985 - loss: 0.0048 - val_accuracy: 0.9905 - val_loss: 0.0478
Epoch 10/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 4ms/step - accuracy: 0.9985 - loss: 0.0044 - val_accuracy: 0.9906 - val_loss: 0.0474
Epoch 11/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9985 - loss: 0.0041 - val_accuracy: 0.9905 - val_loss: 0.0583
Epoch 12/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 10s 4ms/step - accuracy: 0.9980 - loss: 0.0051 - val_accuracy: 0.9882 - val_loss: 0.0807
Epoch 13/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9986 - loss: 0.0042 - val_accuracy: 0.9906 - val_loss: 0.0508
Epoch 14/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9982 - loss: 0.0052 - val_accuracy: 0.9896 - val_loss: 0.0559
Epoch 15/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9996 - loss: 0.0012 - val_accuracy: 0.9873 - val_loss: 0.0695
Epoch 16/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 4ms/step - accuracy: 0.9984 - loss: 0.0057 - val_accuracy: 0.9878 - val_loss: 0.0839
Epoch 17/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9990 - loss: 0.0033 - val_accuracy: 0.9925 - val_loss: 0.0662
Epoch 18/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 4ms/step - accuracy: 0.9986 - loss: 0.0038 - val_accuracy: 0.9906 - val_loss: 0.0597
Epoch 19/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 6s 4ms/step - accuracy: 0.9991 - loss: 0.0034 - val_accuracy: 0.9912 - val_loss: 0.0592
Epoch 20/20
1500/1500 ━━━━━━━━━━━━━━━━━━━━ 5s 4ms/step - accuracy: 0.9999 - loss: 5.1035e-04 - val_accuracy: 0.9904 - val_loss: 0.0811

ユニット数別の学習結果

以下のコードにて学習結果を可視化します。

# ====== 最終結果出力 ======
print("\n===== 最終結果サマリー =====")
print(f"{'Dense units':>12} | {'Train Acc':>10} | {'Val Acc':>8} | {'Test Acc':>9}")
print("-" * 60)

for units in sorted(histories.keys()):
    history = histories[units]
    test_loss, test_acc = test_results[units]

    train_acc = history.history["accuracy"][-1]
    val_acc = history.history["val_accuracy"][-1]

    print(f"{units:>12} | {train_acc:>10.4f} | {val_acc:>8.4f} | {test_acc:>9.4f}")

print("-" * 60)


plt.figure(figsize=(12,5))

# Accuracy
plt.subplot(1,2,1)
for units, history in histories.items():
    plt.plot(history.history["val_accuracy"], label=f"{units}")
plt.title("Validation Accuracy")
plt.xlabel("Epoch")
plt.ylabel("Accuracy")
plt.legend(title="Dense units")

# Loss
plt.subplot(1,2,2)
for units, history in histories.items():
    plt.plot(history.history["val_loss"], label=f"{units}")
plt.title("Validation Loss")
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.legend(title="Dense units")

plt.tight_layout()
plt.show()

実行結果

===== 最終結果サマリー =====
 Dense units |  Train Acc |  Val Acc |  Test Acc
------------------------------------------------------------
          64 |     0.9988 |   0.9896 |    0.9906
         128 |     0.9988 |   0.9916 |    0.9912
         256 |     0.9987 |   0.9911 |    0.9919
         512 |     0.9996 |   0.9904 |    0.9890
------------------------------------------------------------

精度グラフ

精度グラフの比較

損失グラフ

損失グラフの比較

Denseユニット数ごとの最終結果まとめ

Dense units Train Accuracy Validation Accuracy Test Accuracy
64 0.9988 0.9896 0.9906
128 0.9988 0.9916 0.9912
256 0.9987 0.9911 0.9919
512 0.9996 0.9904 0.9890

なぜ Dense=256 付近で性能が頭打ちになるのか?

今回の実験では、Denseユニット数を増やすにつれて 訓練精度(train accuracy)は一貫して向上しました。 これは、ユニット数の増加によりモデルの表現力が高まったためです。

一方で、検証精度およびテスト精度に注目すると、 128〜256ユニット付近で性能が頭打ちとなり、 512ユニットではテスト精度がわずかに低下する結果となりました。

CNNにおいて画像の特徴抽出を担うのは主に畳み込み層(Conv層)であり、 Dense層はそれらの特徴を用いて最終的な分類を行う役割を持ちます。

Dense層のユニット数を過剰に増やすと、 必要以上に複雑な決定境界を学習してしまい、 訓練データにはよく適合する一方で、 未知データに対する汎化性能が低下しやすくなります。

今回の結果から、MNISTのような比較的単純な画像分類タスクでは、 Dense層は「大きくしすぎない」ことが重要であり、 128〜256ユニット程度が精度と汎化性能のバランスが取れた構成であることが分かります。

過学習の出方の違い

Dense層のユニット数を増やすほど、以下の傾向が見られました。

  • train accuracy は速く向上する
  • val loss が途中から上昇しやすい

Flatten直後のDense層はパラメータ数が急増するため、 過学習の影響が特に顕著になります。

実務での目安と注意点

  • MNIST程度の小規模画像分類では Dense 128〜256 が目安
  • ユニット数を増やす前に以下を検討する
    • Dropoutの導入
    • データ拡張

Dense層は「多ければ良い」わけではなく、 精度と過学習のバランスが重要です。

まとめ

本記事では、CNNにおけるDense層のユニット数を変更した場合の影響について、 MNISTデータセットを用いた実験結果をもとに比較・検証しました。

実験から、以下の点が確認できました。

  • Denseユニット数を増やすと、学習の収束はやや速くなる
  • 一方で、検証精度(val_accuracy)の向上は限定的
  • ユニット数が多いほど、検証損失が不安定になり、過学習の兆候が現れやすい
  • MNIST規模のタスクでは、Dense 64〜128ユニットでも十分な性能が得られる

これらの結果から、

CNNにおいては、特徴抽出の主役は畳み込み層であり、
Dense層は必要最小限の分類器として設計するのが合理的です。

Dense層を大きくすれば精度が必ず向上するわけではなく、 モデル容量の増加は過学習リスクとのトレードオフになる点に注意が必要です。

次の検証としては、

  • Dense層にDropoutを追加した場合の比較
  • CIFAR-10など、より複雑なデータセットでの再実験

を行うことで、Dense層設計に対する理解をさらに深めることができます。

次に読むと理解が深まる記事