複雑な数式計算を自動化し、臨床データを「計算可能な形」に変換する3つの核心技術。PyTorchを使えば、医学的知見を直感的にAIモデルへ実装できます。
CT画像や検査値を、AIが計算可能な「多次元の数値」に変換して格納します。GPUを使うことで、膨大な医療データを高速に処理できます。
「どこをどう直せば正解に近づくか(勾配)」を全自動で計算します。複雑な微積分を手計算する必要がなくなり、課題解決に集中できます。
ニューラルネットワークをレゴブロックのように組み立てるための枠組みです。__init__で部品を揃え、forwardで処理の流れを定義します。
医療AIの研究開発の最前線において、現在、事実上の標準(デファクトスタンダード)として多くの研究者に選ばれているフレームワーク、それが PyTorch です。
もちろん、Googleが開発したTensorFlowも非常に強力なツールであり、産業界での実装では広く使われています。しかし、私たち医療従事者や研究者が、臨床現場の課題解決に向けて新しいアイデアを形にしようとしたとき、PyTorchが持つ「直感的な記述法」と「柔軟性」は大きな武器になります。
実際、主要なAI国際会議で発表される最新論文の多くが、PyTorchを用いて実装されているというデータもあります (Paszke et al. 2019)。これは、PyTorchが単なる計算ツールにとどまらず、研究者の思考プロセスをそのままコードに落とし込める、親和性の高い「言語」として機能しているからだと言えるでしょう。
本稿では、このPyTorchを使いこなすために避けては通れない、以下の3つの「心臓部」となる概念について、その数理的背景と実装方法を徹底的に解説します。
- Tensor(テンソル):
CTやMRI、検査値といった医学データを、AIが計算可能な形(多次元の数値の塊)に変換するための「器」です。 - 自動微分(Autograd):
AIが賢くなる(学習する)ためには、間違い(誤差)の原因を突き止める複雑な微積分計算が必要です。これを全自動で肩代わりしてくれる「エンジン」です。 nn.Module:
人間の脳を模したニューラルネットワークを、まるでレゴブロックのように部品を組み合わせて構築するための「設計図」です。
これらは、単なるプログラムの呪文やコードの羅列ではありません。これらを理解することは、医学的知見や臨床データを、計算可能な数理モデルとして表現するための「新しい言葉」を習得することに他なりません。初めて触れる方は少し抽象的に感じるかもしれませんが、コードを動かしながら一つひとつ丁寧に紐解いていきましょう。
0. Deep Learningが「学習」する仕組み:魔法ではなく、泥臭い最適化
PyTorchのコードを書き始める前に、そもそも「AIが学習する」とは、一体何を行っているのかを、数式ではなく直感的なイメージで理解しておきましょう。
多くの人が「AIは大量のデータを読んで勝手に賢くなる」という魔法のようなイメージを持っています。しかし、その実体は「数億個のつまみ(パラメータ)を、ただひたすら微調整し続ける泥臭い作業」に他なりません。
0.1. そもそも「学習」とは?:巨大なブラックボックスの調整
ニューラルネットワークの仕組みを数式で理解しようとすると、どうしても難しく感じてしまうかもしれません。そこで、まずは数学的な定義を一旦脇に置いて、この技術を「無数の調整つまみ(ノブ)がついた、巨大なブラックボックス」として想像してみてください。
入力と出力の関係
例えば、日常の診療で目にする「胸部X線画像」をこの箱に入力するとしましょう。
- 入力(Input):患者さんの胸部X線画像データ
- 箱の中身(Model):画像の色や形、濃淡に反応する数億個の計算式(ここにつまみがついています)
- 出力(Output):その画像が示す疾患の確率(例:「肺炎である確率:80%」)
初期状態は「デタラメ」
工場から出荷されたばかりの(まだ何も学習していない)AIは、この数億個ある「つまみ」の位置がすべてデタラメに設定されています。そのため、医師が見れば一目瞭然の肺炎画像を入力しても、「異常なし(確率 5%)」や「気胸(確率 90%)」といった、全く見当違いな答えを返してきます。
「学習」=「つまみ合わせ」の職人芸
ここで言う「学習(Training)」とは、AIが出したデタラメな答えと、正解(医師の確定診断)を見比べながら、誤差を埋める作業のことです。
「もっと正解(肺炎)に近づけるためには、1番目のつまみを少し右に回して、2番目は左に回して……」といった具合に、予測が正解と一致するように、数億個あるつまみの一つひとつを最適な位置にカチカチと回して微調整し続けること。この泥臭いチューニング作業こそが、ディープラーニングにおける「学習」の正体なのです。
0.2. 「診断のズレ」を数値化する:損失関数 (Loss Function)
数億個の「つまみ」を調整し始める前に、現在のAIが「どれくらい間違っているか」を正確に採点する必要があります。「なんとなく間違っている」という感覚的なフィードバックでは、機械は計算ができないからです。
この間違いの大きさを表すスコアを、専門用語で「損失(Loss)」と呼びます。ゴルフのスコアと同じで、この数値は小さければ小さいほど優秀であることを意味します。
具体例:悪性腫瘍の予測
例えば、ある検査画像が「悪性腫瘍(正解ラベル \(t = 1.0\))」であるケースを考えてみましょう。
- 正解 (\(t\)):1.0(確実に悪性)
- 学習前のAIの予測 (\(y\)):0.2(「20%くらいの確率で悪性かも…?」という自信のない状態)
このとき、AIの診断ミス(損失)は、例えば「予測と正解の引き算(差)の二乗」で計算されます。
\[ \text{Loss} = (\text{予測} – \text{正解})^2 = (0.2 – 1.0)^2 = (-0.8)^2 = 0.64 \]
この 0.64 という数字が、現在のAIの「未熟さ」を表すスコアです。
学習が進むと…
学習が進み、AIが賢くなって予測値が 0.9(「90%の確率で悪性!」)まで改善したとします。すると、損失は劇的に小さくなります。
\[ \text{Loss} = (0.9 – 1.0)^2 = (-0.1)^2 = 0.01 \]
つまり、ディープラーニングにおける「学習のゴール」とは、「この損失(Loss)を限りなく \(0\) に近づけること」と言い換えることができます。
0.3. 霧の山を下る:勾配降下法 (Gradient Descent)
損失(Loss)を計算できるようになったら、次は「どうすればこの損失を最小(ゼロ)にできるか?」を考えます。ここで登場するのが、ディープラーニングの学習を支える最も重要なアルゴリズム、「勾配降下法(こうばいこうかほう)」です。
この仕組みを直感的に理解するために、少し想像力を働かせてみましょう。
アナロジー:濃霧の山岳遭難と脱出
あなたは今、「損失山(Loss Mountain)」という名の、険しい山のどこかに立っています。あなたの目的は、この山の最も標高が低い場所、すなわち「谷底(損失ゼロの地点)」まで下山することです。
しかし、あいにく周りは深い霧に包まれていて、一寸先も見えません。谷底がどの方角にあるのか、地図もコンパスもありません。この絶望的な状況で、確実に標高を下げるための唯一の戦略は何でしょうか?
それは、「足元の地面の傾斜(勾配)を確かめ、傾斜が最も急に下っている方向へ、慎重に一歩踏み出すこと」です。
これを何千歩も繰り返せば、いつかは必ず一番低い場所(あるいは局所的な窪み)にたどり着けるはずです。これが勾配降下法の基本的な考え方です。
学習を左右する2つの要素
この山下りを成功させるためには、2つの重要な要素があります。
- 勾配(Gradient):
足元の傾斜のことです。「右に行けば登り坂、左に行けば下り坂」という情報を教えてくれます。数式では微分(\(\frac{\partial L}{\partial w}\))で求めます。 - 学習率(Learning Rate):
一歩の歩幅の大きさです。これを \(\eta\)(イータ)という記号で表します。
歩幅が大きすぎると、勢いあまって谷底を飛び越えてしまい、いつまで経っても着地できません(発散)。逆に小さすぎると、谷底に着くまでに日が暮れてしまいます(収束が遅い)。適切な歩幅を設定することが、AIエンジニアの腕の見せ所です。
数式で見る「一歩」の更新
このプロセスを数式で表すと、以下のようになります。「現在の位置(パラメータ \(w_{\text{old}}\))」から、「傾きの逆方向(下り方向)」へ、「歩幅(\(\eta\))」の分だけ移動して、「新しい位置(\(w_{\text{new}}\))」を決めます。
\[ w_{\text{new}} = w_{\text{old}} – \eta \cdot \dfrac{\partial L}{\partial w} \]
ここでマイナス(\(-\))がついているのは、「傾きがプラス(登り坂)なら、マイナス方向(左)へ戻る」ためです。常に傾きと逆方向に進むことで、確実に山を下ることができます。
この「傾きを計算して、下る方向へ修正する」という地道な作業を、コンピュータの計算力で何万回も高速に繰り返すこと。これこそが、AIが少しずつ賢くなっていくプロセスの正体なのです。
0.4. 責任のなすりつけ:誤差逆伝播法 (Backpropagation)
勾配降下法で「傾き(勾配)に沿ってパラメータを修正すればよい」ということは分かりました。しかし、ここでディープラーニング特有の巨大な壁が立ちはだかります。
現代のAIモデルは、単純な一つの数式ではありません。何十、何百もの層(Layer)が重なった、巨大で複雑な連鎖構造になっています。
順伝播:情報のバトンリレー
入力された画像データは、第1層、第2層、第3層……と、まるでバトンリレーのように次々と計算・変換され、最後に「予測」が出力されます。これを「順伝播 (Forward Propagation)」と呼びます。
もし、最後のゴールで「損失が大きい(大失敗)」と判明したとしましょう。このとき、チーム監督(最適化アルゴリズム)は困ってしまいます。「一体、どの層の、どのパラメータが足を引っ張ったせいで失敗したのか?」を特定しなければ、修正のしようがないからです。
逆伝播:責任(誤差)の逆流
そこで行われるのが、ゴールからスタートに向かって責任を及ぼしていく「責任のなすりつけ合い」です。
イメージとしては、以下のような「伝言ゲーム」が猛スピードで行われます。
- 出力層:「予測が外れた! これは私の計算ミスのせいじゃない。直前の中間層Bから変なデータが送られてきたせいだ! 修正しろ!」と、誤差の責任(勾配)を後ろへ投げます。
- 中間層B:「いや、それはその前の中間層Aから受け取ったデータがおかしかったせいだ!」と、さらに責任を後ろへ投げます。
- 中間層A:「それは入力層の重みの設定がおかしいせいだ!」
このように、出口(Loss)から入口(Input)に向かって、「お前のせいでこれだけ誤差が出た(だからこれだけ値を修正しろ)」という情報(勾配)を、微分の「連鎖律(Chain Rule)」というルールを使って伝えていく手法。これを「誤差逆伝播法(バックプロパゲーション)」と呼びます (Rumelhart, Hinton and Williams 1986)。
データの流れの全体像
この「順方向の予測」と「逆方向の学習」の流れを整理すると、以下のようになります。
順伝播で「予測」し、逆伝播で「修正量」を計算するイメージ
この仕組みにより、出口での「診断ミス」という一つの結果から、数億個あるパラメータ一つひとつに対して、「君はこれくらい修正が必要」という個別の処方箋(勾配)を配ることができるのです。
0.5. この仕組みを実現するための「PyTorch」
ここまで、AIが学習する理論として「勾配降下法」と「誤差逆伝播法」のお話をしました。理屈としては、「誤差を逆流させて、責任の分だけ修正すればいい」という非常にシンプルなものです。
しかし、これを実際にゼロからプログラミングしようとすると、話は別です。最新の画像診断AIには、パラメータが数千万〜数億個も存在します。「数億個の変数を持つ連立方程式の偏微分」を、人間が手計算で行うことは不可能ですし、それをプログラムで記述しようとしても、あまりに複雑すぎてバグの温床になってしまいます。
そこで、私たちの強力な味方となるのが PyTorch です。
PyTorchは、この複雑怪奇な「学習」のプロセスを、裏側で驚くほど効率的に処理してくれる、医療AI開発のための専用ツールボックスです。私たちが学ぶべきは、複雑な微積分の解き方ではなく、以下の3つの機能の使い方だけです。
- Tensor(テンソル):
CT画像や検査値など、あらゆるデータを計算可能な形に統一して扱う「器」です。 - Autograd(自動微分):
面倒な微分の計算(誤差逆伝播)を、コマンド一つで全自動で行ってくれる「エンジン」です。 - nn.Module:
脳の神経回路網(ニューラルネットワーク)を、レゴブロックのように組み立てるための「設計図」です。
これらを使いこなせば、私たちは「数式の計算」という重労働から解放され、「どのようなモデルを作れば患者さんの役に立つか」という、本質的な課題解決に集中することができるようになります。
それでは次の章から、まずこのすべての基礎となるデータの器、「Tensor」について、実際にコードを動かしながら学んでいきましょう。
1. Tensor(テンソル):多次元データを統一的に扱う「器」
ディープラーニングの世界に足を踏み入れると、必ず耳にする言葉が「Tensor(テンソル)」です。数学的な定義は少し難解ですが、私たち医療者にとっては、「あらゆる医療データを、AIが計算しやすい形に整頓した『多次元の数値の箱』」だとイメージすると分かりやすいでしょう。
例えば、電子カルテの数値データ、心電図の波形、レントゲン写真、そしてMRIの立体画像。これらは一見まったく異なるデータ形式に見えますが、AI(特にPyTorch)の視点から見れば、すべて「Tensor」という共通のフォーマットで記述できます。
この「統一された器」があるおかげで、私たちはデータの種類に関わらず、同じプログラムコードを使って解析を進めることができるのです。
Tensorの「階数 (Rank)」と医療データの対応
Tensorには「階数(Rank)」という概念があります。これは「データの次元数」や「箱の深さ」のようなものです。医療現場で扱うデータが、それぞれどの階数のTensorに対応するのか、以下の表で整理してみましょう。
| 階数 (Rank) | 数学的イメージ | 医療データの具体例 |
|---|---|---|
| 0階 (0-D) | スカラー (ただ1つの数値) | 体温(例:37.5℃) CRP値(例:0.05 mg/dL) 収縮期血圧(1回測定の値) |
| 1階 (1-D) | ベクトル (数値の列・配列) | 1人の患者の検査値リスト 例:[年齢, 身長, 体重, HbA1c, eGFR] 短時間の心電図波形データ |
| 2階 (2-D) | 行列 (表形式・グリッド) | 1枚のグレースケール画像(レントゲンなど) 縦 \(H\) ピクセル × 横 \(W\) ピクセルの輝度値の並び 患者データセットの表(行:患者、列:検査項目) |
| 3階 (3-D) | 立体・スタック (行列を重ねたもの) | ボリュームデータ(CT・MRI) 縦 \(H\) × 横 \(W\) × 深さ(スライス数) \(D\) カラー画像(皮膚科画像・眼底写真) 縦 \(H\) × 横 \(W\) × 色チャンネル(RGBの3色) |
| 4階以上 | バッチ (データを束ねたもの) | ミニバッチ学習用データ 例:MRI画像を32人分まとめて処理する場合 \(N(\text{人数}) \times C(\text{深さ}) \times H(\text{縦}) \times W(\text{横})\) |
特に「4階以上」のTensorは、AIの学習効率を高めるために非常に重要です。患者さん一人ひとりデータを処理するのではなく、数人〜数十人分を「ひとまとめ(バッチ)」にして計算することで、処理時間を劇的に短縮できるからです。
なぜ NumPy ではなく PyTorch の Tensor なのか?
Pythonで数値計算を行う場合、通常は「NumPy(ナムパイ)」というライブラリの ndarray が使われます。PyTorchの torch.Tensor は、このNumPyと使い勝手が非常によく似ており、相互に変換も可能です。
では、なぜAI開発ではわざわざ PyTorch の Tensor を使うのでしょうか? 決定的な理由は以下の2点に集約されます。
- GPU(Graphics Processing Unit)による高速演算
NumPyは基本的にCPU(コンピュータの頭脳)で計算を行いますが、PyTorchのTensorはGPU(画像処理用チップ)上で動作します。
医療画像解析では、数百万画素のデータを数千枚単位で、何万回も繰り返し計算します。これをCPUで行うと数週間かかる処理が、GPUを使うことで数時間で終わることも珍しくありません。GPUへのデータ転送は、現代の医療AI開発において「必須」に近い手順です (Litjens et al. 2017)。 - 勾配(Gradient)を自動で保持できる
これがAI学習の「魔法」のタネです。PyTorchのTensorは、自分がどのような計算に使われたかという「履歴」を記憶し、後から「自動微分」によって勾配(学習に必要な修正量)を計算する機能を備えています (LeCun, Bengio and Hinton 2015)。NumPyにはこの機能がありません。
【実装】Tensorの操作とGPU転送
それでは、実際にコードを動かして Tensor の感覚を掴んでみましょう。
以下のコードは、Python環境でPyTorchがインストールされていることを前提としています。実際に手を動かして、コピペして実行してみてください。
【実行前の準備】
以下のコードには日本語が含まれています。グラフ描画などで日本語を用いる場合に備えて、あらかじめターミナルやコマンドプロンプトで以下のコマンドを実行し、ライブラリをインストールしておくことを強く推奨します。
pip install japanize-matplotlib
import torch
import numpy as np
# 【重要】再現性のためのシード固定
# AIの計算には「乱数(ランダムな数)」が多用されますが、
# 実験のたびに結果が変わると困るため、乱数の種(シード)を固定して
# いつ誰がやっても同じ結果が出るようにします。
torch.manual_seed(42)
# ==========================================
# 1. Tensorの生成:臨床データをAIの言葉に翻訳する
# ==========================================
# 架空の臨床データ(リスト形式)
# 例:3人の患者さんの [年齢, BMI, 収縮期血圧]
clinical_data_list = [
[65, 28.5, 140], # 患者Aさん
[54, 24.0, 128], # 患者Bさん
[72, 31.2, 155] # 患者Cさん
]
# Pythonのリストから、PyTorchのTensor(32bit浮動小数点数)を作成します
# 医療データ解析では、精度とメモリのバランスが良い 'float32' が一般的です
x_tensor = torch.tensor(clinical_data_list, dtype=torch.float32)
print("--- Tensorの基本情報 ---")
print(f"データの中身:\n{x_tensor}")
# 形状の確認: (3, 3) は「3行3列」を意味します(3人の患者 × 3つの検査項目)
print(f"データの形状 (Shape): {x_tensor.shape}")
print(f"データの型 (Dtype): {x_tensor.dtype}")
# ==========================================
# 2. GPUへの転送:計算を加速させる
# ==========================================
# お使いのPCでNVIDIA製のGPU(CUDA)が使えるか確認します。
# 医療画像などの重い計算では、このステップが非常に重要です。
if torch.cuda.is_available():
device = torch.device("cuda") # GPUデバイスを指定
x_gpu = x_tensor.to(device) # データをCPUのメモリからGPUのメモリへ転送
print(f"\n成功: データをデバイス {x_gpu.device} に転送しました。")
# これ以降、x_gpu を使った計算は爆速になります
else:
print("\n注意: GPUは利用できません。CPUで実行します。")
# ==========================================
# 3. 基本的な演算:ブロードキャスティング
# ==========================================
# 全員のデータに対して、特定の項目だけ補正を行いたい場面を想像してください。
# 例:「BMI(2列目)だけ、機器の誤差補正のために1.1倍したい」
# [年齢用, BMI用, 血圧用] の補正係数を作成
factor = torch.tensor([1.0, 1.1, 1.0])
# Tensor同士の掛け算
# x_tensorは (3, 3) の行列、factorは (3) のベクトルですが、
# PyTorchが気を利かせて、factorを自動的に3人分に「拡張」して計算してくれます。
# これを「ブロードキャスティング (Broadcasting)」と呼びます。便利です。
x_updated = x_tensor * factor
print(f"\n演算後のデータ:\n{x_updated}")
# BMI(真ん中の列)だけが 1.1倍 されていることを確認してください。
--- Tensorの基本情報 ---
データの中身:
tensor([[ 65.0000, 28.5000, 140.0000],
[ 54.0000, 24.0000, 128.0000],
[ 72.0000, 31.2000, 155.0000]])
データの形状 (Shape): torch.Size([3, 3])
データの型 (Dtype): torch.float32
注意: GPUは利用できません。CPUで実行します。
演算後のデータ:
tensor([[ 65.0000, 31.3500, 140.0000],
[ 54.0000, 26.4000, 128.0000],
[ 72.0000, 34.3200, 155.0000]])
このように、Tensorを使うことで、複数の患者さんの複数の検査データを一括で、しかも直感的な記述で処理することができます。この「データの入れ物」の扱いに慣れることが、医療AIプログラミングの第一歩です。
2. 自動微分(Autograd):学習のエンジン
前章までで、医療データを扱うための器である「Tensor」について学びました。ここからは、その器に入ったデータを使って、AIがどのように「学習」を進めていくのか、そのエンジンの部分に触れていきます。
ディープラーニングにおける「学習」とは、人間が教科書を読んで知識を蓄えるような受動的なプロセスとは少し異なります。AIにとっての学習とは、実のところ、「予測結果と正解データ(医師の診断など)との誤差を最小にするように、モデル内部にある数億個のパラメータ(重み)をひたすら微調整し続ける、能動的な最適化プロセス」に他なりません (Goodfellow, Bengio and Courville 2016)。
例えば、あるAIが胸部X線画像から「肺炎」を見落としてしまったとします。このとき、AI内部の数あるニューロンのうち、どの接続(重み)がその「判断ミス」の主犯だったのかを突き止め、次からは間違えないようにその接続の強さを少しだけ修正する必要があります。
2.1. 羅針盤としての「勾配(Gradient)」
この修正作業において、私たちが頼りにする唯一の羅針盤が「勾配(Gradient)」です。
第0章でイメージした「濃霧の山下り」を思い出してください。私たちは今、「損失(Loss)」という険しい山の中腹にいて、目指すのは標高がゼロになる「谷底(正解)」です。周りは霧で見えませんが、足元の地面の傾きだけは感じ取ることができます。
勾配とは、数学的には偏微分のことですが、直感的には「あるパラメータ(足元の位置)を少し動かしたとき、エラー(標高)がどれくらい増えるか、あるいは減るか」を示す変化率のことです。
もし、あるパラメータの勾配が「正(プラス)」であれば、それは登り坂を意味します。エラーを減らす(山を下る)ためには、その逆方向である「負(マイナス)」の方向へパラメータを動かせばよいことになります。これを数式で表現すると、以下の「パラメータ更新則」になります (Rumelhart, Hinton and Williams 1986)。
\[ w_{\text{new}} = w_{\text{old}} - \eta \cdot \dfrac{\partial L}{\partial w} \]
- \( w_{\text{new}}, w_{\text{old}} \):パラメータ(重み)の新しい値と古い値
- \( \eta \)(イータ):学習率(Learning Rate)。一歩の歩幅の大きさ。
- \( \dfrac{\partial L}{\partial w} \):勾配。損失 \(L\) をパラメータ \(w\) で微分したもの。
この式にある「マイナス」は、「傾きと逆方向(下り坂)へ進む」という意思表示です。この単純なルールを数億回繰り返すことで、AIは徐々に正解へと近づいていきます。
2.2. なぜ「自動」微分が必要なのか?
理屈はシンプルですが、実務上ではここで巨大な壁に直面します。現代の医療AIモデルは、パラメータを数個ではなく、数千万から数億個も持っています。これら一つひとつについて、人間が手計算で偏微分を行い、勾配を求めることは物理的に不可能です。
そこで登場するのが、PyTorchの心臓部とも言える機能、Autograd(自動微分) です。
Autogradは、順方向(入力→出力)の計算を行う際に、その「計算の履歴」を全て裏側で記録(録画)しています。そして、いざ誤差が判明すると、今度はそのテープを逆再生するように、出口から入り口に向かって「連鎖律(Chain Rule)」という数学のルールを適用し、全てのパラメータの勾配を一瞬で自動計算してくれるのです。
これは、病院での医療安全管理における「根本原因分析(RCA)」に似ています。あるインシデント(誤差)が発生したとき、「最終的な結果」から「プロセス」を遡り、「どの段階のどの処置が原因だったのか」を特定していく作業を、AIが全自動で行ってくれるイメージです。
2.3. 計算グラフとデータの流れ:Define-by-Run
PyTorchが多くの研究者に支持される大きな理由の一つに、「Define-by-Run(動的計算グラフ)」という仕組みがあります (Paszke et al. 2019)。これは、プログラムを実行するたびに、その場で柔軟に計算の設計図(グラフ)を作り直す方式です。
これがなぜ医療AI開発で有利なのでしょうか? それは、医療データが常に定型とは限らないからです。
- 可変長データ:患者AさんのCT画像は30スライスだが、患者Bさんは50スライスある。
- 条件分岐:「検査値Xが異常値の場合のみ、追加で詳細な分析回路を通す」といったトリアージ的な処理。
Define-by-Runであれば、Pythonの標準的な if 文や for 文を使って、こうした複雑な処理を直感的に記述しても、Autogradが自動的に追従して正しい勾配を計算してくれます。
以下は、この「データの流れ(順伝播)」と「責任の追及(逆伝播)」の関係を示した概念図です。
この「逆伝播(Backpropagation)」によって、出口でのたった一つの「診断ミス」という結果から、その原因となった数億個のパラメータそれぞれの「責任の重さ」を特定できるのです。
2.4. 【実装】コードで見る自動微分の仕組み
それでは、実際にコードを動かして、PyTorchがどのように勾配を計算しているのか体感してみましょう。ここでは最も単純な線形モデル \( y = w \cdot x + b \) を例にします。
Copy Source Code
import torch
# ==========================================
# 自動微分の実験:y = wx + b
# ==========================================
# 1. パラメータの定義(テンソルの作成)
# requires_grad=True は「この変数の計算履歴を追跡して!」というフラグです。
# AIモデルの「重み(w)」や「バイアス(b)」は、学習によって値を更新する必要があるため、
# 必ず True に設定します。
w = torch.tensor([2.0], requires_grad=True)
b = torch.tensor([1.0], requires_grad=True)
# 入力データ(x)は単なる観測データ(患者データなど)なので、
# 勾配計算(学習による更新)は不要です。デフォルト(False)のままでOKです。
x = torch.tensor([3.0])
# 2. 順伝播(Forward Pass)
# 通常の数式通りに計算します。
# この裏側で、PyTorchは動的に「計算グラフ」を構築し、
# 「wとxを掛けて、それにbを足した」という履歴をメモリ上に記録しています。
y = w * x + b
# 計算結果の確認: 2.0 * 3.0 + 1.0 = 7.0
print(f"計算結果 y: {y.item()}")
# 3. 逆伝播(Backward Pass)
# ここがハイライトです。
# .backward() を呼ぶだけで、y を起点として、計算に関わった全ての変数(w, b)
# に対する微分係数(勾配)が自動計算され、各変数の .grad 属性に格納されます。
y.backward()
print("\n--- 勾配(Gradient)の確認 ---")
# 数学的な解説:
# 数式 y = wx + b を w で偏微分すると、∂y/∂w = x (つまり 3.0)
# 数式 y = wx + b を b で偏微分すると、∂y/∂b = 1 (つまり 1.0)
# PyTorchがこの計算を正確に行えているか確認しましょう。
print(f"wの勾配 (w.grad): {w.grad.item()}")
print(f"bの勾配 (b.grad): {b.grad.item()}")
# --- 学習のステップ(参考イメージ) ---
# 実際の学習ループでは、この勾配情報を使って以下のようにパラメータを更新します。
# learning_rate = 0.01
# with torch.no_grad():
# w -= learning_rate * w.grad # 勾配の逆方向へ少し動かす
# b -= learning_rate * b.grad
計算結果 y: 7.0
--- 勾配(Gradient)の確認 ---
wの勾配 (w.grad): 3.0
bの勾配 (b.grad): 1.0
実行結果の解釈
上記のコードを実行すると、w.grad には 3.0 が、b.grad には 1.0 が格納されていることが確認できるはずです。
これはつまり、「現時点で、\(w\) を 1 増やすと、結果 \(y\) は 3 増える(だから \(y\) を減らしたければ \(w\) を大きく減らすべきだ)」 という関係性をPyTorchが正確に把握していることを意味します。この情報さえあれば、どの方向にどれだけパラメータを動かせばよいかという「次のアクション」が決まります。
このように、.backward() というたった一行のコマンドの裏側で、複雑な微積分計算がすべて処理されています。この強力なエンジンがあるおかげで、私たち医療者は数学的な計算の細部にとらわれることなく、「どのようなモデル構造にすれば病変の特徴を捉えやすいか」という、より本質的な課題解決に集中することができるのです。
3. nn.Moduleによるネットワーク構築:AIの「設計図」を描く
データの器である「Tensor」、学習のエンジンである「自動微分」について学びました。これらを使って、いよいよ独自のAIモデル(ニューラルネットワーク)を構築します。
PyTorchでモデルを作る際、必ず torch.nn.Module というクラスを継承します。これは、AIモデルを作るための「標準規格」のようなものです。この規格に従うことで、PyTorchはモデル内の数億個のパラメータを一括管理し、GPUへの転送や保存を簡単に行えるようになります (Paszke et al. 2019)。
初心者がまず理解すべきは、このクラスには「2つの必須記入欄」があるということです。それは、__init__(部品の調達)とforward(データの流れ)です。
3.1. 病院に例える「2つのメソッド」の役割
この仕組みを、新しい「診療科(AIモデル)」を開設するプロセスに例えてみましょう。
__init__(初期化):部品の調達と配置
診療に必要な「設備」や「スタッフ」を確保する場所です。「入力層から信号を受け取るニューロンを5個用意する」「計算ユニットを1個用意する」といった具合に、使用するレイヤー(層)を定義します。
イメージ:「まだ患者さんは来ていません。診察室に机やPC、聴診器を配置している状態(静的)」です。forward(順伝播):診療プロセスの定義
患者さん(データ)が来たときに、どの順番で診察を行うかという「手順(クリニカルパス)」を決める場所です。「データが入ってきたら、まず第1層に通し、次に活性化関数でフィルタリングし、最後に第2層で確率を出す」という処理の流れを記述します。
イメージ:「実際に患者さんが来院しました。受付→問診→検査→診断というフローで案内します(動的)」という処理です。
3.2. 【実装】医療データ分類モデルの構築
では、具体的なコードを見てみましょう。ここでは、5つの検査項目(年齢、BMI、血圧、HbA1c、CRP)から、特定の疾患リスク(あり/なし)を判定するAIを作成します。
import torch
import torch.nn as nn
import torch.nn.functional as F
# 1. モデルの設計図(クラス)を作成
class MedicalDiagnosticNet(nn.Module):
def __init__(self, input_dim, hidden_dim):
"""
【ステップ1:部品の調達】
診察室に必要な「部品(レイヤー)」をあらかじめ定義します。
input_dim : 入力特徴量数(例:年齢、血圧など5項目)
hidden_dim: 中間層のニューロン数(思考の複雑さ)
"""
# おまじない:親クラス(nn.Module)の初期化機能を必ず呼び出します
super(MedicalDiagnosticNet, self).__init__()
# --- 第1診察室(入力層 -> 中間層) ---
# nn.Linear(線形変換): データの重要度(重み)を計算する基本部品
# input_dim 個の検査値を受け取り、hidden_dim 個の特徴に変換します
self.fc1 = nn.Linear(input_dim, hidden_dim)
# --- 第2診察室(中間層 -> 出力層) ---
# 最終的に「疾患あり」かどうかの確率を出したいので、出力は 1つ です
self.fc2 = nn.Linear(hidden_dim, 1)
# --- 安全装置(Dropout) ---
# 特定のデータやニューロンに依存しすぎる「過学習(丸暗記)」を防ぐため、
# ランダムに情報の20%を遮断して、応用力を鍛える部品です
self.dropout = nn.Dropout(p=0.2)
def forward(self, x):
"""
【ステップ2:診療フローの定義】
患者データ x が入ってきたら、どう処理して結果を返すか記述します。
"""
# 1. 第1診察室に通す
x = self.fc1(x)
# 2. 活性化関数(ReLU)を通す
# ReLU: 「負の値(意味のない信号)ならゼロにする」というフィルタ。
# これにより、複雑な非線形な関係性を表現できます。
x = F.relu(x)
# 3. Dropoutを通す(訓練時のみランダムに一部を無効化)
x = self.dropout(x)
# 4. 第2診察室(出力層)に通す
x = self.fc2(x)
# 5. 確率に変換(Sigmoid関数)
# 出てきた数値を 0.0 ~ 1.0 の範囲(確率)に押し込めます
output = torch.sigmoid(x)
return output
# --- 実際にモデルを動かしてみる ---
# 設定:入力は5項目、中間層のニューロンは10個
input_features = 5
hidden_neurons = 10
# 設計図から実体(インスタンス)を作成
model = MedicalDiagnosticNet(input_features, hidden_neurons)
print("--- モデルの構造 ---")
print(model)
# ダミーの患者データを作成(4人分 x 5項目)
# randnは正規分布に従う乱数を生成します
dummy_input = torch.randn(4, input_features)
# 診断を実行(forwardメソッドが自動的に呼ばれます)
# ※ model.forward(dummy_input) と直接書かないのが作法です
diagnosis_result = model(dummy_input)
print(f"\n入力形状: {dummy_input.shape}")
print(f"出力形状: {diagnosis_result.shape}")
print(f"診断結果(確率):\n{diagnosis_result.detach().numpy()}")
--- モデルの構造 ---
MedicalDiagnosticNet(
(fc1): Linear(in_features=5, out_features=10, bias=True)
(fc2): Linear(in_features=10, out_features=1, bias=True)
(dropout): Dropout(p=0.2, inplace=False)
)
入力形状: torch.Size([4, 5])
出力形状: torch.Size([4, 1])
診断結果(確率):
[[0.46522382]
[0.47800177]
[0.37911454]
[0.49364242]]
3.3. 初学者が知っておくべき3つの「重要部品」
上記のコードで登場した3つの重要な部品について、医学的なアナロジーで補足します。
1. nn.Linear(全結合層)
入力データに対して「重みづけ」を行います。医師が診断する際、「年齢」と「BMI」を同じ重要度では見ないように、「今回は高齢だから年齢のリスク係数を高くしよう」といった加重計算を行う部分です。
数式では、入力ベクトル \(x\) に対して、重み行列 \(A\) とバイアス \(b\) を用いて以下の計算を行っています。
\[ y = xA^T + b \]
2. F.relu(活性化関数 ReLU)
神経細胞(ニューロン)の発火に近い挙動を模倣する関数です。「ある閾値以下の微弱な刺激には反応せず(0を出力)、強い刺激だけをそのまま伝える」というフィルタリングを行います。
もしこれがないと、どれだけ層を重ねても単純な足し算(線形変換)の繰り返しにしかなりません。このReLUを挟むことで、AIは初めて「複雑で非線形な境界線」を引くことができるようになります (Nair and Hinton 2010)。
3. nn.Dropout(ドロップアウト)
過学習(Overfitting)を防止するためのテクニックです。これは、研修医のチームトレーニングにおいて、あえて「頼りになる特定の指導医(特定のニューロン)」をランダムに不在にさせるようなものです。
すると、残されたチームは特定の人に頼らず、自分たち全体で協力して診断しようと努力します。結果として、未知の症例(テストデータ)にも強い「汎用的な診断能力」が身につくのです (Srivastava et al. 2014)。
3.4. なぜ model.forward(x) と呼ばないのか?
コードの実行部分で、model.forward(dummy_input) ではなく、model(dummy_input) と記述していることに注目してください。
これはPyTorchの非常に重要な作法です。model(x) と記述すると、内部で __call__ という特殊なメソッドが起動し、その中で様々な「準備運動(Hooks)」を行ってから、最後にユーザーが定義した forward(x) を呼び出してくれます。直接 forward を呼んでしまうと、これらの重要な準備処理がスキップされてしまい、正しく動作しない可能性があります。
4. 医療AI開発への示唆:コードと臨床の架け橋
今回解説した nn.Module や Autograd の仕組みは、単なるプログラミングの文法ではありません。これらは、現在世界中で発表されている最先端の医療AI研究の「共通言語」であり、そのすべての基礎となる基盤技術です。
具体的に、歴史的なインパクトを与えた2つの研究事例を、PyTorchの視点(Tensorの形とモデルの構造)から解剖してみましょう。私たちが学んだ概念が、実際の最先端研究の中でどのように生きているかが見えてくるはずです。
4.1. ケーススタディ:論文の中のTensorとModule
事例1:皮膚がんの画像診断
スタンフォード大学の研究チームは、スマートフォン等で撮影された皮膚画像から、悪性黒色腫(メラノーマ)などの皮膚がんを皮膚科専門医と同等の精度で識別するAIを発表しました (Esteva et al. 2017)。
- 入力データ(Tensor):
皮膚のカラー画像です。これは今回学んだ「3階テンソル(高さ \(H\) × 幅 \(W\) × RGB色 \(C\))」として表現されます。 - モデル構造(nn.Module):
「畳み込みニューラルネットワーク(CNN)」と呼ばれる、視覚野の動きを模倣したモデル(Inception v3)が使われています。 - 処理の流れ(forward):
画像データが層を通るたびに、「エッジ(輪郭)」→「テクスチャ(模様)」→「病変の特徴」と、徐々に医学的に意味のある情報へと変換されていくプロセスが記述されています。

事例2:電子カルテからの予後予測
Googleと複数の大学病院のチームは、電子カルテ(EHR)に含まれる非構造化データから、入院患者の死亡リスクや再入院確率を予測するモデルを開発しました (Rajkomar et al. 2018)。
- 入力データ(Tensor):
カルテの自由記述や検査オーダーの履歴です。これらは単語やコードを数値化した列(ベクトル)として扱われ、「2階テンソル(時間 \(T\) × 特徴量 \(D\))」の時系列データとなります。 - モデル構造(nn.Module):
文章の文脈や前後のつながりを読み取るための「リカレントニューラルネットワーク(RNN)」や、現在の主流である「Transformer」が用いられます。 - 処理の流れ(forward):
過去の受診歴から現在の症状まで、時間の流れに沿って情報を蓄積・統合し、将来のリスクを計算するフローが定義されています。
4.2. Computational Thinking(計算論的思考)の獲得
これらの研究は、扱う臓器もデータの種類も異なります。しかし、「臨床データをTensorという数値の箱に入れ、nn.Moduleという関数に通し、Autogradで誤差を修正する」という本質的な構造は、今回私たちが書いたコードと全く同じです。
米国の循環器内科医エリック・トポル博士が著書『Deep Medicine』で指摘するように、医療者がプログラミングを学ぶ最大の意義は、単にコードを書けるようになること自体ではありません (Topol 2019)。
それは、「目の前の臨床課題を、計算可能な数理モデルとして再定義する力(Computational Thinking)」を身につけることです。
「この診断プロセスは、どのような入力と出力の関数として表現できるか?」
「医師の経験則(暗黙知)は、どのようなパラメータとして重みづけできるか?」
この「翻訳」を行う思考法こそが、AI時代の医療者に求められる新しいリテラシーであり、医学と工学をつなぐ共通言語となるのです。
PyTorchを学ぶことは、この新しい思考法を体得するための、最も実践的で強力なトレーニングと言えるでしょう。
次回予告:モデルに命を吹き込む「学習」
設計図(モデル)は完成しました。しかし、今のままでは、まだ一度も患者さんを診たことのない「新人研修医」のような状態です。知識(パラメータ)が空っぽだからです。
次回は、このモデルに大量のデータを与え、正解との誤差を修正しながら賢くしていく「学習ループ(Training Loop)」の実装について詳説します。いよいよ、AIが学習を始める瞬間です。
参考文献
- Esteva, A. et al. (2017) 'Dermatologist-level classification of skin cancer with deep neural networks', Nature, 542(7639), pp. 115–118.
- Goodfellow, I., Bengio, Y. and Courville, A. (2016) Deep Learning. Cambridge, MA: MIT Press.
- LeCun, Y., Bengio, Y. and Hinton, G. (2015) 'Deep learning', Nature, 521(7553), pp. 436–444.
- Litjens, G. et al. (2017) 'A survey on deep learning in medical image analysis', Medical Image Analysis, 42, pp. 60–88.
- Nair, V. and Hinton, G.E. (2010) 'Rectified linear units improve restricted Boltzmann machines', in Proceedings of the 27th International Conference on Machine Learning (ICML-10), pp. 807–814.
- Paszke, A. et al. (2019) 'PyTorch: An imperative style, high-performance deep learning library', in Advances in Neural Information Processing Systems 32, pp. 8026–8037.
- Rajkomar, A. et al. (2018) 'Scalable and accurate deep learning with electronic health records', NPJ Digital Medicine, 1(1), p. 18.
- Rumelhart, D.E., Hinton, G.E. and Williams, R.J. (1986) 'Learning representations by back-propagating errors', Nature, 323(6088), pp. 533–536.
- Srivastava, N. et al. (2014) 'Dropout: a simple way to prevent neural networks from overfitting', The Journal of Machine Learning Research, 15(1), pp. 1929–1958.
- Topol, E.J. (2019a) Deep Medicine: How Artificial Intelligence Can Make Healthcare Human Again. New York: Basic Books.
- Topol, E.J. (2019b) 'High-performance medicine: the convergence of human and artificial intelligence', Nature Medicine, 25(1), pp. 44–56.
※本記事は情報提供を目的としたものであり、特定の治療法を推奨するものではありません。健康に関するご懸念やご相談は、必ず専門の医療機関にご相談ください。
ご利用規約(免責事項)
当サイト(以下「本サイト」といいます)をご利用になる前に、本ご利用規約(以下「本規約」といいます)をよくお読みください。本サイトを利用された時点で、利用者は本規約の全ての条項に同意したものとみなします。
第1条(目的と情報の性質)
- 本サイトは、医療分野におけるAI技術に関する一般的な情報提供および技術的な学習機会の提供を唯一の目的とします。
- 本サイトで提供されるすべてのコンテンツ(文章、図表、コード、データセットの紹介等を含みますが、これらに限定されません)は、一般的な学習参考用であり、いかなる場合も医学的な助言、診断、治療、またはこれらに準ずる行為(以下「医行為等」といいます)を提供するものではありません。
- 本サイトのコンテンツは、特定の製品、技術、または治療法の有効性、安全性を保証、推奨、または広告・販売促進するものではありません。紹介する技術には研究開発段階のものが含まれており、その臨床応用には、さらなる研究と国内外の規制当局による正式な承認が別途必要です。
- 本サイトは、情報提供を目的としたものであり、特定の治療法を推奨するものではありません。健康に関するご懸念やご相談は、必ず専門の医療機関にご相談ください。
第2条(法令等の遵守)
利用者は、本サイトの利用にあたり、医師法、医薬品、医療機器等の品質、有効性及び安全性の確保等に関する法律(薬機法)、個人情報の保護に関する法律、医療法、医療広告ガイドライン、その他関連する国内外の全ての法令、条例、規則、および各省庁・学会等が定める最新のガイドライン等を、自らの責任において遵守するものとします。これらの適用判断についても、利用者が自ら関係各所に確認するものとし、本サイトは一切の責任を負いません。
第3条(医療行為における責任)
- 本サイトで紹介するAI技術・手法は、あくまで研究段階の技術的解説であり、実際の臨床現場での診断・治療を代替、補助、または推奨するものでは一切ありません。
- 医行為等に関する最終的な判断、決定、およびそれに伴う一切の責任は、必ず法律上その資格を認められた医療専門家(医師、歯科医師等)が負うものとします。AIによる出力を、資格を有する専門家による独立した検証および判断を経ずに利用することを固く禁じます。
- 本サイトの情報に基づくいかなる行為によって利用者または第三者に損害が生じた場合も、本サイト運営者は一切の責任を負いません。実際の臨床判断に際しては、必ず担当の医療専門家にご相談ください。本サイトの利用によって、利用者と本サイト運営者の間に、医師と患者の関係、またはその他いかなる専門的な関係も成立するものではありません。
第4条(情報の正確性・完全性・有用性)
- 本サイトは、掲載する情報(数値、事例、ソースコード、ライブラリのバージョン等)の正確性、完全性、網羅性、有用性、特定目的への適合性、その他一切の事項について、何ら保証するものではありません。
- 掲載情報は執筆時点のものであり、予告なく変更または削除されることがあります。また、技術の進展、ライブラリの更新等により、情報は古くなる可能性があります。利用者は、必ず自身で公式ドキュメント等の最新情報を確認し、自らの責任で情報を利用するものとします。
第5条(AI生成コンテンツに関する注意事項)
本サイトのコンテンツには、AIによる提案を基に作成された部分が含まれる場合がありますが、公開にあたっては人間による監修・編集を経ています。利用者が生成AI等を用いる際は、ハルシネーション(事実に基づかない情報の生成)やバイアスのリスクが内在することを十分に理解し、その出力を鵜呑みにすることなく、必ず専門家による検証を行うものとします。
第6条(知的財産権)
- 本サイトを構成するすべてのコンテンツに関する著作権、商標権、その他一切の知的財産権は、本サイト運営者または正当な権利を有する第三者に帰属します。
- 本サイトのコンテンツを引用、転載、複製、改変、その他の二次利用を行う場合は、著作権法その他関連法規を遵守し、必ず出典を明記するとともに、権利者の許諾を得るなど、適切な手続きを自らの責任で行うものとします。
第7条(プライバシー・倫理)
本サイトで紹介または言及されるデータセット等を利用する場合、利用者は当該データセットに付随するライセンス条件および研究倫理指針を厳格に遵守し、個人情報の匿名化や同意取得の確認など、適用される法規制に基づき必要とされるすべての措置を、自らの責任において講じるものとします。
第8条(利用環境)
本サイトで紹介するソースコードやライブラリは、執筆時点で特定のバージョンおよび実行環境(OS、ハードウェア、依存パッケージ等)を前提としています。利用者の環境における動作を保証するものではなく、互換性の問題等に起因するいかなる不利益・損害についても、本サイト運営者は責任を負いません。
第9条(免責事項)
- 本サイト運営者は、利用者が本サイトを利用したこと、または利用できなかったことによって生じる一切の損害(直接損害、間接損害、付随的損害、特別損害、懲罰的損害、逸失利益、データの消失、プログラムの毀損等を含みますが、これらに限定されません)について、その原因の如何を問わず、一切の法的責任を負わないものとします。
- 本サイトの利用は、学習および研究目的に限定されるものとし、それ以外の目的での利用はご遠慮ください。
- 本サイトの利用に関連して、利用者と第三者との間で紛争が生じた場合、利用者は自らの費用と責任においてこれを解決するものとし、本サイト運営者に一切の迷惑または損害を与えないものとします。
- 本サイト運営者は、いつでも予告なく本サイトの運営を中断、中止、または内容を変更できるものとし、これによって利用者に生じたいかなる損害についても責任を負いません。
第10条(規約の変更)
本サイト運営者は、必要と判断した場合、利用者の承諾を得ることなく、いつでも本規約を変更することができます。変更後の規約は、本サイト上に掲載された時点で効力を生じるものとし、利用者は変更後の規約に拘束されるものとします。
第11条(準拠法および合意管轄)
本規約の解釈にあたっては、日本法を準拠法とします。本サイトの利用および本規約に関連して生じる一切の紛争については、東京地方裁判所を第一審の専属的合意管轄裁判所とします。
For J³, may joy follow you.

