3.5 Glaustas tiesinės regresijos įgyvendinimas

Pastarąjį dešimtmetį giliojo mokymosi srityje įvyko tarsi Kambro sprogimas. Metodų, taikymų ir algoritmų gausa gerokai pranoksta ankstesnių dešimtmečių pažangą. Tai lėmė laimingas kelių veiksnių derinys, vienas iš kurių yra galingi nemokami įrankiai, kuriuos siūlo keli atvirojo kodo giliojo mokymosi karkasai. Theano (Bergstra ir kt., 2010), DistBelief (Dean ir kt., 2012) ir Caffe (Jia ir kt., 2014) galima laikyti pirmąja tokių karkasų karta, sulaukusia plataus pritaikymo. Skirtingai nuo ankstesnių (pamatinių) darbų, tokių kaip SN2 (Simulateur Neuristique) (Bottou ir Le Cun, 1988), kuris teikė į Lisp panašią programavimo patirtį, šiuolaikiniai karkasai siūlo automatinį diferencijavimą ir Python patogumą. Šie karkasai leidžia mums automatizuoti ir moduliarizuoti pasikartojantį gradientiniu mokymusi pagrįstų algoritmų įgyvendinimo darbą.

3.4 skyrelis rėmėmės tik (i) tenzoriais duomenims saugoti ir tiesinei algebrai; ir (ii) automatiniu diferencijavimu gradientams skaičiuoti. Praktikoje, kadangi duomenų iteratoriai, nuostolių funkcijos, optimizatoriai ir neuroninių tinklų sluoksniai yra tokie dažni, šiuolaikinės bibliotekos įgyvendina ir šiuos komponentus už mus. Šiame skyrelyje (parodysime, kaip įgyvendinti tiesinės regresijos modelį) iš 3.4 skyrelis (glaustai naudojant aukšto lygio giliojo mokymosi karkasų API).

from d2l import mxnet as d2l
from mxnet import autograd, gluon, init, np, npx
from mxnet.gluon import nn
npx.set_np()
from d2l import torch as d2l
import numpy as np
import torch
from torch import nn
from d2l import tensorflow as d2l
import numpy as np
import tensorflow as tf
from d2l import jax as d2l
from flax import linen as nn
import jax
from jax import numpy as jnp
import optax

3.5.1 Modelio apibrėžimas

Kai 3.4 skyrelis įgyvendinome tiesinę regresiją nuo nulio, aiškiai apibrėžėme savo modelio parametrus ir užkodavome skaičiavimus išvesčiai gauti naudodami pagrindines tiesinės algebros operacijas. Jūs turėtumėte mokėti tai daryti. Bet kai jūsų modeliai taps sudėtingesni ir kai tai reikės daryti beveik kasdien, džiaugsitės pagalba. Situacija panaši į savo tinklaraščio kūrimą nuo nulio. Padaryti tai vieną ar du kartus yra naudinga ir pamokoma, bet būtumėte prastas interneto kūrėjas, jei mėnesį praleistumėte išradinėdami dviratį.

Standartinėms operacijoms galime naudoti karkaso iš anksto apibrėžtus sluoksnius, kurie leidžia mums sutelkti dėmesį į modeliui sudaryti naudojamus sluoksnius, o ne rūpintis jų įgyvendinimu. Prisiminkite vieno sluoksnio tinklo architektūrą, aprašytą 3.1.2 pav.. Sluoksnis vadinamas pilnai sujungtu, nes kiekviena jo įvestis sujungta su kiekviena jo išvestimi per matricos ir vektoriaus daugybą.

Gluon pilnai sujungtas sluoksnis apibrėžiamas klasėje Dense. Kadangi norime sugeneruoti tik vieną skaliarinę išvestį, tą skaičių nustatome lygų 1. Verta pažymėti, kad dėl patogumo Gluon nereikalauja, kad nurodytume kiekvieno sluoksnio įvesties formą. Todėl mums nereikia pasakyti Gluon, kiek įvesčių patenka į šį tiesinį sluoksnį. Kai pirmą kartą perduodame duomenis per savo modelį, pvz., kai vėliau vykdome net(X), Gluon automatiškai nustatys kiekvieno sluoksnio įvesčių skaičių ir taip sukurs teisingą modelį. Kaip tai veikia, aprašysime išsamiau vėliau.

PyTorch pilnai sujungtas sluoksnis apibrėžiamas klasėse Linear ir LazyLinear (prieinamose nuo versijos 1.8.0). Pastaroji leidžia vartotojams nurodyti tik išvesties dimensiją, o pirmoji papildomai klausia, kiek įvesčių patenka į šį sluoksnį. Įvesties formų nurodymas yra nepatogus ir gali reikalauti netrivialių skaičiavimų (pvz., konvoliuciniuose sluoksniuose). Todėl dėl paprastumo, kai tik galėsime, naudosime tokius „tingius" sluoksnius.

Keras pilnai sujungtas sluoksnis apibrėžiamas klasėje Dense. Kadangi norime sugeneruoti tik vieną skaliarinę išvestį, tą skaičių nustatome lygų 1. Verta pažymėti, kad dėl patogumo Keras nereikalauja, kad nurodytume kiekvieno sluoksnio įvesties formą. Mums nereikia pasakyti Keras, kiek įvesčių patenka į šį tiesinį sluoksnį. Kai pirmą kartą bandome perduoti duomenis per savo modelį, pvz., kai vėliau vykdome net(X), Keras automatiškai nustatys kiekvieno sluoksnio įvesčių skaičių. Kaip tai veikia, aprašysime išsamiau vėliau.

class LinearRegression(d2l.Module):  #@save
    """The linear regression model implemented with high-level APIs."""
    def __init__(self, lr):
        super().__init__()
        self.save_hyperparameters()
        if tab.selected('mxnet'):
            self.net = nn.Dense(1)
            self.net.initialize(init.Normal(sigma=0.01))
        if tab.selected('tensorflow'):
            initializer = tf.initializers.RandomNormal(stddev=0.01)
            self.net = tf.keras.layers.Dense(1, kernel_initializer=initializer)
        if tab.selected('pytorch'):
            self.net = nn.LazyLinear(1)
            self.net.weight.data.normal_(0, 0.01)
            self.net.bias.data.fill_(0)
class LinearRegression(d2l.Module):  #@save
    """The linear regression model implemented with high-level APIs."""
    lr: float

    def setup(self):
        self.net = nn.Dense(1, kernel_init=nn.initializers.normal(0.01))

Metode forward tiesiog iškviečiame iš anksto apibrėžtų sluoksnių įtaisytąjį metodą __call__, kad apskaičiuotume išvestis.

@d2l.add_to_class(LinearRegression)  #@save
def forward(self, X):
    return self.net(X)
@d2l.add_to_class(LinearRegression)  #@save
def forward(self, X):
    return self.net(X)

3.5.2 Nuostolių funkcijos apibrėžimas

Modulis loss apibrėžia daug naudingų nuostolių funkcijų. Dėl spartos ir patogumo atsisakome įgyvendinti savo ir vietoj to pasirenkame įtaisytąją loss.L2Loss. Kadangi jos grąžinami loss yra kiekvieno pavyzdžio kvadratinė paklaida, naudojame mean, kad suvidurkintume nuostolius per minirinkinį.

Klasė MSELoss apskaičiuoja vidutinę kvadratinę paklaidą (be 1/21/2 daugiklio (3.1.4)). Pagal numatytuosius nustatymus MSELoss grąžina vidutinius nuostolius per pavyzdžius. Tai greičiau (ir lengviau naudoti) nei įgyvendinti savo.

Klasė MeanSquaredError apskaičiuoja vidutinę kvadratinę paklaidą (be 1/21/2 daugiklio (3.1.4)). Pagal numatytuosius nustatymus ji grąžina vidutinius nuostolius per pavyzdžius.

@d2l.add_to_class(LinearRegression)  #@save
def loss(self, y_hat, y):
    if tab.selected('mxnet'):
        fn = gluon.loss.L2Loss()
        return fn(y_hat, y).mean()
    if tab.selected('pytorch'):
        fn = nn.MSELoss()
        return fn(y_hat, y)
    if tab.selected('tensorflow'):
        fn = tf.keras.losses.MeanSquaredError()
        return fn(y, y_hat)
@d2l.add_to_class(LinearRegression)  #@save
def loss(self, params, X, y, state):
    y_hat = state.apply_fn({'params': params}, *X)
    return d2l.reduce_mean(optax.l2_loss(y_hat, y))

3.5.3 Optimizavimo algoritmo apibrėžimas

Minirinkinio SGD yra standartinė priemonė neuroniniams tinklams optimizuoti, todėl Gluon jį palaiko kartu su keliomis šio algoritmo variacijomis per savo klasę Trainer. Atkreipkite dėmesį, kad Gluon klasė Trainer reiškia optimizavimo algoritmą, o 3.2 skyrelis sukurta klasė Trainer talpina apmokymo metodą, t. y. pakartotinai kviečia optimizatorių modelio parametrams atnaujinti. Kai kuriame Trainer egzempliorių, nurodome parametrus, kuriuos optimizuosime, gaunamus iš mūsų modelio net per net.collect_params(), optimizavimo algoritmą, kurį norime naudoti (sgd), ir hiperparametrų žodyną, reikalingą mūsų optimizavimo algoritmui.

Minirinkinio SGD yra standartinė priemonė neuroniniams tinklams optimizuoti, todėl PyTorch jį palaiko kartu su keliomis šio algoritmo variacijomis modulyje optim. Kai (kuriame SGD egzempliorių,) nurodome parametrus, kuriuos optimizuosime, gaunamus iš mūsų modelio per self.parameters(), ir mokymosi spartą (self.lr), reikalingą mūsų optimizavimo algoritmui.

Minirinkinio SGD yra standartinė priemonė neuroniniams tinklams optimizuoti, todėl Keras jį palaiko kartu su keliomis šio algoritmo variacijomis modulyje optimizers.

@d2l.add_to_class(LinearRegression)  #@save
def configure_optimizers(self):
    if tab.selected('mxnet'):
        return gluon.Trainer(self.collect_params(),
                             'sgd', {'learning_rate': self.lr})
    if tab.selected('pytorch'):
        return torch.optim.SGD(self.parameters(), self.lr)
    if tab.selected('tensorflow'):
        return tf.keras.optimizers.SGD(self.lr)
    if tab.selected('jax'):
        return optax.sgd(self.lr)

3.5.4 Apmokymas

Galbūt pastebėjote, kad modelio išreiškimas per giliojo mokymosi karkaso aukšto lygio API reikalauja mažiau kodo eilučių. Mums nereikėjo atskirai skirstyti parametrų, apibrėžti nuostolių funkcijos ar įgyvendinti minirinkinio SGD. Kai pradėsime dirbti su gerokai sudėtingesniais modeliais, aukšto lygio API pranašumai gerokai išaugs.

Dabar, kai turime visas pagrindines dalis, pats apmokymo ciklas yra toks pat, kaip ir įgyvendintasis nuo nulio. Todėl tiesiog iškviečiame metodą fit (pristatytą 3.2.4 skyrelis), kuris remiasi metodo fit_epoch įgyvendinimu 3.4 skyrelis, kad apmokytume savo modelį.

model = LinearRegression(lr=0.03)
data = d2l.SyntheticRegressionData(w=d2l.tensor([2, -3.4]), b=4.2)
trainer = d2l.Trainer(max_epochs=3)
trainer.fit(model, data)

Žemiau palyginame modelio parametrus, išmoktus apmokant baigtiniuose duomenyse, su tikraisiais parametrais, sugeneravusiais mūsų duomenų rinkinį. Parametrams pasiekti kreipiamės į reikiamo sluoksnio svorius ir poslinkį. Kaip ir įgyvendinime nuo nulio, atkreipkite dėmesį, kad mūsų įvertinti parametrai artimi tikriesiems.

@d2l.add_to_class(LinearRegression)  #@save
def get_w_b(self):
    if tab.selected('mxnet'):
        return (self.net.weight.data(), self.net.bias.data())
    if tab.selected('pytorch'):
        return (self.net.weight.data, self.net.bias.data)
    if tab.selected('tensorflow'):
        return (self.get_weights()[0], self.get_weights()[1])

w, b = model.get_w_b()
@d2l.add_to_class(LinearRegression)  #@save
def get_w_b(self, state):
    net = state.params['net']
    return net['kernel'], net['bias']

w, b = model.get_w_b(trainer.state)
print(f'error in estimating w: {data.w - d2l.reshape(w, data.w.shape)}')
print(f'error in estimating b: {data.b - b}')

3.5.5 Santrauka

Šiame skyrelyje yra pirmasis (šioje knygoje) gilaus tinklo įgyvendinimas, pasinaudojantis patogumais, kuriuos suteikia šiuolaikiniai giliojo mokymosi karkasai, tokie kaip MXNet (Chen ir kt., 2015), JAX (Frostig ir kt., 2018), PyTorch (Paszke ir kt., 2019) ir Tensorflow (Abadi ir kt., 2016). Duomenims įkelti, sluoksniui, nuostolių funkcijai, optimizatoriui ir apmokymo ciklui apibrėžti naudojome karkaso numatytuosius nustatymus. Kai tik karkasas pateikia visas reikalingas funkcijas, paprastai gera mintis jas naudoti, nes bibliotekų šių komponentų įgyvendinimai paprastai būna labai optimizuoti spartai ir tinkamai išbandyti patikimumui. Kartu stenkitės nepamiršti, kad šiuos modulius galima įgyvendinti tiesiogiai. Tai ypač svarbu siekiantiems tapti tyrėjais, norintiems būti modelių kūrimo priešakyje, kur išrasite naujų komponentų, kurių negali būti jokioje dabartinėje bibliotekoje.

Gluon modulis data teikia duomenų apdorojimo įrankius, modulis nn apibrėžia daugybę neuroninių tinklų sluoksnių, o modulis loss apibrėžia daug įprastų nuostolių funkcijų. Be to, initializer suteikia prieigą prie daugelio parametrų inicializavimo pasirinkimų. Vartotojui patogiai dimensija ir saugojimas nustatomi automatiškai. Šios tingios inicializacijos pasekmė yra ta, kad negalima bandyti pasiekti parametrų, kol jie nesukurti (ir neinicializuoti).

PyTorch modulis data teikia duomenų apdorojimo įrankius, modulis nn apibrėžia daugybę neuroninių tinklų sluoksnių ir įprastų nuostolių funkcijų. Parametrus galime inicializuoti pakeisdami jų reikšmes metodais, kurių pavadinimas baigiasi _. Atkreipkite dėmesį, kad turime nurodyti tinklo įvesties dimensijas. Nors kol kas tai nesudėtinga, tai gali turėti reikšmingų grandininių pasekmių, kai norime suprojektuoti sudėtingus tinklus su daug sluoksnių. Norint užtikrinti perkeliamumą, reikia kruopščiai apsvarstyti, kaip parametrizuoti šiuos tinklus.

TensorFlow modulis data teikia duomenų apdorojimo įrankius, modulis keras apibrėžia daugybę neuroninių tinklų sluoksnių ir įprastų nuostolių funkcijų. Be to, modulis initializers teikia įvairius modelio parametrų inicializavimo metodus. Tinklų dimensija ir saugojimas nustatomi automatiškai (bet būkite atsargūs ir nebandykite pasiekti parametrų, kol jie neinicializuoti).

3.5.6 Pratimai

  1. Kaip reikėtų pakeisti mokymosi spartą, jei suminius nuostolius per minirinkinį pakeistumėte nuostolių vidurkiu per minirinkinį?
  2. Peržiūrėkite karkaso dokumentaciją ir pažiūrėkite, kokios nuostolių funkcijos pateikiamos. Visų pirma pakeiskite kvadratinius nuostolius Huberio robastine nuostolių funkcija. Tai yra, naudokite nuostolių funkciją
l(y,y′)={∣y−y′∣−σ2 if ∣y−y′∣>σ12σ(y−y′)2 otherwisel(y,y') = \begin{cases}|y-y'| -\frac{\sigma}{2} & \textrm{ if } |y-y'| > \sigma \\ \frac{1}{2 \sigma} (y-y')^2 & \textrm{ otherwise}\end{cases}
  1. Kaip pasiekti modelio svorių gradientą?
  2. Kokį poveikį sprendiniui turi mokymosi spartos ir epochų skaičiaus keitimas? Ar jis ir toliau gerėja?
  3. Kaip keičiasi sprendinys, keičiant sugeneruotų duomenų kiekį?
    1. Nubraižykite įvertinimo paklaidą w^−w\hat{\mathbf{w}} - \mathbf{w} ir b^−b\hat{b} - b kaip duomenų kiekio funkciją. Užuomina: didinkite duomenų kiekį logaritmiškai, o ne tiesiškai, t. y. 5, 10, 20, 50, ..., 10 000, o ne 1000, 2000, ..., 10 000.
    2. Kodėl užuominoje pateiktas patarimas yra tinkamas?

Diskusijos

Diskusijos

Diskusijos

Diskusijos