Chương 3·Bài 3.1

Tensor và autograd

Bài 1Chiếu đặc trưng

Một batch gồm BB chuỗi, mỗi chuỗi có LL vị trí, mỗi vị trí là một vector dd đặc trưng. Ta xếp chúng thành tensor X có shape (B, L, d). Một lớp tuyến tính với ma trận W shape (d, h) và bias b shape (h,) biến mỗi vị trí thành vector hh chiều.

Hãy tính:

  • projected = X @ W + b, shape (B, L, h). Phép nhân chỉ gộp chiều đặc trưng cuối cùng, hai chiều batch và vị trí giữ nguyên.
  • pooled: trung bình của projected theo chiều vị trí, shape (B, h).

Kể cả khi B=1B=1 hoặc L=1L=1, giữ nguyên số chiều của kết quả.

Dữ liệu vào

Mỗi tệp input.json gồm X, W và b.

Kết quả

Một đối tượng JSON gồm projected và pooled.

Chấm điểm

Có 20 test, mỗi test 5 điểm. Số thực được chấm với sai số khoảng 10−710^{-7}.

Ví dụ

Input mẫu

{
  "X": [[[-0.025661605931689643, -0.2698080588775131]]],
  "W": [
    [-1.7578144276508583, 0.6392774740149706, -0.5579296520252873],
    [0.626881764410742, -0.8309120423662885, 0.5852517710026026]
  ],
  "b": [-0.5710878399185039, 0.09653092353703967, 1.5783323390001442]
}

Output mẫu

{
  "projected": [[[-0.6951172507764617, 0.30431280216665973, 1.4347440655791828]]],
  "pooled": [[-0.6951172507764617, 0.30431280216665973, 1.4347440655791828]]
}

Bài 2Gradient cộng dồn

PyTorch không ghi đè .grad mà cộng gradient mới vào đó. Bài này cho ta tận mắt thấy điều ấy.

Cho tensor x cần gradient và vector hằng a. Đặt J1=mean⁡(x3)J_1=\operatorname{mean}(x^3) và J2=∑iaixiJ_2=\sum_i a_ix_i. Làm theo đúng thứ tự:

  1. Forward J1J_1, gọi backward(). Lưu bản sao của x.grad, gọi là first.
  2. Forward J2J_2 mới, gọi backward() mà không xóa .grad. Lưu bản sao, gọi là accumulated.
  3. Xóa gradient bằng x.grad = None, forward J1J_1 lần nữa rồi backward(). Lưu kết quả, gọi là after_reset.

Mỗi lần backward dùng một lần forward mới, không dùng retain_graph.

Dữ liệu vào

Mỗi tệp input.json gồm x và a, cùng độ dài.

Kết quả

Một đối tượng JSON gồm first, accumulated và after_reset.

Chấm điểm

Có 20 test, mỗi test 5 điểm. Số thực được chấm với sai số khoảng 10−710^{-7}.

Ví dụ

Input mẫu

{
  "x": [
    -0.025661605931689643, -0.2698080588775131, -1.7578144276508583,
    0.6392774740149706, -0.5579296520252873, 0.626881764410742
  ],
  "a": [
    -0.8309120423662885, 0.5852517710026026, -0.5710878399185039,
    0.09653092353703967, 1.5783323390001442, -0.6775748355876624
  ]
}

Output mẫu

{
  "first": [
    0.0003292590094966645, 0.03639819431762578, 1.544955781028757,
    0.2043378443914807, 0.1556427483045291, 0.19649037327536248
  ],
  "accumulated": [
    -0.8305827833567918, 0.6216499653202284, 0.9738679411102532,
    0.30086876792852035, 1.7339750873046733, -0.48108446231229995
  ],
  "after_reset": [
    0.0003292590094966645, 0.03639819431762578, 1.544955781028757,
    0.2043378443914807, 0.1556427483045291, 0.19649037327536248
  ]
}

Bài 3Detach và clone

detach() cắt đường gradient nhưng vẫn dùng chung vùng nhớ với tensor gốc. detach().clone() mới tạo một bản sao độc lập. Bài này kiểm cả hai điều ấy.

Cho tensor x cần gradient và tốc độ học learning_rate.

  1. Tính gradient của ∑x3\sum x^3 theo x, gọi là attached_grad.
  2. Xóa gradient, rồi tính gradient của ∑(x.detach())2⋅x\sum (\text{x.detach()})^2\cdot x theo x, gọi là detached_grad. Giá trị forward như cũ, nhưng một phần đường đi đã bị cắt.
  3. Tạo alias = x.detach() và snapshot = x.detach().clone().
  4. Trong khối torch.no_grad(), cập nhật tại chỗ x -= learning_rate * attached_grad.

Báo lại alias và snapshot sau bước cập nhật.

Dữ liệu vào

Mỗi tệp input.json gồm x và learning_rate.

Kết quả

Một đối tượng JSON gồm attached_grad, detached_grad, alias_after_update (giá trị của alias) và clone_before (giá trị của snapshot).

Chấm điểm

Có 20 test, mỗi test 5 điểm. Số thực được chấm với sai số khoảng 10−710^{-7}.

Ví dụ

Input mẫu

{
  "x": [
    -0.025661605931689643, -0.2698080588775131, -1.7578144276508583,
    0.6392774740149706, -0.5579296520252873, 0.626881764410742
  ],
  "learning_rate": 0.01
}

Output mẫu

{
  "attached_grad": [
    0.001975554056979987, 0.2183891659057547, 9.269734686172542,
    1.2260270663488841, 0.9338564898271746, 1.178942239652175
  ],
  "detached_grad": [
    0.000658518018993329, 0.07279638863525156, 3.0899115620575146,
    0.4086756887829614, 0.3112854966090582, 0.39298074655072496
  ],
  "alias_after_update": [
    -0.025681361472259443, -0.2719919505365706, -1.8505117745125836,
    0.6270172033514817, -0.5672682169235591, 0.6150923420142203
  ],
  "clone_before": [
    -0.025661605931689643, -0.2698080588775131, -1.7578144276508583,
    0.6392774740149706, -0.5579296520252873, 0.626881764410742
  ]
}

Bài 4Đồ thị tính toán

Autograd ghi lại phép tính thành một đồ thị, rồi đi ngược đồ thị để tính gradient. Bài này yêu cầu tự làm việc ấy trên một đồ thị nhỏ.

Đồ thị gồm các đỉnh đánh số từ 00, cho theo thứ tự tính được: mỗi đỉnh chỉ dùng các đỉnh đứng trước nó. Mỗi đỉnh là một trong các loại:

Loại Giá trị
input số value cho sẵn
add, sub, mul u+wu+w, u−wu-w, u⋅wu\cdot w với uu, ww là giá trị hai đỉnh trong args
tanh, exp, relu tanh⁡u\tanh u, eue^u, max⁡(0,u)\max(0,u) với uu là giá trị đỉnh trong args
pow upu^p với số mũ nguyên p

Hai đỉnh trong args có thể trùng nhau, chẳng hạn mul với args là [0, 0] cho u2u^2.

Đầu ra của đồ thị là đỉnh cuối cùng. Hãy tính giá trị mọi đỉnh, và đạo hàm của đầu ra theo từng đỉnh. Một đỉnh được dùng ở nhiều chỗ thì gradient từ các chỗ ấy được cộng lại. Đạo hàm của relu tại u≤0u\le0 quy ước bằng 00.

Dữ liệu vào

Một đối tượng JSON gồm nodes, danh sách các đỉnh. Mỗi đỉnh có op, và tùy loại có thêm value, args, p.

Kết quả

Một đối tượng JSON gồm values (giá trị từng đỉnh) và grads (đạo hàm của đầu ra theo từng đỉnh). Đạo hàm theo chính đỉnh cuối bằng 11.

Giới hạn

  • Không quá 3030 đỉnh. Mọi giá trị có trị tuyệt đối không quá 10001000.

Chấm điểm

Có 20 test, mỗi test 5 điểm. Số thực được chấm với sai số khoảng 10−710^{-7}.

Ví dụ

Input mẫu

{
  "nodes": [
    {"op": "input", "value": 2.0},
    {"op": "input", "value": -3.0},
    {"op": "input", "value": 1.5},
    {"op": "mul", "args": [0, 1]},
    {"op": "add", "args": [3, 0]},
    {"op": "mul", "args": [4, 2]},
    {"op": "pow", "args": [0], "p": 2},
    {"op": "add", "args": [5, 6]}
  ]
}

Output mẫu

{
  "values": [2.0, -3.0, 1.5, -6.0, -4.0, -6.0, 4.0, -2.0],
  "grads": [1.0, 3.0, -4.0, 1.5, 1.5, 1.0, 1.0, 1.0]
}

Bài 5Broadcasting

Broadcasting giúp viết X @ W + b gọn gàng, nhưng cũng có thể âm thầm đổi bài toán: trừ một tensor (2, 1) cho một tensor (2,) không báo lỗi mà cho ra (2, 2). Bài này luyện suy shape trước khi chạy.

Phép theo từng phần tử (add, sub, mul): căn hai shape thẳng từ chiều cuối. Shape ngắn hơn được coi như có thêm các chiều 11 ở đầu. Ở mỗi vị trí, hai kích thước phải bằng nhau hoặc một trong hai bằng 11; kết quả lấy kích thước lớn hơn. Có vị trí không thỏa thì báo lỗi.

Phép matmul, theo quy ước của torch.matmul:

  • Hai vector (n,) và (n,): tích vô hướng, kết quả là số, shape ().
  • (..., n, m) với vector (m,): kết quả (..., n).
  • Vector (m,) với (..., m, p): kết quả (..., p).
  • (..., n, m) với (..., m, p): các chiều đứng trước hai chiều cuối được broadcast như phép theo từng phần tử, kết quả là shape broadcast ấy nối thêm (n, p).

Kích thước không khớp thì báo lỗi.

Hãy cho biết shape kết quả của từng phép, hoặc null nếu báo lỗi. Ngoài ra, liệt kê các phép theo từng phần tử mà kết quả có nhiều phần tử hơn cả hai đầu vào. Đó là dấu hiệu broadcast có thể đang làm sai bài toán.

Dữ liệu vào

Một đối tượng JSON gồm ops, mỗi phép là {"op": ..., "a": [...], "b": [...]} với a, b là shape hai đầu vào.

Kết quả

Một đối tượng JSON gồm shapes (shape kết quả hoặc null, theo thứ tự phép; tích vô hướng ghi []) và suspicious (chỉ số các phép đáng ngờ, tăng dần, đánh số từ 00).

Giới hạn

  • Không quá 1010 phép. Mỗi shape có từ 11 đến 44 chiều, mỗi chiều từ 11 đến 44.

Chấm điểm

Có 20 test, mỗi test 5 điểm.

Ví dụ

Input mẫu

{
  "ops": [
    {"op": "sub", "a": [2, 1], "b": [2]},
    {"op": "add", "a": [8, 3], "b": [3]},
    {"op": "matmul", "a": [8, 3], "b": [3, 5]},
    {"op": "mul", "a": [64, 10], "b": [64]},
    {"op": "matmul", "a": [3], "b": [3]},
    {"op": "matmul", "a": [2, 4, 3], "b": [3]}
  ]
}

Output mẫu

{
  "shapes": [[2, 2], [8, 3], [8, 5], null, [], [2, 4]],
  "suspicious": [0]
}