Từ mảng nhiều chiều tới gradient tự động
Chương 3 · Bài 3.1
Hai mẫu dữ liệu, mỗi mẫu có ba đặc trưng.
Ta muốn tạo hai đặc trưng mới rồi đo lỗi dự đoán.
shape = (2, 3)
detach, no_grad; chuyển tensor giữa CPU và GPU.B là số mẫu, d là số feature, h là số feature đầu ra.
| Tensor | Shape |
|---|---|
| X | (B,d) |
| W | (d,h) |
| b | (h,) |
| Y | (B,h) |
X = torch.tensor([[1., 2., 3.], [4., 5., 6.]])
W = torch.tensor([[1., 0.], [0., 1.], [1., 1.]])
b = torch.tensor([0.5, -0.5])
Y = X @ W + b
print(Y.tolist())
Kết quả[[4.5, 4.5], [10.5, 10.5]]
Mỗi hàng vẫn thuộc một mẫu.
print(torch.tensor([1, 2, 3]).dtype,
torch.tensor([1., 2., 3.]).dtype)
Kết quảtorch.int64 torch.float32
float32.int64 (long).requires_grad=True.Hỏi nhanh
X có shape (8, 3), W có shape (3, 5). X @ W có shape gì?
Nhân ma trận gộp mất chiều 3 ở giữa, giữ 8 hàng của X và 5 cột của W.
A = torch.arange(6).reshape(2, 3); hai hàng ban đầu là [0,1,2] và [3,4,5].
print(A.reshape(3, 2).tolist())
print(A.transpose(0, 1).tolist())
Kết quả[[0, 1], [2, 3], [4, 5]]
[[0, 3], [1, 4], [2, 5]]
Reshape chia lại dãy phần tử.
Transpose đổi hàng và cột.
Shape đúng chưa chứng minh ý nghĩa đúng.
Hỏi nhanh
Bảng A có shape (2, 3). Muốn đổi thành (3, 2) sao cho các cột cũ thành hàng mới, ta dùng gì?
reshape và view chia lại dãy phần tử theo thứ tự cũ, nên hàng đầu vẫn là [0, 1]. transpose đổi chỗ hai trục, hàng đầu thành [0, 3], đúng cột cũ.
Prediction (B,1) trừ target (B,).
Kết quả thành (B,B): so mọi cặp mẫu.
Sửa: pred.squeeze(-1).
Chặn sớm:
assert pred.shape == target.shape
Hỏi nhanh
Dự đoán pred = [[1.], [2.]], shape (2, 1), khớp đúng nhãn target = [1., 2.], shape (2,). Vậy mà ((pred - target) ** 2).mean() lại in ra 0.5. Vì sao?
Phép trừ bị broadcast thành (2, 2), mỗi dự đoán bị trừ cho mọi nhãn. Trung bình bình phương là 2/4=0.5, trong khi MSE đúng bằng 0.
Forward chạy đến đâu, DAG được ghi đến đó; mỗi forward dựng lại DAG mới.
if và vòng lặp Python vẫn đạo hàm được: backward theo nhánh đã chạy.
Một biến đi qua nhiều nhánh: gradient các nhánh được cộng.
Hỏi nhanh
Cho x = torch.tensor(-3., requires_grad=True), rồi y = 2 * x if x > 0 else x ** 2 và y.backward(). x.grad bằng bao nhiêu?
Với x=−3, chỉ phép x2 chạy và được ghi vào graph, nên đạo hàm là 2x=−6. Autograd không lấy trung bình các nhánh.
Gradient dương: tăng w một chút thì loss tăng.
requires_grad=True: ghi lại mọi phép tính dùng w.
w = torch.tensor(2., requires_grad=True)
u = 3 * w
J = (u - 1) ** 2
J.backward()
Kết quả trong notebook:
Kết quả25.0 30.0
Loss 25 · gradient 30.
Backward chưa cập nhật trọng số.
with torch.no_grad(): khi cập nhật thủ công.
Optimizer sẽ làm bước ấy trong training loop.
Hỏi nhanh
Vẫn J=(3w−1)2, nhưng tại w=0. Gradient bằng bao nhiêu, và một bước gradient descent đẩy w theo hướng nào?
Tại w=0, gradient là 2(0−1)⋅3=−6. Gradient descent đi ngược dấu gradient nên w tăng.
Hai lần forward/backward độc lập.
Giữa lần hai và ba: v.grad = None.
Kết quả30.0
60.0
30.0
Xóa gradient cũ trước bước mới.
Mỗi bước: forward mới → backward mới.
Hỏi nhanh
Một bạn tính loss một lần trước vòng lặp, rồi mỗi epoch gọi loss.backward() và optimizer.step(). Sang epoch thứ hai thì báo RuntimeError: Trying to backward through the graph a second time. Sửa thế nào?
Graph của loss bị giải phóng sau backward đầu tiên, mà kể cả giữ lại thì nó vẫn ứng với trọng số cũ. Phải forward lại ở mỗi bước.
detach(): cắt đường gradient.clone(): sao dữ liệu..item(): lấy số Python để ghi log.no_grad(): ngừng ghi graph trong khối.Tại v=2:
v.square().detach() * v
Forward là 8, gradient là 4.
Nhánh square bị coi là hằng số.
Hỏi nhanh
w bằng 2 và đã có w.grad bằng 30. Chạy snap = w.detach(), rồi with torch.no_grad(): w -= 0.1 * w.grad. snap.item() in ra gì?
detach() không sao chép dữ liệu: snap dùng chung vùng nhớ với w, nên phép trừ tại chỗ cho −1 ở cả hai. Muốn giữ số 2 thì dùng .clone().
device = torch.device(
'cuda' if torch.cuda.is_available()
else 'cpu')
X_device = X.to(device)
linear = torch.nn.Linear(3, 2).to(device)
Tensor: .to trả bản mới, phải gán lại.
Module: .to chuyển tại chỗ.
Máy chạy bài không có CUDA:
Kết quảcpu (2, 2)
(2, 2)
Có CUDA thì cùng code chọn cuda.
GPU không tự nhanh hơn với dữ liệu rất nhỏ.
Hỏi nhanh
Model đã gọi model.to('cuda'). Trong vòng lặp có dòng X.to('cuda'), không gán lại, rồi model(X), và PyTorch báo các tensor nằm trên hai device khác nhau. Vì sao?
Với tensor, .to trả về bản mới, X cũ vẫn ở CPU, nên phải viết X = X.to('cuda'). Với module thì .to chuyển tại chỗ.
Lần theo đường bị cắt:
detach(), no_grad().item(), float() giữa forwardu = torch.tensor(3 * w.item())
J = (u - 1) ** 2 + w
Đúng ra dJ/dw=31; autograd chỉ thấy phần +w, cho 1.
assert pred.shape == target.shape.long; hồi quy, BCE float.device đặt một chỗ, không gõ cứng .cuda().Luyện đề 1
Một model khai báo ba lớp fc1, fc2, fc3 trong __init__, nhưng forward chỉ gọi fc1 rồi fc2. Sau loss.backward(), fc3.weight.grad là gì?
fc3 không tham gia forward nên không có trong graph, và .grad vẫn là None. Đồ thị ghi các phép đã chạy, không phải sơ đồ kiến trúc.
Luyện đề 2
X có shape (64, 10): 64 mẫu, mỗi mẫu 10 đặc trưng. Để trừ mỗi hàng cho trung bình của chính hàng đó, một bạn viết m = X.mean(dim=1) rồi X - m. Kết quả thế nào?
X.mean(dim=1) có shape (64,). Broadcasting so từ chiều cuối, 10 gặp 64 nên báo lỗi. Thêm keepdim=True để có shape (64, 1).
Luyện đề 3
loss là tensor 0 chiều, logits là tensor (32, 10), cả hai còn nối với graph. Ta muốn (a) ghi loss vào log dưới dạng số Python, và (b) giữ logits ở dạng tensor nhưng không giữ graph. Gọi gì?
.item() đổi tensor một phần tử thành số Python. .detach() giữ tensor cùng shape nhưng không nối graph.
Tensor có số, shape, dtype và device; đọc cả bốn trước khi tính.
Autograd ghi lại phép tính lúc forward; backward cộng gradient theo mọi đường, chưa cập nhật trọng số.
Làm 10 câu kiểm tra cuối bài trên trang bài học.
Chạy notebook: trước mỗi ô, đoán shape, dtype và gradient sẽ in ra, rồi đối chiếu.