1. Tensor とは
Tensor(テンソル)は、PyTorch の基本データ型です。中身は NumPy 配列とよく似た多次元配列ですが、 大きな違いが2つあります:① GPU に載せて高速計算できる、② 自動微分(autograd)に対応している。
この②が深層学習の要です。L03 では勾配を手で導きましたが、Tensor は「自分がどう計算されたか」を記録しており、
backward() を呼ぶと勾配を自動でさかのぼって計算してくれます。ここからは読み取り専用のコードで、その挙動を確かめます。
2. Tensor の基礎
Tensor は torch.tensor(...) で作れます。.shape で形、.dtype で要素の型が分かります。
NumPy 配列とは torch.from_numpy() / .numpy() で相互変換できます。
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 に入れてくれます。簡単な式で確かめましょう。
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 + 3。x = 2 を入れて 7。
私たちは微分の式を一切書いていません。backward() が計算の記録(計算グラフ)から自動で求めました。
これがautograd(自動微分)です。
4. 線形層+MSE の勾配を autograd で
L03 では dW = Xᵀ · dpred を手で書きました。同じ計算を PyTorch にやらせます。
W を requires_grad=True にして順伝播・損失を計算し、loss.backward() を呼ぶだけ。
勾配は W.grad に入ります(形は W と同じ)。
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() ですが、効く“範囲”が違います。ここを混同しやすいので整理しましょう。
torch.no_grad()=「この“区間”は微分しない」……withで囲んだブロック全体に効きます。中で行う計算はすべて記録されません(そもそも計算グラフを作らない)。評価ループ全体をまとめて囲むのに使います。detach()=「この“1つのTensor”をグラフから切り離す」……ある Tensor に対して呼ぶメソッドで、切り離したコピーを返します。元の Tensor はそのまま(勾配を追跡できる)。特定の値だけを「定数扱い」にしたいときに使います。
ひとことで言うと、no_grad は「範囲(with ブロック)」に、detach は「1個のTensor(コピーを返す)」に効く——これが一番の違いです。
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 との決定的な違いです。
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. 練習問題
.grad に入る値は?
次のコードで x.grad はいくつになりますか。
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 = 2x。x = 3 なので 2×3 = 6。
x.grad には「x で微分した値」が入ります。
backward できる条件は?
loss.backward() を正しく呼ぶために、loss はどうなっている必要がありますか。
- A. スカラー(1要素)であること
- B. 形が (N, 1) のベクトルであること
- C. requires_grad=False であること
答えと解説を見る
正解:A. スカラー(1要素)であること
backward() は「1つの数(損失)を各パラメータで微分」します。だから対象はスカラー。
だからこそ損失は .mean() や .sum() で1つの数にまとめてから backward() します。
(ベクトルに対しては勾配の重みを別途渡す必要があり、通常は使いません。)
no_grad の効果は?
with torch.no_grad(): のブロック内で計算した Tensor の requires_grad はどうなりますか。
- A. True のまま(勾配を記録する)
- B. False になる(勾配を記録しない)
- C. エラーになる
答えと解説を見る
正解:B. False になる(勾配を記録しない)
no_grad ブロックでは計算グラフを記録しないので、結果の requires_grad は False。
推論・評価を高速化し、メモリを節約するために使います。
7. まとめ
このレッスンのポイント
- Tensor は NumPy 配列に「GPU対応」と「自動微分」を加えたもの
requires_grad=Trueの Tensor は計算が記録され、backward()で勾配が.gradに入るbackward()の対象はスカラー(損失)。だから損失は.mean()等で1つにまとめる- autograd は L03 の手計算の逆伝播を自動化している(値も一致)
- 推論・評価では
with torch.no_grad():やdetach()で勾配計算を止める - dtype は基本 float32。整数配列はそのまま層に入れない
腕試し:出力を予想してみよう
このレッスンの仕上げです。次のコードを頭の中で実行して、出力を予想してみましょう (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.grad は x と同じ形で、各要素ごとの勾配が入ります。
次は、この autograd の上に乗る nn.Module を使って、モデルを「層の組み合わせ」として組み立てます。
完了するとコース一覧に進捗が記録されます