論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説
📚 用語解説
PyTorch
PyTorch
ライブラリ

🔖 キーワード索引

PyTorchTensorautogradnn.ModuleGPUPythonic動的グラフMeta
#tensor#autograd#nn.Module#torch.compile#GPU#CUDA#PyTorch Lightning#HuggingFace

「pytorch」は統計データ分析の文脈で扱う重要概念のひとつ。 本ページでは「pytorch」を取り巻く中核キーワードを以下にチップで一覧化する。 各キーワードは関連する概念・手法・道具立てを含み、 文献検索や学習計画の起点になる。

pytorch統計分析SSDSE-B-2026前提条件適用範囲落とし穴関連手法Python 実装検証方法

これらのキーワードは「pytorch の理解 → 適用 → 検証」のプロセスを構成する。 各章で詳しく解説する。

💡 30秒で分かる結論

🍰 まずはやさしく

AIを作るための便利な道具箱です。

深層学習という技術を簡単に使うために使います。

スマホのAIアプリを作る時などに役立ちます。

この章ではPyTorchの特徴や注意点を読みます。

Meta の深層学習フレームワーク

∑ 数学的導出: PyTorch の数式の出どころ

本ページの「📐 定義・数式」セクションでは結果だけを示しましたが、 ここではより詳細に導出を追います。 数式の出どころが分かると、 公式を暗記するのではなく、 必要に応じて再導出できるようになります。

PyTorch の autograd の数学を厳密化します。 計算グラフ $G = (V, E)$ は有向非巡回グラフで、 ノードはテンソル、 エッジは演算 (Function オブジェクト) を表します。 出力テンソル $L$ (スカラー、 通常は損失関数) について、 リーフテンソル $\theta_1, \dots, \theta_K$ に関する勾配 $\frac{\partial L}{\partial \theta_k}$ を計算するのが L.backward() の役割。 連鎖律の再帰式は、 各中間ノード $v$ に対する随伴変数 $\bar{v} = \frac{\partial L}{\partial v}$ について、 $v$ の出力先ノード $u_1, \dots, u_m$ から $\bar{v} = \sum_j \bar{u}_j \frac{\partial u_j}{\partial v}$ と書けます。 ベクトル・行列演算では Jacobian-vector product (JVP) と vector-Jacobian product (VJP) を区別し、 reverse-mode AD では VJP $\bar{v}^\top = \bar{u}^\top J$ を効率的に計算します。 PyTorch の Function クラスはこの VJP を backward() メソッドとして実装し、 ユーザーは順方向計算 (forward()) と逆方向計算 (backward()) をペアで定義できます。 数値精度の問題として、 (a) gradient explosion: $\|\nabla L\| \to \infty$ → gradient clipping (torch.nn.utils.clip_grad_norm_) で対処、 (b) gradient vanishing: $\|\nabla L\| \to 0$ → 適切な活性化関数・初期化・residual connection で対処、 (c) NaN propagation: torch.isnan で検出・torch.autograd.detect_anomaly モードでデバッグ可能。 混合精度学習 (torch.cuda.amp) では fp16 と fp32 を混用してメモリ削減・速度向上を図ります。

📍 あなたが今見ているもの

🍰 まずはやさしく

AI開発に欠かせない共通の言語のようなものです。

最新のAIモデルを実際に動かすために使います。

人口の予測など、データの分析に活用できます。

この章では具体的な使い方と実装の流れを読みます。

Hugging Face、 Stable Diffusion、 LLaMA、 ChatGPT — 現代の AIモデルのほぼすべては PyTorch(または TF)で書かれています。 機械学習を実装するなら避けて通れません。

本ページの主目的は「PyTorch の核 (torch.Tensor / autograd / nn.Module / optim) を SSDSE-B-2026 の人口予測タスクで実装する」ことである。 NumPy と同じ API で書けるが、 自動微分と GPU 対応の二点で深層学習向け基盤となる。

後段では (1) Tensor と DataFrame の相互変換、 (2) 総人口 → 出生数 の単回帰を nn.Linear + SGD で学習し sklearn の解析解と R² が一致することを確認、 (3) MLP に拡張した際の損失曲線を扱う。

🎨 直感で掴む

🍰 まずはやさしく

計算が得意な電卓に自動機能がついたようなものです。

難しい計算をコンピュータに任せて学習させるために使います。

部活の記録を分析して、次の目標を立てる時に似ています。

この章では計算が自動で終わる仕組みについて読みます。

NumPy が CPU で動く配列計算ライブラリだとすれば、 PyTorch は GPU 対応 + 自動微分 付き NumPy です。 x = torch.tensor([1., 2.], requires_grad=True) と書けば、 後の演算が自動的に微分可能になり、 学習に必要な勾配が .backward() 1 回で全部計算されます。

SSDSE-B-2026 で都道府県の総人口から出生数を予測する単回帰を考えてみよう。 NumPy だと w, b の偏微分を手計算してから勾配降下法を書く必要があるが、 PyTorch なら loss = ((w*x + b - y)**2).mean() → loss.backward() だけで w.grad, b.grad が自動的に埋まる。 47 サンプルなら CPU でも一瞬だが、 同じコードを .to('cuda') 一つ付けるだけで GPU に乗り、 数万サンプルでも数秒で学習が終わる。 これが PyTorch の「研究から本番まで同じコードで動く」の意味。

もう一つの直感: PyTorch は「動的計算グラフ (define-by-run)」を採用しているため、 通常の Python の if/for/while がそのまま動く。 例えば「東京都の場合だけ追加の特徴量を使う」といった分岐を、 普通の Python コードで書ける。 これに対し TensorFlow 1.x の静的グラフは事前にグラフ全体を組み立てる必要があり、 デバッグが難しかった。 PyTorch のスタイルは「学習も推論も、 普通の Python 関数を書く感覚」を実現する点で初心者にとって学習コストが低い。

📐 定義/数式

🍰 まずはやさしく

AIの学習ルールを決めるための設計図です。

正解に近づくための計算を正確に行うために使います。

テストの点数を上げるために、間違えた原因を探る作業に似ています。

この章では計算の仕組みや数式について読みます。

PyTorch(PyTorch):Meta の深層学習フレームワーク

【主定義: 自動微分(autograd)の仕組み — 連鎖律】
$$ \frac{\partial L}{\partial \theta_i} = \sum_{\text{path}} \prod_{\text{node}} \frac{\partial \text{out}}{\partial \text{in}} $$
計算グラフを後ろから辿って連鎖律を適用 → 全パラメータ $\theta_i$ の勾配を 1 回の .backward() で取得。 PyTorch は 動的計算グラフ (define-by-run): 順伝播のたびに grad_fn がノードに記録される。 計算量は順伝播の定数倍 (リバースモード自動微分の理論的保証, Baur-Strassen 1983)。
【別表現 1: SGD 更新則 (torch.optim.SGD)】
$$ \theta_{t+1} = \theta_t - \eta\, \nabla_\theta L(\theta_t) $$
最も基本的な最適化アルゴリズム。 学習率 $\eta > 0$ (典型値 $10^{-3} \sim 10^{-1}$)、 モメンタム付きは $v_{t+1} = \mu v_t + \nabla L$, $\theta_{t+1} = \theta_t - \eta v_{t+1}$ ($\mu \approx 0.9$)。 PyTorch では torch.optim.SGD(params, lr=0.01, momentum=0.9) で指定。
【別表現 2: Adam 更新則 (torch.optim.Adam, Kingma & Ba 2014)】
$$ \begin{aligned} m_t &= \beta_1 m_{t-1} + (1-\beta_1)\nabla L \\ v_t &= \beta_2 v_{t-1} + (1-\beta_2)(\nabla L)^2 \\ \hat m_t &= \frac{m_t}{1-\beta_1^t},\ \ \hat v_t = \frac{v_t}{1-\beta_2^t} \\ \theta_{t+1} &= \theta_t - \eta\, \frac{\hat m_t}{\sqrt{\hat v_t}+\epsilon} \end{aligned} $$
推奨条件: $\eta = 10^{-3}$, $\beta_1=0.9$, $\beta_2=0.999$, $\epsilon = 10^{-8}$ (PyTorch デフォルト)。 1 次モーメント $m_t$ (勾配の指数移動平均) と 2 次モーメント $v_t$ (二乗勾配の指数移動平均) で各パラメータに適応的学習率を割り当てる。 SSDSE-B-2026 の小規模回帰には SGD で十分だが、 大規模 NN では Adam がデファクト。
【別表現 3: ミニバッチ勾配の不偏推定】
$$ \nabla_\theta L(\theta) \approx \frac{1}{|\mathcal{B}|}\sum_{i \in \mathcal{B}} \nabla_\theta \ell(y_i, f_\theta(x_i)),\quad \mathbb{E}[\hat\nabla L] = \nabla L $$
バッチサイズ $|\mathcal{B}|$ (通常 32〜512) は全データ $n$ よりずっと小さく、 確率的勾配 $\hat\nabla L$ は不偏推定量となる。 計算コストは $|\mathcal{B}|/n$ に比例、 分散は $\sigma^2/|\mathcal{B}|$。 PyTorch では DataLoader(dataset, batch_size=64, shuffle=True) がこの $\mathcal{B}$ をランダム生成。

🔬 記号・用語の読み解き

記号意味
TensorGPU対応多次元配列(NumPy ndarray の親戚)
autograd計算履歴を記録して自動微分する仕組み
nn.Moduleモデルの基底クラス(forward を定義)
optimSGD, Adam等のオプティマイザ

📐 の 3 つの式を、 記号ごとに PyTorch のどの部品に当たるかへ読み替える。

記号意味PyTorch での姿
$L$損失(スカラー)loss = ((pred - y)**2).mean() の loss。 backward() はスカラーからしか呼べない
$\theta_i$学習するパラメータrequires_grad=True の葉テンソル(nn.Linear の weight・bias)
$\partial \text{out}/\partial \text{in}$1 つの演算の局所微分各テンソルの grad_fn(MulBackward0・AddBackward0 など)が持つ逆伝播の規則
$\sum_{\text{path}}\prod_{\text{node}}$出力から $\theta_i$ までの経路ごとに局所微分を掛け、 経路について足す(連鎖律)loss.backward() が計算グラフを出力側からたどって行う。 結果は theta.grad に足し込まれるので、 毎回 zero_grad() で 0 に戻す
$\eta$学習率optim.SGD(params, lr=0.01) の lr
$m_t,\ v_t$勾配とその 2 乗の指数移動平均optim.Adam が内部に持つ状態(optimizer.state)。 保存・再開のときは重みと一緒に保存する
$\beta_1,\ \beta_2$移動平均の減衰率betas=(0.9, 0.999)(既定値)
$\mathcal{B}$ミニバッチ(データの一部)DataLoader(dataset, batch_size=..., shuffle=True) が 1 回ずつ返すまとまり

読み方の要点は 2 つある。 1 つは、 連鎖律の式が「経路の和」になっていること。 同じパラメータが 2 か所で使われると経路が 2 本になり、 勾配はその和になる。 PyTorch が .grad に上書きせず足し込むのは、 この和を 1 経路ずつ積み上げるためで、 そのせいで zero_grad() を忘れると前回の勾配まで足されてしまう(このページの 🧭 追補で実測している)。 もう 1 つは、 ミニバッチの勾配が全データの勾配の不偏推定だということ。 平均的には正しい向きを指すが 1 回ごとにはぶれるので、 学習曲線はぎざぎざになる。

🧮 実値で計算してみる

PyTorch の核心は autograd(自動微分)です。 例: $y = x^2$ で $x=3$ → $y=9$、 解析的に $\frac{dy}{dx}=2x=6$。 PyTorch なら y.backward(); x.grad で 6 が得られます。

🧮 数式に値を入れて手で計算する: 合成関数の自動微分

合成関数 $f(x) = 3x^2 + 2x + 1$ の自動微分 $df/dx$ を $x=4$ で計算します。

Step 1: 解析解

合成関数を微分:

f(x) = 3x² + 2x + 1 f'(x) = d/dx(3x²) + d/dx(2x) + d/dx(1) = 6x + 2 + 0 = 6x + 2 x=4 を代入: f'(4) = 6 × 4 + 2 = 24 + 2 = 26

Step 2: 自動微分 (PyTorch)

計算グラフを構築して backward() で逆伝播:

x = torch.tensor(4.0, requires_grad=True) y = 3*x**2 + 2*x + 1 # y = 57 (forward) y.backward() # ∂y/∂x を計算 x.grad → tensor(26.) # ← 解析解と一致

Step 3: SSDSE-B-2026 でのミニ線形回帰 (Loss = MSE)

SSDSE-B-2026 の総人口 (x) → 出生数 (y) を線形回帰 ŷ = wx + b で予測、 MSE 損失の勾配を autograd で計算します:

手計算 (n=5 都道府県、 w=0.5、 b=0 で初期化): L = (1/n) Σ(y_i - (w·x_i + b))² ∂L/∂w = -(2/n) Σ x_i (y_i - ŷ_i) ∂L/∂b = -(2/n) Σ (y_i - ŷ_i) 上位 5 県 x = [14.09, 9.23, 8.76, 7.48, 7.33] (総人口, 百万人) y = [86.3, 54.0, 55.3, 48.4, 42.1] (出生数, 千人) ŷ初期 = 0.5 × [14.09, 9.23, ...] = [7.04, 4.61, ...] 残差 = y - ŷ = [79.3, 49.4, 50.9, 44.7, 38.4] ∂L/∂w = -(2/5) × (14.09×79.3 + ...) ≈ -1053.9 ∂L/∂b = -(2/5) × (79.3 + 49.4 + ...) ≈ -105.1

Step 4: 自動微分結果との一致

PyTorch: w = torch.tensor(0.5, requires_grad=True) b = torch.tensor(0.0, requires_grad=True) L = ((y - (w*x + b))**2).mean() L.backward() w.grad → tensor(-1053.9) ← 一致 b.grad → tensor(-105.1) ← 一致

🐍 Python で再現

1
2
3
4
5
6
7
import numpy as np
# numpy で手動微分
def f(x): return 3*x**2 + 2*x + 1
def df(x): return 6*x + 2
x = 4
print(f"f(x) = {f(x)}")
print(f"df/dx = {df(x)}")

📤 実行結果

f(x) = 57 df/dx = 26

💬 手計算 (Step 1) 26 と Python 出力が完全一致。

🐍 Python での実装例

SSDSE-B-2026 などの実データを使った最小コード(約 20 行):

🎯 このコードでやること:PyTorch の nn.Linear + SGD で SSDSE-B-2026(2023 年・47 都道府県)の総人口 → 出生数の単回帰を 200 エポック学習させ、 損失が下がって重み w が最小二乗解(約 6.10)に近づいていくことを確認する。 PyTorch の自動微分・パラメータ更新の最小例。

📥 入力:data/raw/SSDSE-B-2026.csv を英語コードの列名で読み、 2023 年度の 47 行に絞った総人口 A1101(百万人に換算した x、 例: 東京都 14.086)と出生数 A4101(千人に換算した y、 例: 東京都 86.348)。 どちらも (47, 1) の torch.tensor にして渡す。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pandas as pd, torch, torch.nn as nn
torch.manual_seed(0)

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026']==2023][['A1101','A4101']].dropna()  # 総人口・出生数

x = torch.tensor(df['A1101'].values, dtype=torch.float32).view(-1,1) / 1e6   # 総人口(百万人)
y = torch.tensor(df['A4101'].values, dtype=torch.float32).view(-1,1) / 1e3   # 出生数(千人)

model = nn.Linear(1, 1)
loss_fn = nn.MSELoss()
opt = torch.optim.SGD(model.parameters(), lr=0.01)

for epoch in range(200):
    y_pred = model(x)
    loss = loss_fn(y_pred, y)
    opt.zero_grad()
    loss.backward()
    opt.step()
    if (epoch+1) % 50 == 0:
        w = model.weight.item(); b = model.bias.item()
        print(f'epoch {epoch+1:3d}: loss={loss.item():.3f}, w={w:.3f}, b={b:.3f}')

📤 実行例:

epoch 50: loss=3.631, w=5.848, b=0.693 epoch 100: loss=2.992, w=5.950, b=0.147 epoch 150: loss=2.761, w=6.012, b=-0.181 epoch 200: loss=2.677, w=6.048, b=-0.379

💬 結果の読み方:200 epoch で loss が 3.63 → 2.68 と単調に下がり、 重み w は 6.05 付近に落ち着く。 x を百万人・y を千人単位に揃えているので、 これは「総人口が 100 万人増えるごとに出生数が約 6.0 千人増える」という関係。 PyTorch の backward() が自動微分で勾配を計算し、 opt.step() がパラメータを更新する流れが機能している。 torch.manual_seed(0) を置いてあるので、 この 4 行は手元でもそのまま再現する。

※ data/raw/SSDSE-B-2026.csv は e-Stat SSDSE から取得した実データを想定。

🐍 詳細実装例 (SSDSE-B-2026 適用版)

本ページ冒頭の Python 実装はミニマル版でした。 ここでは PyTorch を SSDSE-B-2026 の実データに適用する完全な実装例を掲載します。 コピペすればそのまま動く形になっています。 ファイルパスは data/raw/SSDSE-B-2026.csv を想定。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A4101(出生数) 北海道 24,430 東京都 86,348 沖縄県 12,549 …(全 47 行)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# === PyTorch で MLP を SSDSE-B-2026 に適用する完全例 ===
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split

torch.manual_seed(0)

# データ読み込み(2023 年度の 47 都道府県に絞る)
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]
# 出生数(男)・(女)は足すと出生数そのものなので説明変数から外す(リーク防止)
leak = ['SSDSE-B-2026', 'A4101', 'A410101', 'A410102']
X = df.select_dtypes(include='number').drop(columns=leak).dropna(axis=1).values
y = df['A4101'].values.reshape(-1, 1)  # 出生数を予測(総人口・高齢者数などから)

# 前処理(目的変数も標準化し、評価は元の単位=人に戻す)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)
sx, sy = StandardScaler().fit(X_train), StandardScaler().fit(y_train)

# Tensor 化
X_train_t = torch.tensor(sx.transform(X_train), dtype=torch.float32)
y_train_t = torch.tensor(sy.transform(y_train), dtype=torch.float32)
X_test_t = torch.tensor(sx.transform(X_test), dtype=torch.float32)
y_test_t = torch.tensor(sy.transform(y_test), dtype=torch.float32)
train_loader = DataLoader(TensorDataset(X_train_t, y_train_t), batch_size=8, shuffle=True)

# MLP モデル
class MLP(nn.Module):
    def __init__(self, in_dim):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(in_dim, 64), nn.ReLU(), nn.Dropout(0.2),
            nn.Linear(64, 32), nn.ReLU(),
            nn.Linear(32, 1)
        )
    def forward(self, x):
        return self.net(x)

device = 'cuda' if torch.cuda.is_available() else 'cpu'
model = MLP(X_train.shape[1]).to(device)
optimizer = optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4)
loss_fn = nn.MSELoss()
print(f'学習 {len(X_train)} 県 / テスト {len(X_test)} 県, 特徴量 {X.shape[1]} 列')

# 訓練ループ
for epoch in range(101):
    model.train()
    for xb, yb in train_loader:
        xb, yb = xb.to(device), yb.to(device)
        pred = model(xb)
        loss = loss_fn(pred, yb)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    if epoch % 20 == 0:
        model.eval()
        with torch.no_grad():
            p = model(X_test_t.to(device)).cpu().numpy()
        test_loss = loss_fn(torch.tensor(p), y_test_t).item()
        rmse = np.sqrt(np.mean((sy.inverse_transform(p) - y_test) ** 2))
        print(f'epoch {epoch}: train {loss.item():.4f} / test {test_loss:.4f} (RMSE {rmse:,.0f} 人)')

# 保存(保存先のフォルダが無ければ作る)
import os
os.makedirs('output', exist_ok=True)
torch.save(model.state_dict(), 'output/mlp_ssdse.pt')
📤 実行例(実測) 学習 37 県 / テスト 10 県, 特徴量 106 列 epoch 0: train 0.0903 / test 3.1903 (RMSE 22,014 人) epoch 20: train 0.0069 / test 0.4027 (RMSE 7,821 人) epoch 40: train 0.0421 / test 0.2026 (RMSE 5,548 人) epoch 60: train 0.0006 / test 0.5281 (RMSE 8,956 人) epoch 80: train 0.0031 / test 0.8228 (RMSE 11,180 人) epoch 100: train 0.0012 / test 0.5609 (RMSE 9,231 人)

💬 テストの損失は標準化した単位なので、元の人数に戻した RMSE で読む。epoch 40 で 5,548 人まで下がった後、訓練損失は 0.001 台まで落ちるのにテスト RMSE は 9,231 人に戻っており、37 県で 106 列を覚え込む過学習が起きている。テスト 10 県には出生数 86,348 人の東京都が入っていて学習データの範囲の外にあり、総人口 1 列の単回帰でもテスト RMSE は約 2,473 人なので、この規模のデータでは MLP より単純なモデルが勝つ。12 年度分 564 行を混ぜ、出生数(男)(女)を説明変数に残し、目的変数も標準化しないまま学習すると、損失が 10⁶〜10⁹ の桁になって意味を読み取れない。

⚠️ 実行前に pip install -r requirements.txt で依存パッケージをインストール。 また、 SSDSE-B-2026 の列名は版によって若干異なるので、 df.columns で確認のうえ実コードに合わせて読み替えてください。

🎮 触って理解する

PyTorch の心臓部 autograd(自動微分) を、 最小の計算グラフで体感する。 題材は単一ニューロン + 二乗損失: u = x·w + b, d = u − y, L = d²(目標値 y = 2.0 は定数)。 PyTorch で書けば w = torch.tensor(0.8, requires_grad=True) のように requires_grad=True を付けた葉テンソルだけが .grad に勾配を貯める。 スライダーで x・w・b を動かすと順伝播(forward)の各ノード値がリアルタイムに更新され、 backward() ボタンで連鎖律が計算グラフを逆流する様子をステップごとに追える。

y = 2.0 (定数) x (入力)1.500 w (葉・学習)0.800 b (葉・学習)0.500 m = x·w1.200 u = m + b1.700 d = u − y−0.300 L = d² (損失)0.090

緑 = requires_grad=True の葉テンソル(.grad を貯める) / 灰 = requires_grad=False の入力(勾配は保存されない) / 青 = 演算で作られる中間ノード(grad_fn を持つ) / 赤破線 = backward の勾配逆流

x(入力, requires_grad=False)
1.50
w(重み, requires_grad=True)
0.80
b(バイアス, requires_grad=True)
0.50
> スライダーを動かして forward を観察 → backward() で勾配を逆流させよう

📌 何が起きているか — 連鎖律の逆流

backward() が計算グラフを出力側から辿るとき、 各エッジで連鎖律(chain rule)を 1 回ずつ適用する。 このデモの解析的な勾配は次の通り(d = x·w + b − y):

例えば初期値 x=1.5, w=0.8, b=0.5 では d = −0.3 なので ∂L/∂w = 2×(−0.3)×1.5 = −0.900、 ∂L/∂b = −0.600(本ページ作成時に数値微分と照合し誤差 10⁻¹⁰ 以下で一致確認済み)。 勾配が負 → w を増やすと損失が下がる、 という向きの情報まで含むのが勾配の価値。 optimizer.step() は θ ← θ − η·∂L/∂θ を実行するだけである。 詳しい更新則は 勾配降下法・学習率、 多層への一般化は 誤差逆伝播法 を参照。

💡 直感: define-by-run とテンソルの正体

上のデモで「スライダーを動かすたびに forward が再計算される」ことに注目してほしい。 PyTorch の動的計算グラフ(define-by-run)とはまさにこれで、 グラフは事前に宣言するものではなく、 Python コードが実行された順にその場で記録される。 u = x*w + b と書いた瞬間に mul と add のノードが生え、 各中間テンソルに grad_fn(逆伝播用の局所微分の作り方)がぶら下がる。 だから if 県 == '東京都': のような分岐でグラフの形が毎回変わっても問題ない。 つまり PyTorch のテンソルとは「多次元配列(NumPy ndarray 相当)+ 勾配の置き場 (.grad) + 履歴 (grad_fn)」の三点セットである。 これに対し TensorFlow 1.x の静的グラフ(define-and-run)は先にグラフ全体を組んでから実行する方式だった(TF 2.x は eager 実行で PyTorch に接近)。

⚠️ デモで再現できる落とし穴

🚀 発展: 計算グラフ最適化と torch.compile

define-by-run は柔軟だが、 「毎回グラフを作り直す」オーバーヘッドと「演算を 1 個ずつ GPU カーネルとして発行する」非効率がある。 PyTorch 2.x の model = torch.compile(model) は、 実行トレースから計算グラフを捕捉(TorchDynamo)し、 複数の演算を 1 つの GPU カーネルに融合(kernel fusion)してコード生成(TorchInductor)する。 上のデモで言えば「mul → add → sub → square」の 4 ノードを 1 回のメモリ走査で済ませるイメージで、 典型的に 1.3〜2 倍の高速化が得られる。 動的な柔軟さで書き、 静的な最適化で走らせる — フレームワークの進化は両者のいいとこ取りに向かっている。 この構造を踏まえて ニューラルネットワーク や 深層学習 のページへ進むと、 「どんな巨大モデルも結局はこの小さなグラフの積み重ね」であることが腑に落ちるはずである。 損失関数の選び方は 損失関数 を参照。

⚠️ よくある落とし穴

❌ .to('cuda') 忘れでデバイス不一致
症状: RuntimeError: Expected all tensors to be on the same device で学習が即停止。 原因: SSDSE-B-2026 を torch.tensor(df.values) で作ると CPU、 model.to('cuda') 後の forward で衝突。 回避: device=torch.device('cuda' if torch.cuda.is_available() else 'cpu') を 1 箇所で定義し、 モデル・データ・loss の全てに .to(device) を一括適用。
❌ zero_grad() 忘れで勾配が累積
症状: epoch を重ねるほど loss が発散、 同じ SSDSE 回帰でも seed 固定なのに毎回違う収束値。 原因: PyTorch は backward の勾配を .grad に 加算するため、 ステップ冒頭でクリアしないと前バッチの勾配が混ざる。 回避: 各 batch 先頭で optimizer.zero_grad(set_to_none=True)。 RNN の BPTT で意図的に累積する場合は明示コメントを残す。
❌ model.eval() 忘れでテスト性能崩壊
症状: train loss 0.02 なのに val loss 0.8、 同じ入力で推論結果が毎回違う。 原因: BatchNorm が小さいバッチ統計を使う、 Dropout が推論時もマスクをかける。 回避: 評価ループ冒頭で model.eval() + with torch.no_grad():、 学習復帰時は model.train()。 ハイパラ探索ではこの切替を関数内に隠蔽するのが安全。
❌ loss を .item() せず蓄積してメモリリーク
症状: GPU メモリが epoch ごとに数百 MB ずつ増加、 数 epoch で CUDA out of memory。 原因: total_loss += loss と書くと loss が保持する計算グラフごと蓄積される。 回避: total_loss += loss.item() でスカラ化、 中間出力を保持する場合は .detach() でグラフから切り離す。 nvidia-smi で増加傾向を週次監視。
❌ DataLoader の num_workers / pin_memory 未設定で GPU が遊ぶ
症状: nvidia-smi の GPU 利用率が 20% 未満、 epoch 時間が CPU と変わらない。 原因: 既定の num_workers=0 は I/O が単一プロセスで GPU が待つ。 回避: DataLoader(..., num_workers=4, pin_memory=True, persistent_workers=True)。 SSDSE-B 程度の小データなら全件を一度に .to(device) で GPU 常駐させ I/O を排除。
❌ 勾配のゼロクリアー忘れ
`optimizer.zero_grad()` を呼び忘れると勾配が累積し、学習が崩壊します。各イテレーション冒頭で必ず実行してください。
❌ device 不一致エラー
テンソルとモデルが CPU と GPU に分かれているとエラーになります。`x.to(device)` と `model.to(device)` を両方確実に。
❌ eval モード切り替え忘れ
Dropout・BatchNorm を含むモデルは推論時に `model.eval()` を呼ばないと結果が学習時と異なります。
❌ In-place 演算の罠
`x += y` のような in-place 演算は autograd の計算履歴を壊すことがあります。`x = x + y` を基本に。

🗺 概念マップ — PyTorch の周辺地図

PyTorch 前提: 自動微分 / Tensor 並列: TensorFlow / JAX 発展: TorchScript / FSDP 応用: 画像分類 / NLP 基盤: CUDA / cuDNN 対比: scikit-learn

図の中心が PyTorch。 前提は自動微分と Tensor(NumPy 配列に勾配と GPU を足したもの)、 並列は同じ役割の TensorFlow・JAX、 発展は本番向けの書き出し(TorchScript)と大規模モデルの分散学習(FSDP)、 応用は画像分類や自然言語処理、 基盤は GPU で計算を動かす CUDA・cuDNN、 対比は深層学習を使わない scikit-learn。 SSDSE-B-2026 の 47 行のような小さな表データでは「対比」の scikit-learn で足りることが多く、 PyTorch が必要になるのは画像・文章のような大きなデータや、 自分でモデルの形を組みたいときである。

🗺 補講 R280-5: PyTorch エコシステムの「全体地図」

PyTorch を本格的に使い始めると、 単体ライブラリだけでは足りないことに気付く。 画像、 自然言語、 グラフ、 強化学習、 各領域に PyTorch 系の特化ライブラリがあり、 それらを「いつ使うか」を地図化しておくと、 新しい課題に対しても流用がきく。 本補講では PyTorch エコシステムを 5 領域に整理し、 各領域の代表ライブラリと「素の PyTorch から切り替えるべき判断基準」を示す。

▶ PyTorch エコシステム 5 領域

領域代表ライブラリ機能切替判断
画像torchvisionResNet/VGG 等の事前学習CNN を実装するとき
自然言語transformers (HuggingFace)BERT/GPT 等の API 化NLP タスク全般
グラフtorch_geometricGNN の高速実装グラフ構造を扱う時
強化学習stable-baselines3DQN/PPO 等の標準実装RL 環境を扱う時
高速化/分散PyTorch Lightning / DeepSpeed学習ループの抽象化本番運用 / 大規模学習

補講のまとめ: PyTorch は「ピュアな計算ライブラリ」と「領域特化ライブラリのエコシステム」の二層構造。 まず素の PyTorch で計算グラフと自動微分を理解し、 次に領域特化ライブラリで生産性を上げ、 最後に Lightning / DeepSpeed で運用品質を上げる、 という 3 段階で学習を進めるのが効率的。 TensorFlow との比較、 機械学習ライブラリ としての位置づけ、 深層学習 全体の中の役割を意識して学ぶこと。

📒 補講 R280-4: PyTorch 学習ループの「再現性」と「乱数シード」の正しい固定

機械学習の論文や実験ノートで「再現できない」「同じコードを動かしたのに結果が違う」と詰まる原因の 95% は乱数シードの固定が不完全だからである。 PyTorch は CPU / CUDA / cuDNN / Python 標準 random / NumPy / DataLoader の 6 経路で乱数を使い、 そのどれかを固定漏れすると非決定的になる。 本補講では、 完全に再現可能な PyTorch 学習を実現する 6 行のテンプレを示し、 SSDSE-B-2026 の単回帰で「同じ結果が必ず出る」ことを確認する。

▶ 再現性のための 6 シード固定テンプレ

経路固定コード影響範囲
Python 標準random.seed(s)標準 random
NumPynp.random.seed(s)np.random.* 全般
PyTorch (CPU)torch.manual_seed(s)CPU 演算の乱数
PyTorch (CUDA)torch.cuda.manual_seed_all(s)GPU 演算の乱数
cuDNNtorch.backends.cudnn.deterministic=True畳み込み実装
PYTHONHASHSEEDos.environ['PYTHONHASHSEED']='0'辞書の順序

補講のまとめ: 上記 6 経路をすべて固定して初めて「完全な再現性」が確保される。 ただし cuDNN の deterministic モードは速度が 1.2-1.5 倍遅くなるので、 開発時は固定、 本番学習では性能優先で外す、 の使い分けが現実解。 論文やコンペ提出時は再現性を最優先し、 commit hash と乱数シードを README に明記する習慣を持つこと。 機械学習ライブラリ としての品質保証の入口。

🚀 補講 R280-3: 学習時のメモリ・速度最適化テクニック

PyTorch でモデルが大きくなると、 GPU メモリ不足や学習時間の長期化が深刻になる。 ここでは「メモリ削減」「速度向上」の代表 5 手法を整理し、 SSDSE-B-2026 の単回帰例で使い分けの目安を示す。 小さなデータでも「常に最適化を意識する」癖が、 大規模化したときの安全網になる。

▶ 5 つの最適化手法と適用判断

手法効果適用基準注意点
Mixed Precision (FP16)メモリ半減、 1.5-2x 速GPU が対応 (V100以降)NaN リスク、 scaler 必須
Gradient Accumulation実効バッチサイズ拡大メモリ不足時学習時間は変わらない
Gradient Checkpointingメモリ 1/3 削減深いモデル計算量 1.3x 増
DataLoader workersI/O 並列化ディスク律速時num_workers=4-8 が目安
torch.compileグラフ最適化で 1.3-2x 速PyTorch 2.0+初回コンパイル時間

補講のまとめ: PyTorch は「動的かつ柔軟」な反面、 メモリ・速度の管理は開発者の責任。 まず Mixed Precision と DataLoader の num_workers 設定を試し、 それでも足りなければ Gradient Accumulation / Checkpointing を投入する順序が定石。 大規模学習では torch.compile や DistributedDataParallel を組み合わせ、 GPU クラスタ環境での効率を引き上げる。 各最適化は単独でも効くが、 組み合わせて初めて生産性が劇的に上がる。

⚙️ 補講 R280-2: PyTorch の「テンソル形状」を間違えない読み方

PyTorch を実装していて最も時間を奪うエラーは「shape mismatch」である。 入力テンソルの形状 (batch_size, channels, height, width) を、 各層がどう変形して次の層に渡すのか、 を頭の中で正確にトレースできるかが鍵。 本補講では SSDSE-B-2026 の都道府県データを CNN に入力する仮想シナリオを通じて、 テンソル形状の変化を 1 ステップずつ追う技術を解説する。 同じ思考プロセスは CNN や RNN でも共通して使える。

▶ テンソル形状読みのチェックポイント

層入力形状出力形状変換式
Linear(in, out)(B, in)(B, out)最終次元のみ変換
Conv2d(in, out, k, p)(B, in, H, W)(B, out, H', W')H' = (H+2p-k)/s + 1
MaxPool2d(k)(B, C, H, W)(B, C, H/k, W/k)空間サイズを 1/k
Flatten()(B, C, H, W)(B, C*H*W)空間を 1 次元化
view(B, -1)(任意)(B, 残り)バッチを保ち残り flat

▶ 形状ロガーで CNN の各層を追跡

このコードでやること: SSDSE 都道府県データの 1 列 (人口) を擬似 1D 画像と見なし、 Conv1d で特徴抽出。 各層の入出力形状を print で確認し、 形状が想定通りに変化することを目視。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) 北海道 5,092,000 東京都 14,086,000 沖縄県 1,468,000 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import pandas as pd, torch, torch.nn as nn
torch.manual_seed(0)   # 実行のたびに同じ結果が出るようにする

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
pop = df[df['SSDSE-B-2026']==2023]['A1101'].values  # 総人口 47 値

# Tensor (batch=1, channels=1, length=47) として配置
x = torch.tensor(pop, dtype=torch.float32).view(1, 1, 47) / 1e6

class ShapeLogger(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv1d(1, 8, kernel_size=3, padding=1)
        self.pool = nn.MaxPool1d(2)
        self.conv2 = nn.Conv1d(8, 16, kernel_size=3, padding=1)
        self.fc = nn.Linear(16*11, 1)
    def forward(self, x):
        print(f'input: {tuple(x.shape)}')
        x = self.conv1(x); print(f'after conv1: {tuple(x.shape)}')
        x = self.pool(x);  print(f'after pool1: {tuple(x.shape)}')
        x = self.conv2(x); print(f'after conv2: {tuple(x.shape)}')
        x = self.pool(x);  print(f'after pool2: {tuple(x.shape)}')
        x = x.view(x.size(0), -1); print(f'after flatten: {tuple(x.shape)}')
        x = self.fc(x);    print(f'output: {tuple(x.shape)}')
        return x

model = ShapeLogger()
_ = model(x)

📤 実行例:

input: (1, 1, 47) after conv1: (1, 8, 47) after pool1: (1, 8, 23) after conv2: (1, 16, 23) after pool2: (1, 16, 11) after flatten: (1, 176) output: (1, 1)

💬 結果の読み方: Conv1d は (B, C, L) の形状で動作する。 conv1 では padding=1, kernel=3 なので長さは保たれ 47 のまま、 チャネル数だけ 1→8 に増加。 pool で長さが半分 (47/2=23、 23/2=11) に縮小。 最後の Linear 入力は 16*11=176 で合っている必要がある。 ここを間違えると RuntimeError: mat1 and mat2 shapes cannot be multiplied。 デバッグ時はこの形状ロガーを nn.Module の forward に貼り付けるのが定石。

▶ 学習済モデル保存と復元の標準パターン

このコードでやること: 学習したパラメータを state_dict 経由で保存・復元する。 モデル本体は別途定義してから load_state_dict する「分離保存」が PyTorch の推奨。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import torch, torch.nn as nn
torch.manual_seed(0)   # 実行のたびに同じ結果が出るようにする

# 1) 学習したモデル (例: 総人口 → 出生数 の単回帰)
model = nn.Linear(1, 1)
with torch.no_grad():
    model.weight.fill_(6.048)
    model.bias.fill_(-0.379)

# 2) 保存 (state_dict のみ、 推奨)
torch.save(model.state_dict(), 'pytorch_linear.pt')

# 3) 復元 (同じ構造のモデルを作ってから load)
model2 = nn.Linear(1, 1)
model2.load_state_dict(torch.load('pytorch_linear.pt'))
model2.eval()  # 推論モードに切替 (dropout/batchnorm の挙動が変わる)

print('w:', model2.weight.item(), 'b:', model2.bias.item())
print('出生数予測 (総人口 1000 万):', model2(torch.tensor([[10.0]])).item())

📤 実行例:

w: 6.047999858856201 b: -0.3790000081062317 出生数予測 (総人口 1000 万): 60.10099792480469

💬 結果の読み方: 保存した重み (w=6.048, b=-0.379=前節の 200 epoch 後の値) が復元後も完全一致。 item() は float32 をそのまま出すので 6.047999… と表示されるが、 これは丸め表示の差であって値は一致している。 推論結果 60.10(千人)は w*10 + b(総人口 1000 万人 → x=10)の計算と一致。 「state_dict のみ保存」がベストプラクティスで、 モデル構造は別途コードで管理する分離設計。 これにより PyTorch のバージョン互換性も保たれる。 詳細は公式 機械学習ライブラリ ガイドラインも参照。

📊 PyTorch のDataLoader でミニバッチ学習

このコードでやること: Dataset と DataLoader を組み合わせ、 47 都道府県データを「ミニバッチ単位」で学習する。 大規模データではメモリに全部載らないので、 ミニバッチ取り出しは必須。 SSDSE-B-2026 のような数十行のデータでも、 同じ仕組みを「縮小版」で確認しておくと、 後で大規模化したときにスムーズに移行できる。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A4101(出生数) 北海道 5,092,000 24,430 東京都 14,086,000 86,348 沖縄県 1,468,000 12,549 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import pandas as pd, torch
from torch.utils.data import Dataset, DataLoader

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026']==2023][['A1101','A4101']].dropna()  # 総人口・出生数

class PrefDataset(Dataset):
    def __init__(self, df):
        self.x = torch.tensor(df['A1101'].values, dtype=torch.float32).view(-1,1) / 1e6
        self.y = torch.tensor(df['A4101'].values, dtype=torch.float32).view(-1,1) / 1e3
    def __len__(self): return len(self.x)
    def __getitem__(self, i): return self.x[i], self.y[i]

ds = PrefDataset(df)
dl = DataLoader(ds, batch_size=8, shuffle=True)

for batch_x, batch_y in dl:
    print(f'バッチ形状: x={tuple(batch_x.shape)}, y={tuple(batch_y.shape)}')
    break
print(f'\n全 {len(ds)} 件 / バッチサイズ 8 → {len(dl)} バッチ')

📤 実行例:

バッチ形状: x=(8, 1), y=(8, 1) 全 47 件 / バッチサイズ 8 → 6 バッチ

💬 結果の読み方: 47 件を 8 件ずつ取り出すと 6 バッチ (47÷8=5.875→6 バッチ目は 7 件)。 shuffle=True で毎エポックの並び順が変わり、 確率的勾配降下 (SGD) の効果が発揮される。 画像や音声の大規模データセットでも同じ DataLoader パターンを使う。 並列読み込みは num_workers で制御し、 GPU では pin_memory=True で転送高速化。 バッチ、 勾配降下法 のページと併読推奨。

PyTorch 前提: テンソル / 自動微分 並列: TensorFlow / JAX 発展: Lightning / Ignite 応用: CV / NLP / 強化学習 対比: scikit-learn 統合: torchvision / Hugging Face

🔗 隣接手法への橋渡し

「PyTorch」は単独で完結する手法ではなく、 隣接領域と 接続 / 統合 / 比較 の 3 視点で連携して初めて真価を発揮する。 章 10 が派生カタログ、 章 15 が初期分岐、 本セクションは 実務で隣接技術と組み合わせる切替判断 に集中する。

🔌 接続: 上流 → PyTorch → 下流

PyTorch を中心に、 データ取得から本番デプロイまでの一気通貫パイプラインの隣接ノードを整理:

🧬 統合: PyTorch ×「他手法」の合わせ技 (3 セット)

PyTorch 単独では効果が限定的、 以下 3 つの隣接手法と組み合わせると爆発的に価値が出る:

⚖️ 比較: 類似 DL/ML フレームワークとの切替判断

「PyTorch vs 他フレームワーク」の選定で迷ったらこの表 (相補的 = 用途で使い分け、 競合的 = 1 つを選ぶ):

手法PyTorch に切り替える条件利点注意点
TensorFlow / Keras研究プロトタイピングが多い、 動的計算グラフが必要、 制御フロー (if/while) を含むモデルPythonic で debug 容易、 PyTorch Lightning で訓練ループ抽象化、 学術界圧倒的シェア本番デプロイは TF Serving の方が成熟、 mobile (TFLite) なら TF 優位
JAX関数型プログラミングに慣れている、 jit/vmap/grad を多用、 TPU を使うJAX の方が高速、 vmap で並列化容易、 関数型で副作用なし学習コスト高、 エコシステム小、 動的形状に弱い (再 trace)。 ただし torch.func で PyTorch から類似 API も利用可
scikit-learn表形式データ、 サンプル数 < 10 万、 深層学習が過剰線形・木・SVM 等が一行で書ける、 ハイパラ探索も GridSearchCV で楽GPU 利用不可、 画像/音声/テキストは PyTorch の方が圧倒的に有利
XGBoost / LightGBMSSDSE-B-2026 のような表形式中規模データ、 解釈性も欲しいKaggle テーブル系で勝率高、 SHAP で特徴重要度即提示、 訓練高速画像/テキストの内在表現学習はできない、 シーケンス処理苦手
ONNX Runtime / TensorRT本番推論のレイテンシ削減が目標、 PyTorch で訓練済みモデルを最適化推論 2–10x 高速、 メモリ削減、 cross-platform (Win/Mac/Mobile/Edge)変換時のオペレータ非対応エラー、 動的形状で trace 失敗の事例あり

PyTorch を選ぶべき場面の要約: (i) 研究・実験プロトタイピング、 (ii) 動的計算グラフ (RNN・条件分岐)、 (iii) HuggingFace の事前学習モデル活用、 (iv) 巨大モデルの分散訓練 — のいずれかが該当するなら PyTorch 一択。 SSDSE-B-2026 のような小規模表形式データのみなら scikit-learn / XGBoost で十分。

🌳 手法選択フロー — PyTorch を選ぶか

「PyTorch を使うべきか」を判断するためのフローチャート。 上から順番に Yes/No で答えていけば、 自分の問題に最適な手法選定にたどり着きます。

  1. 1. 研究・実験用途か? Yes → PyTorch 一択 / プロトタイピングが多いなら必須。
  2. 2. 本番デプロイ用途か? Yes → TorchScript or ONNX で書き出し、 TorchServe / Triton / ONNX Runtime で推論。
  3. 3. モバイル展開? Yes → PyTorch Mobile or Core ML 変換 (iOS) / TFLite 変換 (Android)。
  4. 4. 巨大モデル (LLM) の訓練? PyTorch + FSDP or DeepSpeed for マルチノード分散訓練。
  5. 5. GPU は必要か? ローカル開発なら CPU で十分。 学習が遅いと感じたら Google Colab・Kaggle Notebook (無料 GPU)・AWS EC2 / GCP / Azure へ。
  6. 6. 初学者なら何から? 公式チュートリアル「60 分で学ぶ PyTorch」→ PyTorch Lightning で訓練ループ抽象化 → HuggingFace で事前学習モデル活用。

🧭 深掘り追記 ── 直感・落とし穴・発展(追補)

本節は既存の解説を壊さずに補う追記です。 PyTorch の核心である autograd(自動微分)と define-by-run(動的計算グラフ)に絞り、 SSDSE-B-2026 の実測値と再現コードで確かめます。 姉妹ページ TensorFlow が「テンソルを流す(Flow)+小データで MLP が過学習する」という角度だったのに対し、 本節は「勾配が正しく計算されているか」「その勾配がなぜ累積するのか」という PyTorch 固有の挙動を数値で追います。 数値はすべて手元で計算した実測であり、 合成した箇所は「架空」と明記します。

🎨 直感の追補 ── autograd は「テープ」に演算を録画する

PyTorch の define-by-run(実行しながら定義)とは、 Python が 1 行ずつ順伝播を実行するその瞬間に計算グラフを組み立てる方式です。 requires_grad=True のテンソルが関わる演算は、 まるでテープ(tape)に録画されるように順番に記録され、 loss.backward() を呼ぶとテープを逆再生して連鎖律で各葉(leaf)テンソルの勾配 $\partial L/\partial w$ を .grad に書き込みます。 グラフは毎回の順伝播で作り直されるので、 if や for でモデルの形をイテレーションごとに変えられる ── これが静的グラフ時代の TF1 に対する PyTorch の決定的な武器で、 可変長系列や再帰的モデルを自然に書けます。 仕組みの根っこは バックプロパゲーション、 更新則は 勾配降下法 を参照。 なお autograd と tensor の単独ページは本サイト未整備のため、 ここではテキストで補います。

本当に正しい勾配が出ているのか? を SSDSE-B-2026 の実測で確認します。 最新年 2023 年・47 都道府県、 総人口(A1101) を $x$、 65歳以上人口(A1303) を $y$ とし、 両者を標準化して単回帰 $\hat y = w x + b$、 損失は MSE とします。 標準化データでは損失の勾配は解析的に $\partial L/\partial w = -2\,\overline{x z\, y z}$ と書け、 重み初期値 $w=0,\,b=0$ での理論値と PyTorch の autograd を突き合わせると次の通り完全一致します。

量(2023年・47県・標準化・実測)値読み取り
相関 corr(A1101 総人口, A1303 高齢人口)0.991人口が多い県は高齢者も多い(当然の強相関)
標準化データの閉形式スロープ w*(= 相関)0.990979単回帰の最小二乗解
解析的勾配 ∂L/∂w(w=0,b=0)= −2·mean(xz·yz)−1.981958手計算の理論値
PyTorch autograd の w.grad−1.981958理論値と完全一致(autograd は正しい)
解析的・autograd の ∂L/∂b0.0標準化で平均 0 のため切片の勾配は 0

注意:この 0.991 は「総人口が多い県は高齢人口も多い」というほぼ自明な強相関で、 統計的発見ではありません。 ここでの目的はautograd が手計算の微分と一致することを確かめる数値デモです。

⚠️ 落とし穴の追補(重要) ── zero_grad を忘れると勾配は「足し算」される

PyTorch 最大の初学者トラップは .grad が上書きではなく加算されることです。 backward() を呼ぶたびに、 新しい勾配が既存の .grad に足し込まれます。 上と同じデータで、 zero_grad() を挟まずに backward() を 2 回呼ぶと ──

操作(同一データ・実測)w.grad読み取り
1 回目の backward()−1.981958正しい勾配
2 回目の backward()(zero_grad なし)−3.963915ちょうど 2 倍=前回分に加算された

この「加算」はバグではなく仕様です。 勾配累積(gradient accumulation)で擬似的に大きなバッチを作る、 RNN で複数の損失を合算する、 といった高度な使い方をこの性質が支えています。 だからこそ通常の学習ループでは毎ステップ optimizer.zero_grad()(または model.zero_grad())で明示的にリセットする必要があります。 忘れると勾配がイテレーションごとに膨らみ、 実効学習率が暴走して損失が発散します。 なお本ページ上部の「🎮 触って理解する」ウィジェットでも、 この zero_grad の効果を手で確かめられます。

その他、 PyTorch で踏みやすい固有の落とし穴(既存「⚠️ よくある落とし穴」章と重複しない補足):

🐍 実測を再現するコード

上の 2 つの表を再現します(autograd と解析勾配の一致・勾配累積)。 PyTorch が無くても NumPy 部分だけで解析値は確認できます。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) 北海道 5,092,000 1,681,000 東京都 14,086,000 3,205,000 沖縄県 1,468,000 350,000 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import numpy as np, pandas as pd, torch

df = pd.read_csv("data/raw/SSDSE-B-2026.csv", encoding="cp932", skiprows=[1])
d  = df[df["SSDSE-B-2026"] == 2023]                 # 最新年・47都道府県
x  = d["A1101"].astype(float).values               # 総人口
y  = d["A1303"].astype(float).values               # 65歳以上人口
xz = (x - x.mean()) / x.std()
yz = (y - y.mean()) / y.std()

# 解析的勾配(w=0,b=0): dL/dw = -2*mean(xz*yz)
print("analytic dL/dw =", round(-2*np.mean(xz*yz), 6))   # -1.981958

# PyTorch autograd
xt = torch.tensor(xz); yt = torch.tensor(yz)
w  = torch.zeros(1, requires_grad=True)
b  = torch.zeros(1, requires_grad=True)
L  = ((w*xt + b - yt)**2).mean()
L.backward()
print("autograd  w.grad =", round(w.grad.item(), 6))     # -1.981958(一致)

# zero_grad を挟まず 2 回目 → 勾配は加算されて 2 倍
L2 = ((w*xt + b - yt)**2).mean(); L2.backward()
print("2nd backward (no zero_grad) =", round(w.grad.item(), 6))  # -3.963915
📤 実行例(実測) analytic dL/dw = -1.981958 autograd w.grad = -1.981958 2nd backward (no zero_grad) = -3.963915

💬 両方を標準化しているので mean(xz·yz) は総人口と 65 歳以上人口の相関係数そのもので、−1.981958 は −2 × 0.990979 にあたる。autograd の w.grad が小数 6 桁まで手計算と一致し、backward() が式どおりの微分をしていると確かめられた。zero_grad() を挟まずにもう一度 backward() すると −3.963915 とちょうど 2 倍になり、勾配は上書きではなく足し込まれる。訓練ループで optimizer.zero_grad() を忘れると、この加算がバッチごとに積み重なって学習率を勝手に大きくしたのと同じことになる。

🚀 発展の追補

🔗 関連ページ