4.5 Glaustas softmax regresijos įgyvendinimas
Kaip aukšto lygio giliojo mokymosi karkasai palengvino tiesinės regresijos įgyvendinimą (žr. 3.5 skyrelis), taip pat patogūs jie ir čia.
from d2l import mxnet as d2l
from mxnet import gluon, init, npx
from mxnet.gluon import nn
npx.set_np()
from d2l import torch as d2l
import torch
from torch import nn
from torch.nn import functional as F
from d2l import tensorflow as d2l
import tensorflow as tf
from d2l import jax as d2l
from flax import linen as nn
from functools import partial
import jax
from jax import numpy as jnp
import optax
4.5.1 Modelio apibrėžimas
Kaip ir 3.5 skyrelis,
savo pilnai sujungtą sluoksnį sukonstruojame
naudodami įtaisytąjį sluoksnį.
Tada įtaisytasis metodas __call__ iškviečia forward,
kai tik reikia taikyti tinklą kokiai nors įvesčiai.
Nors įvestis X yra ketvirtos eilės tenzorius,
įtaisytasis sluoksnis Dense
automatiškai konvertuos X į antros eilės tenzorių,
nekeisdamas dimensijos pagal pirmąją ašį.
Naudojame sluoksnį Flatten, kad ketvirtos eilės tenzorių X konvertuotume į antros eilės,
nekeisdami dimensijos pagal pirmąją ašį.
Naudojame sluoksnį Flatten, kad ketvirtos eilės tenzorių X konvertuotume,
nekeisdami dimensijos pagal pirmąją ašį.
Flax leidžia naudotojams tinklo klasę rašyti glaustesniu būdu,
naudojant dekoratorių @nn.compact. Su @nn.compact
galima tiesiog užrašyti visą tinklo logiką viename „tiesioginio sklidimo"
metode, nereikia apibrėžti standartinio metodo setup
duomenų klasėje.
class SoftmaxRegression(d2l.Classifier): #@save
"""The softmax regression model."""
def __init__(self, num_outputs, lr):
super().__init__()
self.save_hyperparameters()
self.net = nn.Sequential(nn.Flatten(),
nn.LazyLinear(num_outputs))
def forward(self, X):
return self.net(X)
class SoftmaxRegression(d2l.Classifier): #@save
"""The softmax regression model."""
def __init__(self, num_outputs, lr):
super().__init__()
self.save_hyperparameters()
if tab.selected('mxnet'):
self.net = nn.Dense(num_outputs)
self.net.initialize()
if tab.selected('tensorflow'):
self.net = tf.keras.models.Sequential()
self.net.add(tf.keras.layers.Flatten())
self.net.add(tf.keras.layers.Dense(num_outputs))
def forward(self, X):
return self.net(X)
class SoftmaxRegression(d2l.Classifier): #@save
num_outputs: int
lr: float
@nn.compact
def __call__(self, X):
X = X.reshape((X.shape[0], -1)) # Flatten
X = nn.Dense(self.num_outputs)(X)
return X
4.5.2 Dar kartą apie softmax
4.4 skyrelis apskaičiavome savo modelio išvestį ir pritaikėme kryžminės entropijos nuostolius. Nors tai visiškai pagrįsta matematiškai, skaičiavimo požiūriu tai rizikinga dėl skaitinio nepakankamumo ir perpildymo kėlimo eksponente metu.
Prisiminkite, kad softmax funkcija apskaičiuoja tikimybes pagal . Jei kai kurie yra labai dideli, t. y. labai teigiami, tada gali būti didesnis už didžiausią skaičių, kurį galima turėti tam tikriems duomenų tipams. Tai vadinama perpildymu. Panašiai, jei kiekvienas argumentas yra labai didelis neigiamas skaičius, gausime nepakankamumą. Pavyzdžiui, vienguba tikslumo slankiojo kablelio skaičiai maždaug apima diapazoną nuo iki . Todėl jei didžiausias narys yra už intervalo ribų, rezultatas nebus stabilus. Būdas apeiti šią problemą yra atimti iš visų įrašų:
Pagal konstrukciją žinome, kad visiems . Todėl klasių
klasifikavimo uždaviniui vardiklis yra intervale . Be to,
skaitiklis niekada neviršija , taip išvengiant skaitinio perpildymo. Skaitinis nepakankamumas
atsiranda tik tada, kai skaitiškai įvertinamas kaip . Vis dėlto po kelių žingsnių
galime patekti į bėdą, kai norėsime apskaičiuoti kaip .
Visų pirma atgaliniame sklidime
galime susidurti su ekranu
baisių NaN (Not a Number, ne skaičius) rezultatų.
Laimei, mus išgelbsti tai, kad nors skaičiuojame eksponentines funkcijas, galiausiai ketiname imti jų logaritmą (skaičiuodami kryžminės entropijos nuostolius). Sujungę softmax ir kryžminę entropiją, galime visiškai išvengti skaitinio stabilumo problemų. Turime:
Taip išvengiama ir perpildymo, ir nepakankamumo. Norėsime po ranka turėti įprastą softmax funkciją, jei kada nors norėtume įvertinti mūsų modelio išvesties tikimybes. Bet užuot perdavę softmax tikimybes į naują nuostolių funkciją, tiesiog perduodame logitus ir apskaičiuojame softmax bei jo logaritmą visa tai iš karto kryžminės entropijos nuostolių funkcijos viduje, kuri daro protingus dalykus, pavyzdžiui, „LogSumExp triuką".
@d2l.add_to_class(d2l.Classifier) #@save
def loss(self, Y_hat, Y, averaged=True):
Y_hat = d2l.reshape(Y_hat, (-1, Y_hat.shape[-1]))
Y = d2l.reshape(Y, (-1,))
if tab.selected('mxnet'):
fn = gluon.loss.SoftmaxCrossEntropyLoss()
l = fn(Y_hat, Y)
return l.mean() if averaged else l
if tab.selected('pytorch'):
return F.cross_entropy(
Y_hat, Y, reduction='mean' if averaged else 'none')
if tab.selected('tensorflow'):
fn = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
return fn(Y, Y_hat)
@d2l.add_to_class(d2l.Classifier) #@save
@partial(jax.jit, static_argnums=(0, 5))
def loss(self, params, X, Y, state, averaged=True):
# To be used later (e.g., for batch norm)
Y_hat = state.apply_fn({'params': params}, *X,
mutable=False, rngs=None)
Y_hat = d2l.reshape(Y_hat, (-1, Y_hat.shape[-1]))
Y = d2l.reshape(Y, (-1,))
fn = optax.softmax_cross_entropy_with_integer_labels
# The returned empty dictionary is a placeholder for auxiliary data,
# which will be used later (e.g., for batch norm)
return (fn(Y_hat, Y).mean(), {}) if averaged else (fn(Y_hat, Y), {})
4.5.3 Apmokymas
Toliau apmokome savo modelį. Naudojame Fashion-MNIST vaizdus, išlygintus į 784 dimensijų požymių vektorius.
data = d2l.FashionMNIST(batch_size=256)
model = SoftmaxRegression(num_outputs=10, lr=0.1)
trainer = d2l.Trainer(max_epochs=10)
trainer.fit(model, data)
Kaip ir anksčiau, šis algoritmas konverguoja į sprendinį, kuris yra pakankamai tikslus, nors šį kartą naudojant mažiau kodo eilučių nei anksčiau.
4.5.4 Santrauka
Aukšto lygio API labai patogiai slepia nuo naudotojo potencialiai pavojingus aspektus, tokius kaip skaitinis stabilumas. Be to, jos leidžia naudotojams glaustai projektuoti modelius naudojant labai mažai kodo eilučių. Tai ir palaima, ir prakeiksmas. Akivaizdi nauda yra ta, kad viskas tampa labai prieinama, net inžinieriams, kurie gyvenime nėra lankę nė vienos statistikos paskaitos (iš tiesų jie yra dalis šios knygos tikslinės auditorijos). Tačiau aštrių kampų slėpimas turi ir kainą: nenorą patiems pridėti naujų ir skirtingų komponentų, nes mažai turima įpročio tai daryti. Be to, tai apsunkina dalykų taisymą, kai karkaso apsauginis sluoksnis nepadengia visų kraštutinių atvejų. Vėlgi, tai kyla dėl nepakankamo susipažinimo.
Todėl primygtinai raginame peržiūrėti abi – ir paprastąją, ir elegantiškąją – daugelio tolesnių įgyvendinimų versijas. Nors pabrėžiame supratimo paprastumą, įgyvendinimai vis dėlto paprastai yra gana našūs (konvoliucijos čia yra didelė išimtis). Mūsų ketinimas – leisti jums remtis jais, kai išrasite ką nors naujo, ko joks karkasas negali suteikti.
4.5.5 Pratimai
- Giliajame mokyme naudojama daug skirtingų skaičių formatų, įskaitant FP64 dvigubą tikslumą (naudojamas itin retai), FP32 vienguba tikslumu, BFLOAT16 (tinka suspaustiems atvaizdams), FP16 (labai nestabilus), TF32 (naujas NVIDIA formatas) ir INT8. Apskaičiuokite mažiausią ir didžiausią eksponentinės funkcijos argumentą, kuriam rezultatas nesukelia skaitinio nepakankamumo ar perpildymo.
- INT8 yra labai ribotas formatas, sudarytas iš nenulinių skaičių nuo iki . Kaip galėtumėte išplėsti jo dinaminį diapazoną nenaudodami daugiau bitų? Ar standartinė daugyba ir sudėtis vis dar veikia?
- Padidinkite apmokymo epochų skaičių. Kodėl validavimo tikslumas po kurio laiko gali sumažėti? Kaip tai galėtume ištaisyti?
- Kas nutinka didinant mokymosi spartą? Palyginkite nuostolių kreives esant kelioms mokymosi spartoms. Kuri veikia geriau? Kada?
