MNIST(エムニスト)は、0から9までの手書き数字の画像データセットです。機械学習(特に画像認識)の分類モデルを学習・評価するための標準的なベンチマークとして使われます。
手書き文字を正しく「分類(識別)」するため、今回は機械学習の基本の学習用に、ロジスティック回帰(およびソフトマックス回帰)のモデルを実装します。
ソフトマックス回帰は,ロジスティック回帰を多クラス分類に拡張したものです。
前提
- 基本の学習用に深層モデルやそのライブラリは用いず、NumPyで実装
- 実行環境はGoogle Colab。ランタイムはPython3(T4 GPU)を使用
※ 参照:機械学習・深層学習を勉強する際の検証用環境について - 本記事のコード全容はこちらからダウンロード可能。ipynbファイルであり、そのまま自身のGoogle Driveにアップロードして実行可能
- 数学的知識や用語の説明について、参考文献やリンクを最下部に掲載 (本記事内で詳細には解説しませんが、流れや実施内容がわかるようにしたいと思います)
全体の流れ
大きく分けると 7ステップ になります。
- ライブラリ読み込み・乱数固定
- 数学関数の定義(安定化込み)
- MNISTデータの読み込みと前処理
- 学習用・検証用データに分割
- モデル(ソフトマックス回帰)の定義
- 学習処理(ミニバッチ SGD)
- テスト & 可視化
全体像を一言でいうとMNISTをNumPyだけで前処理し、ソフトマックス回帰をミニバッチSGDで学習し、汎化性能と失敗例を可視化するコードになります。
実装
1. ライブラリ読み込み・乱数固定
本処理では、数値計算に NumPy、データ分割および評価指標の算出に scikit-learn、データセットの取得に Keras を用いる。
また、乱数シードを固定することで、重み初期化およびデータシャッフルの再現性を確保している。
- NumPy:数値計算の本体
- sklearn:データ分割・正解率計算
- keras.datasets:MNIST読み込み用
- matplotlib:結果の可視化
- seed 固定:毎回同じ初期値 → 再現性確保
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from keras.datasets import mnist
import matplotlib.pyplot as plt
np.random.seed(34)
2. 数学関数の定義(安定化込み)
ソフトマックス関数では、指数関数計算におけるオーバーフローを防ぐため、入力から最大値を減算する処理を行っている。
また、クロスエントロピー損失の計算時に対数の入力が 0 となることを防ぐため、微小値によるクリッピングを行っている。
- クロスエントロピー用 log の数値安定化
- Softmax のオーバーフロー防止
- 深層学習フレームワークが内部でやってることを自前で書く
def np_log(x):
return np.log(np.clip(x, 1e-10, 1e+10))
def softmax(x, axis=1):
x -= x.max(axis=axis, keepdims=True)
x_exp = np.exp(x)
return x_exp / x_exp.sum(axis=axis, keepdims=True)
3. MNISTデータの読み込みと前処理
MNISTデータセットを読み込み、画素値を 0–255 の整数値から 0–1 の浮動小数点数に正規化する。
また、クラスラベルを one-hot 表現に変換し、入力画像を 784 次元のベクトルに変換する。
(x_mnist_1, y_mnist_1), (x_mnist_2, y_mnist_2) = mnist.load_data()
x_mnist = np.r_[x_mnist_1, x_mnist_2]
y_mnist = np.r_[y_mnist_1, y_mnist_2]
(x_train_raw, y_train_raw), (x_test_raw, y_test_raw) = mnist.load_data()
x_mnist = x_train_raw
y_mnist = y_train_raw
x_test_mnist = x_test_raw.astype('float32') / 255.
x_test_mnist = x_test_mnist.reshape(x_test_mnist.shape[0], -1)
y_test_mnist = np.eye(10)[y_test_raw]
- 線形モデルに入れるための最低限の前処理
x_mnist = x_mnist.astype('float32') / 255. # 画素値のスケーリング: 0 - 255 (整数) → 0.0 - 1.0 (浮動小数点数)
y_mnist = np.eye(N=10)[y_mnist.astype('int32').flatten()] # 整数ラベル (0–9) を one-hot ベクトル に変換
x_mnist=x_mnist.reshape(x_mnist.shape[0],-1) # 画素の平坦化: 28 * 28 (2次元画像) → 784 (1次元画像)
| 処理 | 意味 |
|---|---|
/255 |
画素値を 0〜1 に正規化 |
| one-hot | クラス分類用 |
| reshape | 28×28 → 784 次元ベクトル |
4. 学習用・検証用データに分割
モデルの汎化性能を評価するため、全データを学習用データと検証用データに分割する。
これにより、学習データへの過学習を検出可能とする。
x_train_mnist, x_valid_mnist, y_train_mnist, y_valid_mnist = train_test_split(x_mnist, y_mnist, test_size=10000)
)
5. モデル(ソフトマックス回帰)の定義
入力次元 784、出力次元 10 の重み行列およびバイアスを初期化し、
線形変換とソフトマックス関数からなる多クラス分類モデルを構築する。
モデルの意味

W_mnist = np.random.uniform(low=-0.08, high=0.08, size=(784, 10)).astype('float32') # 重み: (784, 10)
b_mnist = np.zeros(shape=(10,)).astype('float32')
6. 学習処理(ミニバッチ SGD)
学習データをシャッフルした上でミニバッチに分割し、クロスエントロピー損失に基づいて勾配を計算し、パラメータを更新する。
また、L2 正則化項を導入することで、過学習の抑制を図っている。
- Softmax + Cross Entropy
- 勾配は解析的に計算
- L2正則化で過学習抑制
- NumPyだけで 逆伝播を再現
def train_mnist(x, y, eps=0.1):
global W_mnist, b_mnist
batch_size = x.shape[0]
# 予測
y_hat = softmax(np.matmul(x, W_mnist) + b_mnist) # (batch_size, 出力の次元数)
# 目的関数の評価
cost = (- y * np_log(y_hat)).sum(axis=1).mean()
delta = y_hat - y # (batch_size, 出力の次元数)
# パラメータの更新
lambda_ = 1e-4
dW = np.matmul(x.T, delta) / batch_size + lambda_ * W_mnist
db = delta.mean(axis=0) # バイアス勾配
W_mnist -= eps * dW
b_mnist -= eps * db
return cost
def valid_mnist(x, y):
y_hat = softmax(np.matmul(x, W_mnist) + b_mnist)
cost = (- y * np_log(y_hat)).sum(axis=1).mean()
return cost, y_hat
学習ループ
for epoch in range(200):
perm = np.random.permutation(len(x_train_mnist))
x_train_mnist = x_train_mnist[perm]
y_train_mnist = y_train_mnist[perm]
for i in range(0, len(x_train_mnist), batch_size):
x_batch = x_train_mnist[i:i+batch_size]
y_batch = y_train_mnist[i:i+batch_size]
cost = train_mnist(x_batch, y_batch, eps=0.1)
if epoch % 10 == 9 or epoch == 0:
cost_v, y_pred = valid_mnist(x_valid_mnist, y_valid_mnist)
acc = accuracy_score(
y_valid_mnist.argmax(axis=1),
y_pred.argmax(axis=1)
)
print(
f"EPOCH {epoch+1}: "
f"Valid Loss={cost_v:.3f}, Valid Acc={acc:.3f}"
)
| 処理 | 目的 |
|---|---|
| シャッフル | SGDの偏り防止 |
| ミニバッチ | 安定 & 高速 |
| validation | 学習の健全性確認 |
Google Colab上で学習ループを実行するとValid Acc(評価の正解値)など評価が表示される

7. テスト & 可視化
学習後のモデルを用いて、検証データおよびテストデータに対する予測を行い、分類精度を算出する。
さらに、正解例および誤分類例を画像として可視化することで、モデルの挙動を定性的に評価する。
- 正解・不正解を画像で表示
y_test_true = y_test_mnist.argmax(axis=1)
y_test_pred_label = y_test_pred.argmax(axis=1)
def show_correct_incorrect(x, y_true, y_pred, n=10):
"""
正解・不正解を画像で表示
"""
correct_idx = np.where(y_true == y_pred)[0]
incorrect_idx = np.where(y_true != y_pred)[0]
fig = plt.figure(figsize=(12, 4))
# 正解例
for i, idx in enumerate(correct_idx[:n]):
ax = fig.add_subplot(2, n, i+1, xticks=[], yticks=[])
ax.imshow(x[idx].reshape(28, 28), cmap="gray")
ax.set_title(f"✓ T:{y_true[idx]} P:{y_pred[idx]}", fontsize=9)
# 不正解例
for i, idx in enumerate(incorrect_idx[:n]):
ax = fig.add_subplot(2, n, n+i+1, xticks=[], yticks=[])
ax.imshow(x[idx].reshape(28, 28), cmap="gray")
ax.set_title(f"✗ T:{y_true[idx]} P:{y_pred[idx]}", fontsize=9)
plt.suptitle("MNIST Classification Results (Top: Correct, Bottom: Incorrect)")
plt.tight_layout()
plt.show()
show_correct_incorrect(
x_test_mnist,
y_test_true,
y_test_pred_label,
n=10
)
Google Colab上で実行すると手書き文字が正しく分類できているか可視化される。Tが正解値、Pが予測値。

最後に
本コードでは、MNISTデータセットを用いてソフトマックス回帰による多クラス分類を実装し、
ミニバッチ確率的勾配降下法および正則化を用いて学習を行った。
MNISTの分類では不正解の分類結果も半数ほど見られたが、ソフトマックス回帰が単なる線形分類器であるため限界を確認した。
今後、MLPやCNN(畳み込みニューラルネットワーク)でも検証する必要がある。
参考文献、リンク
- ゼロからつくるPython機械学習プログラミング入門
-
詳解ディープラーニング第2版
※ 詳解とありますが、入門的な内容から丁寧に解説してあります。 -
YouTubeチャンネル - 予備校のノリで学ぶ「大学の数学・物理」
※ 数学的知識の学習としては、世界一わかりやすかったです。