Data augmentation trong CV
This content is not available in your language yet.
Mục tiêu
Phần tiêu đề “Mục tiêu”Sau bài này, bạn sẽ:
- Hiểu tăng cường dữ liệu (data augmentation) là dạy mô hình điều gì không được làm đổi nhãn, và vì sao nó là một dạng regularization.
- Chọn được phép biến đổi hình học (lật, cắt ngẫu nhiên, xoay, co giãn) và quang học (độ sáng, tương phản, màu, nhiễu, làm mờ) hợp với bài toán, và nhận ra khi nào một phép làm sai nhãn.
- Tự viết được cutout, mixup và CutMix, kể cả công thức trộn nhãn của mixup, và biết RandAugment rút việc chọn chính sách augmentation về hai số , .
- Cài đặt được một pipeline
torchvision.transforms.v2biến đổi ảnh, mask và hộp bao cùng lúc, và chỉ augment tập train.
Cần biết trước
Phần tiêu đề “Cần biết trước”- Image Classification with CNNs: AlexNet → VGG → ResNet → MobileNet → EfficientNet, in Computer Vision(Not translated yet) (Learned)
- Data Preprocessing, Augmentation, and Imbalanced Data, in Machine Learning (Learned)
Trực giác
Phần tiêu đề “Trực giác”Một chữ số lệch 1 pixel
Phần tiêu đề “Một chữ số lệch 1 pixel”Bạn huấn luyện một mạng nhỏ nhận chữ số viết tay trên 300 ảnh của load_digits. Trên ảnh test, nó đúng khoảng 94%. Rồi bạn dịch mỗi ảnh test đi tối đa 1 pixel, ngang hoặc dọc. Với mắt người, chữ số không đổi gì. Với mô hình, độ chính xác rơi xuống khoảng 40% (ô scikit-learn ở mục Ví dụ code cho đúng các số này).
Chuyện gì đã xảy ra? Mô hình chưa bao giờ thấy một chữ số 6 nằm lệch sang phải. Nó học “pixel ở cột 3 đậm thì là 6”, không học “hình có vòng tròn ở dưới thì là 6”. Với 300 ảnh, cả hai quy tắc đều giải thích dữ liệu train như nhau, và mô hình chọn quy tắc dễ hơn.
Dạy điều không được làm đổi nhãn
Phần tiêu đề “Dạy điều không được làm đổi nhãn”Cách sửa đơn giản nhất: cho mô hình thấy chữ số ở nhiều vị trí. Bạn không cần thu thêm ảnh. Bạn tự dịch các ảnh train đã có, giữ nguyên nhãn, rồi đưa vào huấn luyện. Mỗi ảnh dịch là một câu nói với mô hình: “dịch 1 pixel không đổi chữ số này”.
Đó là toàn bộ ý tưởng của tăng cường dữ liệu. Bạn biết trước một số phép biến đổi không làm đổi đáp án: chó lật ngang vẫn là chó, ảnh tối hơn một chút vẫn là ảnh đó. Bạn biến kiến thức này thành dữ liệu, vì mô hình chỉ học được từ dữ liệu.
Nhưng không phải phép nào cũng an toàn. Xoay số 6 đi 180 độ được số 9. Lật ngang chữ “b” được chữ “d”. Lật ngang một phim X-quang ngực đưa tim sang bên phải, thành một ca bệnh hiếm. Phép biến đổi nào an toàn tùy vào bài toán, không tùy vào ảnh.
Câu hỏi của bài: chọn phép biến đổi nào, áp vào đâu (ảnh, nhãn, mask, hộp bao), và vì sao việc “chế thêm dữ liệu” này lại làm mô hình tổng quát hóa tốt hơn chứ không chỉ học thuộc nhiều hơn?
Lý thuyết
Phần tiêu đề “Lý thuyết”Ký hiệu
Phần tiêu đề “Ký hiệu”Ảnh (cao , rộng , kênh màu), nhãn , mô hình . Mẫu thứ của tập train là , có mẫu. Một phép biến đổi (transformation) nhận một ảnh và trả về một ảnh cùng cỡ hoặc khác cỡ, ví dụ “lật ngang” hay “dịch phải 1 pixel”. Bài toán segmentation có thêm mask : mỗi pixel một số lớp.
Bất biến và đồng biến
Phần tiêu đề “Bất biến và đồng biến”Với phân loại, ta muốn mô hình bất biến (invariant) với : biến đổi ảnh không làm đổi dự đoán.
Với segmentation thì khác. Lật ảnh thì đáp án đúng cũng phải lật theo: con mèo sang bên trái thì vùng “mèo” trong mask cũng sang bên trái. Ta muốn mô hình đồng biến (equivariant) với : biến đổi đầu vào thì đầu ra đổi theo đúng phép đó.
Phát hiện vật thể (object detection) cũng vậy: hộp bao (bounding box) phải dời theo vật. Đây là lý do mục “Ví dụ code” đưa ảnh, mask và hộp qua cùng một phép biến đổi.
CNN có sẵn một phần tính chất này: tích chập trượt cùng một kernel khắp ảnh nên đồng biến với phép dịch (xem bài Phân loại ảnh với CNN). Nhưng nó không tự bất biến với phép xoay, co giãn hay đổi độ sáng. Mạng kết nối đầy đủ như MLP thì không có sẵn tính chất nào: với nó, pixel ở cột 3 và pixel ở cột 4 là hai đặc trưng chẳng liên quan gì nhau. Augmentation là cách đưa các tính chất còn thiếu vào bằng dữ liệu.
Augmentation trong hàm mất mát
Phần tiêu đề “Augmentation trong hàm mất mát”Huấn luyện bình thường làm nhỏ trung bình mất mát trên tập train:
Có augmentation, mỗi lần mô hình gặp ảnh , ta bốc thăm một phép từ một tập phép biến đổi (ví dụ “dịch ngẫu nhiên tối đa 1 pixel”). Huấn luyện qua nhiều epoch, mỗi epoch một lần bốc thăm, nên điều được làm nhỏ thực chất là mất mát trung bình trên mọi phép :
Kỳ vọng ở đây là trung bình theo cách bốc thăm . Đọc công thức này theo hai cách:
- Thêm dữ liệu. Mỗi ảnh gốc trở thành cả một “đám” ảnh, cùng nhãn. Tập train hiệu dụng lớn hơn nhiều lần.
- Một khoản phạt. Mô hình nào cho dự đoán khác nhau trên và thì chắc chắn sai ở một trong hai, nên bị phạt. Hàm mất mát mới loại bớt những mô hình “học thuộc vị trí pixel”, giống regularization ở bài Ridge, Lasso, Elastic Net loại bớt những mô hình có hệ số khổng lồ.
Vì thế augmentation là một cách chống overfitting: nó không cho mô hình bám vào chi tiết chỉ đúng với vài trăm ảnh train. Và cũng như hệ số phạt , có thể phạt quá tay: biến đổi mạnh tới mức ảnh mất nghĩa thì mô hình underfit.
Phép hình học và phép quang học
Phần tiêu đề “Phép hình học và phép quang học”Phép hình học (geometric) dời chỗ các pixel, không đổi giá trị:
- Lật ngang (horizontal flip): ảnh tự nhiên gần như luôn an toàn. Lật dọc chỉ hợp với ảnh không có “phía trên”, như ảnh vệ tinh hay tiêu bản kính hiển vi.
- Cắt ngẫu nhiên (random crop) và cắt co giãn ngẫu nhiên (random resized crop): lấy một vùng ngẫu nhiên rồi co về cỡ cố định. Mô hình thấy vật ở nhiều vị trí và nhiều cỡ. AlexNet (Krizhevsky và cộng sự, 2012) đã cắt vùng ngẫu nhiên từ ảnh cộng lật ngang; đây là một trong hai cách chống overfitting chính của paper, cách kia là dropout.
- Xoay (rotation) vài độ, co giãn (scaling), xô nghiêng (shear). Khi xoay, các góc ảnh lộ ra chỗ trống phải điền (thường bằng 0), và pixel mới phải nội suy từ pixel cũ.
Phép quang học (photometric) đổi giá trị pixel, giữ nguyên chỗ:
- Độ sáng, tương phản (brightness, contrast): mô phỏng ảnh chụp lúc sáng, lúc tối, máy ảnh khác nhau.
- Màu (color jitter: độ bão hòa, sắc độ): mô phỏng ánh sáng vàng, trắng.
- Nhiễu (noise) và làm mờ (blur): mô phỏng cảm biến kém, ảnh rung.
Phép quang học không dời pixel nào, nên mask và hộp bao giữ nguyên. Chỉ phép hình học phải áp lên cả mask và hộp.
Khi nào phép biến đổi làm sai nhãn
Phần tiêu đề “Khi nào phép biến đổi làm sai nhãn”Phép chỉ an toàn khi nhãn thật sự bất biến với nó. Vài ví dụ nhãn không bất biến:
- Chữ số và chữ viết: xoay 180 độ đổi 6 thành 9; lật ngang biến chữ thành chữ ngược, thứ không có trong dữ liệu thật.
- Hướng có nghĩa: biển “rẽ trái” lật ngang thành “rẽ phải”. Ảnh y tế có trái phải giải phẫu: tim nằm bên trái lồng ngực, nên lật phim X-quang tạo ra một ca bệnh khác.
- Màu có nghĩa: phân loại quả chín hay xanh, đèn giao thông đỏ hay xanh: đổi sắc độ là đổi nhãn.
- Cắt quá tay: cắt ngẫu nhiên một vùng nhỏ có thể bỏ mất cả vật cần nhận.
Kiểm tra đơn giản nhất: in ra vài chục ảnh đã augment và tự hỏi “mình có còn gán đúng nhãn cũ cho ảnh này không?”.
Cutout, mixup, CutMix
Phần tiêu đề “Cutout, mixup, CutMix”Ba kỹ thuật dưới đây không mô phỏng thứ gì có thật. Chúng tạo ảnh “lạ” có chủ đích để mô hình khỏi dựa vào một chi tiết duy nhất.
Cutout (DeVries và Taylor, 2017): xóa một ô vuông cỡ cố định ở vị trí ngẫu nhiên, điền 0. Mô hình phải nhận ra chó cả khi phần đầu bị che, nên không dựa hết vào một vùng.
Mixup (Zhang và cộng sự, 2018): trộn hai ảnh và hai nhãn với cùng một hệ số. Nhãn viết dạng vectơ one-hot (một số 1 ở vị trí lớp đúng, còn lại 0):
Hệ số được bốc thăm từ phân phối Beta; với nhỏ, thường gần 0 hoặc gần 1, tức đa số ảnh chỉ bị trộn nhẹ. Paper thấy từ 0.1 tới 0.4 cải thiện kết quả trên ImageNet, còn lớn làm mô hình underfit. Hai ảnh được trộn thường lấy trong cùng một mini-batch sau khi xáo thứ tự.
Nhãn là nhãn mềm (soft label): ví dụ cho lớp 6, cho lớp 1. Cross-entropy với nhãn mềm vẫn là , với là xác suất mô hình gán cho lớp . Thay vào:
Dòng thứ hai chỉ tách tổng thành hai phần. Mỗi ngoặc là cross-entropy thường với một nhãn cứng. Vậy mất mát của mixup là trung bình có trọng số của hai mất mát: “ảnh này là lớp ” với trọng số , “là lớp ” với trọng số . Nhiều code cài mixup đúng theo dòng thứ hai, không cần tạo vectơ nhãn mềm.
Vì sao trộn hai ảnh vô nghĩa lại giúp? Mixup đòi mô hình đi “thẳng” giữa hai mẫu: ảnh lưng chừng giữa 6 và 1 thì dự đoán cũng lưng chừng, không nhảy đột ngột. Đó là một ràng buộc kiểu regularization, làm mô hình bớt tự tin thái quá.
CutMix (Yun và cộng sự, 2019) gộp hai ý trên: thay vì xóa một ô (cutout) hay trộn mờ cả ảnh (mixup), nó cắt một hình chữ nhật từ ảnh dán đè lên ảnh . Gọi là ma trận cùng cỡ ảnh, bằng 1 ở chỗ giữ ảnh và 0 trong hình chữ nhật, là nhân từng phần tử:
Nhãn trộn như mixup, với bằng tỉ lệ diện tích còn của ảnh . Để hình chữ nhật chiếm đúng phần diện tích, hai cạnh của nó là và : tích hai cạnh là . Hình chữ nhật bị cắt bớt khi chạm mép ảnh, nên code tính lại từ diện tích thật. So với cutout, không pixel nào bị phí; so với mixup, mỗi vùng ảnh vẫn là ảnh thật, mô hình học được “vật ở đâu”.
Từ AutoAugment tới RandAugment
Phần tiêu đề “Từ AutoAugment tới RandAugment”Chọn phép biến đổi nào, mạnh cỡ nào, với xác suất bao nhiêu? Có hàng chục phép, mỗi phép một độ mạnh, nên số tổ hợp rất lớn.
AutoAugment (Cubuk và cộng sự, 2019) để máy tìm. Một chính sách (policy) gồm nhiều chính sách con; mỗi chính sách con có hai phép, mỗi phép kèm xác suất áp dụng và độ mạnh. Mỗi ảnh trong mỗi mini-batch được áp một chính sách con chọn ngẫu nhiên. Thuật toán tìm (học tăng cường) thử rất nhiều chính sách, mỗi lần huấn luyện một mô hình nhỏ trên một phần dữ liệu và chấm trên tập kiểm định. Kết quả tốt, nhưng tìm rất tốn: theo paper RandAugment, riêng bản AutoAugment cho phát hiện vật thể tốn 15 nghìn giờ GPU.
RandAugment (Cubuk và cộng sự, 2020) bỏ hẳn bước tìm. Có một danh sách cố định 14 phép (xoay, xô nghiêng, dịch, tương phản, độ sáng, độ nét…). Với mỗi ảnh:
- Chọn ngẫu nhiên phép trong danh sách, mỗi phép xác suất như nhau.
- Áp lần lượt từng phép với cùng một độ mạnh , trên thang số nguyên 0 tới 10 (10 là mạnh nhất của từng phép).
Chỉ còn hai số, và cả hai dễ hiểu: , càng lớn thì regularization càng mạnh. Paper ước không gian tìm kiếm giảm từ khoảng (AutoAugment) xuống khoảng , nhỏ tới mức thử lưới vài giá trị trên chính bài toán là đủ. Paper còn thấy độ mạnh tốt nhất tăng theo cỡ mô hình và cỡ tập train. Đây là lý do không nên tìm chính sách trên một bài toán nhỏ rồi đem sang bài toán lớn.
Chỉ augment tập train, và test-time augmentation
Phần tiêu đề “Chỉ augment tập train, và test-time augmentation”Augmentation ngẫu nhiên chỉ dùng khi huấn luyện. Tập kiểm định và tập test chỉ đi qua các bước tất định: co về cỡ chuẩn, cắt giữa, chuẩn hóa pixel. Nếu tập test cũng bị biến đổi ngẫu nhiên, điểm test đổi theo mỗi lần chạy và không so được với ai.
Ngoại lệ có chủ đích là test-time augmentation (TTA): lúc dự đoán, đưa vài bản biến đổi cố định của cùng một ảnh test (ví dụ ảnh gốc và ảnh lật ngang) qua mô hình rồi lấy trung bình xác suất. AlexNet làm vậy với 10 vùng cắt (bốn góc, giữa, cùng bản lật của chúng). TTA thường tăng độ chính xác một chút, đổi lại chậm hơn nhiều lần khi suy luận.
Giới hạn
Phần tiêu đề “Giới hạn”- Không tạo ra thông tin mới. Augmentation chỉ nhân các biến thể mà bạn đã biết trước. Mô hình chưa từng thấy ảnh ban đêm thì tăng độ tối không thay được dữ liệu ban đêm thật.
- Có thể phạt quá tay. Biến đổi mạnh làm mô hình underfit, và độ chính xác trên ảnh sạch có thể giảm nhẹ. Mục Ví dụ code cho thấy chuyện này khi thay MLP bằng mô hình tuyến tính.
- Học tự giám sát dùng augmentation làm tín hiệu chính: hai bản biến đổi của cùng ảnh phải cho biểu diễn giống nhau. Bài Self-supervised learning với ảnh đi tiếp ý này.
Minh họa
Phần tiêu đề “Minh họa”Ô dưới đây là phần minh họa chính của bài: cùng một chữ số 6 (chữ số 6 thứ nhất của load_digits, giá trị pixel chia 16 để về đoạn 0 tới 1) qua chín phép biến đổi viết bằng NumPy và scipy.ndimage. Mixup dùng cố định cho dễ đọc, thay vì bốc thăm từ Beta. Để biết mỗi ảnh “trông giống” chữ số nào, ô dùng một bộ phân loại rất thô: tính ảnh trung bình của từng chữ số trên cả 1 797 ảnh, rồi chọn chữ số có ảnh trung bình gần nhất. Bấm Chạy, rồi nhìn vào số trong ngoặc ở tiêu đề mỗi ô. Lần đầu chạy lâu hơn, vì trình duyệt phải tải thêm SciPy, scikit-learn và matplotlib.
Lật, dịch, xoay, nhiễu, cutout, mixup
import matplotlib.pyplot as pltimport numpy as npfrom scipy import ndimagefrom sklearn.datasets import load_digits
rng = np.random.default_rng(0)digits = load_digits()X = digits.images / 16 # 1 797 ảnh 8 x 8, giá trị 0..1x6, x1 = X[6], X[1] # một chữ số 6 và một chữ số 1
# Phép hình học: chỉ dời chỗ các pixelflip = x6[:, ::-1] # lật ngangshift = ndimage.shift(x6, (0, 1), order=0, cval=0) # dịch phải 1 pxrot = ndimage.rotate(x6, 15, reshape=False, order=1) # xoay 15 độrot180 = np.rot90(x6, 2) # xoay 180 độ# Phép quang học: đổi giá trị pixel, giữ nguyên chỗbright = np.clip(1.4 * x6 + 0.1, 0, 1) # tăng tương phản và độ sángnoise = np.clip(x6 + rng.normal(0, 0.15, x6.shape), 0, 1)# Cutout: xóa một ô vuông 3 x 3 ở vị trí ngẫu nhiêncut = x6.copy()r, c = rng.integers(0, 6, size=2)cut[r:r + 3, c:c + 3] = 0# Mixup: trộn hai ảnh, trộn cả nhãn với cùng hệ số lamlam = 0.7mix = lam * x6 + (1 - lam) * x1y_mix = np.zeros(10)y_mix[6], y_mix[1] = lam, 1 - lamprint("mixup label:", np.round(y_mix, 2))
# Bộ phân loại thô: ảnh trung bình của mỗi chữ số, chọn cái gần nhấtmeans = np.stack([X[digits.target == k].mean(0) for k in range(10)])panels = [(x6, "original"), (flip, "flip"), (shift, "shift 1px"), (rot, "rotate 15"), (rot180, "rotate 180"), (bright, "contrast"), (noise, "noise"), (cut, "cutout"), (mix, "mixup")]fig, axes = plt.subplots(3, 3, figsize=(6, 6.4))for ax, (img, name) in zip(axes.flat, panels): near = np.argmin(((means - img) ** 2).sum(axis=(1, 2))) print(f"{name:10s} -> looks like {near}") ax.imshow(img, cmap="gray_r", vmin=0, vmax=1) ax.set_title(f"{name} ({near})") ax.axis("off")plt.tight_layout()plt.show()The first run downloads Python, about 10 MB.
Dòng mixup label in vectơ 10 số: ở vị trí 1 và ở vị trí 6, đúng công thức . Dịch dùng order=0 (nội suy gần nhất) và cval=0 (ô trống điền nền trắng của ảnh, giá trị 0); xoay dùng order=1 (song tuyến), nên ảnh xoay hơi nhòe.
Ba điều đáng để ý:
- Xoay 180 độ cho một hình mà chính bạn cũng đọc là 9: phép này làm sai nhãn, không được dùng cho chữ số.
- Dịch 1 pixel làm bộ phân loại thô đoán sai, dù với mắt người ảnh gần như không đổi. Đó chính là kiểu mô hình mà augmentation dịch chuyển giúp được.
- Mixup cho một hình không phải chữ số nào. Nó không cần trông thật; nhãn của nó là cho 6, cho 1.
Bảng dưới tóm tắt khi nào dùng và khi nào tránh từng phép.
| Phép biến đổi | Hợp với | Tránh khi | Mask, hộp bao |
|---|---|---|---|
| Lật ngang | Ảnh tự nhiên: động vật, đồ vật, cảnh | Chữ viết, biển chỉ hướng, ảnh y tế có trái phải | Lật theo |
| Lật dọc, xoay 90 độ | Ảnh vệ tinh, kính hiển vi (không có “phía trên”) | Ảnh có trọng lực: người, cảnh đường phố | Lật, xoay theo |
| Cắt co giãn ngẫu nhiên | Phân loại ảnh nói chung | Vật nhỏ, dễ bị cắt mất; đếm vật | Cắt theo, bỏ hộp ra ngoài khung |
| Xoay nhỏ, xô nghiêng | Chữ số, ảnh chụp cầm tay | Góc có nghĩa (đồng hồ, kim la bàn) | Xoay theo, nội suy gần nhất cho mask |
| Độ sáng, tương phản | Ảnh chụp ngoài trời, nhiều máy ảnh | Độ sáng có nghĩa (ảnh y tế đo mật độ) | Giữ nguyên |
| Đổi màu, sắc độ | Màu không quyết định lớp | Màu là nhãn: quả chín, đèn giao thông | Giữ nguyên |
| Nhiễu, làm mờ | Ảnh cảm biến rẻ, ảnh rung | Chi tiết nhỏ là nhãn: vết nứt, chữ nhỏ | Giữ nguyên |
| Cutout | Mô hình dựa vào một vùng duy nhất | Vật nhỏ có thể bị che hết | Phải xử lý riêng (vật bị che hết) |
| Mixup, CutMix | Phân loại, nhất là dữ liệu ít, nhiều lớp | Bài toán cần nhãn cứng; segmentation (cần cách trộn mask riêng) | Trộn theo diện tích (CutMix) |
Ví dụ code
Phần tiêu đề “Ví dụ code”Code trong bài chạy thử lần cuối tháng 11/2026: các ô chạy trên trang với Python 3.14, NumPy 2.4, SciPy 1.18 và scikit-learn 1.8 (bản có trong Pyodide), ô PyTorch với Python 3.14, PyTorch 2.15 và torchvision 0.29 trên CPU. Ô scikit-learn dưới đây (và ô ở mục Minh họa) chạy ngay trên trang, ô PyTorch chạy trên máy bạn. Các ô độc lập, không dùng biến của nhau.
Có và không có augmentation dịch chuyển
Phần tiêu đề “Có và không có augmentation dịch chuyển”Ô này làm lại thí nghiệm ở mục Trực giác. Nó chia 300 ảnh train (đủ cả 10 chữ số theo đúng tỉ lệ, nhờ stratify) và 1 497 ảnh test. Tập test có hai bản: ảnh gốc, và cùng các ảnh đó bị dịch ngẫu nhiên tối đa 1 pixel theo mỗi chiều. Mô hình là MLPClassifier, một mạng một lớp ẩn 64 nơ-ron, huấn luyện hai lần cùng seed: một lần trên 300 ảnh gốc, một lần trên 300 ảnh gốc cộng 4 bản dịch ngẫu nhiên của mỗi ảnh (1 500 ảnh). Augmentation chỉ chạm vào tập train. Ô cần khoảng chục giây trở lên, vì phải huấn luyện mạng hai lần.
Huấn luyện có và không có dịch ngẫu nhiên
import numpy as npfrom scipy import ndimagefrom sklearn.datasets import load_digitsfrom sklearn.model_selection import train_test_splitfrom sklearn.neural_network import MLPClassifier
rng = np.random.default_rng(0)digits = load_digits()X = digits.images / 16X_tr, X_te, y_tr, y_te = train_test_split( X, digits.target, train_size=300, stratify=digits.target, random_state=0)
def random_shift(imgs): # Mỗi ảnh dịch ngẫu nhiên tối đa 1 px theo mỗi chiều, ô trống điền 0 moves = rng.integers(-1, 2, size=(len(imgs), 2)) return np.stack([ndimage.shift(im, mv, order=0, cval=0) for im, mv in zip(imgs, moves)])
# Tập test thứ hai: cùng các ảnh test, mỗi ảnh bị dịch ngẫu nhiênX_te_shift = random_shift(X_te)
# Augmentation chỉ cho tập train: thêm 4 bản dịch ngẫu nhiên của mỗi ảnhX_aug = np.concatenate([X_tr] + [random_shift(X_tr) for _ in range(4)])y_aug = np.tile(y_tr, 5)
for name, Xa, ya in [("no aug", X_tr, y_tr), ("shift aug", X_aug, y_aug)]: # MLP nhỏ: một lớp ẩn 64 nơ-ron, cùng seed cho cả hai lần clf = MLPClassifier((64,), max_iter=1000, random_state=0) clf.fit(Xa.reshape(len(Xa), -1), ya) acc = clf.score(X_te.reshape(len(X_te), -1), y_te) acc_s = clf.score(X_te_shift.reshape(len(X_te), -1), y_te) print(f"{name:9s} train {len(Xa):4d}: test {acc:.3f}, " f"shifted test {acc_s:.3f}")The first run downloads Python, about 10 MB.
Đọc kết quả:
- Không augmentation: đúng trên ảnh test gốc, chỉ trên ảnh test bị dịch. Mô hình đã học vị trí pixel, như ở mục Trực giác.
- Có augmentation: trên ảnh gốc, gần như không đổi, và trên ảnh bị dịch. Mô hình đã học được “dịch 1 pixel không đổi chữ số”, dù không thấy thêm ảnh thật nào.
- Mạng không có tính chất dịch chuyển sẵn như CNN, nên mọi bất biến nó có đều đến từ dữ liệu.
Thử đổi mô hình thành LogisticRegression(max_iter=2000) (nhớ sửa dòng import). Kết quả: không augmentation và ; có augmentation và . Mô hình tuyến tính quá đơn giản để vừa nhận đúng ảnh gốc vừa chịu được phép dịch, nên augmentation kéo độ chính xác trên ảnh gốc xuống: đây là chuyện “phạt quá tay” ở mục Giới hạn. Phép biến đổi phải đi cùng một mô hình đủ sức học tính bất biến.
Pipeline torchvision v2: ảnh, mask và hộp bao cùng lúc
Phần tiêu đề “Pipeline torchvision v2: ảnh, mask và hộp bao cùng lúc”Trong dự án thật, bạn không tự viết từng phép mà dùng torchvision.transforms.v2. Từ torchvision 0.15 (3/2023), các phép v2 nhận được cả ảnh, mask và hộp bao trong một lần gọi, nếu chúng được gói trong tv_tensors. File dưới đây tự vẽ một ảnh bằng PIL (hình vuông đỏ là lớp 1, hình tròn xanh là lớp 2), tạo mask và hộp bao của hình vuông, rồi đưa qua pipeline: cắt co giãn ngẫu nhiên về , lật ngang, xoay tối đa 15 độ, đổi độ sáng, tương phản, độ bão hòa, đổi kiểu sang số thực. Để kiểm ảnh và mask còn khớp nhau, file đoán lớp của từng pixel từ màu (đỏ trội là 1, xanh trội là 2) rồi so với mask.
Run on your machine
Biến đổi đồng bộ ảnh, mask và hộp bao
- Install
pip install torch torchvision pillow- Hardware
- CPU là đủ. Phần tính chỉ mất chưa tới một giây; phần lớn thời gian là nạp PyTorch
import torchfrom PIL import Image, ImageDrawfrom torchvision import tv_tensorsfrom torchvision.transforms import v2
torch.manual_seed(0)# Ảnh 128 x 128 tự vẽ: nền xám, hình vuông đỏ (lớp 1), hình tròn xanh (lớp 2)pil = Image.new("RGB", (128, 128), (128, 128, 128))draw = ImageDraw.Draw(pil)draw.rectangle([16, 20, 60, 64], fill=(220, 40, 40))draw.ellipse([70, 60, 116, 106], fill=(40, 40, 220))img = v2.functional.pil_to_tensor(pil) # (3, 128, 128), uint8mask = torch.zeros(128, 128, dtype=torch.uint8)mask[20:65, 16:61] = 1mask[img[2] > 200] = 2 # pixel xanh dương là lớp 2img = tv_tensors.Image(img)mask = tv_tensors.Mask(mask)box = tv_tensors.BoundingBoxes([[16, 20, 61, 65]], format="XYXY", canvas_size=(128, 128))
train_tf = v2.Compose([ v2.RandomResizedCrop(96, scale=(0.5, 1.0)), # cắt ngẫu nhiên rồi co về 96 v2.RandomHorizontalFlip(p=0.5), v2.RandomRotation(15), v2.ColorJitter(brightness=0.4, contrast=0.4, saturation=0.4), v2.ToDtype(torch.float32, scale=True), # uint8 0..255 -> 0..1])# Tập kiểm định, test: chỉ các bước tất định, không ngẫu nhiêneval_tf = v2.Compose([v2.Resize(110), v2.CenterCrop(96), v2.ToDtype(torch.float32, scale=True)])
def color_class(x): # Đoán lớp của từng pixel từ màu: đỏ trội -> 1, xanh trội -> 2 r, b = x[0], x[2] return (r - b > 0.2).long() + 2 * (b - r > 0.2).long()
def agree(x, m): return (color_class(x) == m.long()).float().mean().item()
# Đúng: gọi một lần, ảnh, mask và hộp đi qua cùng một phép biến đổiout_img, out_mask, out_box = train_tf(img, mask, box)print("image:", tuple(out_img.shape), out_img.dtype)print("mask:", tuple(out_mask.shape), out_mask.dtype, "values", out_mask.unique().tolist())print("box before:", box.tolist(), "after:", out_box.round().tolist())print(f"agreement, one call: {agree(out_img, out_mask):.3f}")
# Sai: gọi hai lần riêng, mỗi lần bốc thăm một phép cắt và xoay khác nhauimg2 = train_tf(img)mask2 = train_tf(mask)print(f"agreement, two calls: {agree(img2, mask2):.3f}")print("eval image:", tuple(eval_tf(img).shape))Sample output
From one real run.
image: (3, 96, 96) torch.float32mask: (96, 96) torch.uint8 values [0, 1, 2]box before: [[16, 20, 61, 65]] after: [[48, 0, 87, 41]]agreement, one call: 0.993agreement, two calls: 0.400eval image: (3, 96, 96)Đọc kết quả:
image: cắt co giãn đưa ảnh về ,ToDtype(..., scale=True)đổi 0–255 thành 0–1.mask ... values [0, 1, 2]: mask vẫn chỉ có ba số lớp, không có giá trị lưng chừng. torchvision biếttv_tensors.Masklà nhãn nên dùng nội suy gần nhất, và các phép đổi màu bỏ qua mask.box: hộp của hình vuông dời và co theo ảnh. Sau khi xoay, hộp mới là hộp nhỏ nhất bao được hình đã xoay, nên có thể rộng hơn vật một chút.agreement, one call: 0.993: màu của ảnh và mask khớp ở 99.3% pixel; phần lệch chỉ là các pixel ở viền hình, nơi màu của ảnh đã bị nội suy song tuyến thành màu lưng chừng còn mask thì không.agreement, two calls: 0.400: gọi pipeline hai lần riêng cho ảnh và mask thì mỗi lần bốc thăm một phép cắt, lật, xoay khác nhau. Chỉ 40% pixel còn khớp: mô hình sẽ học từ một mask lệch với ảnh.eval image: pipeline cho tập kiểm định và tập test là mộtComposekhác, chỉ có các bước tất định, nên cùng một ảnh luôn cho cùng một kết quả:
eval_tf = v2.Compose([v2.Resize(110), v2.CenterCrop(96), v2.ToDtype(torch.float32, scale=True)])Muốn thêm RandAugment vào pipeline train, dùng v2.RandAugment(num_ops=2, magnitude=9): , , cũng là giá trị mặc định của torchvision. Lưu ý thang độ mạnh của torchvision mặc định có 31 mức (num_magnitude_bins=31), khác thang 0 tới 10 của paper. Nếu mô hình cần, thêm v2.Normalize(mean, std) ở cuối cả hai pipeline.
Lỗi hay gặp
Phần tiêu đề “Lỗi hay gặp”Kiểm tra nhanh
Phần tiêu đề “Kiểm tra nhanh”Tóm tắt và đọc thêm
Phần tiêu đề “Tóm tắt và đọc thêm”Ý chính:
- Augmentation đưa vào dữ liệu kiến thức “phép biến đổi này không đổi đáp án” (bất biến, cho phân loại) hoặc “đáp án đổi theo đúng phép này” (đồng biến, cho segmentation, detection). Nó làm nhỏ mất mát trung bình trên các phép biến đổi, nên vừa là thêm dữ liệu vừa là một dạng regularization.
- Phép hình học (lật, cắt, xoay) dời pixel và phải áp cả lên mask, hộp bao; phép quang học (sáng, tương phản, màu, nhiễu, mờ) chỉ đổi giá trị pixel. Phép nào an toàn tùy bài toán: xoay 180 độ biến 6 thành 9.
- Cutout xóa một ô, mixup trộn hai ảnh và hai nhãn , CutMix dán một vùng ảnh này lên ảnh kia và trộn nhãn theo diện tích. RandAugment thay việc tìm chính sách tốn kém của AutoAugment bằng hai số , .
- Chỉ augment tập train, sau khi đã chia dữ liệu. Tập kiểm định, test chỉ qua các bước tất định; test-time augmentation là ngoại lệ có chủ đích.
Đọc thêm:
- Shorten, C., Khoshgoftaar, T. M. (2019). A survey on Image Data Augmentation for Deep Learning. Journal of Big Data 6, 60. Tổng quan các nhóm phép biến đổi, đọc mục về phép hình học, quang học và tính “an toàn” của phép biến đổi.
- Zhang, H., Cissé, M., Dauphin, Y. N., Lopez-Paz, D. (2018). mixup: Beyond Empirical Risk Minimization. ICLR 2018. Mục 2 có công thức và đoạn code PyTorch vài dòng.
- Yun, S., Han, D., Oh, S. J., Chun, S., Choe, J., Yoo, Y. (2019). CutMix: Regularization Strategy to Train Strong Classifiers with Localizable Features. ICCV 2019. Mục 3 có cách lấy hình chữ nhật theo ; paper so trực tiếp với cutout và mixup.
- Cubuk, E. D., Zoph, B., Shlens, J., Le, Q. V. (2020). RandAugment: Practical automated data augmentation with a reduced search space. Mục 3 là cả thuật toán (hai dòng Python); hình 3 cho thấy độ mạnh tốt nhất tăng theo cỡ mô hình và cỡ dữ liệu.
- PyTorch. Transforming and augmenting images, tài liệu torchvision. Danh sách phép v2 và cách dùng với mask, hộp bao (
tv_tensors).
Your browser is blocking storage. Your progress will be lost when you reload the page.
Content licensed under CC BY-NC-SA 4.0. Code under the MIT license.About