医用画像AIを学ぶとき、最初の壁は「モデル」ではなくデータの扱いです。DICOMという独特のフォーマット、CTのHU値のような物理量、3Dボリューム——一般的な画像認識の常識がそのまま通用しません。
この壁をまとめて面倒見てくれるのが MONAI(Medical Open Network for AI)です。PyTorchベースの医用画像特化フレームワークで、研究から製品開発まで幅広く使われています。
この記事では、MONAI公式チュートリアルでも使われる公開データセット MedNIST を題材に、環境構築→データ準備→学習→評価までを一気に通します。所要時間はGPUなしでも30分程度です。
MONAIとは
- PyTorchベースの医用画像ディープラーニング用フレームワーク(Project MONAI)
- NVIDIAやKing’s College Londonなどが中心となって開発するオープンソース
- 医用画像特有の機能が最初から揃っている
- DICOM / NIfTI などのローダー
- 医用画像向けの前処理・Augmentation(強度正規化、3D対応の回転・切り出しなど)
- UNet・DenseNet・SwinUNETRなどのネットワーク実装
- Diceなど医用画像で定番の損失・評価指標
「PyTorchは書けるが医用画像は初めて」という人が、車輪の再発明をせずに済むための道具箱です。
環境構築
Python 3.10以降の仮想環境で、次をインストールします。
python -m venv .venv
source .venv/bin/activate
pip install "monai[pillow]" torch matplotlib scikit-learn
GPUがある場合は、環境に合ったPyTorchを pytorch.org の手順で入れてください。今回のデータは小さいのでCPUでも学習できます。
MedNISTデータセット
MedNISTは、医用画像AIの学習用に整備された教材用データセットです。6クラス・約6万枚の2D画像(64×64)で構成されます。
AbdomenCT— 腹部CTBreastMRI— 乳房MRICXR— 胸部X線ChestCT— 胸部CTHand— 手のX線HeadCT— 頭部CT
つまり課題は「この画像はどのモダリティ(撮像の種類)か」を当てる6クラス分類です。臨床的な意味は薄いですが、医用画像パイプラインの練習台として最適です。
MONAIには専用のダウンローダーが用意されています。
from monai.apps import MedNISTDataset
root_dir = "./data"
train_ds = MedNISTDataset(
root_dir=root_dir,
section="training",
download=True,
seed=0,
)
print(len(train_ds)) # 学習用サンプル数
print(train_ds[0]["image"].shape, train_ds[0]["label"])
MedNISTDataset は辞書形式({"image": ..., "label": ...})でサンプルを返します。MONAIの前処理は、この辞書ベースのTransform(末尾に d が付くAPI)で組むのが流儀です。
前処理とAugmentation
from monai.transforms import (
Compose,
LoadImaged,
EnsureChannelFirstd,
ScaleIntensityd,
RandRotated,
RandFlipd,
RandZoomd,
)
train_transforms = Compose([
LoadImaged(keys="image", image_only=True),
EnsureChannelFirstd(keys="image"), # (H, W) -> (C, H, W)
ScaleIntensityd(keys="image"), # 強度を [0, 1] に正規化
RandRotated(keys="image", range_x=0.26, prob=0.5, keep_size=True),
RandFlipd(keys="image", spatial_axis=0, prob=0.5),
RandZoomd(keys="image", min_zoom=0.9, max_zoom=1.1, prob=0.5),
])
val_transforms = Compose([
LoadImaged(keys="image", image_only=True),
EnsureChannelFirstd(keys="image"),
ScaleIntensityd(keys="image"),
])
一般的な画像認識と比べたときのポイントは2つです。
ScaleIntensityd(強度の正規化)。医用画像はピクセル値が「明るさ」ではなく物理量(CTならHU値)を表すことがあり、モダリティごとにレンジがまったく違います。ImageNetの平均・分散で正規化する、という常識は通用しません。- Augmentationは「起こり得る変動」だけにする。左右反転は解剖学的に不自然な画像を作ることがあります(心臓が右にある胸部X線など)。今回はモダリティ分類なので雑でも動きますが、臨床タスクではAugmentationの妥当性自体をドメイン知識で検証します。
モデルと学習ループ
モデルはMONAI組み込みの DenseNet121 を使います。2D・1チャネル・6クラスであることを明示するだけです。
import torch
from monai.data import DataLoader
from monai.networks.nets import DenseNet121
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
train_ds = MedNISTDataset(
root_dir=root_dir, section="training",
transform=train_transforms, download=True, seed=0,
)
val_ds = MedNISTDataset(
root_dir=root_dir, section="validation",
transform=val_transforms, download=False, seed=0,
)
train_loader = DataLoader(train_ds, batch_size=128, shuffle=True, num_workers=4)
val_loader = DataLoader(val_ds, batch_size=256, num_workers=4)
model = DenseNet121(spatial_dims=2, in_channels=1, out_channels=6).to(device)
loss_fn = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4)
for epoch in range(3):
model.train()
for batch in train_loader:
images = batch["image"].to(device)
labels = batch["label"].to(device)
optimizer.zero_grad()
loss = loss_fn(model(images), labels)
loss.backward()
optimizer.step()
print(f"epoch {epoch + 1} done (last loss: {loss.item():.4f})")
MedNISTは易しいデータセットなので、3エポックも回せば検証精度は99%前後に到達します。
評価 — Accuracyだけ見ない癖をつける
医用画像AIでは、混同行列と感度・特異度を必ず確認する癖をつけましょう。臨床タスクではクラス不均衡が極端(有病率1%など)で、Accuracyが高くても見逃しだらけ、ということが平気で起きるからです。
import numpy as np
from sklearn.metrics import classification_report, confusion_matrix
model.eval()
y_true, y_pred = [], []
with torch.no_grad():
for batch in val_loader:
images = batch["image"].to(device)
logits = model(images)
y_pred.extend(logits.argmax(dim=1).cpu().numpy())
y_true.extend(batch["label"].numpy())
class_names = ["AbdomenCT", "BreastMRI", "CXR", "ChestCT", "Hand", "HeadCT"]
print(classification_report(y_true, y_pred, target_names=class_names, digits=4))
print(confusion_matrix(y_true, y_pred))
今回のような易しいタスクでも、混同行列を見ると「間違えるときは AbdomenCT と ChestCT の間」といった、解剖学的に納得感のある誤りパターンが観察できて面白いはずです。
ここから先へ — 実戦への3ステップ
MedNISTで「パイプラインを通す」経験ができたら、次はより実戦的な課題へ進みましょう。
- セグメンテーション: MONAIの
UNetとDice損失で、臓器・病変の領域抽出に挑戦する(Medical Segmentation Decathlonの公開データが定番) - DICOMを直接扱う:
pydicomでヘッダを読み、HU値変換・ウィンドウ処理を自分で書いてみる - 3Dへの拡張: CT/MRIをボリュームとして扱い、
spatial_dims=3のネットワークとパッチベース学習を試す
まとめ
- MONAIは医用画像特有の面倒(フォーマット・前処理・3D)を吸収してくれるPyTorchベースのOSS
- MedNIST分類なら、CPUでも30分で「医用画像AIのパイプライン一周」を体験できる
- 医用画像では強度の正規化・Augmentationの妥当性・Accuracy以外の評価指標が重要
- 次のステップはセグメンテーション、DICOMの生読み、3D化
「なぜ医用画像AIがビジネスとして成立しにくいのか(規制の壁)」に興味が湧いた人は、SaMD入門もあわせてどうぞ。