Tích chập 1D và 2D: kernel, stride, padding
Đây là bài mở đầu của Module 1, và cũng là viên gạch đầu tiên của đồ án mạng 2 lớp ở cuối khóa. Bạn đã có sẵn vector, ma trận và đạo hàm riêng, nên không cần thêm công cụ toán mới: tích chập chỉ là một phép dot product được lặp lại trên những cửa sổ trượt. Điều cần luyện hôm nay là nhìn ra cửa sổ đó, đếm đúng số phần tử, và dự đoán shape trước khi chạy code.
Sau bài này bạn có thể:
- Viết phép tích chập 1D dưới dạng tổng có chỉ số và tính tay một ví dụ ngắn.
- Đọc kernel 2D như một bộ dò đặc trưng và tính tay một phần tử output.
- Dự đoán shape output từ input, kernel, stride, padding bằng công thức.
- Đếm số tham số của một lớp conv và giải thích vì sao nó nhỏ hơn fully connected.
- Chọn kernel, stride, padding cho lớp conv đầu tiên trong đồ án.
flowchart TD
A["🖼️ Input C_in x H x W"] --> B["⬜ Thêm viền padding P"]
B --> C["🔍 Trượt kernel K x K với bước S"]
C --> D["✖️ Dot product + bias"]
D --> E["📐 Output C_out x H_out x W_out"]
Tích chập 1D: một tổng trên cửa sổ trượt
Với tín hiệu 1D $x$ dài $L$, kernel $w$ dài $K$, stride $S$, padding $P$, output tại vị trí $i$ là:
$$ y[i] = \sum_{k=0}^{K-1} w[k] \cdot x[i \cdot S + k] + b $$
Đọc công thức theo kiểu "bóc lớp": cố định $i$, bạn lấy đúng $K$ phần tử liên tiếp của $x$ bắt đầu từ $i \cdot S$, nhân từng cặp với $w$, cộng lại, thêm bias. Sau đó tăng $i$ lên 1, cửa sổ dịch sang $S$ vị trí. Không có gì khác ngoài dot product lặp lại.
Ví dụ tính tay: $x = [1, 2, 3, 4, 5]$, $w = [1, 0, -1]$, $S=1$, $P=0$, $b=0$.
- $y[0] = 1\cdot1 + 2\cdot0 + 3\cdot(-1) = -2$
- $y[1] = 2\cdot1 + 3\cdot0 + 4\cdot(-1) = -2$
- $y[2] = 3\cdot1 + 4\cdot0 + 5\cdot(-1) = -2$
Output là $[-2, -2, -2]$: kernel này đo chênh lệch giữa phần tử đầu và phần tử cuối của cửa sổ, nên nó bằng phẳng trên một dãy tăng đều.
Note: PyTorch và hầu hết framework thực chất chạy cross-correlation, không lật kernel như định nghĩa toán học của convolution. Vì kernel là tham số học được, việc lật hay không chỉ đổi dấu chỉ số, không đổi năng lực biểu diễn. Trong khóa này ta dùng đúng công thức trên, tức cross-correlation, và vẫn gọi là conv.
Bài tập 1. Cho $x = [2, 1, 0, 3, 4]$, $w = [1, -1]$, $S=2$, $P=0$. Tính output.
Xem đáp án
Chiều dài output: $\lfloor (5 - 2)/2 \rfloor + 1 = 2$. $y[0] = 2\cdot1 + 1\cdot(-1) = 1$; $y[1] = x[2]\cdot1 + x[3]\cdot(-1) = 0 - 3 = -3$. Output: $[1, -3]$. Lưu ý $x[4]$ bị bỏ qua vì stride 2 không phủ hết.
Tích chập 2D: kernel như bộ dò đặc trưng
Input 2D $X$ kích thước $H \times W$, kernel $K_h \times K_w$:
$$ Y[i, j] = \sum_{u=0}^{K_h-1} \sum_{v=0}^{K_w-1} W[u, v] \cdot X[i \cdot S + u,\ j \cdot S + v] + b $$
Mỗi phần tử output là một dot product giữa kernel và một patch cùng kích thước. Kernel 2D là một bộ dò: kernel $[[1,0],[0,-1]]$ bắt các chuyển đổi chéo, kernel toàn $1$ là phép lấy trung bình cục bộ, kernel $[[0,1,0],[1,-4,1],[0,1,0]]$ phản ứng mạnh với biên.
Tính tay một ví dụ, $X$ là ảnh $3\times3$, kernel $2\times2$, $S=1$, $P=0$:
X = 1 2 0 W = 1 0
0 1 3 0 -1
2 1 0
- $Y[0,0] = 1\cdot1 + 2\cdot0 + 0\cdot0 + 1\cdot(-1) = 0$
- $Y[0,1] = 2\cdot1 + 0\cdot0 + 1\cdot0 + 3\cdot(-1) = -1$
- $Y[1,0] = 0\cdot1 + 1\cdot0 + 2\cdot0 + 1\cdot(-1) = -1$
- $Y[1,1] = 1\cdot1 + 3\cdot0 + 1\cdot0 + 0\cdot(-1) = 1$
Kết quả:
Y = 0 -1
-1 1
Bài tập 2. Với cùng $X$ trên, đổi kernel thành $[[1,1],[1,1]]$ và $b=0$. Tính $Y$.
Xem đáp án
Mỗi output là tổng của một patch $2\times2$, tức tổng cục bộ: $Y = [[4, 6], [4, 5]]$. Kiểm tra nhanh $Y[0,0] = 1+2+0+1 = 4$. Đây chính là box blur chưa chuẩn hóa.
Kernel, stride, padding quyết định shape
Công thức duy nhất cần nhớ, áp dụng cho từng chiều không gian:
$$ H_{out} = \left\lfloor \frac{H + 2P - K_h}{S} \right\rfloor + 1 $$
Ba chế độ padding thường gặp:
| Chế độ | Giá trị $P$ | Tác dụng |
|---|---|---|
| valid | $0$ | Không thêm viền, output nhỏ hơn input |
| same | $(K-1)/2$ với $K$ lẻ, $S=1$ | Giữ nguyên $H_{out} = H$ |
| full | $K-1$ | Output lớn hơn input |
Vài quy tắc thực dụng:
- $S > 1$ làm giảm chiều, đây là cách downsample rẻ và không có tham số.
- $P = (K-1)/2$ chỉ đúng khi $S=1$ và $K$ lẻ. Với $K$ chẵn, "same" phải lệch một bên, nên hầu hết thiết kế dùng $K$ lẻ ($3, 5, 7$).
- Nếu $H + 2P < K$, công thức cho kết quả không hợp lệ: không có cửa sổ nào vừa input.
Ví dụ: đầu vào $32\times32$, $K=5$, $S=1$, $P=2$ cho $H_{out} = (32 + 4 - 5)/1 + 1 = 32$, đúng kiểu conv giữ nguyên chiều. Cùng input nhưng $K=3$, $S=2$, $P=1$: $H_{out} = \lfloor (32+2-3)/2 \rfloor + 1 = 16$.
Số tham số của một lớp conv là _____, tức nó không phụ thuộc vào $H$ và $W$. Đó là lý do một lớp $3\times3$ trên ảnh $224\times224$ chỉ tốn _____ tham số, trong khi fully connected tương ứng tốn hàng chục triệu.
Bài tập 3. Lớp conv đầu tiên của đồ án nhận ảnh $28\times28\times1$, bạn muốn output $28\times28$ với $K=3$. Chọn $S$ và $P$ nào?
Xem đáp án
$S=1$ và $P=(3-1)/2=1$, tức chế độ same. Kiểm tra: $(28+2-3)/1+1 = 28$. Với $C_{out}=8$, số tham số là $8 \times (1\cdot3\cdot3 + 1) = 80$.
Chọn kernel, stride, padding cho lớp conv đầu tiên
Vài mốc để bạn không phải đoán:
- Lớp đầu tiên thường dùng $K=3$, $S=1$, $P=1$: giữ nguyên độ phân giải, học được biên và texture, chi phí thấp.
- Chỉ dùng $S=2$ khi bạn thực sự muốn giảm chiều nhanh, ví dụ ở các lớp đầu của mạng lớn.
- $K=1$ là phép trộn kênh theo từng pixel, rất hữu ích để đổi $C_{out}$ mà không đổi $H, W$.
- Kernel lớn ($7\times7$, $11\times11$) chỉ hợp lý ở lớp đầu tiên trên ảnh lớn, và thường đi kèm $S=2$ để bù chi phí.
Ba cái bẫy khi chọn kernel, stride, padding
- Quên nhân với $C_{in}$. Mỗi kênh input có kernel riêng, tổng hợp lại thành một kênh output. Đếm tham số mà bỏ $C_{in}$ là sai ngay từ lớp thứ hai.
- Lẫn "same" với giữ nguyên khi $S>1$. $P=(K-1)/2$ chỉ bảo toàn chiều khi stride bằng 1. Với $S=2$, output còn khoảng một nửa, dù $P$ vẫn là 1.
- Đặt padding quá lớn để "cho đẹp shape". Viền zero tạo ra biên giả, và nếu $P \geq K/2$ thì phần lớn tín hiệu trong cửa sổ là zero. Hãy để công thức shape quyết định, không ép shape bằng padding.
Một mẹo kiểm tra: trước khi train, in shape sau mỗi lớp cho một batch giả $1 \times C \times H \times W$. Nếu shape lệch so với dự đoán tay của bạn, lỗi nằm ở công thức hoặc ở thứ tự chiều, không nằm ở optimizer.
Front: Công thức output 1D của conv? Back: y[i] = tổng theo k của w[k] * x[i*S + k], cộng bias.
Front: Công thức chiều dài output theo H, P, K, S? Back: H_out = floor((H + 2P - K)/S) + 1.
Front: Padding 'same' với K lẻ, S = 1 bằng bao nhiêu? Back: P = (K-1)/2, output giữ nguyên chiều input.
Front: Stride có tham số học được không? Back: Không. Stride chỉ là bước trượt, không có trọng số.
Front: Số tham số của một lớp conv? Back: C_out * (C_in * K_h * K_w + 1), không phụ thuộc H và W.
Front: Framework deep learning chạy convolution hay cross-correlation? Back: Cross-correlation, tức không lật kernel.
Front: Kernel 1x1 làm gì? Back: Trộn các kênh tại từng pixel, đổi C_out mà giữ H, W.
Front: Vì sao hầu hết thiết kế dùng kernel lẻ? Back: Để padding same chia đều hai bên: P = (K-1)/2 là số nguyên.
Front: Với S = 2, K = 3, P = 1, input 32 cho output bao nhiêu? Back: floor((32+2-3)/2) + 1 = 16.
Front: Điều kiện để một cửa sổ kernel vừa input? Back: H + 2P >= K, nếu không thì không có output hợp lệ.
Front: Tích chập 2D khác gì so với fully connected ở góc nhìn tham số? Back: Cùng một kernel dùng lại ở mọi vị trí (weight sharing), nên tham số rất nhỏ.
Front: Padding 'full' cho output lớn hơn input bao nhiêu? Back: Với P = K-1, output rộng hơn input đúng K-1 phần tử mỗi chiều.
Chốt lại và bước tiếp theo
Ba thứ cần mang theo: tích chập là dot product trên cửa sổ trượt, shape do $\lfloor (H + 2P - K)/S \rfloor + 1$ quyết định, và số tham số chỉ phụ thuộc $C_{in}, C_{out}, K$ nên conv rẻ hơn fully connected rất nhiều. Kernel là bộ dò đặc trưng, stride là bộ downsample, padding là cách bạn kiểm soát biên.
Bài tới sẽ lấy chính công thức này và biến nó thành code: kiểm tra shape bằng tay trên nhiều cấu hình, rồi vector hóa toàn bộ vòng lặp trượt bằng im2col để forward pass chạy nhanh và viết backward dễ hơn.