從神經元輸出到類別分數:logits
讓我們直接接續上一篇的結尾。我們把一張影像送過一疊神經元,抵達最後一層——通常是一個 全連接層,每個類別配一個輸出神經元。如果你的任務是判斷一張圖是貓、狗還是鳥,這最後一層就會交還三個數字,每個類別一個。到目前為止它們還沒被加工過;它們只是從最後一層算出來的加權和而已。
這些最後一層的原始數字有個名字:logits。關於 logits 最重要的一件事是:它們是未歸一化的。一個 logit 可以是任何實數——很大的正數、很大的負數,或是零。它們不是機率:不會落在 0 到 1 之間,加總起來也不會等於任何特定值。到目前為止,只有它們之間的相對大小順序才有意義——較大的 logit 表示網路更傾向那個類別,但傾向多少還沒有用任何誠實、可比較的方式表達出來。
一張影像被送入網路、並對數個候選類別標籤各產生一個分數的示意圖。
我們把它具體化。假設對某張照片,最後一層對 [貓, 狗, 鳥] 回傳的 logits 是 [2.0, 0.5, -1.0]。我們能立刻讀出排名:貓分數最高,接著是狗,再來是鳥(甚至變成負數)。但現在問兩個真實系統一定要回答的問題:我們要預測哪一個類別?我們有多少把握?第一個很容易——挑最大的 logit,也就是「貓」。第二個就真的尷尬了。2.0 算「很有把握」嗎?跟什麼比?跟狗的差距是 1.5,這算大嗎?根本沒有一把尺。原始 logits 就是沒辦法告訴我們「貓,機率 79%」——而這正是下游決策(或一位醫生、或一台自駕車)所需要的數字。
Softmax:把分數變成機率
把一串 logits 轉成誠實機率的標準工具是 softmax。它一次吃進整串 logits,回傳一串等長的新向量,其中每一項都是正數、而且所有項加起來剛好等於 1——換句話說,就是一個貨真價實、橫跨各類別的機率分布。關鍵是它保留了排名(最大的 logit 仍然變成最大的機率),但此刻數值的大小有了我們能比較、能回報的意義。
Softmax 用兩個直覺的步驟完成工作。第一,對每個 logit 取指數——把 e 自乘到每個 logit 次方。取指數一次解決兩個問題:e 的任何次方都恆為正(所以負的 logit 不再是問題),而且指數函數成長得很快,因此會放大最大 logit 的領先。第二,歸一化:把所有指數值加總,再把每一個除以這個總和。除以總和迫使結果加起來等於 1,這正是讓它們成為一個機率分布的關鍵。
Softmax:對一個 logit 取指數,再除以所有指數值的總和。
我們逐一讀懂每個符號。向量 z 裝著全部 K 個 logits,而 z_i 是我們正在打分的那一個類別 i 的 logit(在我們的例子裡 K = 3 個類別)。分子 e^{z_i} 是那個 logit 的指數值——恆為正,而且較大的 logit 會給出不成比例地更大的值。分母,也就是 j 從 1 到 K 的 e^{z_j} 大加總,單純把每一個類別的指數值都加起來;它是那個保證輸出加總為 1 的歸一化分母。整個比值就是模型分配給類別 i 的機率:「我的指數值,相對於大家的指數值全部加起來,佔多大?」
import math logits = [2.0, 0.5, -1.0] # raw final-layer scores for [cat, dog, bird] # Step 1: exponentiate every logit exps = [math.exp(z) for z in logits] # exps -> [7.389, 1.649, 0.368] # Step 2: normalize by the total so the result sums to 1 total = sum(exps) # 7.389 + 1.649 + 0.368 = 9.406 probs = [e / total for e in exps] # probs -> [0.786, 0.175, 0.039] print(probs, "sum =", sum(probs)) # sum = 1.0
順著我們的例子算一遍。取指數得到 e^{2.0} ≈ 7.389、e^{0.5} ≈ 1.649、e^{-1.0} ≈ 0.368。它們的總和約為 9.406。把每一個除以這個總和,得到貓 7.389/9.406 ≈ 0.786、狗 1.649/9.406 ≈ 0.175、鳥 0.368/9.406 ≈ 0.039。注意它們加起來是 0.786 + 0.175 + 0.039 = 1.000,正如承諾的一樣。我們含糊的 logits [2.0, 0.5, -1.0] 已經變成清楚的陳述「貓 79%、狗 18%、鳥 4%」。這現在是一個我們真的能用的把握度。
為什麼要取指數,而不是說,把負數直接截成零再相除就好?有兩個理由,等我們開始學習時會極為重要。指數函數處處平滑可微,所以網路可以被微小的梯度步伐輕推(這是第 4 篇的主題);而硬截斷有一塊平坦的死區,會扼殺那些梯度。而且指數以乘法方式獎勵把握度——在 logit 空間裡領先一個單位,會把你的佔比乘上一個 e 倍(約 2.7 倍),這給了模型一個合理、與尺度無關的方式來表達強烈的信念。
損失函數究竟是什麼
我們現在能說出網路相信什麼了——「貓 79%」。但它對了嗎?如果不對,又錯得多離譜?損失函數用一個數字回答這件事:它衡量網路的預測與真實標籤之間的差距。規則簡單而不容妥協——越低越好,零代表完美的預測。有把握又正確的答案得到極小的損失;有把握卻錯誤的答案得到很大的損失。
把損失想成網路的成績單——一個總結它在某道題目上表現的單一分數。它的重要性遠不只是告訴我們結果,因為損失正是學習過程(第 4 篇)將設法往下壓的那個量。網路沒有別的指南針:它靠著改變權重、讓這一個數字變小來進步。我們在這裡的所有選擇——先 softmax、再選哪一種損失——都形塑了「變得更好」究竟是什麼意思。
一張循環示意圖,顯示預測餵入損失量測,損失驅動權重更新,再迴圈回到下一次預測。
- 預測:把影像向前送過網路得到 logits,再經 softmax 得到各類別機率。
- 量測:用損失函數把預測和真實標籤相比,得到一個數字。
- 調整:把每個權重往「會讓損失更小」的方向輕推一點(這是第 4 篇的工作)。
- 重複:在許多範例上一遍又一遍地做,直到損失不再下降為止。
一個重要的區別:單一範例上的損失是一道題目的一張成績單。我們真正想最小化的,是整個資料集上的平均損失——有時稱為成本(cost)或目標函數(objective)。取平均很重要,因為我們要的是一個對所有貓、狗、鳥都表現好的網路,而不是一個只在某張幸運影像上拿滿分的網路。接下來幾節的每條公式都是針對單一範例寫的;在心裡,請把它想成在數千個範例上取了平均。
分類用的交叉熵損失
交叉熵損失是分類任務中 softmax 的天生搭檔。它的直覺非常直接:它只看網路分配給正確類別的那個機率,並問「它有多高?」如果你把大部分機率押在正確答案上,你的損失就極小。當那個機率往零滑落,損失就會往上爬——一開始溫和,接著變得殘酷——這都是因為一個對數。用白話說,交叉熵懲罰答錯,而且對有把握地答錯懲罰得重得多。
單一範例、橫跨 K 個類別的交叉熵損失。
逐一看符號:y_i 是寫成 one-hot 向量的真實標籤——對唯一正確的類別是 1,對其他每個類別都是 0。所以對真實標籤「貓」,y = [1, 0, 0]。p_i 是網路給類別 i 的 softmax 機率。加總跑遍全部 K 個類別,但巧妙之處在這裡:因為對每個錯誤類別 y_i 都是 0,那些項全部消失,加總塌縮成只剩一個倖存者——那個 y_i = 1 的正確類別。所以整條公式化簡為 L = -log(p_correct):你押在正確答案上那個機率的負對數。
把數字走一遍。我們的網路說貓的機率是 0.786,而貓就是真相,所以損失是 L = -log(0.786) ≈ 0.241(用自然對數)——很小,好的猜測本該如此。現在想像一個糟糕的日子,網路只給正確類別 0.05:損失變成 L = -log(0.05) ≈ 3.00,是前者的十二倍多。把正確類別的機率推到 0.99,損失是 -log(0.99) ≈ 0.01,幾乎是零;讓它往 0 落下,-log(p) 就會衝向無限大。這正是我們要的形狀:有把握又正確得到接近零的損失,有把握卻錯誤得到極巨大的損失,而這正是教會網路既正確又校準良好的那股壓力。
均方誤差,以及何時目標是回歸
並不是每個視覺任務都是「哪一個類別」。有時候網路必須預測一個連續數字——這稱為回歸,而它在視覺裡層出不窮:圍住某物件的邊界框的四個座標、每個像素的深度(距相機的距離)、一張臉的旋轉角度、去雜訊後影像的亮度。這裡沒有「正確類別」的概念;有的是一個目標值和一個預測值,而我們在乎它們相差多遠。這種任務的標準損失是 均方誤差(MSE)。
在 n 個範例(或 n 個預測數字)上的均方誤差。
讀法:y_k 是項目 k 的真實目標值(比方說某個像素的真實深度,以公尺計),而 ŷ_k——「y-hat」——是網路為它預測的值。它們的差 (ŷ_k - y_k) 是原始誤差,可正可負。把它平方做了兩件事:讓每個誤差都變正(這樣高估和低估都算壞、無法互相抵消),並且不成比例地懲罰大誤差——差 2 貢獻 4,但差 10 貢獻 100。最後 (1/n) 把 n 個數字加總再取平均,得到一個整潔的分數。小例子:預測 [3.0, 5.0] 對上目標 [2.0, 5.0],誤差是 1 和 0,平方後是 1 和 0,平均 (1+0)/2 = 0.5。
把 MSE 和交叉熵並排放著,這樣你永遠不會搞混。交叉熵回答「哪一個類別」:它拿一個機率分布去對照 one-hot 標籤打分,位在 softmax 的下游。MSE 回答「一個數字差多遠」:它拿一個預測值去對照目標值打分,位在一個普通線性輸出(沒有 softmax)的下游。一個衡量選擇的錯誤程度;另一個衡量數值大小的錯誤程度。平方項也是為什麼單一個巨大的失誤會主宰 MSE——一個錯得離譜的深度像素,可能蓋過數百個近乎完美的像素。
二元與多標籤問題用的 sigmoid
我們先前見過 sigmoid 激活函數,當時它是神經元內部的擠壓函數。在這裡它以新角色回歸:作為產生機率的輸出。回想 softmax 的假設——剛好一個類別是真的,所以機率必須加總為 1 並互相競爭。這個假設在兩種常見情況下明顯是錯的:一個單純的是非(二元)問題,以及多標籤問題——每個標籤都可以獨立地存在。一張照片真的可以同時是「海灘」且「夕陽」且「有人」;硬要它們加總為 1,會讓它們去爭一份它們本不該共享的預算。
解法是在每個輸出神經元上各放一個獨立的 sigmoid。每個 sigmoid 把自己的 logit 擠壓成自己介於 0 到 1 的機率,完全不理會其他的——所以「海灘」可以是 0.95,同時「夕陽」也是 0.90、「雪」是 0.02,沒有任何要它們加總為某值的要求。每個輸出現在都是它自己的是非題。而與單一 sigmoid 輸出搭配的損失是二元交叉熵,也就是我們稍早建立的 交叉熵損失的兩類別表親——對每個標籤各算一次,再把所有標籤加總或取平均。
單一標籤的二元交叉熵,其中 p 是那個標籤 logit 的 sigmoid 值。
為單一標籤拆解它。y 是這個標籤的真實答案,不是 1(標籤存在)就是 0(不存在)。p = σ(z) 是那個標籤 logit z 的 sigmoid 值——模型預測該標籤存在的機率。公式有兩項,而同一時間剛好只有一項是活的。當 y = 1 時,(1-y) 這個因子是 0,只剩 -log(p):這懲罰「標籤確實在那兒、p 卻很低」。當 y = 0 時,y 這個因子是 0,只剩 -log(1-p):這懲罰「標籤確實不在、p 卻很高」。舉例來說,若真實標籤是 1(「夕陽存在」)而模型說 p = 0.90,損失是 -log(0.90) ≈ 0.105——小而恰當。若它對同一個存在的標籤說了 p = 0.10,損失就是 -log(0.10) ≈ 2.30,大得多。
這個二元形式並不是另一個概念——它就是先前的交叉熵特化到兩種結果(存在 vs 不存在)。而它補完了我們的輸出層工具箱。記住整個故事的一個好辦法:softmax 代表「剛好挑一個」(互斥類別、機率競爭著加總為 1、由交叉熵打分),而獨立的 sigmoid 代表「每個標籤各自回答是非」(多標籤、機率彼此自由、由二元交叉熵打分)。如今手上有了 logits、softmax、sigmoid 以及相配的損失,我們已經備齊一切,能把一個猜測變成一個說明它錯得多離譜的數字——而在下一篇,我們終於要用那個數字來學習,方法是用反向傳播把它沿著網路逆向追溯回去。