7.2 Softmax és többosztályos cross-entropy

7.2.1 Cél, előismeret és induló kérdés

Ebben az alfejezetben egyetlen megfigyelés logitvektorából stabil módon számítunk normalizált pontszámot (softmax) és többosztályos cross-entropy veszteséget, majd levezetjük a logit szerinti gradienst: ∇zL=p−ey\nabla_z L=p-e_y. Az előismeret a 7.1. Entrópia, cross-entropy és KL alfejezetben bevezetett diszkrét cross-entropy és KL, a 3.2. Mátrixszorzás és affín réteg alfejezetbeli affin réteg, valamint a 6.2. Konvexitás és sima optimalizálási határ alfejezetbeli gradienslépés. Induló kérdés: ha z=(900,901,902)z=(900,901,902) float32-ben, miért nem számolhatjuk a softmaxot a exp\exp közvetlen kiértékelésével, és miért nem segít önmagában a softmax a stabil veszteséghez?

7.2.2 Logit, softmax és logsumexp

Definíció (logit). Legyen K≥2K\ge 2 az osztályok száma. Egy megfigyelés logitvektora z∈ℝKz\in\mathbb R^K, ahol zjz_j a jj-edik osztály aktiváció előtti pontszáma; a jj indexet a könyv jelölési szerződésének megfelelően 0,…,K−10,\ldots,K-1 között használjuk, mert a PyTorch célindex is 0-tól indul. A batchelt logit Z∈ℝN×KZ\in\mathbb R^{N\times K}, egy megfigyelés egy sor.

Definíció (softmax). A softmax:ℝK→(0,1)K\operatorname{softmax}:\mathbb R^K\to(0,1)^K leképezés pj=softmax(z)j=ezj∑k=0K−1ezk,j=0,…,K−1. p_j=\operatorname{softmax}(z)_j=\frac{e^{z_j}}{\sum_{k=0}^{K-1}e^{z_k}}, \qquad j=0,\ldots,K-1. A kimenetre pj>0p_j>0 és ∑jpj=1\sum_j p_j=1, tehát pp diszkrét valószínűségi vektorként értelmezhető. A softmax invariáns a konstans eltolásra: softmax(z+c𝟏)=softmax(z)\operatorname{softmax}(z+c\mathbf 1)=\operatorname{softmax}(z), mert a számláló és a nevező is ece^c-vel szorzódik. Ez az invariancia a gradiensre is öröklődik.

Definíció (logsumexp). A logsumexp függvény LSE(z)=log∑k=0K−1ezk. \operatorname{LSE}(z)=\log\sum_{k=0}^{K-1}e^{z_k}. Ez a softmax nevezőjének logaritmusa. A név gyakori angol rövidítése: logsumexp. A log természetes alapú, ahogy az egész könyvben.

Integer target és one-hot. Az osztályozási cél y∈{0,…,K−1}y\in\{0,\ldots,K-1\} egész index (PyTorchban int64). Az ey∈ℝKe_y\in\mathbb R^K one-hot vektor (ey)j=δjy={1,j=y,0,j≠y. (e_y)_j=\delta_{jy}= \begin{cases} 1,&j=y,\\ 0,&j\neq y. \end{cases} A batchelt cél y∈{0,…,K−1}Ny\in\{0,\ldots,K-1\}^N, a hozzá tartozó one-hot mátrix Yonehot∈{0,1}N×KY_{onehot}\in\{0,1\}^{N\times K}, ahol (Yonehot)nj=δj,yn(Y_{onehot})_{nj}=\delta_{j,y_n}.

Definíció (egy megfigyelés cross-entropy-ja logitsból). A p=softmax(z)p=\operatorname{softmax}(z) predikció és a yy célindex esetén L(z,y)=−logpy=−logezy∑kezk=−zy+LSE(z). L(z,y)=-\log p_y =-\log\frac{e^{z_y}}{\sum_k e^{z_k}} =-z_y+\operatorname{LSE}(z). Mivel py>0p_y>0 minden valós zz-re, a függvény mindenhol értelmes.

Stabil LSE. A log∑kezk\log\sum_k e^{z_k} közvetlen kiértékelése float32-ben már zk≈89z_k\approx 89 fölött inf-et ad, pedig a softmax és a veszteség jól definiált. Legyen c=maxkzkc=\max_k z_k. Ekkor LSE(z)=log(ec∑kezk−c)=c+log∑kezk−c. \operatorname{LSE}(z) =\log\left(e^c\sum_k e^{z_k-c}\right) =c+\log\sum_k e^{z_k-c}. Itt minden zk−c≤0z_k-c\le 0, ezért ezk−c∈(0,1]e^{z_k-c}\in(0,1], az összeg 11 és KK közé esik, tehát a számítás nem csordul túl. A stabil veszteség ezzel L(z,y)=−zy+c+log∑kezk−c. L(z,y)=-z_y+c+\log\sum_k e^{z_k-c}. Ha zy=cz_y=c, akkor a veszteség log∑kezk−c≤logK\log\sum_k e^{z_k-c}\le\log K.

7.2.3 Batch-veszteség és reduction

A batchelt logitokra Z∈ℝN×KZ\in\mathbb R^{N\times K} és a y∈{0,…,K−1}Ny\in\{0,\ldots,K-1\}^N célra a softmax soronként számolandó, azaz Pnj=softmax(Zn,:)jP_{nj}=\operatorname{softmax}(Z_{n,:})_j. Az átlagolt (mean) cross-entropy ℓ(Z,y)=1N∑n=1NL(Zn,:,yn)=1N∑n=1N(−Zn,yn+LSE(Zn,:)). \ell(Z,y)=\frac1N\sum_{n=1}^N L(Z_{n,:},y_n) =\frac1N\sum_{n=1}^N\left(-Z_{n,y_n}+\operatorname{LSE}(Z_{n,:})\right). A [D07] dokumentáció szerint a PyTorch torch.nn.CrossEntropyLoss logits bemenetet vár, egész osztályindex célját fogadja el, és a reduction paramétere 'mean' (alapértelmezés), 'sum' vagy 'none'. A 'none' mintánkénti veszteségvektort ad; a 'sum' a fenti összeg 1/N1/N nélkül.

7.2.4 Levezetés: a logitgradiens

Lemma (cross-entropy logitgradiense). Legyen z∈ℝKz\in\mathbb R^K, y∈{0,…,K−1}y\in\{0,\ldots,K-1\}, p=softmax(z)p=\operatorname{softmax}(z) és L(z,y)=−logpyL(z,y)=-\log p_y. Ekkor ∇zL(z,y)=p−ey. \nabla_z L(z,y)=p-e_y. A gradiens alakja ℝK\mathbb R^K, megegyezik zz alakjával.

Bizonyítás. Rögzítsük jj-t, és írjuk S=∑kezkS=\sum_k e^{z_k}, így pj=ezj/Sp_j=e^{z_j}/S. A softmax Jacobi-eleme a hányados- és láncszabályból: ∂pj∂zi=δijeziS−ezjeziS2=pi(δij−pj), \frac{\partial p_j}{\partial z_i} =\frac{\delta_{ij}e^{z_i}S-e^{z_j}e^{z_i}}{S^2} =p_i(\delta_{ij}-p_j), mert pi=ezi/Sp_i=e^{z_i}/S és pj=ezj/Sp_j=e^{z_j}/S. Ezután a L=−logpyL=-\log p_y összetett függvényre a láncszabály: ∂L∂zi=−1py∂py∂zi=−1pypi(δiy−py)=pi−δiy. \frac{\partial L}{\partial z_i} =-\frac{1}{p_y}\frac{\partial p_y}{\partial z_i} =-\frac{1}{p_y}p_i(\delta_{iy}-p_y) =p_i-\delta_{iy}. Az utolsó egyenlőséget esetszétválasztással ellenőrizzük: ha i=yi=y, akkor −1pypy(1−py)=py−1-\frac{1}{p_y}p_y(1-p_y)=p_y-1; ha i≠yi\neq y, akkor −1pypi(0−py)=pi-\frac{1}{p_y}p_i(0-p_y)=p_i. Mindkét eset éppen pi−δiyp_i-\delta_{iy}. A KK parciális deriváltat vektorba gyűjtve ∇zL=p−ey\nabla_z L=p-e_y. A shape-ellenőrzés: pp és eye_y is ℝK\mathbb R^K-beli, így a különbségük is. A lemma feltétele minden z∈ℝKz\in\mathbb R^K-ra teljesül, mert a softmax nevezője pozitív.

Következmény (batch-átlag gradiense). Ha P∈ℝN×KP\in\mathbb R^{N\times K} a soronkénti softmax kimenet és YonehotY_{onehot} a one-hot mátrix, akkor a mean redukciójú veszteség gradiense ∇Zℓ=1N(P−Yonehot)∈ℝN×K. \nabla_Z\ell=\frac1N(P-Y_{onehot})\in\mathbb R^{N\times K}. A 'sum' redukció esetén ugyanez 1/N1/N nélkül. A levezetés a Lemma mintánkénti alkalmazásából és a véges összeg linearitásából adódik. A gyakorlatban a keretrendszer az upstream gradienst gg szerint is skálázza: ha a veszteséghez egy további skalár szorzó kapcsolódik, a VJP g⋅1N(P−Yonehot)g\cdot\frac1N(P-Y_{onehot}).

7.2.5 Calibration előkép

A softmax kimenet formálisan diszkrét eloszlás, ezért sokan „valószínűségnek” nevezik. Ez a lépés heurisztika, nem bizonyított kalibrációs állítás. A cross-entropy minimalizálása a célosztály log-valószínűségét növeli, és ideális, végtelen mintás feltételek mellett a feltételes osztályeloszlást közelítheti; a véges mintán tanított modell azonban lehet túl magabiztos vagy alulmagabiztos. A kalibráció azt méri, hogy a 0,70{,}7-es konfidencia átlagosan 70%70\%-ban helyes-e. Ennek előképe éppen az itt definiált pp, de a tényleges kalibrációs elemzés külön metrikát (például ECE, reliability diagram) és független tesztadatot igényel; ezt a 23.2. Kalibráció és döntési küszöb alfejezetben tárgyaljuk.

7.2.6 Kézi példa

Legyen K=3K=3, z=(2,−1,0,5)⊤z=(2,\,-1,\,0{,}5)^\top és y=2y=2. Az exponenciális értékek e2=7,3891,e−1=0,3679,e0,5=1,6487, e^2=7{,}3891,\quad e^{-1}=0{,}3679,\quad e^{0{,}5}=1{,}6487, ezért S=9,4057S=9{,}4057. A softmax: p=(7,38919,4057,0,36799,4057,1,64879,4057)≈(0,7856,0,0391,0,1753). p=\left(\frac{7{,}3891}{9{,}4057},\frac{0{,}3679}{9{,}4057},\frac{1{,}6487}{9{,}4057}\right) \approx(0{,}7856,\ 0{,}0391,\ 0{,}1753). A veszteség L=−logp2=−log0,1753≈1,741L=-\log p_2=-\log 0{,}1753\approx1{,}741. A gradiens ∇zL=p−e2=(0,7856,0,0391,0,1753−1)=(0,7856,0,0391,−0,8247). \nabla_z L=p-e_2 =(0{,}7856,\ 0{,}0391,\ 0{,}1753-1) =(0{,}7856,\ 0{,}0391,\ -0{,}8247). Ellenőrzés: a célosztály gradiense negatív, tehát a gradienslépés növeli z2z_2-t, a többi logitot pedig csökkenti, ahogy a 6.2. Konvexitás és sima optimalizálási határ alfejezetben megismert csökkenési iránytól várható.

7.2.7 Labor: véges differencia és nagy konstans eltolás

A következő kódot a hallgató futtatja; az itt közölt kimenet a képletekből számolt várt érték, nem mért futás. A fájl a lab/n026/check_ce_grad.py munkakönyvtárban van, CPU-n, float64-ben; véletlen seed nem szükséges.

# lab/n026/check_ce_grad.py
import numpy as np

def lse(z):
    c = np.max(z)
    return c + np.log(np.sum(np.exp(z - c)))

def softmax(z):
    s = np.exp(z - np.max(z))
    return s / np.sum(s)

def ce_loss(z, y):
    return -z[y] + lse(z)

def ce_grad(z, y):
    e_y = np.zeros_like(z)
    e_y[y] = 1.0
    return softmax(z) - e_y

z = np.array([2.0, -1.0, 0.5], dtype=np.float64)
y = 2
print("z =", z)
print("softmax =", softmax(z))
print("loss =", ce_loss(z, y))
g = ce_grad(z, y)
print("analytical grad =", g)

eps = 1e-5
num = np.zeros_like(z)
for i in range(z.shape[0]):
    e_i = np.zeros_like(z)
    e_i[i] = 1.0
    num[i] = (ce_loss(z + eps * e_i, y) - ce_loss(z - eps * e_i, y)) / (2.0 * eps)
print("numerical grad =", num)
print("max abs diff =", np.max(np.abs(num - g)))

shift = 1000.0
zs = z + shift
print("shifted loss =", ce_loss(zs, y))
print("shifted softmax =", softmax(zs))
print("shifted grad =", ce_grad(zs, y))

Számított várt kimenet (kerekítve, float64-ben a hiba körülbelül 10−1210^{-12} nagyságrendű): a softmax (0,7856,0,0391,0,1753)(0{,}7856,0{,}0391,0{,}1753), a veszteség 1,74131{,}7413, az analitikus gradiens (0,7856,0,0391,−0,8247)(0{,}7856,0{,}0391,-0{,}8247), a numerikus gradiens ugyanez, a max abszolút eltérés 10−1210^{-12} alatti; az eltolt esetben a veszteség, a softmax és a gradiens azonos az eltolás előttivel. A futtatási parancs: python lab/n026/check_ce_grad.py. A központi differencia ε=10−5\varepsilon=10^{-5} mellett a truncációs hiba O(ε2)O(\varepsilon^2), float64-ben a kerekítési hiba nem dominál.

7.2.8 Gyakori tévedés: softmax a CrossEntropyLoss előtt

A [D07] dokumentáció szerint a PyTorch CrossEntropyLoss logits bemenetet vár, és belül végzi el a stabil softmax + log műveletet. Ha a felhasználó előre alkalmazza a torch.softmax-ot, akkor a loss a már normalizált értékre újra softmaxot számít, tehát a ténylegesen minimalizált függvény nem a kívánt −logpy-\log p_y. Ellenpélda: z=(1,0)z=(1,0), y=0y=0. A helyes veszteség −logee+1≈0,3133-\log\frac{e}{e+1}\approx0{,}3133. Ha előbb softmaxot számítunk, p≈(0,7311,0,2689)p\approx(0{,}7311,0{,}2689), és ezt logitsként átadva a loss belül újra normalizál, így −log0,73110,7311+0,2689≈0,4870-\log\frac{0{,}7311}{0{,}7311+0{,}2689}\approx0{,}4870. A két érték nem azonos. Nagy logitoknál a hiba még súlyosabb: float32-ben a közvetlen softmax z=(500,0)z=(500,0) esetén (1,0)(1,0)-t adhat, a log pedig log0=−∞\log 0=-\infty-t; a stabil logits-alapú loss viszont véges marad.

7.2.9 Feladatok

F1 (fogalmi, nehézség 1). Magyarázd el, mi a különbség a softmax és a logsumexp szerepe között a stabil cross-entropy-ban. Miért nem elegendő önmagában a softmax stabil számítása z=(1000,1001)z=(1000,1001) esetén?

F2 (levezetési, nehézség 2). Vezesd le a K=2K=2 speciális esetben, hogy y=1y=1 esetén a logitgradiens ∇zL=(p0,p1−1)\nabla_z L=(p_0,\ p_1-1). Írd át a gradienst a s=σ(z1−z0)s=\sigma(z_1-z_0) logisztikus szigmoiddal, ahol σ(t)=1/(1+e−t)\sigma(t)=1/(1+e^{-t}), és indokold, miért negatív az első komponens.

F3 (implementációs, nehézség 2). Írj stable_ce_grad(z, y) Python-függvényt, amely float64 bemenetre visszaadja a loss-t és a gradienst. Teszteld z=(1000,999,1000,5)z=(1000,999,1000{,}5), y=2y=2 esetén; hasonlítsd össze a közvetlen exp\exp implementációval (ha az nem overflowol), és adj meg numerikus toleranciát.

F4 (hibakeresési, nehézség 2). Adott a következő hibás sor:

loss = nn.CrossEntropyLoss()(torch.softmax(logits, dim=1), target)

Lokalizáld a hibát, írd le a következményét, add meg a javított sort, és ellenőrizd a javítást logits=[[1.0, 0.0]], target=[0] esetén.

F5 (kísérleti/transzfer, nehézség 3). Tervezz kísérletet a GlyphLab 24×24-es képosztályozó adatán: egy tanított CNN logitjaira számíts reliability diagramot és várható kalibrációs hibát (ECE) a test spliten. Add meg a protokollt, a megfigyelési egységet, a baseline-t és a következtetési korlátot.

7.2.10 Megoldások és indoklás

F1. A softmax normalizált pp vektort ad, de a cross-entropy −logpy-\log p_y számításához a nevező logaritmusára is szükség van; nagy logitoknál a ∑ezk\sum e^{z_k} közvetlen kiértékelése túlcsordul, ezért a log∑ezk\log\sum e^{z_k}-t a maximális elem kiemelésével számoljuk. A softmax maga is stabilizálható a z−maxzz-\max z trükkel, de így csak pp-t kapjuk; a logpy=−zy+LSE(z)\log p_y=-z_y+\operatorname{LSE}(z) előállításhoz az LSE-t külön, a c=maxzc=\max z kiemeléssel kell számolni. A z=(1000,1001)z=(1000,1001) esetben a közvetlen e1000e^{1000} float32-ben inf, így a softmax (nan, nan) lehet, míg a stabil LSE a (z−c)(z-c) értékekkel dolgozik.

F2. Az általános lemma speciális esete: y=1y=1, tehát ey=(0,1)e_y=(0,1). Ekkor ∇zL=p−ey=(p0,p1−1)\nabla_z L=p-e_y=(p_0,\ p_1-1). A logisztikus szigmoiddal p0=σ(z0−z1)p_0=\sigma(z_0-z_1) és p1=σ(z1−z0)p_1=\sigma(z_1-z_0); pontosabban p1=ez1/(ez0+ez1)=σ(z1−z0)p_1=e^{z_1}/(e^{z_0}+e^{z_1})=\sigma(z_1-z_0), ezért a gradiens (σ(z0−z1),σ(z1−z0)−1)(\sigma(z_0-z_1),\sigma(z_1-z_0)-1). Mivel p0>0p_0>0 mindig, az első komponens pozitív, tehát a gradiens z0z_0-t csökkenti, z1z_1-et pedig növeli, amikor a cél az 1. osztály; ez összhangban van a cross-entropy minimalizálásával. A levezetés feltétele minden valós zz-re teljesül.

F3. Az elvárt függvény:

import numpy as np

def stable_ce_grad(z, y):
    z = np.asarray(z, dtype=np.float64)
    c = np.max(z)
    log_norm = c + np.log(np.sum(np.exp(z - c)))
    loss = -z[y] + log_norm
    e_y = np.zeros_like(z)
    e_y[y] = 1.0
    p = np.exp(z - c) / np.sum(np.exp(z - c))
    return loss, p - e_y

# teszt
z = np.array([1000.0, 999.0, 1000.5])
y = 2
loss, grad = stable_ce_grad(z, y)
print(loss, grad)

A várt érték: a zz eltolás-invarianciája miatt ugyanaz, mint a z−c=(0,−1,−0,5)z-c=(0,-1,-0{,}5) esetben; a loss −loge1000,5e1000+e999+e1000,5-\log\frac{e^{1000{,}5}}{e^{1000}+e^{999}+e^{1000{,}5}} számítható stabilan, értéke 1,2411{,}241 körül, a gradiens pedig (p0,p1,p2−1)(p_0,p_1,p_2-1). A közvetlen exp\exp NumPy float64-ben még nem overflowol, de float32-ben igen; a tolerancia a stabil és közvetlen számítás között float64-ben 10−1210^{-12} abszolút eltérés alatt van. A függvény visszatérési értéke (loss, grad) float64.

F4. A hiba oka, hogy a CrossEntropyLoss logitsot vár, nem normalizált valószínűségi vektort. A torch.softmax előzetes alkalmazása kettős normalizálást okoz, és a tényleges veszteség nem a kívánt −logpy-\log p_y. Javítás: loss = nn.CrossEntropyLoss()(logits, target). Ellenőrzés: logits=torch.tensor([[1.0,0.0]]), target=torch.tensor([0]). A helyes loss −logee+1≈0,3133-\log\frac{e}{e+1}\approx0{,}3133; a hibás változat ≈0,4870\approx0{,}4870. A javított sor a [D07] szerinti szerződést teljesíti.

F5. Protokollvázlat: (1) rögzítsük a GlyphLab splitjét (train/val/test, scene_id szerint); tanítsunk egy fix CNN-t trainen, válasszunk best checkpointot val szerint. (2) A test 60 képén, tehát 30 független scene-en számítsuk a softmax kimenet konfidenciáját; a megfigyelési egység a scene, mert a két nézet függő. (3) A reliability diagramhoz binekjük a konfidenciákat 5 egyenlő szélességű intervallumba, és ábrázoljuk az átlagos konfidencia–pontosság párokat. (4) Az ECE a binenkénti ||átlag konfidencia −- pontosság|| súlyozott átlaga; baseline lehet egy konstans prior (osztálygyakoriság) predikció. (5) Korlát: a 60 képből 30 független scene kevés, az ECE becslése zajos; a 24×24-es szintetikus adat nem természetes képi kalibrációt mér. A kísérlet nem igényel GPU-t; CPU float32 elegendő. A konfidencia itt a softmax kimenet, de a kalibráció a 23.2. Kalibráció és döntési küszöb alfejezetben tárgyalt külön metrika.

7.2.11 Önellenőrzés és visszahívás

Ellenőrizd, hogy a softmax kimenet komponensei pozitívak és összegük 1; a gradiens a célosztályon py−1∈(−1,0)p_y-1\in(-1,0), máshol pi∈(0,1)p_i\in(0,1); a batch-átlag gradiense a 1N(P−Yonehot)\frac1N(P-Y_{onehot}) alakú; a stabil LSE a c=maxzc=\max z kiemeléssel nem overflowol. Visszahívás: a 7.1. Entrópia, cross-entropy és KL alfejezetben a cross-entropy H(p,q)=−∑ipilogqiH(p,q)=-\sum_i p_i\log q_i volt, itt pp az one-hot cél, qq a softmax kimenet; a 3.2. Mátrixszorzás és affín réteg alfejezetben az affin réteg adta a logitot, itt a loss annak kimenetére épül; a 6.2. Konvexitás és sima optimalizálási határ alfejezetben a gradienslépés iránya volt a csökkenés feltétele, és a most levezetett gradiens adja a tényleges lépésirányt.

7.2.12 Rövid összefoglaló

A softmax a logitvektort diszkrét eloszlássá alakítja, és invariáns a konstans eltolásra. A többosztályos cross-entropy logitsból L=−zy+LSE(z)L=-z_y+\operatorname{LSE}(z) alakban stabilan számítható; az LSE-t a maximális logit kiemelésével értékeljük. Egy megfigyelésre a logitgradiens p−eyp-e_y, ahol pp a softmax kimenet és eye_y a cél one-hot vektora; batchelt mean redukciónál 1N(P−Yonehot)\frac1N(P-Y_{onehot}). A CrossEntropyLoss logitsot és int64 célindexet vár, előzetes softmax hibás. A softmax kimenet kalibrációs előkép, de önmagában nem bizonyított kalibráció. A labor kézi háromlogitos példán, véges differenciával és 10001000-es eltolással ellenőrzi a gradienst. A következő alkalmazás a 7.3. Bináris, multilabel és regressziós cél alfejezetben a bináris és multilabel veszteség.

7.2.13 Források