Lesson 4 / 12

Tensorと自動微分(autograd)

このレッスンで学ぶこと

  • PyTorch の Tensor の作り方・shape・dtype・NumPyとの相互変換が分かる
  • requires_grad=Truebackward() で勾配が .grad に入る流れを読める
  • autograd が L03 の手計算の逆伝播を自動化していると理解できる
  • torch.no_grad() / detach() で勾配計算を止める意味が分かる

1. Tensor とは

Tensor(テンソル)は、PyTorch の基本データ型です。中身は NumPy 配列とよく似た多次元配列ですが、 大きな違いが2つあります:① GPU に載せて高速計算できる② 自動微分(autograd)に対応している

この②が深層学習の要です。L03 では勾配を手で導きましたが、Tensor は「自分がどう計算されたか」を記録しており、 backward() を呼ぶと勾配を自動でさかのぼって計算してくれます。ここからは読み取り専用のコードで、その挙動を確かめます。

2. Tensor の基礎

Tensor は torch.tensor(...) で作れます。.shape で形、.dtype で要素の型が分かります。 NumPy 配列とは torch.from_numpy() / .numpy() で相互変換できます。

tensor_basics.py(PyTorch・読むだけ)
import torch
import numpy as np

x = torch.tensor([[1., 2., 3.],
                  [4., 5., 6.]])
print(x.shape)        # 形
print(x.dtype)        # 要素の型(小数は float32 が既定)

# NumPy 配列から Tensor を作る
a = np.array([[1, 2], [3, 4]])
t = torch.from_numpy(a)
print(t.dtype)        # 整数配列なので整数型になる
▼ 出力
torch.Size([2, 3])
torch.float32
torch.int64

⚠ dtype の落とし穴: モデルの計算は通常 float32 で行います。整数配列から作った Tensor(int)をそのまま層に入れると 型エラーになることがあります。torch.from_numpy(a).float()a.astype(np.float32) で float に変換しておくのが安全です。E資格でも「dtype 不一致」はよくある引っかけです。

3. requires_grad と backward(自動微分の入口)

Tensor を requires_grad=True で作ると、PyTorch はその Tensor を使った計算を記録します。 最後の出力(スカラー)で backward() を呼ぶと、記録をさかのぼって勾配を計算し、 元の Tensor の .grad に入れてくれます。簡単な式で確かめましょう。

autograd_scalar.py(PyTorch・読むだけ)
import torch

x = torch.tensor(2.0, requires_grad=True)
y = x**2 + 3*x        # y = x^2 + 3x

y.backward()             # dy/dx を計算
print(x.grad)            # dy/dx = 2x + 3 = 2*2 + 3 = 7
▼ 出力
tensor(7.)

💡 何が起きた?: y = x² + 3x を微分すると dy/dx = 2x + 3x = 2 を入れて 7。 私たちは微分の式を一切書いていませんbackward() が計算の記録(計算グラフ)から自動で求めました。 これがautograd(自動微分)です。

4. 線形層+MSE の勾配を autograd で

L03 では dW = Xᵀ · dpred を手で書きました。同じ計算を PyTorch にやらせます。 Wrequires_grad=True にして順伝播・損失を計算し、loss.backward() を呼ぶだけ。 勾配は W.grad に入ります(形は W と同じ)。

autograd_linear.py(PyTorch・読むだけ)
import torch
torch.manual_seed(0)            # 乱数を固定

X = torch.randn(5, 3)          # 入力 (N=5, D=3)
y = torch.randn(5, 1)          # 正解 (5, 1)
W = torch.randn(3, 1, requires_grad=True)
b = torch.zeros(1, requires_grad=True)

pred = X @ W + b              # 順伝播 (5, 1)
loss = ((pred - y)**2).mean()  # MSE(スカラー)

loss.backward()                  # 逆伝播(勾配を自動計算)

print("loss:", round(loss.item(), 4))
print("W.grad.shape:", W.grad.shape)  # Wと同じ (3,1)
print(W.grad)
▼ 出力
loss: 2.3787
W.grad.shape: torch.Size([3, 1])
tensor([[-2.2990],
        [ 2.0405],
        [ 2.0957]])
理論の確認: この W.grad は、L03 で書いた Xᵀ · (2(pred−y)/N)同じ値になります(手計算と autograd は一致)。 違いは「式を書くか・自動で出すか」だけ。loss は必ずスカラー(1要素)にしてから backward() する点に注意。

5. 勾配を止める:no_grad と detach

推論(予測だけ)や評価のときは、勾配を計算する必要がありません。記録を取らない方が速く・メモリも節約できます。 そのための道具が torch.no_grad()detach() ですが、効く“範囲”が違います。ここを混同しやすいので整理しましょう。

ひとことで言うと、no_grad は「範囲(with ブロック)」に、detach は「1個のTensor(コピーを返す)」に効く——これが一番の違いです。

no_grad_detach.py(PyTorch・読むだけ)
import torch
torch.manual_seed(0)
X = torch.randn(5, 3)
W = torch.randn(3, 1, requires_grad=True)
b = torch.zeros(1, requires_grad=True)

# 推論時は no_grad で囲む → 記録しない
with torch.no_grad():
    pred = X @ W + b
print("no_grad内 requires_grad:", pred.requires_grad)

# detach() でも計算グラフから切り離せる
pred2 = (X @ W + b).detach()
print("detach後 requires_grad:", pred2.requires_grad)
▼ 出力
no_grad内 requires_grad: False
detach後 requires_grad: False

もう一歩踏み込みます。detach()切り離したコピーを返すだけで、元のTensorはグラフを保ったまま。 だから元の側では backward() が普通にできます。ここが no_grad との決定的な違いです。

detach_detail.py(PyTorch・読むだけ)
import torch
a = torch.tensor(2.0, requires_grad=True)
b = a * 3            # b は a から作られた(グラフ上にある)
c = b.detach()       # c は b を切り離したコピー

print(b.requires_grad)   # True (元の b はそのまま)
print(c.requires_grad)   # False(c は切り離された)
print(c.item())          # 6.0 (値は b と同じ)

b.backward()           # 元の b は逆伝播できる
print(a.grad)           # 3.0 (db/da = 3)
▼ 出力
True
False
6.0
tensor(3.)
観点torch.no_grad()detach()
with で囲むブロックTensorに対するメソッド
効く範囲ブロック内のすべての計算その1つのTensorだけ
返り値なし(区間の設定)切り離した新しいTensor
計算グラフそもそも作らない(軽い)作った上で一部を切る
主な用途評価・推論ループ全体を囲む値の定数化/NumPy変換/勾配を止めたい1点

💡 使いどころ: 学習中は勾配が必要なので普通に計算、検証・推論では with torch.no_grad(): でまとめて囲むのが定石です(L06・L08で登場)。 一方、損失の値だけをログに残す・Tensorを .detach().numpy() で取り出すといった「1点だけ切りたい」場面では detach()。 「いつ勾配を取り、いつ止めるか」はコードを読むうえで大事なポイントです。

6. 練習問題

問題 1

.grad に入る値は?

次のコードで x.grad はいくつになりますか。

quiz_1.py
x = torch.tensor(3.0, requires_grad=True)
y = x**2          # y = x^2
y.backward()
print(x.grad)
  • A. tensor(3.)
  • B. tensor(6.)
  • C. tensor(9.)
答えと解説を見る

正解:B. tensor(6.)

y = x² の微分は dy/dx = 2xx = 3 なので 2×3 = 6x.grad には「x で微分した値」が入ります。

問題 2

backward できる条件は?

loss.backward() を正しく呼ぶために、loss はどうなっている必要がありますか。

  • A. スカラー(1要素)であること
  • B. 形が (N, 1) のベクトルであること
  • C. requires_grad=False であること
答えと解説を見る

正解:A. スカラー(1要素)であること

backward() は「1つの数(損失)を各パラメータで微分」します。だから対象はスカラー。 だからこそ損失は .mean().sum() で1つの数にまとめてから backward() します。 (ベクトルに対しては勾配の重みを別途渡す必要があり、通常は使いません。)

問題 3

no_grad の効果は?

with torch.no_grad(): のブロック内で計算した Tensor の requires_grad はどうなりますか。

  • A. True のまま(勾配を記録する)
  • B. False になる(勾配を記録しない)
  • C. エラーになる
答えと解説を見る

正解:B. False になる(勾配を記録しない)

no_grad ブロックでは計算グラフを記録しないので、結果の requires_gradFalse。 推論・評価を高速化し、メモリを節約するために使います。

7. まとめ

このレッスンのポイント

  • Tensor は NumPy 配列に「GPU対応」と「自動微分」を加えたもの
  • requires_grad=True の Tensor は計算が記録され、backward() で勾配が .grad に入る
  • backward() の対象はスカラー(損失)。だから損失は .mean() 等で1つにまとめる
  • autograd は L03 の手計算の逆伝播を自動化している(値も一致)
  • 推論・評価では with torch.no_grad():detach() で勾配計算を止める
  • dtype は基本 float32。整数配列はそのまま層に入れない

腕試し:出力を予想してみよう

このレッスンの仕上げです。次のコードを頭の中で実行して、出力を予想してみましょう (PyTorchはブラウザでは動かせないので、メモに書き出してから答えで確認するのがおすすめ)。

challenge.py(PyTorch・予想してみよう)
import torch
x = torch.tensor([2.0, 3.0], requires_grad=True)
y = (x ** 2).sum()    # y = x0^2 + x1^2
y.backward()

print(y.item())           # ① いくつ?
print(x.grad)              # ② どんなTensor?
答えと解説を見る
13.0
tensor([4., 6.])

y = 2² + 3² = 4 + 9 = 13.0.sum()スカラーにしているから backward() できます。
y = x² の微分は 2x なので、x=[2, 3] では [4., 6.]x.gradx と同じ形で、各要素ごとの勾配が入ります。

次は、この autograd の上に乗る nn.Module を使って、モデルを「層の組み合わせ」として組み立てます。

完了するとコース一覧に進捗が記録されます