Machine learning se referă la extragerea informațiilor din date. Așa că v-ați putea întreba, ce am putea învăța de la niște date sintetice? Deși s-ar putea să nu ne pese intrinsec de modelele pe care noi înșine le-am introdus într-un model artificial de generare a datelor, astfel de seturi de date sunt totuși utile în scopuri didactice, ajutându-ne să evaluăm proprietățile algoritmilor noștri de învățare și să confirmăm că implementările noastre funcționează conform așteptărilor. De exemplu, dacă creăm date pentru care parametrii corecți sunt cunoscuți a priori, atunci putem verifica dacă modelul nostru îi poate recupera într-adevăr.
%matplotlib inline from d2l import torch as d2l import torch import random
Pentru acest exemplu, vom lucra în dimensiuni mici pentru concizie. Următorul fragment de cod generează 1000 de exemple cu caracteristici bidimensionale extrase dintr-o distribuție normală standard. Matricea de design rezultată $\mathbf{X}$ aparține $\mathbb{R}^{1000 \times 2}$. Generăm fiecare etichetă prin aplicarea unei funcții liniare de bază (ground truth), corupându-le prin zgomot aditiv $\boldsymbol{\epsilon}$, extras independent și identic pentru fiecare exemplu:
($$\mathbf{y}= \mathbf{X} \mathbf{w} + b + \boldsymbol{\epsilon}.$$)
Pentru comoditate, presupunem că $\boldsymbol{\epsilon}$ este extras dintr-o distribuție normală cu media $\mu= 0$ și deviația standard $\sigma = 0.01$. Rețineți că pentru designul orientat pe obiecte adăugăm codul la metoda __init__ a unei subclase a d2l.DataModule (introdusă în ). Este o practică bună să permiteți setarea oricăror hiperparametri suplimentari. Realizăm acest lucru cu save_hyperparameters(). Dimensiunea lotului (batch_size) va fi determinată ulterior.
class SyntheticRegressionData(d2l.DataModule): #@save """Date sintetice pentru regresia liniară.""" def __init__(self, w, b, noise=0.01, num_train=1000, num_val=1000, batch_size=32): super().__init__() self.save_hyperparameters() n = num_train + num_val self.X = d2l.randn(n, len(w)) noise = d2l.randn(n, 1) * noise self.y = d2l.matmul(self.X, d2l.reshape(w, (-1, 1))) + b + noise
Mai jos, setăm parametrii reali la $\mathbf{w} = [2, -3.4]^\top$ și $b = 4.2$. Ulterior, putem verifica parametrii estimați față de aceste valori de bază (ground truth).
data = SyntheticRegressionData(w=d2l.tensor([2, -3.4]), b=4.2)
[Fiecare rând din features constă dintr-un vector în $\mathbb{R}^2$ și fiecare rând din labels este un scalar.] Să aruncăm o privire la prima intrare.
print('features:', data.X[0],'\nlabel:', data.y[0])
Antrenarea modelelor de machine learning necesită adesea treceri multiple printr-un set de date, preluând câte un minilot (minibatch) de exemple deodată. Aceste date sunt apoi utilizate pentru a actualiza modelul. Pentru a ilustra cum funcționează acest lucru, [implementăm metoda get_dataloader,] înregistrând-o în clasa SyntheticRegressionData prin intermediul add_to_class (introdusă în ). Aceasta (primește o dimensiune de lot, o matrice de caracteristici și un vector de etichete și generează miniloturi de dimensiune batch_size.) Astfel, fiecare minilot constă dintr-un tuplu de caracteristici și etichete. Rețineți că trebuie să fim atenți dacă ne aflăm în modul de antrenare sau de validare: în primul caz, vom dori să citim datele într-o ordine aleatorie, în timp ce pentru cel de-al doilea, capacitatea de a citi datele într-o ordine predefinită poate fi importantă în scopuri de depanare.
@d2l.add_to_class(SyntheticRegressionData) def get_dataloader(self, train): if train: indices = list(range(0, self.num_train)) # Exemplele sunt citite în ordine aleatorie random.shuffle(indices) else: indices = list(range(self.num_train, self.num_train+self.num_val)) for i in range(0, len(indices), self.batch_size):
Pentru a ne forma o intuiție, să inspectăm primul minilot de date. Fiecare minilot de caracteristici ne furnizează atât dimensiunea sa, cât și dimensionalitatea caracteristicilor de intrare. În mod similar, minilotul nostru de etichete va avea o formă corespunzătoare dată de batch_size.
X, y = next(iter(data.train_dataloader())) print('X shape:', X.shape, '\ny shape:', y.shape)
While seemingly innocuous, the invocation of iter(data.train_dataloader()) illustrates the power of Python’s object-oriented design. Note that we added a method to the SyntheticRegressionData class after creating the data object. Nonetheless, the object benefits from the ex post facto addition of functionality to the class.
Deși pare inofensivă, invocarea iter(data.train_dataloader()) ilustrează puterea designului orientat pe obiecte din Python. Rețineți că am adăugat o metodă clasei SyntheticRegressionData după crearea obiectului data. Cu toate acestea, obiectul beneficiază de adăugarea ex post facto a funcționalității clasei.
Pe parcursul iterației obținem miniloturi distincte până când întregul set de date a fost epuizat (încercați acest lucru). Deși iterația implementată mai sus este utilă în scopuri didactice, este ineficientă în moduri care ne-ar putea crea probleme în cazul unor probleme reale. De exemplu, aceasta necesită încărcarea tuturor datelor în memorie și efectuarea multor accese aleatorii la memorie. Iteratorii încorporați, implementați într-un cadru de deep learning (framework), sunt considerabil mai eficienți și pot gestiona surse precum date stocate în fișiere, date primite printr-un stream și date generate sau procesate din mers (on the fly). În continuare, să încercăm să implementăm aceeași metodă utilizând iteratorii încorporați.
În loc să scriem propriul iterator, putem [apela API-ul existent într-un framework pentru a încărca date.] Ca și până acum, avem nevoie de un set de date cu caracteristicile X și etichetele y. Dincolo de asta, setăm batch_size în încărcătorul de date încorporat și lăsăm acesta să se ocupe eficient de amestecarea (shuffling) exemplelor.
@d2l.add_to_class(d2l.DataModule) #@save def get_tensorloader(self, tensors, train, indices=slice(0, None)): tensors = tuple(a[indices] for a in tensors) dataset = torch.utils.data.TensorDataset(*tensors) return torch.utils.data.DataLoader(dataset, self.batch_size, shuffle=train)
@d2l.add_to_class(SyntheticRegressionData) #@save def get_dataloader(self, train): i = slice(0, self.num_train) if train else slice(self.num_train, None) return self.get_tensorloader((self.X, self.y), train, i)
Noul încărcător de date se comportă exact ca cel anterior, cu excepția faptului că este mai eficient și are unele funcționalități adăugate.
X, y = next(iter(data.train_dataloader())) print('X shape:', X.shape, '\ny shape:', y.shape)
De exemplu, încărcătorul de date furnizat de API-ul framework-ului suportă metoda încorporată __len__, astfel încât să îi putem interoga lungimea, adică numărul de loturi.
len(data.train_dataloader())
Încărcătoarele de date sunt un mod convenabil de a abstractiza procesul de încărcare și manipulare a datelor. În acest fel, același algoritm de machine learning este capabil să proceseze multe tipuri și surse diferite de date fără a fi necesară vreo modificare. Unul dintre aspectele plăcute ale încărcătoarelor de date este că acestea pot fi compuse. De exemplu, am putea încărca imagini și apoi să avem un filtru de postprocesare care să le decupeze sau să le modifice în alte moduri. Ca atare, încărcătoarele de date pot fi utilizate pentru a descrie o întreagă conductă (pipeline) de procesare a datelor.
În ceea ce privește modelul în sine, modelul liniar bidimensional este cam cel mai simplu pe care l-am putea întâlni. Acesta ne permite să testăm acuratețea modelelor de regresie fără să ne facem griji cu privire la cantități insuficiente de date sau la un sistem subdeterminat de ecuații. Vom pune acest lucru în practică în secțiunea următoare.
w, cât și numărul de exemple num_examples sunt mari.