難易度:★★☆☆☆
💡この第3章では、第1・2章で学んだ知識を活かし、実際にニューラルネットワークを構築してMNIST手書き数字データを分類してみましょう。モデルの定義から訓練、精度の評価、そして誤認識の可視化まで、ディープラーニングの基本サイクルを一通り実装します。
実際に手を動かしてみることで、「学習とは何か?」「誤差逆伝播がどう使われているか?」といった概念が具体的に理解できるはずです。生成系AIの本格的なモデル実装に入る前に、シンプルなニューラルネットで学習の原理を確かめておきましょう。
深層学習の一連のプロセスを概念的に理解・おさらいしておきたい方は、過去の回「【医療AI教室:Vol.7】誤差を逆さにたどる!? “深層学習”の学習プロセス」を一読されることをお勧めします!
1. MNISTデータセットとは?
MNISTは、米国商務省配下の研究所が構築した、28×28ピクセルの手書き数字(0~9)画像が7万枚(学習用6万枚 + テスト用1万枚)含まれる有名なベンチマークデータセットです。

- モノクロ1チャネル画像
- ピクセル値を 0~255 → 0~1 や -1~1 の範囲に正規化
- 「機械学習初心者が最初に試す定番データセット」として広く利用される
PyTorchでは torchvision.datasets.MNIST
を使って自動ダウンロード&簡単に扱えます。ニューラルネットの構造を理解する上で非常に取り組みやすい例です。
2. Google Colab で試すための手順
- Google Colab にアクセスし、新しいノートブックを作成します。
- 上部メニューの「ランタイム」→「ランタイムのタイプを変更」→「ハードウェアアクセラレータ」を
GPU
に変更(CPUでも動作しますがGPUが速い)。 - 以下のコードブロックをすべて同じノートブックに貼り付け、上から順に実行してください。

3. 学習用コード例
ここでは、MNISTを使ったニューラルネットによる多クラス分類を実装します。
- データの準備
- サンプル画像の表示
- モデルの定義
- 学習ループ(損失推移グラフつき)
- テスト評価(誤認識表示、混同行列)
各セルのコードを順番にGoogle Colab のノートブックにコピペしていき、実行し、出力を確認してみましょう。

(1) ライブラリのインストール & インポート
# --- ライブラリとは? ---
# プログラミングでよく使う便利な機能(たとえば数学の計算、画像の表示、AIの学習など)を、
# あらかじめまとめて用意してくれている“道具箱”のようなものです。
# 自分でゼロから作らずに済むので、効率よく開発ができます。
# 1) ライブラリのインストール (Google Colab用)
!pip install torch torchvision scikit-learn seaborn
# ↑ AI・画像処理・機械学習・可視化に必要な4つのライブラリをまとめてインストールします。
# - torch:PyTorchというディープラーニングライブラリの本体
# - torchvision:画像データの読み込みや変換を助けるライブラリ
# - scikit-learn:機械学習の評価や分類などに便利な関数群
# - seaborn:グラフや図をきれいに表示するための可視化ライブラリ
# 2) 必要なライブラリをインポート
import torch
# ↑ PyTorchの基本機能(テンソル計算、自動微分など)を使うために読み込みます。
import torch.nn as nn
# ↑ ニューラルネットワークを構成するためのモジュール(レイヤーなど)を提供。
import torch.nn.functional as F
# ↑ 活性化関数(ReLUなど)や損失関数(CrossEntropyなど)が含まれています。
import torch.optim as optim
# ↑ 最適化アルゴリズム(例:SGDやAdam)を使うためのモジュールです。
import torchvision
# ↑ 画像データセット(MNISTやCIFARなど)や、画像処理のユーティリティが含まれています。
import torchvision.transforms as transforms
# ↑ 画像の前処理(リサイズ、正規化など)を簡単に行うためのツール群。
import matplotlib.pyplot as plt
# ↑ 画像やグラフの描画に使うライブラリ。Matplotlibの基本機能です。
import numpy as np
# ↑ 配列や行列などの数値計算を高速で処理できるライブラリです。
import seaborn as sns # 混同行列可視化に利用
# ↑ データの可視化に特化したライブラリ。特にヒートマップ表示が得意です。
from sklearn.metrics import confusion_matrix
# ↑ モデルの分類結果を評価する「混同行列」を作る関数が入っています。
# PyTorchでGPUが使えるかどうかチェックし、利用可能ならGPUを使用
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# ↑ CUDAというNVIDIA製GPUが使える場合はGPU、なければCPUを選択する仕組みです。
print("使用デバイス:", device)
# ↑ 実際に使われるデバイス(CPU or GPU)を出力して確認します。

(2) データセットとDataLoaderの準備
# 3) データ前処理のTransform定義
transform = transforms.Compose([
transforms.ToTensor(), # 画像をTensor(PyTorch用の数値配列)に変換(0〜1の範囲に正規化)
transforms.Normalize((0.5,), (0.5,)) # 平均0.5、標準偏差0.5でさらに正規化(-1〜1の範囲にスケーリング)
])
# ↑ ディープラーニングでは、画像データをそのまま使うのではなく、
# モデルに合った形式(テンソル)に変換し、学習しやすいように「正規化」します。
# transforms.Compose は、複数の前処理を順番にまとめて適用できる便利な仕組みです。
# 4) MNIST学習データセットの作成
train_dataset = torchvision.datasets.MNIST(
root='./data', # データの保存先フォルダ(なければ自動で作られます)
train=True, # 訓練用データ(True)か、テスト用データ(False)かを指定
transform=transform, # 上で定義した画像の前処理を適用
download=True # データがなければ自動でインターネットからダウンロード
)
# ↑ torchvisionに含まれるMNIST(手書き数字画像)のデータセットを準備します。
# 画像は28x28ピクセルで、0〜9の数字がラベルとして付いています。
# 5) バッチサイズを設定し、DataLoaderを作成
train_loader = torch.utils.data.DataLoader(
train_dataset, # 上で作成したデータセットを使用
batch_size=64, # 1回の学習で使用するデータの数(バッチサイズ)
shuffle=True # 毎回データの順番をシャッフルして、学習の偏りを防ぐ
)
# ↑ DataLoaderは、データを小分けにしてモデルに渡すための仕組みです。
# バッチ学習と呼ばれ、効率的に学習できるようになります。
print("学習用データ数:", len(train_dataset))
# ↑ ダウンロードした学習データの総数(60,000件)を表示して確認します。

(3) サンプル画像の可視化
# 6) DataLoaderから最初のバッチを取り出し、画像を表示
data_iter = iter(train_loader) # イテレータを取得
# ↑ DataLoaderからデータを1バッチずつ取り出せる「イテレータ」という仕組みを取得します。
# Pythonの「for文」で使う仕組みの裏側です。
images, labels = next(data_iter) # 最初のバッチ(64枚)
# ↑ イテレータから最初のバッチを取り出します。
# images: 画像データ(64枚分のテンソル)/ labels: 各画像に対応する正解の数字ラベル
images_np = images.numpy()
# ↑ PyTorchのTensor形式を、NumPy形式(Pythonで一般的な数値配列)に変換します。
# 画像表示のときはNumPyに変換すると便利です。
# ※GPU上のTensorだと .numpy() はエラーになります。GPU使用時は .cpu().numpy() が必要。
fig = plt.figure(figsize=(8, 8))
# ↑ 描画用の図(Figure)を作成。サイズは8x8インチ。
for i in range(9):
ax = fig.add_subplot(3, 3, i+1)
# ↑ 3行×3列のグリッドに、画像を1枚ずつ並べる準備をします。
ax.imshow(np.squeeze(images_np[i]), cmap='gray_r')
# ↑ 画像を表示します。
# - np.squeeze():画像の余計な次元(例:1チャンネル)を取り除きます。
# - cmap='gray_r':白黒(反転グレースケール)で表示します。
ax.set_title(f"Label: {labels[i].item()}")
# ↑ 画像の上に「正解ラベル(数字)」をタイトルとして表示。
ax.axis('off')
# ↑ 画像の周りの軸(枠)を非表示にします。
plt.suptitle("MNISTサンプルイメージ", fontsize=16)
# ↑ 図全体にタイトルを付けます。
plt.show()
# ↑ 表示用のウィンドウにグラフ(ここでは画像)を出力します。

(4) ニューラルネット定義
# 7) ニューラルネットワークの定義
# ======================================
# ネットワーク構造(テキストベース図)
# ======================================
#
# 入力画像 (28x28)
# │
# ▼
# [ Flatten ] ← 画像を1次元(784)に変換
# │
# ▼
# [ fc1: Linear(784 → 128) ]
# │
# ▼
# [ ReLU ] ← 活性化関数(非線形変換)
# │
# ▼
# [ fc2: Linear(128 → 10) ]
# │
# ▼
# 出力(logits) ← 各クラス(0〜9)のスコア
#
# ======================================
class SimpleNet(nn.Module):
# ↑ PyTorchのnn.Moduleを継承して、自作のネットワーク「SimpleNet」を定義します。
# nn.Moduleは、ニューラルネットワークを作るための“ひな形”のような存在です。
def __init__(self):
super(SimpleNet, self).__init__()
# ↑ 親クラス(nn.Module)の初期化を呼び出します。これがないと正しく動きません。
self.fc1 = nn.Linear(28*28, 128) # 入力784 → 出力128
# ↑ 入力画像(28×28 = 784ピクセル)を128次元のベクトルに変換する「全結合層(全てのノードが接続)」
self.fc2 = nn.Linear(128, 10) # 出力10クラス (0~9)
# ↑ 128次元の特徴量を、0〜9の「10クラス」に分類する出力層です。
def forward(self, x):
# ↑ モデルにデータを渡したとき、どのように処理されるかを定義する関数です。
# これは「順伝播(forward pass)」と呼ばれ、入力から出力への流れを意味します。
x = x.view(-1, 28*28) # 2次元画像を1次元に
# ↑ 画像は (1, 28, 28) のような形ですが、全結合層に入れるために (784,) に変形します。
# -1は「バッチサイズは自動で合わせて」という意味。
x = F.relu(self.fc1(x))
# ↑ 第1層(fc1)に入力を通し、活性化関数ReLUで非線形変換します。
# ReLU(Rectified Linear Unit)は、0以下を切り捨て、0より大きい値だけを通します。
x = self.fc2(x)
# ↑ 第2層(fc2)に通して、最終的なクラスごとのスコア(logits)を出力します。
return x
# ↑ 出力(logits)は、CrossEntropyLossなどの損失関数にそのまま渡せます。
model = SimpleNet().to(device)
# ↑ 上で定義したモデルをインスタンス化し、CPUまたはGPUに移動します(.to(device))。
# こうすることで、モデルとデータの両方が同じデバイス上で計算されるようになります。
print(model)
# ↑ モデルの構造(レイヤー構成など)を出力して確認します。

(5) 損失関数 & Optimizer
# 8) 損失関数 & 最適化手法
criterion = nn.CrossEntropyLoss()
# ↑ 「クロスエントロピー損失関数」を定義します。
# これは分類問題(0〜9などのカテゴリ分類)でよく使われる損失関数です。
# モデルの出力(logits)と、正解ラベルとの“ズレ”を数値化してくれます。
# ズレが大きいほど損失も大きくなり、学習によってこの損失を最小化していきます。
optimizer = optim.Adam(model.parameters(), lr=0.001)
# ↑ 「Adam(アダム)最適化アルゴリズム」を使って学習を行う設定です。
# model.parameters() は、学習すべきモデル内の全パラメータ(重みやバイアス)を指定します。
# lr=0.001 は「学習率(learning rate)」と呼ばれ、どのくらいのスピードで学習するかを調整する重要な値です。
# 💡補足
# - 損失関数(criterion)は「どれくらい間違っているか?」を測るもの。
# - 最適化手法(optimizer)は「どうやってパラメータを直すか?」を決めるものです。
「正解をどれだけ自信を持って予測できたか?」を評価する指標です。
たとえば、正解が「3」の画像に対して、モデルが「3」と予測し、その確率が高ければ損失は小さく、逆に外れていれば損失は大きくなります。
数式でいうと:
損失 = -log(正解クラスの確率)
つまり、正解データについて、正解と予測した確率が高い=損失が小さいという仕組みです。
PyTorchではこの一行でOK:
criterion = nn.CrossEntropyLoss()
学習では、この損失をもとに「間違った分だけ」モデルが修正されるように工夫されています。

(6) 学習ループと損失推移グラフ
# 9) 学習ループの実行
num_epochs = 5
# ↑ 学習全体の繰り返し回数(エポック数)を指定します。
# エポックとは、「全データを1周して学習させること」を1回と数えます。
epoch_losses = []
# ↑ 各エポックごとの損失(loss)の平均を保存するためのリストを用意します。
for epoch in range(num_epochs):
running_loss = 0.0 # 1エポック内での累積損失
total_batch = 0 # バッチ数のカウント
for images, labels in train_loader:
# ↑ データローダーから、画像とラベルを1バッチずつ取り出します。
images = images.to(device)
labels = labels.to(device)
# ↑ 画像とラベルをGPUまたはCPUへ転送(モデルと同じデバイスにする必要があります)。
optimizer.zero_grad()
# ↑ 前のバッチで計算された勾配(grad)をリセットします。
# 勾配は蓄積されるため、毎回0に初期化が必要です。
outputs = model(images)
# ↑ モデルに画像を入力し、出力(logits)を取得します。
loss = criterion(outputs, labels)
# ↑ 出力と正解ラベルの差(誤差)を計算します(損失関数)。
loss.backward()
# ↑ 誤差(loss)を使って、各パラメータの勾配を自動で計算します(逆伝播)。
optimizer.step()
# ↑ 勾配をもとに、モデルのパラメータを更新します(学習)。
running_loss += loss.item()
# ↑ そのバッチでの損失値を足し合わせます(.item() は純粋な数値に変換するためのもの)。
total_batch += 1
# ↑ バッチのカウントを1つ進めます。
avg_loss = running_loss / total_batch
# ↑ 1エポック分の平均損失を計算します。
epoch_losses.append(avg_loss)
# ↑ 平均損失を記録して、後でグラフに使えるようにします。
print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {avg_loss:.4f}")
# ↑ 各エポック終了後に、損失を表示して学習の進み具合を確認します。
print("学習が完了しました!")
# ↑ すべてのエポックが終わったことを表示します。
# 10) 損失の推移をプロット
plt.figure(figsize=(6, 4))
# ↑ グラフ用の図を作成(サイズ指定)。
plt.plot(range(1, num_epochs+1), epoch_losses, marker='o')
# ↑ 各エポックにおける損失の推移を線グラフで表示(丸印をつけて見やすくしています)。
plt.title("Training Loss")
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.grid(True)
# ↑ グラフの装飾(タイトル、軸ラベル、グリッド線)
plt.show()
# ↑ グラフを画面に表示します。
- (a) 勾配リセット → (b) 順伝播 → (c) 損失計算 → (d) 逆伝播 → (e) パラメータ更新
- 学習が進むと
Loss
が徐々に減少

(7) テストデータ評価 & 誤認識例の可視化
# 11) テストデータのDataLoaderを作成
test_dataset = torchvision.datasets.MNIST(
root='./data', # 学習時と同じフォルダに保存
train=False, # テストデータ(False)を指定
transform=transform, # 学習時と同じ前処理(ToTensor+Normalize)を適用
download=True # 必要に応じてダウンロード
)
# ↑ テスト用のMNISTデータセット(10,000枚)を用意します。
test_loader = torch.utils.data.DataLoader(
test_dataset,
batch_size=64,
shuffle=False # テストでは順番をシャッフルしない(再現性のため)
)
# ↑ 学習時と同様、DataLoaderでテストデータを扱いやすくします。
print("テスト用データ数:", len(test_dataset))
# ↑ テストデータが10,000件あることを確認します。
# 12) テストデータを使って正解率を計算
model.eval()
# ↑ モデルを「評価モード」に切り替えます。
# これはDropoutやBatchNormのような層の振る舞いを学習用から変更するために必要です。
correct = 0
total = 0
# ↑ 正解した数と、全体のサンプル数を数えるためのカウンター。
misclassified_images = []
misclassified_true = []
misclassified_pred = []
# ↑ 間違えた画像と、その正解ラベル/予測ラベルを保存するリスト(最大9件)
with torch.no_grad():
# ↑ 評価中は勾配計算が不要なので、無効化して処理を軽く・速くします。
for images, labels in test_loader:
images = images.to(device)
labels = labels.to(device)
outputs = model(images)
# ↑ テスト画像をモデルに通して、出力(各クラスのスコア)を取得。
_, predicted = torch.max(outputs, 1)
# ↑ 各画像について、最もスコアが高いクラスを予測結果とします。
total += labels.size(0)
correct += (predicted == labels).sum().item()
# ↑ 全体の枚数に加算し、正解だったものだけカウントを増やします。
# 誤認識を9枚までピックアップ
for i in range(len(labels)):
if predicted[i] != labels[i]:
if len(misclassified_images) < 9:
misclassified_images.append(images[i].cpu().numpy())
misclassified_true.append(labels[i].cpu().item())
misclassified_pred.append(predicted[i].cpu().item())
# ↑ 間違えた画像・正解・予測をそれぞれ記録。
# GPU上のTensorをCPUに移して、NumPy配列や整数として扱えるように変換しています。
accuracy = correct / total
print(f"テストデータでの正解率: {accuracy*100:.2f}%")
# ↑ 全体に対して、どれくらい正解したか(正解率)をパーセントで表示します。
# 13) 誤認識した画像を可視化
if len(misclassified_images) > 0:
fig = plt.figure(figsize=(8, 8))
for i in range(len(misclassified_images)):
ax = fig.add_subplot(3, 3, i+1)
ax.imshow(np.squeeze(misclassified_images[i]), cmap='gray_r')
# ↑ 誤認識した画像を白黒表示。squeezeで余分な次元を除去。
ax.set_title(f"True: {misclassified_true[i]}, Pred: {misclassified_pred[i]}")
# ↑ タイトルとして、「本当のラベル」と「予測されたラベル」を表示。
ax.axis('off')
# ↑ 枠線や目盛りを非表示にして画像を見やすくします。
plt.suptitle("Misclassified Samples", fontsize=16)
plt.show()
else:
print("誤認識がありませんでした。高精度です!")
# ↑ もし9枚以内に誤認識が見つからなければ、それも結果として表示。
model.eval()
: 推論モードtorch.no_grad()
: 勾配追跡を無効化し計算負荷を削減- 誤認識例を数枚確認することで、どのように間違えているか理解しやすい

(8) (オプション)混同行列の描画
# 14) 混同行列の可視化
y_true = []
y_pred = []
# ↑ 実際のラベル(正解)と、モデルの予測ラベルを保存するリストを用意します。
model.eval()
# ↑ 評価モードに切り替え(BatchNormやDropoutなどの挙動を固定)
with torch.no_grad():
# ↑ 評価中は勾配計算が不要なので、処理を高速・軽量化します。
for images, labels in test_loader:
images = images.to(device)
labels = labels.to(device)
outputs = model(images)
# ↑ モデルにテスト画像を入力して、クラスごとのスコアを取得
_, predicted = torch.max(outputs, 1)
# ↑ スコアが最も高いクラスを予測結果として取得します。
y_true.extend(labels.cpu().numpy())
y_pred.extend(predicted.cpu().numpy())
# ↑ 正解ラベル(labels)と予測ラベル(predicted)をそれぞれCPUに移し、
# NumPy配列としてリストに追加します(extendで複数を一気に追加)。
cm = confusion_matrix(y_true, y_pred)
# ↑ sklearnの関数を使って「混同行列(confusion matrix)」を作成します。
# 行が実際のクラス、列が予測されたクラスを表し、どこで間違えたかがわかります。
plt.figure(figsize=(8,6))
# ↑ 描画サイズを指定して新しい図を作成
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
# ↑ Seabornライブラリを使って、混同行列をヒートマップ形式で可視化します。
# - annot=True:各マスに数値を表示
# - fmt='d':整数形式で表示
# - cmap='Blues':青系のカラーマップを使用
plt.title("Confusion Matrix (MNIST)")
plt.xlabel("Predicted Label")
plt.ylabel("True Label")
plt.show()
# ↑ グラフを表示します。
- クラス0~9の誤認識分布が一目でわかる
- 対角成分が多いほど正しく識別できている

4. 実行結果
セルをすべて実行すると、以下のようなログやグラフ・画像が出力されます。
- 学習前のサンプル画像
- 手書き数字がどんな形でデータ化されているか分かる
- 損失表示 & 推移グラフ
- 各エポックごとに損失が減少していく様子
- テストデータでの正解率
- シンプルな全結合ネットでも5エポックで90~95%超が出やすい
- 誤認識例
- 「True: 9, Pred: 4」など、間違えたケースを可視化
- 混同行列(オプション)
- クラス間の誤分類を俯瞰できる
5. まとめと次回予告
MNISTは手書き数字0~9のモノクロ画像(28×28)。
PyTorchを使い、以下の流れで学習フローを体験しました。
- DataLoader でバッチ分割
- モデルに入力 → 順伝播
- 損失計算
- 逆伝播
- パラメータ更新
医療画像や他分野のデータでも、基本構造は同じです。
医療AIへの応用イメージ
- レントゲンやCT、病理画像などは、さらに大きなモデル(CNN/Transformer) や転移学習が有効。
- データ量やチャネル数は異なっても、学習ループの仕組みは同じ。
- 実運用では、患者情報の保護や倫理審査などの面が非常に重要。
次回予告: 「データ準備と前処理の基礎(医療データの扱い方)」
- テキスト・画像・音声のクリーニング、フォーマット変換
- 患者データの匿名化、倫理審査、個人情報保護法やHIPAAなどの法令遵守
- データ分割と過学習対策(Train/Validation/Test)
医療AIプロジェクトでは「データの質」が成果を大きく左右します。どうぞお楽しみに!
次章予告: もっとPythonと深層学習中級編
前章で触れた「計算グラフ」や「損失関数」、「誤差逆伝播」など、Pythonと深層学習中級編を、第2回の4章以降で、技術的な内容に触れていければと思います。
参考リンク
- Google Colab
- VS Code 公式サイト
- Anaconda 公式サイト / Miniconda
- PyTorch 公式ドキュメント
- Python venv ドキュメント(公式)
- scikit-learn confusion_matrix
注意
- 本記事の内容は執筆時点のものです。環境やバージョンによって変更の可能性があります。
- 実際に患者情報を扱う際は、プライバシー保護と法令遵守を徹底してください。
- 特に患者画像やカルテ情報は、組織ガイドラインや倫理審査(IRB)手続きを踏んだうえで適切に扱いましょう。
これで「MNISTを用いた簡単なニューラルネットの構築」の解説は終了です。
ぜひColab等で動かし、学習過程や誤認識を可視化しながら、ニューラルネットの理解を深めてください。
コメント