Implementarea Concisă a Regresiei Liniare

Deep learning a fost martorul unei sort de explozii Cambriene în ultimul deceniu. Numărul mare de tehnici, aplicații și algoritmi depășește cu mult progresul deceniilor anterioare. Acest lucru se datorează unei combinații fortuite de factori multipli, unul dintre aceștia fiind instrumentele puternice gratuite oferite de o serie de framework-uri de deep learning open-source. Theano 1), DistBelief 2) și Caffe 3) reprezintă probabil prima generație de astfel de modele care au găsit o adoptare pe scară largă. În contrast cu lucrările anterioare (seminale) precum SN2 (Simulateur Neuristique) 4), care ofereau o experiență de programare asemănătoare cu Lisp, framework-urile moderne oferă diferențiere automată și confortul limbajului Python. Aceste framework-uri ne permit să automatizăm și să modularizăm munca repetitivă de implementare a algoritmilor de învățare bazați pe gradient.

În , ne-am bazat doar pe (i) tensori pentru stocarea datelor și algebră liniară; și (ii) diferențiere automată pentru calcularea gradienților. În practică, deoarece iteratorii de date, funcțiile de pierdere, optimizatorii și straturile rețelelor neuronale sunt atât de comune, bibliotecile moderne implementează aceste componente și pentru noi. În această secțiune, (vă vom arăta cum să implementați modelul de regresie liniară) din ' (**în mod concis folosind API-urile de nivel înalt**) ale framework-urilor de deep learning. <code python> from d2l import torch as d2l import numpy as np import torch from torch import nn </code> ===== Definirea Modelului ===== Când am implementat regresia liniară de la zero în , ne-am definit parametrii modelului în mod explicit și am creat codul pentru calcule pentru a produce rezultatul folosind operații de bază de algebră liniară. //Ar trebui// să știți cum să faceți acest lucru. Dar odată ce modelele dumneavoastră devin mai complexe și odată ce trebuie să faceți acest lucru aproape în fiecare zi, vă veți bucura de asistență. Situația este similară cu scrierea propriului blog de la zero. Să o faceți o dată sau de două ori este plin de satisfacții și instructiv, dar ați fi un dezvoltator web slab dacă ați petrece o lună reinventând roata. Pentru operații standard, putem [**folosi straturile predefinite ale unui framework,**] care ne permit să ne concentrăm pe straturile utilizate pentru a construi modelul, mai degrabă decât să ne facem griji cu privire la implementarea lor. Reamintiți-vă arhitectura unei rețele cu un singur strat, așa cum este descrisă în . Stratul se numește //complet conectat// (fully connected), deoarece fiecare dintre intrările sale este conectată la fiecare dintre ieșirile sale prin intermediul unei multiplicări matrice-vector. În PyTorch, stratul complet conectat este definit în clasele ''Linear'' și ''LazyLinear'' (disponibile începând cu versiunea 1.8.0). Aceasta din urmă permite utilizatorilor să specifice //doar// dimensiunea de ieșire, în timp ce prima cere suplimentar numărul de intrări care intră în acest strat. Specificarea formelor de intrare este inconvenientă și poate necesita calcule complexe (cum ar fi în straturile convoluționale). Astfel, pentru simplitate, vom folosi astfel de straturi „lazy” ori de câte ori putem. <code python> class LinearRegression(d2l.Module): #@save """Modelul de regresie liniară implementat cu API-uri de nivel înalt.""" def __init__(self, lr): super().__init__() self.save_hyperparameters() self.net = nn.LazyLinear(1) self.net.weight.data.normal_(0, 0.01) self.net.bias.data.fill_(0) </code> În metoda ''forward'' pur și simplu apelăm metoda încorporată ''call'' a straturilor predefinite pentru a calcula ieșirile. <code python> @d2l.add_to_class(LinearRegression) #@save def forward(self, X): return self.net(X) </code> ===== Definirea Funcției de Pierdere ===== [**Clasa ''MSELoss'' calculează eroarea medie pătratică (fără factorul $1/2$ din :eqref:''eq_mse'').**] În mod implicit, ''MSELoss'' returnează pierderea medie pe exemple. Este mai rapidă (și mai ușor de utilizat) decât implementarea proprie. <code python> @d2l.add_to_class(LinearRegression) #@save def loss(self, y_hat, y): fn = nn.MSELoss() return fn(y_hat, y)</code> ===== Definirea Algoritmului de Optimizare ===== SGD de minilot este un instrument standard pentru optimizarea rețelelor neuronale și, prin urmare, PyTorch îl acceptă alături de o serie de variații ale acestui algoritm în modulul ''optim''. Când (**instanțiem o instanță ''SGD'',**) specificăm parametrii de optimizat, care pot fi obținuți din modelul nostru prin intermediul ''self.parameters()'', și rata de învățare (''self.lr'') necesară algoritmului nostru de optimizare. <code python> @d2l.add_to_class(LinearRegression) #@save def configure_optimizers(self): return torch.optim.SGD(self.parameters(), self.lr)</code> ===== Antrenarea ===== S-ar putea să fi observat că exprimarea modelului nostru prin API-uri de nivel înalt ale unui framework de deep learning necesită mai puține linii de cod. Nu a trebuit să alocăm parametrii individual, să definim funcția noastră de pierdere sau să implementăm SGD de minilot. Odată ce începem să lucrăm cu modele mult mai complexe, avantajele API-ului de nivel înalt vor crește considerabil. Acum că avem toate piesele de bază la locul lor, [**bucla de antrenament în sine este aceeași cu cea pe care am implementat-o de la zero.**] Deci, apelăm pur și simplu metoda ''fit'' (introdusă în ), care se bazează pe implementarea metodei ''fit_epoch'' din , pentru a ne antrena modelul. <code python> 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) </code> Mai jos, [**comparăm parametrii modelului învățați prin antrenament pe date finite și parametrii reali**] care au generat setul nostru de date. Pentru a accesa parametrii, accesăm ponderile și biasul stratului de care avem nevoie. Ca și în implementarea noastră de la zero, observați că parametrii noștri estimați sunt aproape de corespondenții lor reali. <code python> @d2l.add_to_class(LinearRegression) #@save def get_w_b(self): return (self.net.weight.data, self.net.bias.data) w, b = model.get_w_b() </code> <code python> print(f'error in estimating w: {data.w - d2l.reshape(w, data.w.shape)}') print(f'error in estimating b: {data.b - b}') </code> ===== Rezumat ===== Această secțiune conține prima implementare a unei rețele profunde (din această carte) care utilizează facilitățile oferite de framework-urile moderne de deep learning, precum MXNet ((Chen.Li.Li.ea.2015)), JAX ((Frostig.Johnson.Leary.2018)), PyTorch ((Paszke.Gross.Massa.ea.2019)) și Tensorflow ((Abadi.Barham.Chen.ea.2016)). Am folosit setările implicite ale framework-ului pentru încărcarea datelor, definirea unui strat, a unei funcții de pierdere, a unui optimizator și a unei bucle de antrenament. Ori de câte ori framework-ul oferă toate caracteristicile necesare, este în general o idee bună să le folosim, deoarece implementările din bibliotecă ale acestor componente tind să fie puternic optimizate pentru performanță și testate corespunzător pentru fiabilitate. În același timp, încercați să nu uitați că aceste module //pot// fi implementate direct. Acest lucru este deosebit de important pentru cercetătorii aspiranți care doresc să trăiască în avangarda dezvoltării modelelor, unde veți inventa noi componente care nu pot exista în nicio bibliotecă actuală. În PyTorch, modulul ''data'' oferă instrumente pentru procesarea datelor, modulul ''nn'' definește un număr mare de straturi de rețele neuronale și funcții de pierdere comune. Putem inițializa parametrii prin înlocuirea valorilor lor cu metode care se termină în ''_%%'. Rețineți că trebuie să specificăm dimensiunile de intrare ale rețelei. Deși acest lucru este trivial deocamdată, acesta poate avea efecte de propagare semnificative atunci când dorim să proiectăm rețele complexe cu multe straturi. Sunt necesare considerente atente asupra modului de parametrizare a acestor rețele pentru a permite portabilitatea.

Exerciții

  1. Cum ar trebui să schimbați rata de învățare dacă înlocuiți pierderea agregată pe minilot cu o medie a pierderii pe minilot?
  2. Consultați documentația framework-ului pentru a vedea ce funcții de pierdere sunt furnizate. În special, înlocuiți pierderea pătratică cu funcția de pierdere robustă a lui Huber. Adică, utilizați funcția de pierdere $$l(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}$$
  3. Cum accesați gradientul ponderilor modelului?
  4. Care este efectul asupra soluției dacă schimbați rata de învățare și numărul de epoci? Continuă să se îmbunătățească?
  5. Cum se schimbă soluția pe măsură ce variați cantitatea de date generată?
    1. Trasați eroarea de estimare pentru $\hat{\mathbf{w}} - \mathbf{w}$ și $\hat{b} - b$ în funcție de cantitatea de date. Sugestie: creșteți cantitatea de date logaritmic mai degrabă decât liniar, adică 5, 10, 20, 50, …, 10.000 în loc de 1000, 2000, …, 10.000.
    2. De ce este potrivită sugestia din indicație?

Discuții

1)
Bergstra.Breuleux.Bastien.ea.2010
2)
Dean.Corrado.Monga.ea.2012
3)
Jia.Shelhamer.Donahue.ea.2014
4)
Bottou.Le-Cun.1988