把交叉熵損失講透
在第一篇指南中,我們用一句話認識了交叉熵:它衡量模型對「正確答案」感到有多意外。現在,我們要真正搞懂它「為什麼」這麼好用,才能在本篇後面的內容裡放心地依賴它。回想一下影像分類的設定:網路會輸出一串原始、無上下界的分數,稱為 logits;softmax 把它們壓成全為正、且總和為 1 的機率;而標籤則是一個one-hot 標籤編碼——一個在正確類別上為 1、其餘類別全為 0 的向量。
one-hot 目標 y 與預測分佈 p 之間的交叉熵。
我們逐個符號來讀。求和符號跑過所有類別 i。其中 y_i 是 one-hot 目標(正確類別為 1,其餘為 0),p_i 則是模型分配給類別 i 的 softmax 機率。由於除了正確類別外,每個 y_i 都是 0,整個總和會塌縮成單一項:L = −log(p_true)。所以交叉熵其實只在乎一個數字——你給正確答案的機率。若 p_true = 1,損失為 −log 1 = 0(完美);若 p_true = 0.5,損失為 −log 0.5 ≈ 0.69;若 p_true = 0.01,損失為 −log 0.01 ≈ 4.6(錯得離譜)。當正確機率趨近於零時,損失會衝向無窮大——這正是我們想要的「絕不要錯得很自信」的壓力。
交叉熵對 logits 的梯度,簡潔得令人驚艷。
這條小小的方程式是整個訓練過程的引擎,所以讓我們拆解每一個符號。z_i 是類別 i 的 logit(原始分數),p_i 是類別 i 的 softmax 機率,y_i 是 one-hot 目標(正確類別為 1,其餘為 0)。梯度就是「預測值減真實值」:p_i − y_i。對正確類別來說,p_true − 1 是負的,所以梯度下降會把那個 logit 往上推;對每個錯誤類別來說,p_i − 0 是正的,所以梯度下降把那些 logit 往下壓。而且每次推動的幅度,正比於你錯得多離譜。舉個三類別、正確類別為貓(索引 0)的例子:設 logits 為 z = [2.0, 1.0, 0.1],softmax 得 p ≈ [0.659, 0.242, 0.099]。梯度即 p − y = [0.659 − 1, 0.242 − 0, 0.099 − 0] = [−0.341, +0.242, +0.099]。貓的 logit 拿到負梯度(於是上升),兩個錯誤 logit 拿到正梯度(於是下降)——正是「把正確類別往上推、錯誤類別往下壓」。
一個損失曲面,球以一步步的方式滾下山坡朝最低點前進,每一步的大小正比於梯度。
為什麼硬標籤會傷害模型:標籤平滑
這裡藏著一個不易察覺、卻就擺在眼前的病灶。使用純粹的 one-hot 標籤編碼時,交叉熵 L = −log(p_true) 唯有在 p_true 恰好等於 1 時才真正最小化——而對 softmax 而言,要讓 p_true = 1,就需要正確的 logit 相對其他類別跑向 +無窮大。優化器永遠到不了那裡,於是它只會不斷把差距撐得越來越大。模型因此變得病態地過度自信:即使面對模稜兩可的影像,也學會輸出 0.9999。這種過度自信會傷害泛化能力(模型記住的是尖銳的邊界,而非穩健的邊界),也會破壞校準(它宣稱的信心不再對得上它真正的準確率)。
標籤平滑正是解方,它的口號是「保持自信,但留一點懷疑」。我們不再告訴模型答案是 100% 貓、其餘全為 0%,而是告訴它答案是,比方說,92% 貓,再把一小撮機率分散到其他類別上。模型於是不再有任何動機去追逐無窮大的 logit,因為它瞄準的目標是有限、且真正能達到的。
平滑後的目標取代了原本非黑即白的 one-hot 值。
逐個符號來看:y_c 是類別 c 原本的 one-hot 值(1 或 0),ε(epsilon)是微小的平滑強度(常見取值為 0.1),K 是類別數。把兩種情況代進去:對正確類別,y_c = 1,所以 y_c^LS = (1−ε)·1 + ε/K = 1 − ε + ε/K;對其他每個類別,y_c = 0,所以 y_c^LS = 0 + ε/K = ε/K。以 K = 5 個類別、ε = 0.1 為例:正確類別得到 1 − 0.1 + 0.1/5 = 0.9 + 0.02 = 0.92,其餘四個類別各得到 0.1/5 = 0.02。對一個「類別 0 為正解」的範例,整個平滑後的目標就是 [0.92, 0.02, 0.02, 0.02, 0.02]。注意它的總和仍是 0.92 + 4×0.02 = 1.00——它依舊是個合法的機率分佈,只是不再是非黑即白。
資料增強:從舊資料變出新資料
損失的調整形塑模型「如何」學習;資料增強則改變它「從什麼」去學。核心想法是:對訓練影像施加保持標籤不變的變換——隨機裁切、水平翻轉、色彩抖動、小角度旋轉——好讓模型看到同一張圖的許多不同樣貌。每個變換都經過挑選,確保它不會改變影像分類的正確答案:一隻平移、翻轉、或稍微改了色調的貓,標籤依然是「貓」。透過看遍這些變體,模型被迫學會正確的不變性——不論貓位在哪裡、面朝哪個方向、燈光多暖,都能認出牠。
為什麼這麼有效?回想第二篇指南裡的過度擬合圖像:一個容量很大的模型可以把訓練像素一字不差地背下來,在訓練集上拿滿分,卻在新影像上慘敗。增強之所以能對抗它,是因為擴大了有效資料集——如果每一輪(epoch)每張圖都以全新的隨機方式裁切、改色,模型幾乎不會看到兩次完全相同的像素,於是沒有固定的東西可背。它只能轉而學習底層、可泛化的特徵。論性價比,增強是你手上最便宜、也最強大的正則化手段之一。
# A typical training-time augmentation pipeline (all label-preserving).
# Each epoch, every image is transformed a fresh random way,
# so the model almost never sees the exact same pixels twice.
train_transform = Compose([
RandomResizedCrop(224), # random zoom + crop -> position/scale invariance
RandomHorizontalFlip(p=0.5), # OK for cats; NOT ok for text or '6'/'9'
ColorJitter(brightness=0.4, # small lighting / colour changes
contrast=0.4,
saturation=0.4),
ToTensor(),
Normalize(mean=IMAGENET_MEAN, # match the stats the backbone expects
std=IMAGENET_STD),
])
# IMPORTANT: the validation/test set gets NO random augmentation,
# only a deterministic resize + center crop, so the scorecard stays honest.
val_transform = Compose([
Resize(256),
CenterCrop(224),
ToTensor(),
Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD),
])一張貓的照片,旁邊是它的裁切、水平翻轉與色彩抖動版本,全都仍標為「貓」。
Mixup:同時混合影像與標籤
逐張影像的增強一次只變換一張圖。mixup則邁出一大步:它一次結合兩個訓練樣本,把它們的像素與標籤都按同樣的比例混合。拿一張貓的圖和一張狗的圖,以 70% / 30% 的不透明度疊在一起,得到一個朦朧的混合體,而且——關鍵在於——把目標設成 70% 貓、30% 狗,而不是單一的硬標籤。你正在教模型一件 one-hot 資料永遠教不了的事:在類別之間的空間裡該如何反應。
用同一個權重 λ 同時混合像素與標籤。
逐個符號來看:x_a 與 x_b 是兩張輸入影像,y_a、y_b 是它們的 one-hot 標籤(就是第一篇指南裡那種 one-hot 向量)。λ(lambda)是混合權重,一個介於 [0, 1] 的數。於是 x̃ 是逐像素的混合(把影像 a 的每個像素乘以 λ、影像 b 的每個像素乘以 1−λ,再相加),而 ỹ 是對應的軟標籤,用完全相同的權重混合。模型就在這一個合成的配對 (x̃, ỹ) 上訓練。以 λ = 0.7 為例,影像 a 是貓(one-hot [1, 0])、影像 b 是狗(one-hot [0, 1]):混合影像 x̃ = 0.7·(貓的像素) + 0.3·(狗的像素),是一張半透明的貓狗疊影;混合目標 ỹ = 0.7·[1, 0] + 0.3·[0, 1] = [0.7, 0.3]。標籤的拆分恰好對應像素的拆分——這份一致性正是整件事的重點。
import numpy as np
import torch
import torch.nn.functional as F
def mixup_batch(images, labels, num_classes, alpha=0.2):
# Turn integer class ids into one-hot vectors (see Guide 1).
y = F.one_hot(labels, num_classes).float()
lam = np.random.beta(alpha, alpha) # mixing weight in [0, 1], fresh each step
perm = torch.randperm(images.size(0)) # pick a random partner for each image
mixed_x = lam * images + (1 - lam) * images[perm]
mixed_y = lam * y + (1 - lam) * y[perm]
return mixed_x, mixed_y # train on blended pixels AND blended labels一張貓與一張狗的照片以 70/30 不透明度混合,目標顯示為 0.7/0.3 的軟標籤。
CutMix:剪下、貼上、共享標籤
cutmix保留了 mixup「混合標籤」的洞見,卻丟掉了半透明這件事。它不像雙重曝光那樣整張影像疊合,而是從一張圖剪下一塊矩形區塊,實心地貼到另一張圖上,再依區塊面積的比例混合兩個標籤。成果看起來像一張真實、不透明的照片——一隻狗的角落被蓋上一塊方形的貓——而且沒有疊影。模型現在必須理解並排放著的兩塊真實、局部的證據。
標籤的拆分,恰好對應像素面積的拆分。
逐個符號來看:y_a 是宿主影像的 one-hot 標籤(被貼上去的那張圖),y_b 是來源影像的標籤(區塊取自的那張圖),λ(lambda)則是影像中仍屬於宿主的比例。我們定義 λ = 1 − (貼上區塊的面積) / (影像面積),所以 1 − λ 恰好就是區塊的面積比例。舉例:貼上一塊覆蓋影像 30% 的區塊,那麼 1 − λ = 0.30,故 λ = 0.70。混合標籤為 ỹ = 0.70·y_a + 0.30·y_b。若宿主是狗、區塊是貓,目標就成了 70% 狗 + 30% 貓——恰好對應「可見像素中 70% 是狗、30% 是貓」這個事實。這筆帳算得很實在:輸入的是像素面積,輸出的是標籤比例。
為什麼會偏好它勝過 mixup?因為貓的證據被侷限在一個角落,模型無法只靠單一的全域線索(整體色調、平均紋理)取勝——它必須運用來自不同影像區域的局部證據,這能增進它對物件位於何處的感知,也讓它對遮擋(物件被其他東西部分擋住)更穩健。粗略的指引:CutMix 在大型自然影像資料集與物件繁多的任務上往往表現亮眼,因為它的影像維持寫實;而當物件很小或佔滿整個畫面、或可接受半透明混合時,mixup 通常更溫和,是個好的預設。許多強力配方乾脆每個批次在兩者之間隨機輪替,兼得雙方之長。
一張狗的照片,被一塊覆蓋其 30% 的矩形貓區塊蓋住;目標為 0.7 狗 / 0.3 貓的標籤。
整合起來:現代訓練配方
這些技巧沒有一個是萬靈丹,但合起來,它們構成了每一個冠軍分類器的骨幹。貫穿其中的主題是同時從多個角度對抗過度自信與過度擬合:強力且保持標籤不變的增強擴大了有效資料集;mixup與 cutmix把類別之間的決策邊界抹平;而標籤平滑則阻止模型追逐無窮大的 logit。它們透過不同的機制攻擊同一種病——一個太急於背誦、又太篤定自我的模型——這正是為什麼把它們疊加會讓效益相乘,而非彼此多餘。
- 從乾淨的基準開始:標準的縮放/裁切/翻轉增強,搭配普通的交叉熵。確認它能訓練、甚至有點過度擬合——這證明模型有足夠的容量學會這個任務。
- 強化保持標籤不變的增強(RandomResizedCrop、安全時才翻轉、溫和的色彩抖動),以反制過度擬合。
- 開啟標籤平滑,ε ≈ 0.1,讓模型停止追逐無窮大的 logit,並變得更好校準。
- 加入 mixup 或 cutmix,每個批次從 Beta 分佈取樣 λ;許多強力配方會在兩者之間隨機輪替。
- 訓練更久:這些正則化手段會減緩背誦,所以模型需要更多輪(epoch)才能收斂到最佳的驗證分數。
- 依驗證集的 top-1/top-5 準確率來選模型並回報,絕不要依(如今被灌水的)訓練損失。
兩個重要的警告。第一,別把小資料集過度正則化:在極小的資料集上施加激進的 mixup/cutmix 再加上重度增強,會讓乾淨訊號被餓死,模型反而欠擬合——準確率下降。資料稀少時,請把強度調低(或關掉),並盯著驗證曲線來決定。第二,當心「軟度」被重複計算:mixup 與 cutmix 本身已經產生軟標籤,所以再疊上強烈的標籤平滑,可能把目標過度軟化、浪費容量。若要同時使用,請用較小的 ε,或乾脆讓混合式增強自己提供軟度。
這一切之中還藏著一份額外的紅利。這裡的每個技巧,都把模型從脆弱的 0.999 篤定推向更柔和、更聽話的機率——而聽話的機率,正是校準的原料;所謂校準,就是讓模型宣稱的信心真正具有意義的藝術。那正是第五篇指南的起點。不過,下一篇指南要面對的,是即使訓練良好的分類器也會吃力的情況:細粒度、不平衡、與結構化標籤。你剛打造的這套配方,正是兩者共同的發射台。