少ない学習データでも画像分類はできる?──MNISTで「各クラス10枚」を使って検証した結果
画像データが十分に集められないとき、「少ない学習データでもモデルは作れるの?」という疑問は多くの方が持つと思います。 本記事では、MNIST を使って 各クラス 10 枚(合計 100 枚)だけでどこまで画像分類が可能なのか、実際に検証した結果をまとめます。
この記事で分かること
- 学習データが極端に少ない場合でも画像分類は可能か?
- 精度はどこまで出るのか?
- 精度を上げるために必要なテクニックは?
- データ拡張(Augmentation)の効果はどれくらい?
実験条件:各クラス10枚の“超小規模データセット”
MNIST(手書き数字データセット)から、ランダムに各クラス 10 枚ずつサンプリングし、 合計 100 枚だけでモデルを学習させました。
使用したもの
- データ:MNIST
- 学習データ:各クラス 10 枚(合計100枚)
- モデル:全結合ニューラルネットワーク (MLP)
- エポック:30
- フレームワーク:TensorFlow/Keras
- 学習環境:Google Colab + GPU
Google DriveをColabにマウント
まず、Google Drive を Colab に接続します👇from google.colab import drive
drive.mount('/content/drive')
すると、認証リンクが出るので、指示に従って認証してください。マウントが完了すると、Drive のファイルが /content/drive/MyDrive/ 以下に見えるようになります。
データ拡張
1枚の画像から10通り以上のパターンが生成されます。
from tensorflow.keras.preprocessing.image import ImageDataGenerator
# データ拡張の設定
datagen = ImageDataGenerator(
rescale=1./255,
preprocessing_function=lambda x: 1.0 - x,
rotation_range=5,
width_shift_range=0.05,
height_shift_range=0.05,
zoom_range=0.1,
shear_range=0.1,
validation_split=0.2
)
train_generator = datagen.flow_from_directory(
'/content/drive/MyDrive/Colab/digits',
target_size=(28, 28),
color_mode='grayscale',
batch_size=32,
class_mode='sparse',
subset='training',
shuffle=True
)
val_generator = datagen.flow_from_directory(
'/content/drive/MyDrive/Colab/digits',
target_size=(28, 28),
color_mode='grayscale',
batch_size=32,
class_mode='sparse',
subset='validation'
)
画像の確認
取り込んでいる画像の確認を行います。
import matplotlib.pyplot as plt
import numpy as np
# 1枚画像を取り出して拡張
x_batch, y_batch = next(train_generator)
# 最初の8枚を表示
plt.figure(figsize=(8, 4))
for i in range(8):
plt.subplot(2, 4, i + 1)
plt.imshow(x_batch[i].reshape(28, 28), cmap="gray")
plt.axis("off")
plt.show()
表示される画像
学習
モデルはKerasのSequential APIでシンプルに構築しました。
入力画像を1次元に変換(Flatten)し、128ユニットの全結合層とDropout層を通じて過学習を防止、最後に10クラスのsoftmaxで分類する構成です。
import tensorflow as tf
from tensorflow import keras
model = keras.models.Sequential([
keras.layers.Input(shape=(28, 28, 1)),
keras.layers.Flatten(),
keras.layers.Dense(128, activation='relu'),
keras.layers.Dropout(0.3),
keras.layers.Dense(10, activation='softmax')
])
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
# 学習
history = model.fit(train_generator,epochs=30,validation_data=val_generator)
簡素な構成ですが、過学習対策や損失関数の工夫も取り入れています。
それでも、データが少なすぎると限界があります。
実験結果1:全結合ニューラルネットワーク (MLP)での検証
データ拡張のおかげで、当初のランダム推測(10%前後)から50%まで精度が改善しました。
/usr/local/lib/python3.12/dist-packages/keras/src/trainers/data_adapters/py_dataset_adapter.py:121: UserWarning: Your `PyDataset` class should call `super().__init__(**kwargs)` in its constructor. `**kwargs` can include `workers`, `use_multiprocessing`, `max_queue_size`. Do not pass these arguments to `fit()`, as they will be ignored.
self._warn_if_super_not_called()
Epoch 1/30
3/3 ━━━━━━━━━━━━━━━━━━━━ 22s 10s/step - accuracy: 0.0573 - loss: 2.9963 - val_accuracy: 0.0000e+00 - val_loss: 2.5600
Epoch 2/30
3/3 ━━━━━━━━━━━━━━━━━━━━ 0s 142ms/step - accuracy: 0.0708 - loss: 2.6698 - val_accuracy: 0.2000 - val_loss: 2.2692
Epoch 3/30
3/3 ━━━━━━━━━━━━━━━━━━━━ 0s 115ms/step - accuracy: 0.1516 - loss: 2.3662 - val_accuracy: 0.1500 - val_loss: 2.3154
Epoch 4/30
3/3 ━━━━━━━━━━━━━━━━━━━━ 0s 120ms/step - accuracy: 0.2141 - loss: 2.1787 - val_accuracy: 0.3000 - val_loss: 2.2103
・・・(中略)・・・
Epoch 25/30
3/3 ━━━━━━━━━━━━━━━━━━━━ 0s 145ms/step - accuracy: 0.5792 - loss: 1.4292 - val_accuracy: 0.5000 - val_loss: 1.5111
Epoch 26/30
3/3 ━━━━━━━━━━━━━━━━━━━━ 0s 145ms/step - accuracy: 0.6094 - loss: 1.3204 - val_accuracy: 0.5000 - val_loss: 1.5197
Epoch 27/30
3/3 ━━━━━━━━━━━━━━━━━━━━ 0s 115ms/step - accuracy: 0.6398 - loss: 1.3897 - val_accuracy: 0.4500 - val_loss: 1.5253
Epoch 28/30
3/3 ━━━━━━━━━━━━━━━━━━━━ 0s 123ms/step - accuracy: 0.6062 - loss: 1.3865 - val_accuracy: 0.6000 - val_loss: 1.5247
Epoch 29/30
3/3 ━━━━━━━━━━━━━━━━━━━━ 0s 142ms/step - accuracy: 0.7708 - loss: 1.0637 - val_accuracy: 0.5500 - val_loss: 1.4967
Epoch 30/30
3/3 ━━━━━━━━━━━━━━━━━━━━ 0s 138ms/step - accuracy: 0.6187 - loss: 1.2483 - val_accuracy: 0.5000 - val_loss: 1.4303
/usr/local/lib/python3.12/dist-packages/keras/src/trainers/data_adapters/py_dataset_adapter.py:121: UserWarning: Your `PyDataset` class should call `super().__init__(**kwargs)` in its constructor. `**kwargs` can include `workers`, `use_multiprocessing`, `max_queue_size`. Do not pass these arguments to `fit()`, as they will be ignored.
self._warn_if_super_not_called()
この警告は Keras の内部仕様によるもので、通常の学習では無視して構いません。
カスタム Dataset を使っていない場合、特に対応は不要です。
学習グラフ
# 精度をプロット
plt.plot(history.history['accuracy'], label='Train Accuracy')
plt.plot(history.history['val_accuracy'], label='Val Accuracy')
plt.title('Model Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.legend()
plt.show()
# 損失をプロット
plt.plot(history.history['loss'], label='Train Loss')
plt.plot(history.history['val_loss'], label='Val Loss')
plt.title('Model Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.show()
精度グラフ
損失グラフ
実験結果2:簡易CNNモデルでの検証と考察
モデルを以下のCNN二変更した場合の実験結果
model = keras.models.Sequential([
keras.layers.Input(shape=(28, 28, 1)),
keras.layers.Conv2D(16, (3, 3), activation='relu'),
keras.layers.MaxPooling2D((2, 2)),
keras.layers.Flatten(),
keras.layers.Dense(64, activation='relu'),
keras.layers.Dropout(0.3),
keras.layers.Dense(10, activation='softmax')
])
実行結果
Epoch 1/30 3/3 ━━━━━━━━━━━━━━━━━━━━ 22s 6s/step - accuracy: 0.0818 - loss: 2.4731 - val_accuracy: 0.1000 - val_loss: 2.3169 Epoch 2/30 3/3 ━━━━━━━━━━━━━━━━━━━━ 0s 148ms/step - accuracy: 0.1354 - loss: 2.3902 - val_accuracy: 0.1000 - val_loss: 2.3199 Epoch 3/30 3/3 ━━━━━━━━━━━━━━━━━━━━ 1s 151ms/step - accuracy: 0.1203 - loss: 2.3777 - val_accuracy: 0.2000 - val_loss: 2.2901 Epoch 4/30 3/3 ━━━━━━━━━━━━━━━━━━━━ 0s 115ms/step - accuracy: 0.1187 - loss: 2.3126 - val_accuracy: 0.3000 - val_loss: 2.2742 Epoch 5/30 3/3 ━━━━━━━━━━━━━━━━━━━━ 0s 114ms/step - accuracy: 0.1771 - loss: 2.2499 - val_accuracy: 0.1500 - val_loss: 2.2342 ・・・(中略)・・・ Epoch 26/30 3/3 ━━━━━━━━━━━━━━━━━━━━ 0s 118ms/step - accuracy: 0.6359 - loss: 1.4574 - val_accuracy: 0.6000 - val_loss: 1.6175 Epoch 27/30 3/3 ━━━━━━━━━━━━━━━━━━━━ 0s 110ms/step - accuracy: 0.4682 - loss: 1.4875 - val_accuracy: 0.6000 - val_loss: 1.6022 Epoch 28/30 3/3 ━━━━━━━━━━━━━━━━━━━━ 0s 111ms/step - accuracy: 0.5292 - loss: 1.4663 - val_accuracy: 0.8000 - val_loss: 1.5215 Epoch 29/30 3/3 ━━━━━━━━━━━━━━━━━━━━ 0s 124ms/step - accuracy: 0.5867 - loss: 1.4288 - val_accuracy: 0.7000 - val_loss: 1.5208 Epoch 30/30 3/3 ━━━━━━━━━━━━━━━━━━━━ 0s 114ms/step - accuracy: 0.6187 - loss: 1.2982 - val_accuracy: 0.6500 - val_loss: 1.4946
精度グラフ
損失グラフ
考察
少量データにおいては、データ拡張の効果がモデル構造(MLP vs CNN)の違いよりも圧倒的に大きいことが示された。 いずれのモデルでも、データ拡張のおかげでランダム推測(10%)を脱却し、最大80%の精度(val_accuracy: 0.8000)を達成できた
精度が上がらない原因
- 学習データの多様性が不足している
- 同じ数字でも書き方が違うため過学習しやすい
- テストデータと分布が一致していない
対策1:データ拡張(Augmentation)は非常に有効
今回は data augmentation を追加して再学習しました。 すると、データが増えたわけではないにもかかわらず、 精度は 5〜15%ほど改善 する傾向が見られました。
追加した Augmentation
- 軽い回転(±10度)
- わずかな平行移動
- 拡大・縮小
対策2:モデルを小さくして過学習を抑える
データが少ない時は、大きなモデルほど過学習しやすいため逆効果です。 Dense層を削る・Conv層を減らすなど、シンプルなモデルの方が安定しました。
対策3:正則化(Dropout・L2)
Dropout(0.3〜0.5)を挟むことで、汎化性能がやや向上。 データ不足時は特に有効でした。
実験結果の比較と考察
2つの実験から、次の重要な知見が得られました。
- データ拡張の圧倒的な効果: データ拡張が適用されたおかげで、MLP(最高50%)およびCNN(最高80%)という、ランダム推測(10%)を大きく超える精度を達成できました。少量データ学習におけるデータ拡張の有効性は疑いようがありません。
- CNNの不安定性: 簡易CNNモデルは一時的に最高80%という高い精度を記録しましたが、その後の学習曲線(損失グラフ)は非常に激しく変動しており、モデルが不安定であることがわかります。これは、データ拡張を用いても、100枚というデータ量ではモデルが過学習と汎化の狭間で揺れ動いていることを示しています。
まとめ:少量データ学習の知見と限界
- データ拡張は少量データ学習の生命線であり、ランダム推測(10%)から実用的な精度(MLP: 50%〜60%、CNN: 最高80%)への改善に不可欠。
- CNNはMLPよりもわずかに高い精度(最高80%)を達成したが、その学習は非常に不安定であった(グラフ参照)
- CNNはMLPより高い精度を出す可能性があるが、その学習は不安定であり、過学習対策(モデルの小型化、Dropoutなど)が必須。
- 結論: 100枚という極端なデータ量では、精度を安定させることに限界がある。
次のステップ:実務的な解決策「転移学習」
今回の実験でデータ拡張の限界が見えたように、実際の業務で安定した高精度モデルを構築するには、転移学習 (Transfer Learning)の活用が不可欠です。
VGGやResNetなどの事前学習済みモデルの重みを利用し、少量の独自データで最終層だけを再学習するファインチューニングこそが、 少ないデータで最も高い精度と安定性を出すための決定版と言えます。






0 件のコメント:
コメントを投稿