Self-supervised learning với ảnh
Mục tiêu
Phần tiêu đề “Mục tiêu”Sau bài này, bạn sẽ:
- Hiểu vì sao cần học tự giám sát (self-supervised learning) với ảnh, bài toán phụ (pretext task) là gì, và cách đo chất lượng biểu diễn bằng linear probe hoặc fine-tune.
- Tự dẫn được hàm mất mát NT-Xent của SimCLR và giải thích được vai trò của cặp dương (positive pair), mẫu âm (negative example) và nhiệt độ .
- So sánh được ba họ phương pháp: học đối chiếu (SimCLR, MoCo), không cần mẫu âm (BYOL, DINO) và mô hình hóa ảnh bị che (masked image modeling, MAE).
- Cài được SimCLR tí hon bằng PyTorch rồi đo nó bằng linear probe với scikit-learn.
Cần biết trước
Phần tiêu đề “Cần biết trước”- Phân loại ảnh với Transformer: Attention, ViT, thuộc phần Thị giác máy tính (Đã học)
- Data augmentation trong CV, thuộc phần Thị giác máy tính (Đã học)
Trực giác
Phần tiêu đề “Trực giác”Một kho ảnh không ai gán nhãn
Phần tiêu đề “Một kho ảnh không ai gán nhãn”Bạn có một triệu ảnh chụp linh kiện trong nhà máy. Chụp thì rẻ: camera chạy cả ngày. Gán nhãn thì đắt: một kỹ sư phải xem từng ảnh và ghi “lỗi hàn”, “nứt”, “bình thường”. Sau một tháng, bạn có nhãn cho 2 000 ảnh.
Học có giám sát chỉ dùng được 2 000 ảnh đó. 998 000 ảnh còn lại nằm im. Như bài ViT đã nói, mô hình lớn cần rất nhiều dữ liệu, nên 2 000 ảnh là quá ít.
Câu hỏi của bài: có cách nào để 998 000 ảnh không nhãn dạy cho mô hình biết “nhìn” không?
Tự đặt câu hỏi, tự có đáp án
Phần tiêu đề “Tự đặt câu hỏi, tự có đáp án”Ý tưởng: đặt ra một bài toán mà đáp án nằm sẵn trong ảnh. Ví dụ, xoay ảnh một góc ngẫu nhiên rồi bắt mô hình đoán góc đã xoay. Nhãn “90 độ” do chính ta tạo ra, không tốn người gán.
Muốn biết một chiếc xe đang lộn ngược, mô hình phải biết bánh xe thường ở dưới, mui xe ở trên. Tức là nó phải học được hình dạng của vật. Đó chính là thứ ta cần.
Bài toán phụ chỉ là cái cớ. Học xong, ta bỏ phần đoán góc, giữ lại bộ mã hóa (encoder) đã biết nhìn. Rồi dùng 2 000 ảnh có nhãn để dạy nó việc thật.
Hai tấm ảnh của cùng một con mèo
Phần tiêu đề “Hai tấm ảnh của cùng một con mèo”Cách nổi lên từ khoảng năm 2020 tinh tế hơn. Lấy một ảnh con mèo. Tạo hai bản khác nhau: bản thứ nhất cắt một góc rồi đổi màu, bản thứ hai lật ngang rồi làm mờ. Mỗi bản gọi là một góc nhìn (view).
Hai góc nhìn trông khác nhau, nhưng vẫn là một con mèo. Ta muốn bộ mã hóa cho hai vectơ gần nhau. Còn góc nhìn của các ảnh khác trong lô (con chó, chiếc xe) thì phải cho vectơ ở xa.
Để làm được, mô hình phải bỏ qua những gì augmentation làm thay đổi (màu, vị trí, độ mờ) và giữ những gì không đổi (con mèo). Đó là học đối chiếu (contrastive learning), cùng ý với CLIP ở bài Ảnh + văn bản (1), chỉ khác là cả hai phía đều là ảnh.
Bài toán của bài: làm sao học một bộ mã hóa ảnh tốt chỉ từ ảnh không nhãn, sao cho mô hình không “ăn gian” bằng một lời giải tầm thường, và đo thế nào để biết nó học được gì?
Lý thuyết
Phần tiêu đề “Lý thuyết”Ký hiệu
Phần tiêu đề “Ký hiệu”Một lô có ảnh (bài báo SimCLR viết ; ở phần CV, đã là số patch). Mỗi ảnh qua hai phép augmentation ngẫu nhiên thành hai góc nhìn , . Bộ mã hóa cho biểu diễn . Một MLP nhỏ cho . Độ tương tự cosine:
Pretext cổ điển: xoay ảnh và ghép hình
Phần tiêu đề “Pretext cổ điển: xoay ảnh và ghép hình”Hai bài toán phụ nổi tiếng trước năm 2020:
- Đoán góc xoay (Gidaris và cộng sự, 2018): xoay ảnh 0, 90, 180 hoặc 270 độ, mạng phân loại 4 lớp. Đơn giản, nhưng buộc mạng biết chiều “đứng thẳng” của vật.
- Ghép hình (jigsaw, Noroozi và Favaro, 2016): cắt ảnh thành 9 mảnh, xáo theo một trong 1 000 hoán vị chọn trước (trong hoán vị), mạng đoán hoán vị nào. Muốn ghép đúng phải biết đầu mèo nằm trên thân mèo.
Điểm yếu chung: mạng hay tìm đường tắt (shortcut). Bài báo ghép hình kể rằng mạng có thể ghép nhờ đường viền và thống kê cấp thấp khớp nhau giữa hai mảnh liền kề, không cần hiểu vật. Bài toán phụ do người thiết kế, và nó tốt tới đâu thì biểu diễn tốt tới đó.
Đo biểu diễn: linear probe và fine-tune
Phần tiêu đề “Đo biểu diễn: linear probe và fine-tune”Học xong, làm sao biết tốt? Có hai cách chính, đều cần một ít ảnh có nhãn:
- Linear probe (bài báo SimCLR gọi là “linear evaluation protocol”): đóng băng , chỉ học một bộ phân loại tuyến tính trên . Bộ phân loại tuyến tính rất yếu, nên nếu nó vẫn đạt độ chính xác cao thì thông tin về lớp phải nằm sẵn trong , gần như tách được bằng một mặt phẳng.
- Fine-tune: dùng làm điểm khởi đầu rồi cập nhật cả mạng trên dữ liệu có nhãn. Gần với cách dùng thật hơn, nhưng đo cả khả năng “học lại” chứ không chỉ chất lượng biểu diễn.
SimCLR: kéo hai góc nhìn lại gần
Phần tiêu đề “SimCLR: kéo hai góc nhìn lại gần”SimCLR (Chen và cộng sự, 2020) gồm bốn phần: augmentation, encoder , projection head (MLP một lớp ẩn, ReLU) và hàm mất mát NT-Xent.
Bước 1: tạo góc nhìn. Mỗi ảnh trong lô cho hai góc nhìn, nên lô có vectơ . Với một cặp dương , bài báo không lấy mẫu âm riêng: góc nhìn còn lại trong lô chính là mẫu âm.
Bước 2: biến bài toán thành phân loại. Từ góc nhìn , hãy “chọn đúng” góc nhìn trong số ứng viên (mọi góc nhìn trừ chính nó). Điểm của mỗi ứng viên là cosine chia nhiệt độ: . Qua softmax, xác suất chọn đúng là:
bằng 1 khi , bằng 0 khi : nó bỏ cặp “một vectơ với chính nó”, vì cặp này luôn có cosine bằng 1 và không dạy gì.
Bước 3: lấy cross-entropy. Đáp án đúng là , nên mất mát là âm log của xác suất đó:
Muốn nhỏ, tử số phải lớn (cặp dương gần nhau) và mẫu số phải nhỏ (mẫu âm xa). Loss của lô là trung bình trên mọi cặp dương, tính cả lẫn . Bài báo gọi nó là NT-Xent (normalized temperature-scaled cross entropy): cross-entropy trên cosine (đã chuẩn hóa) chia nhiệt độ.
Bước 4: nhiệt độ làm gì. Chia cho nhỏ làm các khác biệt cosine phóng to, softmax nhọn lại: mẫu âm nào gần nhất (khó nhất) bị phạt nặng nhất. Khi rất lớn, mọi điểm gần bằng 0, mọi số hạng gần bằng nhau, nên và
dù cặp dương gần hay xa: loss không còn phân biệt được gì. Ô NumPy ở dưới kiểm cả hai đầu này.
Ba phát hiện của bài báo, đều quan trọng khi bạn tự làm:
- Augmentation quan trọng nhất. Không phép biến đổi đơn lẻ nào đủ. Ghép cắt ngẫu nhiên với đổi màu cho kết quả nổi bật. Lý do: chỉ cắt thì hai mảnh của cùng ảnh có histogram màu gần giống nhau, mạng chỉ cần so màu là ghép đúng. Đổi màu chặn đường tắt đó. Đổi màu mạnh giúp SimCLR, trong khi với học có giám sát nó không giúp, thậm chí làm hại.
- Projection head rồi bỏ đi. Tính loss trên nhưng dùng cho tác vụ sau: tốt hơn hơn 10 điểm phần trăm ở linear probe. Giải thích của tác giả: loss ép không đổi theo augmentation, nên mất những thông tin như màu hay hướng, vốn có thể có ích về sau; gánh phần “vứt bỏ” đó thay cho .
- Cần lô lớn. Lô càng lớn càng nhiều mẫu âm. Bài báo thử từ 256 tới 8 192 ( cho 16 382 mẫu âm mỗi cặp dương, trên TPU), lợi rõ nhất khi huấn luyện ít epoch. Về : với lô 4 096 và 100 epoch, cho linear probe 64.4%, còn 59.7%, còn 58.0%. Khi huấn luyện trên 300 epoch, tốt nhất trong ba giá trị 0.1, 0.5, 1.0.
Kết quả: linear probe 76.5% top-1 trên ImageNet với ResNet-50 rộng gấp 4, ngang ResNet-50 thường học có giám sát (ResNet-50 thường với SimCLR đạt 69.3%).
MoCo: hàng đợi và encoder động lượng
Phần tiêu đề “MoCo: hàng đợi và encoder động lượng”Lô 8 192 ảnh cần phần cứng mà ít ai có. MoCo (He và cộng sự, 2020) tách số mẫu âm khỏi cỡ lô.
Hàng đợi. Giữ một hàng đợi (queue) các vectơ của những lô trước, vectơ. Mỗi bước, vectơ của lô mới vào cuối hàng, lô cũ nhất ra khỏi đầu. Mẫu âm lấy từ hàng đợi, nên lô nhỏ vẫn có hàng chục nghìn mẫu âm.
Encoder động lượng (momentum encoder). Vấn đề: vectơ trong hàng đợi được tính bởi encoder ở các bước trước. Nếu encoder đổi nhanh, vectơ cũ và mới không còn so sánh được. MoCo dùng hai encoder: encoder truy vấn học bằng gradient, và encoder khóa chỉ trôi chậm theo nó:
Đây là trung bình động lũy thừa (EMA): mỗi bước giữ phần cũ, thêm phần mới. Bài báo viết hệ số này là ; bài này dùng vì đã là số mẫu. Với , encoder khóa đổi rất chậm nên các khóa trong hàng đợi nhất quán. Ablation của bài báo: tới đều ổn, tụt rõ, (chép thẳng) thì loss dao động và không hội tụ.
Không cần mẫu âm: BYOL và DINO
Phần tiêu đề “Không cần mẫu âm: BYOL và DINO”Mẫu âm có một vai trò ngầm: chống sụp đổ biểu diễn (representation collapse). Nếu chỉ kéo cặp dương lại gần, lời giải dễ nhất là cho mọi ảnh cùng một vectơ: cặp dương nào cũng trùng nhau, loss bằng 0, biểu diễn vô dụng. Mẫu âm cấm điều đó. Bỏ mẫu âm thì phải chống sụp đổ bằng cách khác.
BYOL (Grill và cộng sự, 2020) xuất phát từ một quan sát lạ: lấy một mạng khởi tạo ngẫu nhiên, cố định, làm “thầy”, rồi dạy một mạng khác dự đoán vectơ của thầy. Mạng ngẫu nhiên tự nó chỉ đạt 1.4% linear probe trên ImageNet, nhưng mạng học dự đoán nó đạt 18.8%. Trò đã giỏi hơn thầy. Vậy thì lặp lại: lấy trò làm thầy mới.
BYOL làm điều đó liên tục:
- Nhánh online gồm encoder, projector và thêm một predictor. Nhánh target có encoder và projector, trọng số là EMA của online (như encoder khóa của MoCo).
- Online nhìn góc nhìn thứ nhất và dự đoán vectơ mà target cho với góc nhìn thứ hai. Mất mát là bình phương khoảng cách giữa hai vectơ đã chuẩn hóa, tức .
- Gradient chỉ đi qua online; target bị stop-gradient (coi là hằng số).
Sự bất đối xứng (predictor chỉ ở một nhánh, target trôi chậm, không nhận gradient) giúp BYOL không sụp đổ trong thực nghiệm, dù bài báo thừa nhận nó không cấm sụp đổ bằng chứng minh. Linear probe đạt 74.3% với ResNet-50 thường.
DINO (Caron và cộng sự, 2021) nhìn bài toán như tự chắt lọc (self-distillation): chắt lọc tri thức (knowledge distillation) mà không có thầy cho sẵn, thầy là bản EMA của chính trò.
- Trò và thầy cùng kiến trúc, cùng cho ra một phân phối xác suất (softmax trên chiều). Trò học để phân phối của mình khớp phân phối của thầy (cross-entropy).
- Multi-crop: mỗi ảnh cho 2 góc nhìn toàn cục (phủ hơn nửa ảnh) và vài góc nhìn cục bộ (phủ dưới nửa ảnh). Thầy chỉ thấy góc nhìn toàn cục, trò thấy tất cả. Trò phải đoán được “cả bức tranh” từ một mảnh nhỏ.
- Chống sụp đổ bằng centering và sharpening ở đầu ra của thầy. Có hai kiểu sụp đổ: một chiều thống trị (mọi ảnh dồn vào một “lớp”), hoặc phân phối đều (không nói gì cả). Centering trừ đi trung bình chạy của đầu ra thầy, nên không chiều nào thống trị mãi được, nhưng lại đẩy về phân phối đều. Sharpening dùng nhiệt độ thầy thấp (0.04 tới 0.07, so với 0.1 của trò) làm phân phối nhọn, chống phân phối đều. Hai lực ngược nhau cân bằng lẫn nhau.
Phát hiện đáng nhớ nhất của DINO: với ViT học kiểu này, attention của token [CLS] ở lớp cuối tự tách vật khỏi nền. Nhìn bản đồ attention của các đầu, bạn thấy hình con vật hay chiếc xe hiện ra rõ nét, dù mô hình chưa từng thấy một nhãn hay một mask nào. Tác giả ghi rằng tính chất này kém rõ hơn nhiều ở ViT học có giám sát và ở CNN.
Che ảnh rồi đoán lại: MAE
Phần tiêu đề “Che ảnh rồi đoán lại: MAE”Hướng thứ ba mượn ý từ BERT: che một phần đầu vào, bắt mô hình đoán phần bị che. MAE (He và cộng sự, 2022) làm với ảnh:
- Cắt ảnh thành patch như ViT. Che ngẫu nhiên 75% số patch.
- Encoder ViT chỉ nhận 25% patch còn lại, không có token nào cho chỗ bị che.
- Một decoder nhẹ nhận các token đã mã hóa, chèn thêm một “mask token” học được vào mỗi chỗ bị che (kèm embedding vị trí), rồi tái tạo pixel của patch.
- Mất mát là MSE giữa pixel tái tạo và pixel gốc, chỉ tính trên patch bị che.
Vì sao che nhiều tới vậy? Ảnh rất dư thừa: che 15% như BERT thì mô hình chỉ cần nội suy từ các điểm ảnh bên cạnh. Che 75% thì phải hiểu vật mới đoán lại được.
Vì sao rẻ? Encoder là phần nặng, và nó chỉ xử lý một phần tư số token. Self-attention tốn theo bình phương số token, nên với ảnh và patch 16, encoder làm việc trên 49 token thay vì 196. Decoder chỉ tốn dưới 10% tính toán mỗi token so với encoder, và bị bỏ đi sau pre-train. Tác giả báo cáo huấn luyện nhanh hơn từ 3 lần trở lên. ViT-H học bằng MAE, fine-tune trên ImageNet-1K, đạt 87.8% top-1.
Một điểm khác với họ đối chiếu: biểu diễn MAE khó tách bằng mặt phẳng hơn. Linear probe của MAE thấp hơn MoCo v3, nhưng chỉ cần fine-tune một khối cuối của ViT-L là linear probe 73.5% lên 81.0%, và khi fine-tune vài khối thì MAE vượt MoCo v3. Tác giả nói hai cách đo “phần lớn không tương quan”. Vì vậy khi đọc một bảng so sánh, hãy xem nó đo bằng cách nào.
DINOv2, và CLIP nằm ở đâu
Phần tiêu đề “DINOv2, và CLIP nằm ở đâu”DINOv2 (Oquab và cộng sự, 2023) mở rộng DINO: một bộ dữ liệu 142 triệu ảnh được tuyển chọn tự động (LVD-142M), ghép loss mức ảnh của DINO với loss mức patch theo kiểu che ảnh của iBOT. Mục tiêu là đặc trưng dùng được ngay khi đóng băng, cho nhiều tác vụ (phân loại, phân đoạn, ước lượng độ sâu) mà không cần fine-tune.
CLIP là học đối chiếu giữa hai phương thức: cặp dương là (ảnh, chú thích của nó), mẫu âm là các chú thích khác trong lô. SimCLR là cùng ý trong một phương thức: cặp dương là hai góc nhìn của cùng ảnh. Công thức gần như nhau; khác biệt nằm ở chỗ “điều gì không đổi” do ai quy định: augmentation do người chọn, hay chú thích do người viết trên Internet.
Giới hạn
Phần tiêu đề “Giới hạn”- Augmentation mã hóa giả định của bạn. Học đối chiếu học cách bỏ qua đúng những gì augmentation thay đổi. Nếu màu quan trọng cho tác vụ (phân loại quả chín hay xanh), đổi màu mạnh sẽ làm mất thông tin đó. Bài Data augmentation trong CV bàn cách chọn phép biến đổi.
- Vẫn cần nhãn để dùng. Học tự giám sát cho bộ mã hóa, không cho bộ phân loại. Bước cuối luôn cần dữ liệu có nhãn, dù ít hơn nhiều.
- Tốn tính toán. Hai góc nhìn mỗi ảnh, lô lớn, hàng trăm epoch: pre-train tự giám sát thường tốn hơn học có giám sát cùng dữ liệu. Lợi ích đến khi dùng lại bộ mã hóa cho nhiều tác vụ.
Minh họa
Phần tiêu đề “Minh họa”Hình dưới vẽ khung chung của SimCLR. Hãy nhìn vào hai nhánh của cùng một ảnh: chúng dùng cùng một bộ mã hóa, và chỉ khác nhau ở phép augmentation. Mũi tên “kéo lại gần” nối hai vectơ của cùng ảnh, mũi tên “đẩy ra xa” nối tới vectơ của ảnh khác.
Bảng dưới đặt ba họ phương pháp cạnh nhau. Các con số là của từng bài báo, đo trên ImageNet, và không so trực tiếp được với nhau vì kiến trúc và cách đo khác nhau.
| Tiêu chí | Học đối chiếu (SimCLR, MoCo) | Không mẫu âm (BYOL, DINO) | Che ảnh (MAE) |
|---|---|---|---|
| Tín hiệu học | Chọn đúng góc nhìn kia giữa nhiều mẫu âm | Trò dự đoán đầu ra của thầy (EMA) trên góc nhìn khác | Tái tạo pixel của patch bị che |
| Cần augmentation mạnh | Có, quan trọng nhất | Có | Gần như không: che ảnh là “augmentation” |
| Chống sụp đổ bằng | Mẫu âm | Predictor, stop-gradient (BYOL); centering, sharpening (DINO) | Không có nguy cơ: đáp án là pixel thật |
| Phụ thuộc cỡ lô | Nhiều (SimCLR); MoCo bớt nhờ hàng đợi | Ít hơn | Ít |
| Kiến trúc hay gặp trong bài báo | ResNet | ResNet (BYOL), ViT (DINO) | ViT |
| Mạnh ở | Linear probe | Linear probe, k-NN; DINO cho attention tách vật | Fine-tune, mô hình rất lớn |
| Số trong bài báo | SimCLR: 76.5% linear (ResNet-50 rộng gấp 4) | BYOL: 74.3% linear (ResNet-50); DINO: 80.1% linear (ViT-B) | 87.8% fine-tune (ViT-H) |
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: ô NumPy với Python 3.14 và NumPy 2.4 (bản có trong Pyodide), ô PyTorch với Python 3.14, PyTorch 2.15 và scikit-learn 1.9 trên CPU. Ô NumPy chạy ngay trên trang, ô PyTorch chạy trên máy bạn. Hai ô độc lập, không dùng biến của nhau.
Hàm mất mát NT-Xent bằng NumPy
Phần tiêu đề “Hàm mất mát NT-Xent bằng NumPy”Ô dưới tạo 4 “ảnh” giả, mỗi ảnh là một vectơ nội dung 8 chiều (hạt giống 0). Mỗi góc nhìn là nội dung cộng nhiễu. Nhiễu nhỏ (0.1) cho cặp dương gần nhau; nhiễu lớn (3.0) cho cặp dương gần như ngẫu nhiên. Hàm nt_xent làm đúng ba bước ở mục Lý thuyết: cosine chia , bỏ đường chéo bằng , rồi cross-entropy với đáp án là cột của cặp dương.
NT-Xent với cặp dương gần và xa
import numpy as np
rng = np.random.default_rng(0)N, D = 4, 8 # 4 ảnh trong lô, embedding 8 chiềuimg = rng.normal(size=(N, D)) # "nội dung" của từng ảnh
def views(noise): # hai góc nhìn của mỗi ảnh = nội dung + nhiễu a = img + noise * rng.normal(size=(N, D)) b = img + noise * rng.normal(size=(N, D)) return np.vstack([a, b]) # 2N hàng: hàng i và i + N cùng ảnh
def nt_xent(z, tau): z = z / np.linalg.norm(z, axis=1, keepdims=True) s = z @ z.T / tau # cosine chia nhiệt độ, cỡ 2N x 2N np.fill_diagonal(s, -np.inf) # bỏ cặp một vectơ với chính nó m = s.max(axis=1, keepdims=True) log_p = s - m - np.log(np.exp(s - m).sum(axis=1, keepdims=True)) pos = np.r_[np.arange(N, 2 * N), np.arange(N)] # cột của cặp dương return -log_p[np.arange(2 * N), pos].mean()
print(f"log(2N-1) = {np.log(2 * N - 1):.3f}")z_close, z_far = views(0.1), views(3.0)for name, z in [("close", z_close), ("far", z_far)]: zn = z / np.linalg.norm(z, axis=1, keepdims=True) cos = (zn[:N] * zn[N:]).sum(axis=1).mean() print(f"{name}: positive cos = {cos:.2f}")
print("tau close far")for tau in [0.05, 0.1, 0.5, 1.0, 10.0]: lc, lf = nt_xent(z_close, tau), nt_xent(z_far, tau) print(f"{tau:<5} {lc:.3f} {lf:.3f}")Lần chạy đầu cần tải Python, khoảng 10 MB.
Đọc kết quả:
log(2N-1) = 1.946: với ảnh, mỗi góc nhìn có ứng viên, và là loss khi mô hình “đoán bừa”. (Trong code,Nlà số ảnh của lô, tức của bài.)- Cosine của cặp dương: 0.99 ở trường hợp gần, 0.22 ở trường hợp xa.
- Cột
close: cặp dương gần thì nhỏ cho loss gần 0 (0.000 ở , 0.002 ở ). Khi tăng, loss tăng dần (0.627, 1.166) và ở đạt 1.858, sát : loss không còn “thưởng” cho việc cặp dương gần nhau. - Cột
far: cặp dương xa thì nhỏ phạt rất nặng: 7.087 ở , cao hơn hẳn , vì có mẫu âm gần hơn cả cặp dương và nhỏ phóng to khác biệt đó. Ở hai cột gần bằng nhau (1.858 và 1.933): quá lớn thì loss gần như không phân biệt được biểu diễn tốt và xấu.
SimCLR tí hon trên chữ số, rồi linear probe
Phần tiêu đề “SimCLR tí hon trên chữ số, rồi linear probe”File dưới học SimCLR trên 1 797 ảnh chữ số của load_digits, không dùng nhãn. Augmentation viết bằng PyTorch: dịch ngẫu nhiên tối đa khoảng 1 pixel, thêm nhiễu, che một ô (cutout). Encoder là MLP , nên biểu diễn chỉ có 16 số; projection head là . Học 40 epoch với lô 256 và . Phần augmentation:
theta = torch.tensor([[1.0, 0, 0], [0, 1.0, 0]]).repeat(n, 1, 1)theta[:, :, 2] = (torch.rand(n, 2) - 0.5) * 0.5grid = F.affine_grid(theta, list(x.shape), align_corners=False)x = F.grid_sample(x, grid, align_corners=False)x = x + 0.1 * torch.randn_like(x)Sau đó là linear probe: đóng băng , lấy của mọi ảnh, học hồi quy logistic của scikit-learn trên nửa dữ liệu, đo trên nửa còn lại. File so encoder đã học với một encoder cùng kiến trúc nhưng khởi tạo ngẫu nhiên, ở hai mức nhãn: chỉ 100 ảnh có nhãn, và cả 898 ảnh của nửa train. Cách cài PyTorch bản CPU có ở bài Xây dựng LLM đầu tiên.
Chạy trên máy bạn
SimCLR tí hon và linear probe
- Cần cài
pip install torch scikit-learn- Phần cứng
- CPU là đủ. Khoảng 20 đến 45 giây trên máy phát triển
import torchfrom torch import nnimport torch.nn.functional as Ffrom sklearn.datasets import load_digitsfrom sklearn.linear_model import LogisticRegressionfrom sklearn.model_selection import train_test_splitfrom sklearn.pipeline import make_pipelinefrom sklearn.preprocessing import StandardScaler
torch.manual_seed(0)X, y = load_digits(return_X_y=True) # nhãn y chỉ dùng ở bước linear probeX = torch.tensor(X / 16.0, dtype=torch.float32).view(-1, 1, 8, 8)
def augment(x): # dịch tối đa 1 pixel, thêm nhiễu, che một ô 3x3 n = len(x) theta = torch.tensor([[1.0, 0, 0], [0, 1.0, 0]]).repeat(n, 1, 1) theta[:, :, 2] = (torch.rand(n, 2) - 0.5) * 0.5 grid = F.affine_grid(theta, list(x.shape), align_corners=False) x = F.grid_sample(x, grid, align_corners=False) x = x + 0.1 * torch.randn_like(x) r, c = torch.randint(0, 6, (2, n, 1, 1, 1)) i, j = torch.arange(8).view(8, 1), torch.arange(8).view(1, 8) hole = (i >= r) & (i < r + 3) & (j >= c) & (j < c + 3) return x.masked_fill(hole, 0.0)
def encoder(): # f: ảnh 8x8 -> biểu diễn h có 16 chiều return nn.Sequential(nn.Flatten(), nn.Linear(64, 256), nn.ReLU(), nn.Linear(256, 16))
def nt_xent(z, tau=0.5): # hàng i và i + n là hai góc nhìn của một ảnh z = F.normalize(z, dim=1) s = (z @ z.T / tau).fill_diagonal_(float("-inf")) n = len(z) // 2 pos = torch.cat([torch.arange(n, 2 * n), torch.arange(n)]) return F.cross_entropy(s, pos)
random_f, f = encoder(), encoder()head = nn.Sequential(nn.Linear(16, 64), nn.ReLU(), nn.Linear(64, 32))opt = torch.optim.Adam([*f.parameters(), *head.parameters()], lr=1e-3)for epoch in range(40): # học không cần nhãn for idx in torch.randperm(len(X)).split(256): views = torch.cat([augment(X[idx]), augment(X[idx])]) loss = nt_xent(head(f(views))) opt.zero_grad() loss.backward() opt.step()print(f"last NT-Xent loss: {loss.item():.3f}")
# Linear probe: đóng băng f, chỉ học hồi quy logistic trên htr, te = train_test_split(range(len(y)), test_size=0.5, random_state=0, stratify=y)for n_labels in [100, len(tr)]: for name, enc in [("random", random_f), ("SimCLR", f)]: with torch.no_grad(): h = enc(X).numpy() probe = make_pipeline(StandardScaler(), LogisticRegression(max_iter=2000)) probe.fit(h[tr[:n_labels]], y[tr[:n_labels]]) acc = probe.score(h[te], y[te]) print(f"{n_labels} labels, {name} encoder: {acc:.3f}")Kết quả mẫu
Lấy từ một lần chạy thật.
last NT-Xent loss: 1.428100 labels, random encoder: 0.740100 labels, SimCLR encoder: 0.840898 labels, random encoder: 0.843898 labels, SimCLR encoder: 0.918Đọc kết quả:
last NT-Xent loss: 1.428: lô 256 ảnh có 511 ứng viên mỗi góc nhìn, nên đoán bừa cho (tự tính). Loss 1.428 nghĩa là mô hình đã nhận ra cặp dương khá tốt.- Với 100 nhãn: encoder ngẫu nhiên đạt 0.740, encoder SimCLR đạt 0.840. Cùng 16 số mỗi ảnh, nhưng 16 số do SimCLR chọn mang nhiều thông tin về chữ số hơn.
- Với 898 nhãn: 0.843 so với 0.918. Khoảng cách vẫn còn: một phép chiếu ngẫu nhiên xuống 16 chiều làm mất thông tin mà thêm nhãn cũng không lấy lại được.
Hai lần chạy trên máy phát triển cho kết quả trùng nhau. Máy khác (khác CPU hay khác bản PyTorch) có thể lệch vài phần nghìn.
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:
- Học tự giám sát tạo nhãn từ chính ảnh (bài toán phụ) để học bộ mã hóa từ ảnh không nhãn, rồi dùng ít nhãn cho tác vụ thật. Đo bằng linear probe (đóng băng, chỉ học lớp tuyến tính) hoặc fine-tune; hai cách đo có thể xếp hạng khác nhau.
- Học đối chiếu kéo hai góc nhìn của cùng ảnh lại gần, đẩy góc nhìn của ảnh khác ra xa. NT-Xent là cross-entropy của bài toán chọn đúng cặp dương trong ứng viên, trên cosine chia . SimCLR cần augmentation mạnh (cắt + đổi màu) và lô lớn; MoCo thay lô lớn bằng hàng đợi và encoder động lượng.
- Không có mẫu âm thì phải chống sụp đổ cách khác: BYOL dùng predictor, target EMA và stop-gradient; DINO dùng thầy EMA cùng centering và sharpening, và attention của ViT tự tách vật.
- MAE che 75% patch, encoder chỉ xem phần còn lại, decoder nhẹ tái tạo pixel: rẻ, mở rộng tốt, mạnh khi fine-tune. CLIP là học đối chiếu giữa ảnh và chữ.
Đọc thêm:
- Chen, T., Kornblith, S., Norouzi, M., Hinton, G. (2020). A Simple Framework for Contrastive Learning of Visual Representations, arXiv:2002.05709, mục 2 và 3. Công thức NT-Xent và các thí nghiệm về augmentation, viết rất dễ đọc.
- He, K., Fan, H., Wu, Y., Xie, S., Girshick, R. (2020). Momentum Contrast for Unsupervised Visual Representation Learning, arXiv:1911.05722, mục 3. Giả mã Algorithm 1 chỉ khoảng 20 dòng, đủ hàng đợi và cập nhật động lượng.
- Grill, J.-B. và cộng sự (2020). Bootstrap Your Own Latent: A New Approach to Self-Supervised Learning, arXiv:2006.07733, mục 1 và 3. Thí nghiệm mạng ngẫu nhiên làm thầy, và kiến trúc online, target.
- Caron, M. và cộng sự (2021). Emerging Properties in Self-Supervised Vision Transformers, arXiv:2104.14294, hình 1 và mục 3. Nên xem hình 1 để thấy attention của DINO tách vật.
- He, K., Chen, X., Xie, S., Li, Y., Dollár, P., Girshick, R. (2022). Masked Autoencoders Are Scalable Vision Learners, arXiv:2111.06377, mục 3 và 4. Kiến trúc MAE và bàn luận về linear probe so với fine-tune.
- Oquab, M. và cộng sự (2023). DINOv2: Learning Robust Visual Features without Supervision, arXiv:2304.07193. Đọc phần giới thiệu để biết vì sao đặc trưng đóng băng dùng được cho nhiều tác vụ.
- Gidaris, S., Singh, P., Komodakis, N. (2018). Unsupervised Representation Learning by Predicting Image Rotations, arXiv:1803.07728; và Noroozi, M., Favaro, P. (2016). Unsupervised Learning of Visual Representations by Solving Jigsaw Puzzles, arXiv:1603.09246. Hai bài toán phụ cổ điển.
Trình duyệt đang chặn lưu dữ liệu. Tiến độ sẽ mất khi bạn tải lại trang.
Nội dung theo giấy phép CC BY-NC-SA 4.0. Mã nguồn theo giấy phép MIT.Giới thiệu