🧠 STEP 8: 初めてのニューラルネットワーク実装
MNISTデータセットで手書き数字認識を実装しよう
📋 このステップで学ぶこと
- MNISTデータセットとは?読み込み方法
- データの前処理(正規化、平坦化、one-hot化)
- シンプルなMLP(多層パーセプトロン)の構築
- モデルの訓練と学習曲線の可視化
- テストデータでの評価と予測結果の確認
🎯 1. MNISTデータセットとは?
1-1. MNISTの概要
MNIST(エムニスト)は、手書き数字(0〜9)の画像データセットです。
機械学習・ディープラーニングの「Hello World」として世界中で使われています。
- 画像サイズ:28×28ピクセル(グレースケール)
- 訓練データ:60,000枚
- テストデータ:10,000枚
- クラス数:10クラス(0〜9の数字)
- ピクセル値:0〜255(0=黒、255=白)
以下の図は横スクロールできます。
1-2. なぜMNISTを使うのか?
- ダウンロード不要:Kerasに組み込まれており、1行で読み込める
- サイズが小さい:学習が速く、CPUでも数分で完了
- 視覚的にわかりやすい:結果が正しいか目で確認できる
- 適度な難しさ:簡単すぎず、難しすぎない
- 定番:チュートリアルや論文で広く使われている
1-3. 手書き数字認識の目標
入力: 28×28ピクセルの画像(784個の数値)
出力: 0〜9のどの数字か(10クラス分類)
例: 「5」の画像を入力 → モデルが「5」と予測
📥 2. データの読み込みと確認
2-1. MNISTのダウンロードと読み込み
keras.datasets.mnist.load_data()
MNISTデータセットを自動でダウンロードして読み込みます。
初回実行時のみダウンロード(約11MB)が行われ、2回目以降はキャッシュから読み込みます。
(X_train, y_train), (X_test, y_test)
データは「訓練用」と「テスト用」に分かれて返されます。
X = 画像データ、y = ラベル(正解)
===== データの形状 ===== 訓練データの形状: (60000, 28, 28) 訓練ラベルの形状: (60000,) テストデータの形状: (10000, 28, 28) テストラベルの形状: (10000,)
X_train.shape = (60000, 28, 28)
↑ 60000枚の画像、各画像は28×28ピクセル
y_train.shape = (60000,)
↑ 60000個のラベル(各画像に対応する正解)
2-2. データの可視化
実際にどんな画像が入っているか、目で確認してみましょう。
plt.figure(figsize=(10, 10))
グラフの大きさを10×10インチに設定
plt.subplot(5, 5, i + 1)
5行5列のグリッドで、i+1番目の位置にプロット
plt.imshow(X_train[i], cmap=’gray’)
i番目の画像をグレースケールで表示
plt.xlabel(f”Label: {y_train[i]}”)
その画像の正解ラベルを表示
2-3. 1枚の画像を詳しく見る
===== 最初の画像の情報 ===== ピクセル値の範囲: 0 〜 255 ラベル(正解): 5 画像の左上5×5ピクセル: [[ 0 0 0 0 0] [ 0 0 0 0 0] [ 0 0 0 0 0] [ 0 0 0 0 0] [ 0 0 0 0 0]]
・0 = 完全な黒(背景)
・255 = 完全な白(数字の部分)
・1〜254 = グレー(境界部分)
左上は背景なので0が並んでいます。
🔧 3. データの前処理
3-1. なぜ前処理が必要なのか?
ピクセル値が0〜255のままだと、以下の問題が起きます:
- 値の範囲が大きすぎる:学習が不安定になる
- 活性化関数に不適:Sigmoid、ReLUなどの入力として大きすぎる
- 勾配の問題:勾配が爆発または消失しやすい
3-2. 前処理の全体像
以下の図は横スクロールできます。
3-3. 前処理1: 正規化(0〜1に変換)
astype(‘float32’)
整数型(int)を浮動小数点型(float32)に変換します。
割り算の結果を正確に保存するために必要です。
/ 255.0
全ピクセル値を255で割ることで、0〜1の範囲に正規化します。
例: 128 ÷ 255 = 0.502…
===== 正規化後 ===== 値の範囲: 0.0 〜 1.0 データ型: float32
3-4. 前処理2: 平坦化(2次元→1次元)
Denseレイヤー(全結合層)は1次元の入力を期待します。
28×28の2次元配列を、784次元の1次元ベクトルに変換する必要があります。
reshape(-1, 28 * 28)
-1:「自動計算」の意味。サンプル数は自動で60000になる
28 * 28:784次元のベクトルに変換
===== 平坦化後 ===== 訓練データの形状: (60000, 784) テストデータの形状: (10000, 784)
元の形状: (60000, 28, 28)
↓ reshape(-1, 784)
変換後: (60000, 784)
28 × 28 = 784
各画像が784次元のベクトルになりました!
3-5. 前処理3: One-hotエンコーディング
ラベルを数字のまま使うと、「9は0より大きい」という誤った順序関係を学習してしまいます。
One-hot化すると、各クラスが独立した情報として扱われます。
from tensorflow.keras.utils import to_categorical
One-hot変換用の関数をインポート
to_categorical(y_train, 10)
ラベルを10クラスのOne-hotベクトルに変換
第2引数の10はクラス数(0〜9の10種類)
===== One-hot化後 ===== 変換前のラベル(最初の5個): [5 0 4 1 9] 変換後の形状: (60000, 10) 変換後のラベル(最初の5個): [[0. 0. 0. 0. 0. 1. 0. 0. 0. 0.] ← 5 [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.] ← 0 [0. 0. 0. 0. 1. 0. 0. 0. 0. 0.] ← 4 [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.] ← 1 [0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]] ← 9
インデックス: 0 1 2 3 4 5 6 7 8 9
ラベル0 → [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
ラベル3 → [0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
ラベル5 → [0, 0, 0, 0, 0, 1, 0, 0, 0, 0]
ラベル9 → [0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
→ 該当するインデックスだけが1、他は0
- 正規化:0〜255 → 0〜1(学習を安定させる)
- 平坦化:(28, 28) → (784,)(Denseレイヤー用)
- One-hot化:5 → [0,0,0,0,0,1,0,0,0,0](クラスを独立に)
🏗️ 4. モデルの構築
4-1. モデル設計の考え方
今回はMLP(多層パーセプトロン)というシンプルなモデルを作ります。
Denseレイヤー(全結合層)のみで構成されます。
以下の図は横スクロールできます。
4-2. モデルの構築コード
layers.Dense(512, activation=’relu’, input_shape=(784,))
・512: このレイヤーのユニット数
・activation=’relu’: ReLU活性化関数(負の値を0にする)
・input_shape=(784,): 入力は784次元(最初の層のみ指定)
layers.Dense(256, activation=’relu’)
2層目は入力の形状を指定しなくてOK(自動で推論される)
layers.Dense(10, activation=’softmax’)
・10: 出力クラス数(0〜9の10種類)
・activation=’softmax’: 出力を確率分布に変換(合計1.0)
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense (Dense) (None, 512) 401920
dense_1 (Dense) (None, 256) 131328
dense_2 (Dense) (None, 10) 2570
=================================================================
Total params: 535,818
Trainable params: 535,818
Non-trainable params: 0
_________________________________________________________________
第1層: 784 × 512 + 512 = 401,920
第2層: 512 × 256 + 256 = 131,328
第3層: 256 × 10 + 10 = 2,570
─────────────────────────
合計: 535,818パラメータ
- 入力層:784ユニット(画像のピクセル数に合わせる)
- 隠れ層:512 → 256 と徐々に減らす(情報を圧縮)
- 出力層:10ユニット(分類するクラス数に合わせる)
- 隠れ層の活性化関数:ReLU(現在の標準)
- 出力層の活性化関数:Softmax(多クラス分類の標準)
⚙️ 5. モデルのコンパイル
optimizer=’adam’
最も人気のある最適化アルゴリズム。学習率を自動調整。
loss=’categorical_crossentropy’
多クラス分類の損失関数。One-hotラベルと組み合わせて使用。
metrics=[‘accuracy’]
学習中に正解率を表示するよう設定。
- optimizer=’adam’:最も人気があり、多くの場合でうまく機能する
- loss=’categorical_crossentropy’:10クラス分類 + One-hotラベルの組み合わせに最適
- metrics=[‘accuracy’]:正解率が直感的にわかりやすい
🎓 6. モデルの訓練
6-1. fit()で学習を実行
batch_size=128
一度に128サンプルを処理。MNISTは軽いので大きめでOK。
epochs=10
全データを10回繰り返し学習。MNISTなら十分な回数。
validation_split=0.2
訓練データの20%を検証用に分ける(60000枚中12000枚)。
verbose=1
学習の進捗をプログレスバーで表示。
Epoch 1/10 375/375 [==============================] - 4s 9ms/step - loss: 0.2615 - accuracy: 0.9238 - val_loss: 0.1323 - val_accuracy: 0.9593 Epoch 2/10 375/375 [==============================] - 3s 8ms/step - loss: 0.1056 - accuracy: 0.9676 - val_loss: 0.0992 - val_accuracy: 0.9698 Epoch 3/10 375/375 [==============================] - 3s 8ms/step - loss: 0.0689 - accuracy: 0.9784 - val_loss: 0.0862 - val_accuracy: 0.9743 ... Epoch 10/10 375/375 [==============================] - 3s 8ms/step - loss: 0.0172 - accuracy: 0.9946 - val_loss: 0.0883 - val_accuracy: 0.9782
375/375 → 48000÷128 ≈ 375ステップ(バッチ数)
loss: 0.2615 → 訓練データでの損失
accuracy: 0.9238 → 訓練データでの正解率(92.38%)
val_loss: 0.1323 → 検証データでの損失
val_accuracy: 0.9593 → 検証データでの正解率(95.93%)
6-2. 学習曲線の可視化
- 訓練損失が減少:モデルが学習している証拠
- 検証損失も減少:汎化性能(未知データへの対応力)も向上
- 訓練精度 > 検証精度:若干の過学習があるが、許容範囲
- 最終的に安定:これ以上学習しても大きな改善は見込めない
📊 7. モデルの評価
7-1. テストデータで最終評価
===== 最終評価結果 ===== テスト損失: 0.0831 テスト精度: 0.9784 正解率: 97.84% → 9784枚 / 10000枚 を正しく分類!
わずか10エポック、数分の学習で、
97.84%の正解率を達成しました!
10,000枚のテスト画像のうち、9,784枚を正しく認識できています。
7-2. 混同行列で詳しく分析
予測結果と正解を比較した表です。
どの数字をどの数字と間違えやすいかがわかります。
===== 各数字の正解率 ===== 数字0: 98.57% (966/980) 数字1: 99.12% (1125/1135) 数字2: 97.77% (1009/1032) 数字3: 98.02% (990/1010) 数字4: 98.17% (964/982) 数字5: 97.31% (868/892) 数字6: 98.54% (944/958) 数字7: 97.28% (1000/1028) 数字8: 96.51% (940/974) 数字9: 96.53% (974/1009) → すべての数字で96%以上の正解率!
🔮 8. 予測結果の可視化
8-1. 正解例と間違い例を表示
8-2. 確率付きで予測を表示
間違えた画像を見ると、人間でも判断しにくいものが多いことがわかります:
・「4」と「9」の混同(形が似ている)
・「3」と「8」の混同
・乱れた筆跡の数字
これはモデルの問題ではなく、データの難しさを示しています。
📋 9. 完全なコード(まとめ)
ここまでの内容を1つにまとめた完全なコードです。
Google Colabにコピー&ペーストして実行できます。
以下のコードは横スクロールできます。
📝 STEP 8 のまとめ
- MNISTは手書き数字認識の標準データセット(28×28ピクセル、10クラス)
- 前処理は3ステップ:正規化、平坦化、One-hot化
- シンプルなMLP(3層)で97%以上の精度を達成
- 学習曲線で訓練の進捗と過学習をチェック
- 混同行列でクラスごとの性能を確認
わずか50万パラメータ、10エポックの学習で、
97.8%の正解率を達成しました!
これがディープラーニングの威力です。
従来の機械学習では、特徴量エンジニアリングが必要でしたが、
ディープラーニングは生のピクセル値から自動で特徴を学習します。
次のSTEP 9では、モデルの保存と読み込みを学び、
学習したモデルを再利用する方法を理解しましょう!
📝 練習問題
MNISTデータセット
MNISTについて正しいものを選んでください。
- A. カラー画像(RGB)のデータセット
- B. 手書き数字(0〜9)のグレースケール画像
- C. 動物の画像データセット
- D. 音声データのデータセット
MNISTの特徴
MNISTは手書き数字(0〜9)のグレースケール画像データセットです。
他の選択肢の解説:
Aが間違い:MNISTはグレースケール(白黒)です。カラー画像はCIFAR-10など。
C、Dが間違い:動物画像はCIFAR-10、音声データはLibriSpeechなど別のデータセットです。
データの正規化
ピクセル値を0〜255から0〜1に正規化する理由として正しいものを選んでください。
- A. メモリを節約するため
- B. 学習を安定させ、収束を速くするため
- C. データ量を減らすため
- D. 画像を小さくするため
正規化の効果
正規化は学習を安定させ、収束を速くするために行います。
他の選択肢の解説:
A、Cが間違い:正規化はメモリやデータ量には影響しません(float32になるのでむしろ増える)。
Dが間違い:画像サイズ(28×28)は変わりません。値の範囲が変わるだけです。
reshape()の役割
以下のコードの目的は何ですか?
- A. 画像を28×28ピクセルに拡大する
- B. 2次元配列(28×28)を1次元配列(784)に平坦化する
- C. 画像を圧縮する
- D. カラー画像に変換する
reshape()の動作
reshape()は2次元配列を1次元配列に平坦化しています。
補足:
CNN(畳み込みニューラルネットワーク)を使えば、画像の2次元構造を保ったまま処理できます。
Part 4で詳しく学習します。
One-hotエンコーディング
ラベル「3」をOne-hotエンコーディングすると、どうなりますか?
- A. [1, 1, 1, 0, 0, 0, 0, 0, 0, 0]
- B. [0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
- C. [3, 0, 0, 0, 0, 0, 0, 0, 0, 0]
- D. [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
One-hotエンコーディングの仕組み
One-hotエンコーディングは、該当クラスだけ1、他は0にする変換です。
なぜOne-hot化するのか?
数字をそのまま使うと、「9は0より大きい」という誤った順序関係を学習してしまいます。
One-hot化すると、各クラスが独立した情報として扱われます。
モデルの改善
現在のモデル(97.8%の精度)をさらに改善するために、最も効果的な方法を選んでください。
- A. epoch数を100に増やす
- B. CNNを使う(畳み込み層を追加)
- C. batch_sizeを1に減らす
- D. 活性化関数をすべてSigmoidに変える
なぜCNNが効果的なのか?
画像認識では、CNN(畳み込みニューラルネットワーク)が最も効果的です。
他の選択肢の解説:
Aが効果的でない:10エポックで既に収束。増やしても改善は小さく、過学習のリスクが増える。
Cが逆効果:batch_size=1は学習が非常に不安定になる。
Dが逆効果:Sigmoidは勾配消失問題があり、ReLUより性能が劣る。
CNNのサンプルコード(Part 4で詳しく学習):
学習メモ
ディープラーニング基礎 - Step 8