STEP 8:初めてのニューラルネットワーク実装

🧠 STEP 8: 初めてのニューラルネットワーク実装

MNISTデータセットで手書き数字認識を実装しよう

📋 このステップで学ぶこと

  • MNISTデータセットとは?読み込み方法
  • データの前処理(正規化、平坦化、one-hot化)
  • シンプルなMLP(多層パーセプトロン)の構築
  • モデルの訓練と学習曲線の可視化
  • テストデータでの評価と予測結果の確認

🎯 1. MNISTデータセットとは?

1-1. MNISTの概要

MNIST(エムニスト)は、手書き数字(0〜9)の画像データセットです。
機械学習・ディープラーニングの「Hello World」として世界中で使われています。

✏️ MNISTデータセットの特徴
  • 画像サイズ:28×28ピクセル(グレースケール)
  • 訓練データ:60,000枚
  • テストデータ:10,000枚
  • クラス数:10クラス(0〜9の数字)
  • ピクセル値:0〜255(0=黒、255=白)

以下の図は横スクロールできます。

【MNISTの画像データイメージ】 1枚の画像: 28×28ピクセル = 784ピクセル ┌────────────────────────────┐ │ □□□□□□□□□□□□□□□□□□□□□□□□□□□□ │ ← 28ピクセル │ □□□□□□□■■■■□□□□□□□□□□□□□□□□□ │ │ □□□□□□■■■■■■□□□□□□□□□□□□□□□□ │ │ □□□□□■■■□□■■■□□□□□□□□□□□□□□□ │ │ □□□□□■■□□□□■■□□□□□□□□□□□□□□□ │ ← 数字「5」 │ □□□□□■■■■■■■□□□□□□□□□□□□□□□□ │ のイメージ │ □□□□□□□□□□□■■■□□□□□□□□□□□□□□ │ │ □□□□□□□□□□□□■■■□□□□□□□□□□□□□ │ │ □□□□□■■■■■■■■■□□□□□□□□□□□□□□ │ │ □□□□□□□□□□□□□□□□□□□□□□□□□□□□ │ └────────────────────────────┘ ↑ 28ピクセル ラベル: 5(この画像が何の数字かを示す正解)

1-2. なぜMNISTを使うのか?

🎯 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 = ラベル(正解)

from tensorflow import keras import numpy as np import matplotlib.pyplot as plt # MNISTデータセットの読み込み (X_train, y_train), (X_test, y_test) = keras.datasets.mnist.load_data() # データの形状を確認 print(“===== データの形状 =====”) print(“訓練データの形状:”, X_train.shape) print(“訓練ラベルの形状:”, y_train.shape) print(“テストデータの形状:”, X_test.shape) print(“テストラベルの形状:”, y_test.shape)
実行結果:
===== データの形状 =====
訓練データの形状: (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]}”)

その画像の正解ラベルを表示

# 最初の25枚を表示 plt.figure(figsize=(10, 10)) for i in range(25): plt.subplot(5, 5, i + 1) plt.xticks([]) # x軸の目盛りを非表示 plt.yticks([]) # y軸の目盛りを非表示 plt.imshow(X_train[i], cmap=’gray’) plt.xlabel(f”Label: {y_train[i]}”) plt.tight_layout() plt.show()

2-3. 1枚の画像を詳しく見る

# 1枚の画像の詳細を確認 print(“===== 最初の画像の情報 =====”) print(“ピクセル値の範囲:”, X_train[0].min(), “〜”, X_train[0].max()) print(“ラベル(正解):”, y_train[0]) print(“\n画像の左上5×5ピクセル:”) print(X_train[0][:5, :5])
実行結果:
===== 最初の画像の情報 =====
ピクセル値の範囲: 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ステップ】 ステップ1: 正規化 0〜255 → 0〜1 に変換 例: 128 → 128/255 = 0.502 ステップ2: 平坦化(Flatten) (28, 28) → (784,) に変換 2次元の画像を1次元のベクトルに ステップ3: One-hot化 5 → [0,0,0,0,0,1,0,0,0,0] に変換 ラベルを10次元のベクトルに 【変換の流れ】 X_train: (60000, 28, 28) → 正規化 → (60000, 28, 28) → 平坦化 → (60000, 784) y_train: (60000,) → One-hot化 → (60000, 10)

3-3. 前処理1: 正規化(0〜1に変換)

📝 コードの解説

astype(‘float32’)

整数型(int)を浮動小数点型(float32)に変換します。
割り算の結果を正確に保存するために必要です。

/ 255.0

全ピクセル値を255で割ることで、0〜1の範囲に正規化します。
例: 128 ÷ 255 = 0.502…

# ラベルのバックアップ(後で比較用) y_train_original = y_train.copy() # ピクセル値を0〜1の範囲に正規化 X_train = X_train.astype(‘float32’) / 255.0 X_test = X_test.astype(‘float32’) / 255.0 print(“===== 正規化後 =====”) print(“値の範囲:”, X_train[0].min(), “〜”, X_train[0].max()) print(“データ型:”, X_train.dtype)
実行結果:
===== 正規化後 =====
値の範囲: 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次元のベクトルに変換

# 28×28の2次元配列を、784次元の1次元配列に変換 X_train = X_train.reshape(-1, 28 * 28) X_test = X_test.reshape(-1, 28 * 28) print(“===== 平坦化後 =====”) print(“訓練データの形状:”, X_train.shape) print(“テストデータの形状:”, X_test.shape)
実行結果:
===== 平坦化後 =====
訓練データの形状: (60000, 784)
テストデータの形状: (10000, 784)
📐 変換の計算

元の形状: (60000, 28, 28)
 ↓ reshape(-1, 784)
変換後: (60000, 784)

28 × 28 = 784
各画像が784次元のベクトルになりました!

3-5. 前処理3: One-hotエンコーディング

📝 なぜ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エンコーディング from tensorflow.keras.utils import to_categorical y_train = to_categorical(y_train, 10) y_test = to_categorical(y_test, 10) print(“===== One-hot化後 =====”) print(“変換前のラベル(最初の5個):”, y_train_original[:5]) print(“変換後の形状:”, y_train.shape) print(“\n変換後のラベル(最初の5個):”) print(y_train[:5])
実行結果:
===== 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
💡 One-hotエンコーディングの仕組み

インデックス: 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

✅ 前処理のまとめ
  1. 正規化:0〜255 → 0〜1(学習を安定させる)
  2. 平坦化:(28, 28) → (784,)(Denseレイヤー用)
  3. One-hot化:5 → [0,0,0,0,0,1,0,0,0,0](クラスを独立に)

🏗️ 4. モデルの構築

4-1. モデル設計の考え方

今回はMLP(多層パーセプトロン)というシンプルなモデルを作ります。
Denseレイヤー(全結合層)のみで構成されます。

以下の図は横スクロールできます。

【モデルの構造】 入力: 784次元(28×28ピクセル) ↓ ┌─────────────────────────────┐ │ Dense(512, activation=’relu’) │ ← 隠れ層1: 512ユニット │ パラメータ数: 784×512+512 = 401,920 │ └──────────────┬──────────────┘ ↓ ┌──────────────┴──────────────┐ │ Dense(256, activation=’relu’) │ ← 隠れ層2: 256ユニット │ パラメータ数: 512×256+256 = 131,328 │ └──────────────┬──────────────┘ ↓ ┌──────────────┴──────────────┐ │ Dense(10, activation=’softmax’) │ ← 出力層: 10ユニット │ パラメータ数: 256×10+10 = 2,570 │ └─────────────────────────────┘ ↓ 出力: 10次元(各数字の確率) 合計パラメータ数: 535,818

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)

from tensorflow.keras import layers # モデルの構築 model = keras.Sequential([ # 入力層(784次元) → 隠れ層1(512ユニット) layers.Dense(512, activation=’relu’, input_shape=(784,)), # 隠れ層1 → 隠れ層2(256ユニット) layers.Dense(256, activation=’relu’), # 隠れ層2 → 出力層(10ユニット、10クラス分類) layers.Dense(10, activation=’softmax’) ]) # モデルの構造を確認 model.summary()
実行結果:
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’]

学習中に正解率を表示するよう設定。

# モデルのコンパイル model.compile( optimizer=’adam’, # 最適化アルゴリズム loss=’categorical_crossentropy’, # 多クラス分類の損失関数 metrics=[‘accuracy’] # 評価指標 ) print(“モデルのコンパイルが完了しました!”)
💡 設定の理由まとめ
  • 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

学習の進捗をプログレスバーで表示。

# モデルの訓練 history = model.fit( X_train, y_train, batch_size=128, # バッチサイズ epochs=10, # エポック数 validation_split=0.2, # 検証データ20% 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. 学習曲線の可視化

# 学習曲線のプロット plt.figure(figsize=(12, 4)) # 損失の推移 plt.subplot(1, 2, 1) plt.plot(history.history[‘loss’], label=’訓練データ’) plt.plot(history.history[‘val_loss’], label=’検証データ’) plt.xlabel(‘エポック’) plt.ylabel(‘損失’) plt.legend() plt.title(‘損失の推移’) # 精度の推移 plt.subplot(1, 2, 2) plt.plot(history.history[‘accuracy’], label=’訓練データ’) plt.plot(history.history[‘val_accuracy’], label=’検証データ’) plt.xlabel(‘エポック’) plt.ylabel(‘精度’) plt.legend() plt.title(‘精度の推移’) plt.tight_layout() plt.show()
💡 学習曲線から読み取れること
  • 訓練損失が減少:モデルが学習している証拠
  • 検証損失も減少:汎化性能(未知データへの対応力)も向上
  • 訓練精度 > 検証精度:若干の過学習があるが、許容範囲
  • 最終的に安定:これ以上学習しても大きな改善は見込めない

📊 7. モデルの評価

7-1. テストデータで最終評価

# テストデータで最終評価 test_loss, test_accuracy = model.evaluate(X_test, y_test, verbose=0) print(“===== 最終評価結果 =====”) print(f”テスト損失: {test_loss:.4f}”) print(f”テスト精度: {test_accuracy:.4f}”) print(f”正解率: {test_accuracy * 100:.2f}%”) print(f”\n→ {int(test_accuracy * 10000)}枚 / 10000枚 を正しく分類!”)
実行結果(例):
===== 最終評価結果 =====
テスト損失: 0.0831
テスト精度: 0.9784
正解率: 97.84%

→ 9784枚 / 10000枚 を正しく分類!
🎉 すごい結果!

わずか10エポック数分の学習で、
97.84%の正解率を達成しました!

10,000枚のテスト画像のうち、9,784枚を正しく認識できています。

7-2. 混同行列で詳しく分析

📝 混同行列(Confusion Matrix)とは?

予測結果と正解を比較した表です。
どの数字をどの数字と間違えやすいかがわかります。

from sklearn.metrics import confusion_matrix import seaborn as sns # 予測を実行 y_pred = model.predict(X_test, verbose=0) y_pred_classes = np.argmax(y_pred, axis=1) # 確率→クラス番号 y_true = np.argmax(y_test, axis=1) # One-hot→クラス番号 # 混同行列を計算 cm = confusion_matrix(y_true, y_pred_classes) # ヒートマップで表示 plt.figure(figsize=(10, 8)) sns.heatmap(cm, annot=True, fmt=’d’, cmap=’Blues’, xticklabels=range(10), yticklabels=range(10)) plt.xlabel(‘予測’) plt.ylabel(‘正解’) plt.title(‘混同行列(Confusion Matrix)’) plt.show() # 各数字の正解率を計算 print(“===== 各数字の正解率 =====”) for i in range(10): correct = cm[i, i] total = cm[i, :].sum() accuracy = correct / total * 100 print(f”数字{i}: {accuracy:.2f}% ({correct}/{total})”)
実行結果(例):
===== 各数字の正解率 =====
数字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. 正解例と間違い例を表示

# 正解・不正解の判定 correct = (y_pred_classes == y_true) incorrect = ~correct print(f”正解数: {correct.sum()}枚”) print(f”間違い数: {incorrect.sum()}枚”) # 間違えた画像を10枚表示 plt.figure(figsize=(15, 6)) incorrect_indices = np.where(incorrect)[0][:10] for i, idx in enumerate(incorrect_indices): plt.subplot(2, 5, i + 1) plt.imshow(X_test[idx].reshape(28, 28), cmap=’gray’) plt.title(f”正解: {y_true[idx]}\n予測: {y_pred_classes[idx]}”, color=’red’, fontsize=12) plt.axis(‘off’) plt.suptitle(‘間違えた例(赤字)’, fontsize=14) plt.tight_layout() plt.show()

8-2. 確率付きで予測を表示

# ランダムに10枚選んで、確率付きで表示 np.random.seed(42) random_indices = np.random.choice(len(X_test), 10, replace=False) plt.figure(figsize=(20, 4)) for i, idx in enumerate(random_indices): plt.subplot(2, 10, i + 1) plt.imshow(X_test[idx].reshape(28, 28), cmap=’gray’) plt.axis(‘off’) # 予測確率 pred_class = y_pred_classes[idx] true_class = y_true[idx] confidence = y_pred[idx][pred_class] * 100 # 正解なら緑、不正解なら赤 color = ‘green’ if pred_class == true_class else ‘red’ plt.title(f”正解:{true_class}\n予測:{pred_class}\n({confidence:.1f}%)”, color=color, fontsize=9) plt.tight_layout() plt.show()
💡 間違いの分析ポイント

間違えた画像を見ると、人間でも判断しにくいものが多いことがわかります:

・「4」と「9」の混同(形が似ている)
・「3」と「8」の混同
・乱れた筆跡の数字

これはモデルの問題ではなく、データの難しさを示しています。

📋 9. 完全なコード(まとめ)

ここまでの内容を1つにまとめた完全なコードです。
Google Colabにコピー&ペーストして実行できます。

以下のコードは横スクロールできます。

# ===== 必要なライブラリのインポート ===== from tensorflow import keras from tensorflow.keras import layers from tensorflow.keras.utils import to_categorical import numpy as np import matplotlib.pyplot as plt # ===== 1. データの読み込み ===== (X_train, y_train), (X_test, y_test) = keras.datasets.mnist.load_data() print(“データ読み込み完了”) print(f”訓練データ: {X_train.shape}, テストデータ: {X_test.shape}”) # ===== 2. データの前処理 ===== # 正規化(0-255 → 0-1) X_train = X_train.astype(‘float32’) / 255.0 X_test = X_test.astype(‘float32′) / 255.0 # 平坦化(28×28 → 784) X_train = X_train.reshape(-1, 784) X_test = X_test.reshape(-1, 784) # One-hot化 y_train = to_categorical(y_train, 10) y_test = to_categorical(y_test, 10) print(“前処理完了”) # ===== 3. モデルの構築 ===== model = keras.Sequential([ layers.Dense(512, activation=’relu’, input_shape=(784,)), layers.Dense(256, activation=’relu’), layers.Dense(10, activation=’softmax’) ]) print(“モデル構築完了”) # ===== 4. コンパイル ===== model.compile( optimizer=’adam’, loss=’categorical_crossentropy’, metrics=[‘accuracy’] ) print(“コンパイル完了”) # ===== 5. 訓練 ===== history = model.fit( X_train, y_train, batch_size=128, epochs=10, validation_split=0.2, verbose=1 ) # ===== 6. 評価 ===== test_loss, test_accuracy = model.evaluate(X_test, y_test, verbose=0) print(f”\n===== 最終結果 =====”) print(f”テスト精度: {test_accuracy * 100:.2f}%”)

📝 STEP 8 のまとめ

✅ このステップで学んだこと
  • MNISTは手書き数字認識の標準データセット(28×28ピクセル、10クラス)
  • 前処理は3ステップ:正規化、平坦化、One-hot化
  • シンプルなMLP(3層)で97%以上の精度を達成
  • 学習曲線で訓練の進捗と過学習をチェック
  • 混同行列でクラスごとの性能を確認
💡 重要ポイント

わずか50万パラメータ10エポックの学習で、
97.8%の正解率を達成しました!

これがディープラーニングの威力です。
従来の機械学習では、特徴量エンジニアリングが必要でしたが、
ディープラーニングは生のピクセル値から自動で特徴を学習します。

次のSTEP 9では、モデルの保存と読み込みを学び、
学習したモデルを再利用する方法を理解しましょう!

📝 練習問題

問題1 やさしい

MNISTデータセット

MNISTについて正しいものを選んでください。

  • A. カラー画像(RGB)のデータセット
  • B. 手書き数字(0〜9)のグレースケール画像
  • C. 動物の画像データセット
  • D. 音声データのデータセット
正解:B

MNISTの特徴

MNISTは手書き数字(0〜9)のグレースケール画像データセットです。

【MNISTの仕様】 ・画像サイズ: 28×28ピクセル ・色: グレースケール(白黒) ・ピクセル値: 0〜255 ・クラス数: 10(0〜9の数字) ・訓練データ: 60,000枚 ・テストデータ: 10,000枚

他の選択肢の解説:

Aが間違い:MNISTはグレースケール(白黒)です。カラー画像はCIFAR-10など。
C、Dが間違い:動物画像はCIFAR-10、音声データはLibriSpeechなど別のデータセットです。

問題2 やさしい

データの正規化

ピクセル値を0〜255から0〜1に正規化する理由として正しいものを選んでください。

  • A. メモリを節約するため
  • B. 学習を安定させ、収束を速くするため
  • C. データ量を減らすため
  • D. 画像を小さくするため
正解:B

正規化の効果

正規化は学習を安定させ、収束を速くするために行います。

【正規化の効果】 ・値の範囲が小さい方が、勾配降下法が効率的に動作 ・活性化関数(ReLU、Sigmoid)の入力として適切な範囲 ・勾配の爆発や消失を防ぐ ・異なる特徴量間のスケールを揃える 【計算例】 正規化前: 128(0〜255の中間) 正規化後: 128 / 255 = 0.502(0〜1の中間)

他の選択肢の解説:

A、Cが間違い:正規化はメモリやデータ量には影響しません(float32になるのでむしろ増える)。
Dが間違い:画像サイズ(28×28)は変わりません。値の範囲が変わるだけです。

問題3 ふつう

reshape()の役割

以下のコードの目的は何ですか?

X_train = X_train.reshape(-1, 28 * 28)
  • A. 画像を28×28ピクセルに拡大する
  • B. 2次元配列(28×28)を1次元配列(784)に平坦化する
  • C. 画像を圧縮する
  • D. カラー画像に変換する
正解:B

reshape()の動作

reshape()は2次元配列を1次元配列に平坦化しています。

【変換の様子】 元の形状: (60000, 28, 28) ↓ reshape(-1, 28*28) 変換後: (60000, 784) ・-1は「自動計算」を意味(60000になる) ・28 * 28 = 784次元のベクトルに変換 【なぜ必要?】 Denseレイヤー(全結合層)は1次元の入力を期待するため 28×28の2次元画像を784次元のベクトルに変換する必要がある

補足:

CNN(畳み込みニューラルネットワーク)を使えば、画像の2次元構造を保ったまま処理できます。
Part 4で詳しく学習します。

問題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]
正解:B

One-hotエンコーディングの仕組み

One-hotエンコーディングは、該当クラスだけ1、他は0にする変換です。

【ラベル「3」の場合】 インデックス: 0 1 2 3 4 5 6 7 8 9 One-hot: [0, 0, 0, 1, 0, 0, 0, 0, 0, 0] ↑ 3番目が1(インデックスは0から始まる) 【他の例】 0 → [1, 0, 0, 0, 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]

なぜOne-hot化するのか?

数字をそのまま使うと、「9は0より大きい」という誤った順序関係を学習してしまいます。
One-hot化すると、各クラスが独立した情報として扱われます。

問題5 むずかしい

モデルの改善

現在のモデル(97.8%の精度)をさらに改善するために、最も効果的な方法を選んでください。

  • A. epoch数を100に増やす
  • B. CNNを使う(畳み込み層を追加)
  • C. batch_sizeを1に減らす
  • D. 活性化関数をすべてSigmoidに変える
正解:B

なぜCNNが効果的なのか?

画像認識では、CNN(畳み込みニューラルネットワーク)が最も効果的です。

【CNNの利点】 ・画像の2次元構造を活かせる ・局所的な特徴(エッジ、形状)を効率的に学習 ・パラメータ数が少なくて済む(計算効率が良い) ・MNISTでは99%以上の精度を達成可能 【MLPの限界】 ・画像を1次元に平坦化するため、位置情報を失う ・隣り合うピクセルの関係を活かせない

他の選択肢の解説:

Aが効果的でない:10エポックで既に収束。増やしても改善は小さく、過学習のリスクが増える。
Cが逆効果:batch_size=1は学習が非常に不安定になる。
Dが逆効果:Sigmoidは勾配消失問題があり、ReLUより性能が劣る。

CNNのサンプルコード(Part 4で詳しく学習):

model = keras.Sequential([ layers.Conv2D(32, (3, 3), activation=’relu’, input_shape=(28, 28, 1)), layers.MaxPooling2D((2, 2)), layers.Conv2D(64, (3, 3), activation=’relu’), layers.MaxPooling2D((2, 2)), layers.Flatten(), layers.Dense(128, activation=’relu’), layers.Dense(10, activation=’softmax’) ]) # 結果: 99%以上の精度!
📝

学習メモ

ディープラーニング基礎 - Step 8

📋 過去のメモ一覧
#artnasekai #学習メモ
LINE