自動微分 (Autograd)

学習目標: PyTorchの自動微分システムと勾配計算の仕組みを理解する

なぜ自動微分が必要か

ニューラルネットワークの学習には勾配(微分)の計算が不可欠です。損失関数を各パラメータで微分し、勾配降下法でパラメータを更新します。

パラメータ更新式: θ = θ - α × ∂L/∂θ

θ: パラメータ、α: 学習率、L: 損失関数

基本的な使い方

import torch

# 勾配追跡を有効にしてテンソルを作成
x = torch.tensor([2.0, 3.0], requires_grad=True)
print(x.requires_grad)  # True

# 計算を実行
y = x ** 2 + 3 * x + 1
z = y.sum()  # スカラー化

# 逆伝播(勾配計算)
z.backward()

# 勾配を確認
print(x.grad)  # tensor([7., 9.])
# dy/dx = 2x + 3 → x=2のとき7、x=3のとき9

計算グラフ

PyTorchは計算を自動的に記録し、計算グラフを構築します。backward()を呼ぶと、このグラフを逆向きにたどって勾配を計算します。

x 3x + z 順伝播 → ← 逆伝播(勾配計算)

勾配追跡の制御

# 勾配追跡を一時的に無効化(推論時など)
x = torch.tensor([1.0], requires_grad=True)

with torch.no_grad():
    y = x * 2
    print(y.requires_grad)  # False

# detach(): 勾配追跡を切り離した新しいテンソルを作成
z = x.detach()
print(z.requires_grad)  # False

# 勾配のリセット(学習ループで必要)
x = torch.tensor([1.0], requires_grad=True)
y = x * 2
y.backward()
print(x.grad)  # tensor([2.])

# 勾配は累積されるので、明示的にリセット
x.grad.zero_()  # またはoptimizer.zero_grad()
重要: 勾配はbackward()のたびに累積されます。学習ループでは毎回zero_grad()を呼んでリセットしてください。

実践例:線形回帰

import torch

# データ
X = torch.tensor([[1.0], [2.0], [3.0], [4.0]])
y = torch.tensor([[2.0], [4.0], [6.0], [8.0]])

# パラメータ(勾配追跡あり)
w = torch.tensor([[1.0]], requires_grad=True)
b = torch.tensor([[0.0]], requires_grad=True)

learning_rate = 0.1

for epoch in range(100):
    # 順伝播
    y_pred = X @ w + b

    # 損失計算
    loss = ((y_pred - y) ** 2).mean()

    # 逆伝播
    loss.backward()

    # パラメータ更新(勾配追跡なしで)
    with torch.no_grad():
        w -= learning_rate * w.grad
        b -= learning_rate * b.grad

    # 勾配リセット
    w.grad.zero_()
    b.grad.zero_()

print(f"w: {w.item():.4f}, b: {b.item():.4f}")
# 結果: w ≈ 2.0, b ≈ 0.0

理解度チェック

Q. 学習ループで毎回zero_grad()を呼ぶ理由は?