另一種範式:預測缺失的像素
在這個系列裡,我們一直透過比較視圖來學習表徵。對比學習(第 2-3 講)把同一張影像的兩個增強裁切拉近、把不同影像推遠;蒸餾與分群方法(第 4 講)拋開了負樣本,但仍然是拿學生視圖去比對教師視圖。每一種做法,學習訊號都來自兩張變換後影像「之間」的關係。這最後一講要引入一個真正不同的想法:我們不再比較視圖,而是重建被隱藏的內容。我們遮住影像的大部分,訓練網路把缺失的部分補回來。這是一種生成式的前置任務(pretext task),而它所定義的這一家方法就稱為 遮罩影像建模(masked image modeling,MIM)。
理解 MIM 最清楚的方式,是類比自然語言處理。BERT 模型用遮罩語言建模徹底改變了 NLP:拿一句話如「the cat sat on the [MASK]」,藏起幾個字,訓練模型從上下文預測缺失的字。要猜出「mat(墊子)」,網路必須理解文法、語意以及世界知識。遮罩影像建模整套照搬這個招式,只是這裡的詞元(token)不再是字詞,而是影像的區塊(patch):我們藏起圖片的某些區塊,再從留下來可見的區塊預測它們的內容。同樣的配方——先遮住、再從上下文預測——只是從文字搬到了像素。
一張照片上覆蓋著規則網格,把它分成等大的方形區塊,每一個區塊都被當作一個詞元。
那麼讓我們把它定義清楚。遮罩影像建模是一種 自監督學習 方法:它遮住影像中很大一部分的區塊,並訓練模型從可見的區塊重建被隱藏的內容(可以是原始像素,也可以是它們的某種編碼)。本講接下來會拆解兩個里程碑式的配方——重建像素的 MAE,以及預測離散視覺詞元的 BeiT——接著退一步比較 MIM 學到什麼、對比方法又學到什麼,最後以自監督視覺的現況為整個系列收尾。
把影像切成區塊:ViT 的基礎
在我們能「遮住一個區塊」之前,需要一個前提:圖片必須先被切成區塊。這正是視覺轉換器(Vision Transformer,ViT)所做的事。給定一張輸入影像——假設是 224x224 像素——ViT 把它切成一個規則網格,由許多小而不重疊的方塊組成,通常每塊是 16x16 像素。因此一張 224x224 的影像會變成 14x14 = 196 個區塊的網格。每個區塊都是原圖的一小片,而且關鍵是這些片不會重疊,所以每個像素恰好只屬於一個區塊。這就是你在上圖看到的同一個網格。
接著每個區塊被展平成一個詞元。一個含 3 個色彩通道的 16x16 區塊有 16x16x3 = 768 個數字;ViT 把這個向量送過一個小型線性層,產生一個固定長度的嵌入向量(embedding)——也就是一個詞元——再加上位置編碼,好讓模型知道這個區塊來自網格的哪個位置。結果是一個由 196 個詞元組成的序列,轉換器用自注意力(self-attention)來處理它:每個詞元都能看見其他所有詞元,並決定每一個對自己有多重要。這跟語言轉換器處理一句話裡的字詞如出一轍,那裡每個字詞元都會關注其他所有字詞。區塊之於影像,就像字詞之於句子。
同一個區塊網格,每個方格都標上編號詞元,呈現出「影像即序列」的觀點。
這套區塊詞元化(patch tokenization)正是 BERT 式遮罩能如此乾淨地從文字搬到影像的幕後原因。一旦一張圖片變成一串離散的詞元,「藏起某些詞元再預測它們」就是一個定義明確的操作——你真的就是把某個區塊詞元從序列中移除,就像 BERT 移除一個字詞一樣。換成卷積網路,並沒有天然可供丟棄的詞元;但有了 ViT,遮罩變得輕而易舉。這正是為什麼遮罩影像建模與 ViT 是攜手崛起的:是這個架構,才讓 BERT 配方第一次有可能用在視覺上。
MAE:遮罩自編碼器
MIM 最乾淨、最易擴展的實例,是 遮罩自編碼器(Masked Autoencoder,MAE)。名字已經說明一切:它是一個自編碼器——一個負責壓縮的編碼器、一個負責重建的解碼器——只是訓練在被遮罩的輸入上。讓 MAE 成名的是兩個刻意而且有點反直覺的設計選擇。掌握這兩個想法,你就懂了 MAE。
選擇一:非常高的遮罩比例,約 75%。 在 BERT 裡,遮住約 15% 的字詞就夠了,因為語言資訊密度高——每個字都承載許多訊息。影像恰恰相反:它在空間上是冗餘的。一塊缺失的藍天區塊,幾乎可以毫不費力地從鄰近的天空區塊猜出來。如果你只遮住影像的 15%,網路靠著從鄰居那裡天真地複製、內插就能補上空缺,幾乎學不到任何關於物體的東西。透過遮住 75% 的區塊,MAE 讓空缺大到局部複製失效——模型被迫去推理整個物體與場景結構,才能重建缺失的部分。困難的任務才是有資訊量的任務。
選擇二:非對稱的編碼器-解碼器。 這是讓 MAE 能便宜地擴展的關鍵。沉重的 ViT 編碼器只處理可見的那 25% 區塊——被遮罩的區塊根本不餵給它。由於自注意力的計算成本隨序列長度快速增長,只在四分之一的詞元上跑編碼器便宜得多。被遮罩的位置只在之後、在解碼器階段才被重新引入:編碼後的可見詞元被放回它們在網格中的位置,而每個被遮罩的空位則填入一個共享、可學習的遮罩詞元(mask token,再加上它的位置編碼,好讓解碼器知道要重建哪個位置)。接著一個輕量的解碼器對這整組詞元做注意力運算,並對被遮罩的區塊輸出像素預測。
一個轉換器區塊的示意圖:層正規化、多頭自注意力、殘差相加,接著一個 MLP 與另一個殘差相加。
- 把影像切成不重疊的詞元網格(即 ViT 的步驟)。
- 隨機選出約 75% 的區塊來遮罩;保留剩下約 25% 作為可見區塊。
- 只在可見區塊上跑深層編碼器——被遮罩的區塊完全不進入編碼器。
- 在每個被遮罩的位置插入一個共享、可學習的遮罩詞元(附上位置編碼)。
- 在完整序列上跑輕量解碼器,預測每個被遮罩區塊的像素。
- 只在被遮罩的區塊上計算重建損失;反向傳播。
- 預訓練結束後,丟棄解碼器,保留編碼器作為可遷移的骨幹網路。
最後那一步才是重點所在。解碼器的存在,只是為了在訓練時讓重建成為可能;一旦編碼器被迫建立起豐富的內部表徵,解碼器就完成了它的任務。我們把它丟掉、留下編碼器,再針對下游任務對它做微調(或接上一個線性頭)。驅動這一切的訓練目標,是一個簡單的像素重建損失:
MAE 重建損失:僅在被遮罩區塊上計算的均方誤差。
讓我們逐符號讀。M 是被遮罩區塊的索引集合——也就是網路被迫去猜的那些區塊——而 |M| 是它們的數量(約佔所有區塊的 75%)。求和跑遍 `i ∈ M`,所以這個損失只在被遮罩的區塊上計算;編碼器早就看過的可見區塊毫無貢獻,因為重建已經遞到你手上的東西,根本測不出理解力。對每個被遮罩的區塊,x_i 是那個區塊的原始像素(實務上會做逐區塊正規化,也就是在區塊內做平均與變異數標準化,這在經驗上會讓目標更銳利),而 x̂_i 是解碼器對那些像素的重建。`‖x_i − x̂_i‖²` 這一項是兩者之差的 L2 範數平方:把每個像素的誤差平方再加總——就是普通的均方誤差。除以 |M| 則是取平均。
具體來說:假設某個被遮罩的天空區塊有真實(已正規化)的像素值,而解碼器輸出在它的 768 個數字上各差了 0.1。那麼這個區塊對內層加總貢獻約 768 x 0.1² = 7.68;把它在全部約 147 個被遮罩區塊上取平均,就是要最小化的損失。整個目標背後的直覺是:要讓 x̂_i 逼近 x_i,網路必須從周圍可見的上下文推斷缺失的內容,而在 75% 被藏起來的情況下,唯一辦法就是學會物體的形狀、紋理與場景佈局。同時,因為編碼器自始至終只碰那可見的 25%,把 MAE 擴展到巨大的 ViT 編碼器與龐大的資料集,成本仍然可以負擔。高遮罩比例讓任務有資訊量;非對稱設計讓它便宜。
# MAE training step (pseudocode) — see the asymmetry: encoder sees only visibles
def mae_step(image, mask_ratio=0.75):
patches = patchify(image) # [N, patch_dim], N = 196 for 224x224 @ 16px
target = per_patch_normalize(patches) # reconstruction target x_i
# 1) randomly partition patches into visible / masked
keep_idx, mask_idx = random_split(N=len(patches), mask_ratio=mask_ratio)
# 2) ENCODER runs on the visible ~25% ONLY (this is the compute saving)
vis_tokens = linear_embed(patches[keep_idx]) + pos_embed[keep_idx]
enc_vis = encoder(vis_tokens) # deep ViT, short sequence
# 3) rebuild full sequence: encoded visibles + shared mask token at holes
full = scatter(enc_vis, keep_idx, into_length=len(patches))
full[mask_idx] = mask_token # one shared learnable vector
full = full + pos_embed # tell decoder WHERE each slot is
# 4) lightweight DECODER reconstructs pixels everywhere
pred = decoder(full) # [N, patch_dim]
# 5) loss is MSE over MASKED patches only -> (1/|M|) * sum_{i in M} ||x_i - x_hat_i||^2
loss = mse(pred[mask_idx], target[mask_idx])
return loss
# After pretraining: throw away `decoder`, keep `encoder` as the backbone.BeiT:預測視覺詞元而非像素
MAE 要求網路去回歸原始的像素值。MIM 的第二種重要風格——BeiT(Bidirectional Encoder representation from Image Transformers,影像轉換器的雙向編碼器表徵)——透過分類而非回歸,更貼近最初的 BERT 配方。想法是:與其預測被遮罩區塊的精確像素,不如預測一個概括該區塊內容的離散視覺詞元,就像 BERT 在固定詞彙表上預測被遮罩字詞的離散字詞 id 一樣。
這些視覺詞元從哪來?在訓練主模型之前,BeiT 先用一個獨立、凍結的分詞器(tokenizer)——一個事先訓練好的離散變分自編碼器(discrete VAE,dVAE)——把每個區塊映射成一個整數 id,這個 id 取自一個學習得到的視覺詞彙表(可以想成一本含例如 8192 個「視覺字詞」的碼本)。於是一張影像變成一個整數詞元 id 的網格,就像一句話是一串字詞 id。預訓練時,BeiT 遮住輸入影像的某些區塊,ViT 必須以分類的方式,預測每個被遮罩區塊在分詞器下的 id。分詞器永遠不被更新;它只是提供標準答案目標。
BeiT 的目標:在每個被遮罩區塊上最大化正確視覺詞元的對數機率(等價於最小化交叉熵)。
逐符號讀這個目標:M 一樣是被遮罩區塊的索引集合。z_i 是區塊 i 的離散詞元 id,也就是凍結的 dVAE 分詞器產生的整數——這是我們要模型輸出的標籤,相當於視覺版的「正確字詞」。x̃(x 上加波浪號)是被破壞的影像:也就是把被遮罩區塊移除或替換後的輸入。p(z_i | x̃) 是模型的預測機率——在整個視覺詞元詞彙表上做 softmax——表示在給定被破壞影像下,被遮罩區塊 i 是詞元 z_i 的機率。我們最大化正確詞元對數機率的總和;等價地說,我們最小化模型 softmax 與真實詞元 id 之間的交叉熵,這正是訓練任何分類器所用的損失。直覺上:對每個被挖空的區塊,網路會在數千個「視覺字詞」上輸出一個分布,並因為把機率質量放在正確的那一個上而獲得獎勵。
現在它與 MAE 的對比變得鮮明。MAE 的損失 `‖x_i − x̂_i‖²` 是對連續像素值的回歸;BeiT 的損失則是對離散詞元 id 的分類。這裡所做的取捨是實實在在的:離散視覺詞元傾向捕捉更語意、更高層次的資訊,並捨棄一些低層次的像素細節,因為分詞器把整個區塊壓縮成單一 id。這會讓目標較少關乎精確紋理、較多關乎「這裡是什麼」。代價是多了一個分詞器階段——你得先訓練(或下載)一個離散 VAE,這是一個額外的環節,而以像素為直接目標的 MAE 完全避開了它。
像素、詞元、對比:各自學到什麼
我們現在有三大類自監督視覺方法:對比學習(第 2-3 講)、蒸餾/分群(第 4 講),以及遮罩影像建模(本講)。它們產出的表徵並不能互換,而要選得好,就得理解大量論文中浮現出的經驗規律。以下據實陳述。
對比學習 與蒸餾方法產出的特徵高度線性可分:你可以凍結骨幹網路,在上面接一個單一線性層,就已經能得到不錯的分類準確率。因此它們在第 1 講介紹的線性探測(linear-probe)與 kNN 評估協定下表現亮眼——這些協定測的正是:在幾乎不額外學習的情況下,從凍結特徵裡能讀出多少有用結構。遮罩影像建模則相反:MAE/BeiT 的特徵較不線性可分,所以它們的線性探測數字相對看起來偏弱。但經過完整的微調(fine-tuning)——解凍整個骨幹並讓它適應任務——遮罩模型往往能追平甚至勝過對比模型,而且它們在密集預測任務上特別出色,例如物件偵測與語意分割,這些任務正需要精細的局部結構。
為何會有這種分歧?它直接回溯到每個目標各自最佳化什麼。對比與蒸餾方法建立在實例判別(instance discrimination)之上:訊號是全域、影像層級的——「這張影像跟那張是同一張嗎?」這種壓力把整張影像組織成一個乾淨、線性可讀的向量,正合線性探測的胃口,但它可能沖淡精細的空間細節。遮罩建模最佳化的是局部、具空間細節的訊號——「在這個位置、這個 16x16 的洞裡,到底該放什麼?」這迫使網路在整張特徵圖上保留逐位置的結構,而這正是偵測與分割所需要的;但它不會預先把特徵排列成單一條漂亮可分的方向,所以一個光禿禿的線性探測讀起來會比較吃力。
所以實務上的建議是具體的,而非含糊其辭。如果你打算凍結骨幹、需要開箱即用的強特徵——少樣本、kNN 檢索,或快速跑一個 線性評估協定 讀數——就選對比或蒸餾模型(DINO 在這方面特別突出)。如果你打算微調,尤其當目標是密集任務(偵測、分割)時,遮罩影像建模是極佳的初始化來源。兩者都是通往良好 表徵學習 的有效路線;該選哪一條,取決於你的下游預算是否允許微調,以及你的任務有多麼仰賴局部資訊。
自監督視覺的現況與前沿
退一步,看看這個系列走過的整段旅程。我們從(第 1 講)手工設計的前置任務出發——預測旋轉、解拼圖、上色——巧妙但臨時拼湊,每個任務只教會視覺所需的一小片。接著是對比學習(第 2-3 講):SimCLR 與 MoCo 把「這是同一張影像嗎?」變成一個強大而通用的訊號,而 MoCo 的動量佇列解決了「找到足夠負樣本」這個難題。第 4 講顯示我們甚至能拋開負樣本:BYOL 與 SimSiam 靠帶停止梯度的自蒸餾學習,SwAV 加上線上分群,而 DINO 透過搭配動量教師的自蒸餾,產出極具語意的特徵。本講則帶來遮罩影像建模(MAE、BeiT),把 BERT 的「藏起來再預測」配方引進視覺。四大家族,一個目標:在沒有標籤的情況下從影像中學習。
前沿正往哪走?最豐碩的方向,是把遮罩與蒸餾結合起來,而不是把它們當成對手。最清楚的例子是 DINOv2 式的訓練配方,它把 DINO 的自蒸餾(提供乾淨、語意、線性可分的全域特徵)與遮罩建模目標(提供豐富的局部結構)融合在一起——實際上等於同時收下上一節兩欄的優點。與此並行,這個領域也在規模上大力推進:把這些目標訓練在從網路上抓來的龐大、未經人工整理的影像集合上,常搭配自動化的資料整理流程來過濾與平衡它們,因為自監督終於讓我們能用上那些從來沒有人標註過的影像。
把更好的目標與龐大規模結合起來,回報就是基礎骨幹(foundation backbone):一個單一的預訓練編碼器,其凍結特徵能遷移到極為廣泛的任務——分類、偵測、分割、深度估計、檢索——而且往往只需在上面訓練一個輕量的頭、用上極少甚至零個任務專屬的標註資料。這正是大型語言模型在 NLP 中扮演的角色,如今在視覺裡實現了。一個從未標註影像中一次學成、然後處處重用的通用視覺編碼器。
這也讓整個系列繞回到第 1 講的承諾。監督式學習把視覺鎖在標註瓶頸上——每一項新能力都得要一份全新、昂貴、由人工標註的資料集。自監督學習 打破了這條鎖鏈:它把網路上源源不絕、未標註的影像變成訓練訊號,造就了標籤永遠達不到的規模下的通用視覺 表徵學習。遮罩影像建模 是這套工具箱裡最新、也最強大的工具之一——視覺的 BERT 時刻。標籤從來就不是重點;影像本身一直都已經足夠。