Înapoi la catalog
Season 55 17 Episoade 1h 4m 2026

PyTorch Fundamentals

v2.11 — Ediția 2026. Un curs audio cuprinzător despre construirea modelelor de deep learning folosind PyTorch versiunea 2.11. Acoperă Tensors, Autograd, Neural Networks, Optimizers, DataLoaders și compilatorul PyTorch.

Framework-uri AI/ML Python Core Știința datelor
PyTorch Fundamentals
Se redă acum
Click play to start
0:00
0:00
1
Identitatea de bază a PyTorch
Descoperă scopul fundamental al PyTorch și ce îl diferențiază de bibliotecile matematice tradiționale. Acest episod explică rolul Tensors, Autograd și al accelerării GPU în deep learning-ul modern.
3m 50s
2
Înțelegerea PyTorch Tensors
Aprofundează Tensors, structura de date fundamentală a PyTorch. Învață cum fac legătura între datele brute și Neural Networks și cum partajează memoria fără probleme cu array-urile Numpy.
4m 14s
3
Operațiuni cu Tensors și memorie
Învață cum să manipulezi Tensors eficient. Acest episod acoperă operațiunile aritmetice, concatenarea, transferurile între dispozitive și implicațiile asupra memoriei ale operațiunilor in-place.
3m 33s
4
Magia Autograd
Descoperă motorul care face posibil deep learning-ul în PyTorch. Învață cum Autograd urmărește dinamic operațiunile și calculează automat derivate complexe.
3m 39s
5
Controlul urmăririi gradientului
Descoperă cum să dezactivezi urmărirea gradientului în PyTorch pentru a economisi memorie și a accelera calculele. Esențial pentru rularea inferenței și înghețarea parametrilor modelului.
4m 09s
6
Datasets și manipularea datelor
Învață cum să decuplezi procesarea datelor de arhitectura modelului folosind clasa Dataset din PyTorch. Explorăm lazy loading și structurile personalizate de dataset-uri.
4m 01s
7
DataLoaders și batching
Eliberează întreaga viteză a hardware-ului tău împachetând Datasets în DataLoaders. Învață cum să faci batching, shuffling și multiprocessing pe fluxurile tale de date.
3m 41s
8
Transformări de date
Descoperă cum să preprocesezi datele brute din mers înainte ca acestea să ajungă în rețeaua ta neuronală. Acoperim transformările torchvision precum ToTensor și funcțiile Lambda personalizate.
4m 07s
9
Proiectarea rețelelor cu nn.Module
Explorează planul structural al fiecărei rețele neuronale PyTorch. Învață cum să creezi subclase pentru nn.Module, să definești straturile la inițializare și să direcționezi datele în forward pass.
4m 01s
10
Straturi Linear și activări
Privește în interiorul rețelei neuronale. Analizăm modulul nn.Linear și explicăm de ce funcțiile de activare neliniare precum ReLU sunt esențiale din punct de vedere matematic.
4m 02s
11
Containerul nn.Sequential
Eficientizează-ți codul PyTorch folosind containerul nn.Sequential. Învață cum să îmbini straturile curat și să inspectezi parametrii modelului tău.
3m 27s
12
Înțelegerea funcțiilor Loss
Înainte ca un AI să poată învăța, trebuie să-și măsoare greșelile. Aprofundăm funcțiile loss din PyTorch, comparând CrossEntropyLoss pentru clasificare și MSELoss pentru regresie.
3m 39s
13
Optimizers și Gradient Descent
Explorează modul în care optimizer-ul actualizează ponderile modelului pentru a reduce eroarea. Învață dansul crucial în trei pași: zero_grad(), backward() și step().
3m 47s
15
Validare și inferență
Evaluează-ți modelul în mod obiectiv. Învață cum să-ți treci rețeaua în modul de evaluare, să îngheți gradienții și să extragi predicții precise pe date nevăzute.
3m 53s
16
Salvarea și încărcarea modelelor
Nu-ți pierde progresul obținut cu greu! Discutăm cele mai sigure modalități de a serializa ponderile modelului tău folosind state_dict și de a le încărca înapoi în siguranță.
3m 35s
17
Accelerarea vitezei cu torch.compile
Deblochează funcția definitorie a PyTorch 2.0. Învață cum decoratorul torch.compile face JIT-compiles codului tău Python în kernel-uri optimizate pentru accelerări masive.
3m 18s
18
Compilatoare și Graph Breaks
Pătrunde sub capota compilatorului PyTorch. Explorăm graph breaks, fluxul de control dinamic și de ce torch.compile reușește acolo unde sistemele vechi au eșuat.
4m 01s

Episoade

1

Identitatea de bază a PyTorch

3m 50s

Descoperă scopul fundamental al PyTorch și ce îl diferențiază de bibliotecile matematice tradiționale. Acest episod explică rolul Tensors, Autograd și al accelerării GPU în deep learning-ul modern.

Descarcă
Salut, sunt Alex de la DEV STORIES DOT EU. Fundamentele PyTorch, episodul 1 din 18. Scrii un model matematic complex în Python, dar când îl scalezi, îți blochează complet procesorul. Trebuie să rulezi aceste calcule pe hardware paralel și să calculezi continuu toate derivatele lor, dar să rescrii totul într-un limbaj low-level ar dura săptămâni. Această tensiune este rezolvată de identitatea de bază a PyTorch. Când te uiți prima dată la PyTorch, adesea pare exact ca NumPy. Creezi arrays, înmulțești matrice și manipulezi numere. Această asemănare vizuală provoacă multă confuzie inițială. Lumea presupune că PyTorch este doar un alt library de matematică standard. Nu este. În timp ce acele libraries de matematică standard sunt construite pentru calcul numeric CPU-bound, PyTorch este conceput de la zero pentru a valorifica hardware-ul paralel și a construi computational graphs dinamice. Piatra de temelie a acestui framework este tensorul. Un tensor este în esență un array multidimensional. Dacă ai o grilă de numere care reprezintă o imagine, o undă sonoră sau un bloc de text, o stochezi într-un tensor. Diferența critică dintre un array standard și un tensor PyTorch constă în locul unde acele date pot să existe și să fie executate. Tensorii se pot muta fără probleme din memoria sistemului tău pe un GPU. Să luăm o înmulțire masivă de matrice. Ai două grile care conțin milioane de numere. Dacă îi ceri unui CPU standard să le înmulțească, acesta procesează calculele secvențial sau în batches foarte mici. Procesul se chinuie și se blochează. Pentru că tensorii sunt proiectați explicit pentru hardware acceleration, poți trimite exact aceleași date către un GPU. GPU-ul conține mii de cores mici concepute pentru a executa operații matematice simultan. Un calcul masiv care durează minute întregi pe un CPU se termină instantaneu pe un GPU. PyTorch acționează ca o punte, traducând codul tău Python standard în instrucțiuni pentru acel hardware paralel. Hardware-ul rapid este doar jumătate din cerința pentru machine learning. Antrenarea unui neural network necesită calcul continuu. Trebuie să știi exact cum modificarea unei variabile îți schimbă output-ul final, ceea ce înseamnă calcularea constantă a gradienților. Să faci asta manual pentru un model cu miliarde de parametri este imposibil. Acest lucru ne aduce la al doilea pilon al PyTorch, care este Autograd. Autograd este un engine de diferențiere automată. Când efectuezi operații matematice pe tensori, PyTorch nu calculează doar numărul final. Construiește silențios o hartă în background. Înregistrează fiecare adunare, înmulțire și transformare de date într-un computational graph dinamic. Când ajungi la sfârșitul calculului, pur și simplu ceri framework-ului să calculeze gradienții. PyTorch parcurge înapoi acel graph invizibil, aplicând automat chain rule din calculus. Primești derivatele exacte pentru absolut fiecare parametru din modelul tău, fără să scrii tu vreun cod de calculus. Pentru că acest graph este construit dinamic on the fly, se adaptează la codul tău. Dacă un loop standard Python sau un if-statement modifică fluxul datelor tale, graph-ul se ajustează imediat. Adevărata putere a PyTorch nu constă doar în faptul că rulează rapid sau face calculus. Îți oferă viteza de execuție a unui supercomputer și rigoarea matematică a unui engine de calculus automat, totul complet ascuns în spatele unui cod Python obișnuit și lizibil. Dacă vrei să ajuți la continuarea acestor episoade, poți căuta DevStoriesEU pe Patreon și poți susține emisiunea. Asta e tot pentru acest episod. Mulțumesc că ai ascultat și continuă să construiești!
2

Înțelegerea PyTorch Tensors

4m 14s

Aprofundează Tensors, structura de date fundamentală a PyTorch. Învață cum fac legătura între datele brute și Neural Networks și cum partajează memoria fără probleme cu array-urile Numpy.

Descarcă
Salut, sunt Alex de la DEV STORIES DOT EU. Fundamentele PyTorch, episodul 2 din 18. Fiecare imagine, undă sonoră și document text pe care le introduci într-o rețea neuronală se transformă în cele din urmă în exact aceeași structură de date. Dacă totul este doar o grilă de numere, s-ar putea să te întrebi de ce avem nevoie de un obiect specializat în loc să ne bazăm pe array-uri standard de programare. Răspunsul stă în înțelegerea tensorilor din PyTorch. Un tensor este o structură de date specializată care arată și se comportă foarte asemănător cu un array sau o matrice. În PyTorch, tensorii sunt moneda universală. Ei stochează inputurile brute, outputurile generate de modelul tău și parametrii interni ai rețelei neuronale. Lumea presupune adesea că tensorii sunt complet identici cu array-urile NumPy. Într-adevăr, arată similar și împart multe comportamente comune. Diferența critică este ceea ce deblochează tensorii. În timp ce un array standard stă în memoria principală a sistemului și rulează pe CPU, un tensor este construit pentru a fi mutat cu ușurință pe o unitate de procesare grafică, sau GPU, pentru o accelerare hardware masivă. Tensorii conțin, de asemenea, infrastructura internă necesară pentru gradient tracking, ceea ce permite rețelelor neuronale să învețe. Poți inițializa un tensor în mai multe moduri. Cea mai directă cale este să pasezi date brute, cum ar fi o listă standard de numere din Python, direct în constructorul tensorului. De asemenea, poți crea un tensor nou pe baza unuia existent. Când faci asta, noul tensor moștenește automat proprietățile originalului, ceea ce înseamnă că va avea aceleași dimensiuni și același data type, cu excepția cazului în care le suprascrii explicit. Alternativ, dacă ai nevoie doar de un placeholder, poți defini un shape, care este o simplă colecție de numere ce reprezintă dimensiunile dorite, și poți cere PyTorch să genereze un tensor umplut cu numere random, doar cu unu sau doar cu zero, pe baza acelui shape. Odată ce ai un tensor, vei verifica frecvent trei atribute principale. Primul este shape-ul, care îți spune dimensiunea exactă a tensorului pe fiecare dimensiune. Al doilea este data type-ul, care indică felul numerelor stocate în interior, cum ar fi float-uri pe 32 de biți sau integeri. Al treilea este atributul device. Acesta îți spune unde se află fizic tensorul în acest moment, fie că este pe CPU, fie pe un anumit GPU. Trebuie să ții cont de asta, deoarece PyTorch cere ca tensorii să fie pe același device înainte să poată interacționa. Tensorii și array-urile standard trebuie adesea să lucreze împreună, ceea ce ne aduce la bridge-ul NumPy. Tensorii care se află pe CPU își pot partaja memoria subiacentă cu un array NumPy. Să zicem că încarci o fotografie de înaltă rezoluție folosind o librărie standard de procesare a imaginilor din Python. Acea imagine se încarcă în memoria sistemului tău ca un array NumPy standard. Poți pasa acel array în PyTorch folosind o funcție dedicată care creează un tensor din NumPy. PyTorch nu duplică datele pixelilor subiacenți într-un nou bloc de memorie. Pur și simplu face un wrap cu propria interfață de tensor peste adresa de memorie existentă. Schimbarea unei valori din tensor modifică imediat valoarea din array-ul NumPy și invers. Această conversie zero-copy economisește atât memorie, cât și timp de procesare. Când ai terminat de trecut datele prin modelul tău și trebuie să dai rezultatele înapoi unui tool standard de vizualizare, apelezi o singură metodă pe tensor pentru a-l expune înapoi ca un array NumPy, folosind exact aceeași memorie partajată. Adevărata putere a unui tensor nu constă doar în stocarea unei grile de numere, ci în a oferi contextul hardware specific și structura de memorie necesare pentru a trece datele brute printr-o rețea neuronală fără fricțiune. Mulțumesc pentru ascultare, happy coding tuturor!
3

Operațiuni cu Tensors și memorie

3m 33s

Învață cum să manipulezi Tensors eficient. Acest episod acoperă operațiunile aritmetice, concatenarea, transferurile între dispozitive și implicațiile asupra memoriei ale operațiunilor in-place.

Descarcă
Salut, sunt Alex de la DEV STORIES DOT EU. Fundamentele PyTorch, episodul 3 din 18. Un singur underscore în codul tău poate economisi gigabytes de memorie, dar ar putea, de asemenea, să-ți strice silențios întreaga rețea neuronală. Să știi când să folosești acel underscore se reduce la a înțelege operațiile cu tensori și memoria. Ia în calcul un scenariu practic. Ai trei feature vectors separați care reprezintă date de tip text, audio și imagine. Vrei să-i combini și să-i înmulțești cu o matrice de weights. By default, PyTorch creează tensorii pe CPU. Dar pentru calcule matematice grele cu matrice, vrei să folosești un accelerator hardware. Poți verifica dacă ai un GPU disponibil folosind verificările built-in din framework. Dacă ai, îți muți tensorii apelând metoda to pe ei. Pasezi numele device-ului target, cum ar fi string-ul cuda, în această metodă. PyTorch copiază apoi tensorul din memoria RAM a sistemului în memoria dedicată a plăcii tale video. Cu tensorii pe hardware-ul potrivit, trebuie să combini cei trei feature vectors separați într-unul singur. Faci asta folosind funcția concatenate, scrisă de obicei cat. Îi pasezi o listă cu tensorii tăi și specifici o dimensiune. Dacă îi combini pe dimensiunea coloanei, cei trei tensori înguști sunt uniți side-by-side pentru a forma un tensor mai lat. Acum ai un input unificat care stă în memoria GPU-ului. PyTorch gestionează peste o sută de operații diferite, dar aritmetica este baza. Ca să procesezi acel feature vector combinat, trebuie să-l înmulțești cu o matrice de weights. Poți folosi metoda matmul, sau pur și simplu simbolul at ca un shorthand convenabil. Asta execută o matrix multiplication matematică reală, calculând dot products pentru rânduri și coloane, și returnează un tensor complet nou care conține rezultatele. Uneori ai nevoie în schimb de matematică element-wise. Să presupunem că vrei să aplici o mască binară pe tensorul tău, forțând anumite valori la zero. Pentru asta, folosești metoda mul, sau operatorul standard asterisc. Asta nu face matrix multiplication. Pur și simplu înmulțește primul element din tensorul A cu primul element din tensorul B, al doilea cu al doilea, și așa mai departe. De fiecare dată când rulezi operații precum matrix multiplication sau adunare element-wise, PyTorch alocă memorie nouă pentru rezultat. Când operezi pe milioane de parametri, asta îți consumă rapid memoria hardware disponibilă. Aici trebuie să fii atent. PyTorch oferă operații in-place pentru a gestiona memory overhead-ul. Orice operație care se termină cu un underscore operează in-place. Dacă folosești metoda standard add, obții un tensor nou. Dacă folosești metoda add cu un underscore, PyTorch suprascrie direct valorile din interiorul tensorului existent. Memory footprint-ul rămâne exact același. Deși operațiile in-place sunt foarte eficiente pentru memorie, ele sunt și periculoase. Când suprascrii un tensor, îi ștergi valorile anterioare. Rețelele neuronale se bazează pe o înregistrare completă a stărilor trecute pentru a calcula derivatele în timpul fazei de învățare. Dacă suprascrii un tensor folosind o operație in-place, distrugi istoricul de calcul de care sistemul are nevoie pentru a face update modelului. Rezervă operațiile in-place pentru formatarea datelor înainte ca acestea să intre în modelul tău, și rămâi la operațiile standard în timpul antrenamentului pentru a-ți păstra istoricul de calcul intact. Asta e tot pentru acest episod. Mulțumesc că m-ai ascultat și continuă să construiești!
4

Magia Autograd

3m 39s

Descoperă motorul care face posibil deep learning-ul în PyTorch. Învață cum Autograd urmărește dinamic operațiunile și calculează automat derivate complexe.

Descarcă
Salut, sunt Alex de la DEV STORIES DOT EU. Fundamentele PyTorch, episodul 4 din 18. Antrenarea unei rețele neuronale înseamnă calcularea derivatei erorii tale în raport cu milioane de parametri. Să faci asta manual ar necesita pagini întregi de calcule și rescriere constantă de fiecare dată când schimbi arhitectura modelului. PyTorch rezolvă asta urmărindu-ți calculele în background, un concept cunoscut sub numele de Magia Autograd. Autograd este motorul de diferențiere built-in al PyTorch. Acesta calculează automat gradienții pentru orice computational graph. Ca să vezi cum funcționează, imaginează-ți o transformare liniară standard. Ai un input tensor care îți conține datele, o weight matrix și un bias vector. Scopul este să calculezi un output, să îl compari cu valoarea target reală și să calculezi eroarea, sau loss-ul. Datele tale de input sunt fixe, deci nu ai nevoie de derivatele lor. Dar tensorii de weights și bias trebuie updatați mai târziu, ceea ce înseamnă că ai absolută nevoie de gradienții lor. Semnalezi asta către PyTorch setând un flag numit requires grad pe true atunci când creezi acei tensori de parametri. Asta îi spune engine-ului autograd să înceapă să îi urmărească. Când faci operații pe acești tensori urmăriți — înmulțirea input-ului cu weights, adăugarea bias-ului și calcularea loss-ului final — PyTorch face două lucruri deodată. Calculează rezultatul numeric real și, simultan, construiește un Directed Acyclic Graph, sau DAG. În acest graf, tensorii tăi de start sunt frunzele, iar operațiile matematice pe care le-ai aplicat sunt rădăcinile. Fiecare tensor nou creat de o operație are un atribut care stochează o referință către funcția care l-a creat. Asta îi spune lui autograd exact cum să calculeze derivata pentru acel pas matematic specific. Acest graf nu este o structură statică definită la începutul scriptului tău. PyTorch construiește DAG-ul dinamic, de la zero, în timpul fiecărei iterații. Când rulezi un forward pass, un graf complet nou este construit on the fly. Această execuție dinamică înseamnă că rețeaua ta își poate schimba comportamentul la fiecare pas. Poți folosi control flow standard din Python, cum ar fi if statements sau loops, iar engine-ul va urmări curat orice cale au luat datele în timpul acelei rulări specifice. Odată ce forward pass-ul tău produce tensorul final de loss, declanșezi calculul gradientului apelând metoda backward pe acel loss. Autograd parcurge imediat graful în sens invers. Folosește chain rule pentru a calcula derivatele loss-ului în raport cu fiecare tensor care are requires grad setat pe true. Apoi ia acele valori calculate și le stochează în atributul grad al tensorilor tăi de weights și bias. Calculul complex este complet abstractizat. Sunt momente când vrei doar să treci datele prin model fără să calculezi gradienții, cum ar fi atunci când evaluezi un model antrenat. Urmărirea istoricului necesită memorie și putere de calcul în plus. Poți opri autograd din a construi graful în întregime dacă îți pui blocul de cod în context manager-ul torch no grad. Asta oprește temporar urmărirea și execută calculele mult mai rapid. Adevărata putere a autograd este că transformă cod arbitrar de Python într-o structură matematică complet diferențiabilă, fără ca tu să fii nevoit vreodată să scrii manual formulele derivatelor. Mulțumesc pentru ascultare. Aveți grijă de voi.
5

Controlul urmăririi gradientului

4m 09s

Descoperă cum să dezactivezi urmărirea gradientului în PyTorch pentru a economisi memorie și a accelera calculele. Esențial pentru rularea inferenței și înghețarea parametrilor modelului.

Descarcă
Salut, sunt Alex de la DEV STORIES DOT EU. PyTorch Fundamentals, episodul 5 din 18. Dacă rulezi un model nou antrenat pe un batch mare de imagini de test fără să modifici o setare specifică, aplicația ta va da crash până la urmă din cauza unei erori de out-of-memory. Chiar dacă faci doar predicții, modelul acumulează memorie în secret pentru a reține fiecare operație matematică pe care o face, până când sistemul moare. Controlul de Gradient Tracking este modul prin care poți preveni asta. By default, tensorii PyTorch sunt construiți să învețe. Dacă un tensor are cerința de gradient setată pe true, PyTorch face tracking pe fiecare operațiune efectuată asupra lui. Construiește un computation graph în background, legând inputs, weights și outputs, ca să poată calcula gradienții mai târziu, în timpul de backpropagation. Acest tracking engine este genial pentru training, dar vine cu mult overhead. Când procesul de training este gata, prioritățile tale se schimbă. Să zicem că tocmai ai terminat de antrenat un image classifier și trebuie să rulezi predicții pe un milion de imagini noi. Nu mai ai nevoie să actualizezi weights-urile modelului. Vrei doar un forward pass. Dacă lași mecanismul de tracking să ruleze, PyTorch construiește un computation graph masiv și inutil pentru acel milion de imagini, consumându-ți memoria RAM și încetinind ciclurile de compute. Ca să oprești asta, ai două tool-uri principale. Primul este un context manager numit torch dot no grad. Îl folosești ca să faci wrap la blocuri întregi de cod. Când pui acel forward pass în interiorul unui bloc no grad, îi spui lui PyTorch să oprească temporar acel tracking engine. Orice operațiuni făcute în interiorul acelui bloc nu vor fi înregistrate. Chiar dacă tensorii de input au tracking-ul activat în mod normal, output-urile create în interiorul blocului vor avea cerințele de gradient setate pe false. Ăsta este tool-ul tău pentru a rula evaluation, testing sau bulk predictions. Oprește graful pentru tot ce se află în scope-ul său. Al doilea tool este metoda detach. În timp ce no grad se ocupă de blocuri de cod, detach se ocupă de tensori individuali. Apelarea metodei detach pe un tensor returnează un nou tensor care partajează exact aceleași date la bază ca originalul, dar este complet deconectat de computation graph. Nu are istoric. Lumea confundă adesea când să folosească unul sau altul. Folosește context manager-ul torch dot no grad atunci când vrei să oprești tracking-ul pentru o secvență de operații, cum ar fi trecerea de la training la inference. Folosește metoda detach atunci când construiești activ un computation graph în timpul de training, dar ai nevoie să scoți un tensor specific din acel graf. Un use case comun pentru detach este atunci când trebuie să pasezi un tensor către o altă librărie Python, cum ar fi NumPy, care nu înțelege computation graphs din PyTorch. Dai detach la tensor mai întâi, eliminând bagajul de tracking, și apoi predai numerele brute. Dezactivarea de gradient tracking este, de asemenea, o tehnică de bază pentru a da freeze la parametri. Dacă faci fine-tuning pe un model pre-trained masiv, probabil că nu vrei să antrenezi totul from scratch. Poți face un loop prin base layers din model și să le setezi cerințele de gradient pe false. PyTorch oprește complet tracking-ul lor. În timpul de backward pass, acele frozen layers nu vor calcula gradienți și nu își vor face update, salvând cantități masive de memorie și accelerând dramatic procesul tău de fine-tuning. Gradient tracking este o mașinărie industrială grea, concepută strict pentru învățare. Ori de câte ori un tensor nu are nevoie să învețe, oprește mașinăria pentru a-ți recupera memoria și viteza. Asta e tot pentru acest episod. Mersi că m-ai ascultat și continuă să construiești!
6

Datasets și manipularea datelor

4m 01s

Învață cum să decuplezi procesarea datelor de arhitectura modelului folosind clasa Dataset din PyTorch. Explorăm lazy loading și structurile personalizate de dataset-uri.

Descarcă
Salut, sunt Alex de la DEV STORIES DOT EU. Fundamentele PyTorch, episodul 6 din 18. Modelul tău este la fel de bun ca datele pe care i le dai, dar ce faci când dataset-ul tău are o dimensiune de un terabyte, iar mașina ta are doar șaisprezece gigabytes de RAM? Răspunsul stă în modul în care faci fetch la acele date. Acest episod acoperă Datasets și Data Handling. Logica de procesare a datelor poate deveni rapid haotică. Dacă amesteci codul de citire, decodare și formatare a fișierelor direct în training loop-ul modelului tău, proiectul devine fragil și greu de întreținut. PyTorch te încurajează să decuplezi aceste responsabilități. Vrei ca pregătirea datelor să fie complet separată de algoritmul de training. Pentru a realiza asta, PyTorch oferă o primitivă numită Dataset, aflată în modulul torch dot utils dot data. Clasa Dataset acționează ca un wrapper standardizat peste datele tale raw. Pentru a gestiona propriile tale fișiere specifice, creezi o clasă custom care moștenește din această primitivă. Când construiești un dataset custom, trebuie să implementezi trei metode specifice. Acestea sunt init, len și getitem. Metoda init rulează exact o singură dată atunci când creezi obiectul de tip dataset. Aici îți configurezi directoarele și path-urile. O greșeală frecventă pe care o fac începătorii este să încerce să încarce toate datele efective în memorie chiar aici. Nu face asta. Dacă ai cincizeci de mii de imagini high-resolution, citirea tuturor în memorie în timpul inițializării îți va da crash imediat la mașină. În schimb, folosește init pentru a încărca un index lightweight. De exemplu, ai putea citi un fișier CSV care conține numele fișierelor de imagine într-o coloană și label-urile de text corespunzătoare în alta. Tu doar construiești harta, nu ții tot teritoriul. Urmează metoda len. Aceasta returnează pur și simplu numărul total de sample-uri din dataset-ul tău. Dacă fișierul tău CSV are cincizeci de mii de rânduri, această metodă returnează numărul cincizeci de mii. Sistemul se bazează pe asta pentru a ști limitele absolute ale datelor tale disponibile, astfel încât să nu ceară un index care nu există. Munca grea se face în metoda getitem. Această funcție este gândită să încarce și să returneze un singur sample la un anumit index cerut. Când sistemul are nevoie de sample-ul numărul patruzeci și doi, apelează getitem și îi pasează acel număr. Codul tău caută rândul patruzeci și doi în CSV-ul pe care l-ai încărcat mai devreme. Citește string-ul cu path-ul fișierului din acel rând. Apoi, și doar atunci, accesează discul, găsește fișierul și decodează pixelii efectivi ai imaginii în memorie. Ia label-ul din același rând din CSV și returnează imaginea și label-ul împreună, ca un tuple. Această tehnică se numește lazy loading. Consumi memorie doar pentru bucata specifică de date de care ai nevoie, exact în momentul în care ești gata să o procesezi. Izolând această logică în metoda getitem, codul tău de training nu trebuie să știe niciodată dacă datele au venit de pe un hard disk local, un network stream sau o bază de date complexă. El doar cere un index și primește un output standardizat. Separarea mecanismului prin care se face fetch la date de modul în care acestea sunt consumate este fundamentul unui cod de machine learning scalabil. Dacă ți se par utile aceste episoade și vrei să susții emisiunea, poți căuta DevStoriesEU pe Patreon. Asta e tot pentru acest episod. Mersi de ascultare și continuă să construiești!
7

DataLoaders și batching

3m 41s

Eliberează întreaga viteză a hardware-ului tău împachetând Datasets în DataLoaders. Învață cum să faci batching, shuffling și multiprocessing pe fluxurile tale de date.

Descarcă
Salut, sunt Alex de la DEV STORIES DOT EU. PyTorch Fundamentals, episodul 7 din 18. GPU-urile sunt incredibil de rapide, dar vor sta complet idle dacă CPU-ul tău nu le poate furniza date suficient de repede. Training loop-urile devin adesea un bottleneck nu din cauza matematicii, ci la încărcarea următorului set de fișiere de pe disk. Soluția la această problemă este separarea extragerii datelor de execuția modelului folosind DataLoaders și Batching. Este ușor să confunzi rolurile unui Dataset și ale unui DataLoader. Un Dataset are exact un singur job: să facă fetch la un singur item și la label-ul său. Nu știe nimic despre procesul mai amplu de training. DataLoader-ul este un wrapper în jurul acelui Dataset. Acționează ca un manager responsabil cu organizarea acelor itemi individuali în grupuri, randomizarea ordinii lor și folosirea mai multor procese pentru a-i încărca eficient. În timpul training-ului, modelele se uită rareori la un singur data point pe rând. Își actualizează weights-urile interne pe baza unui grup de itemi evaluați simultan, cunoscut sub numele de minibatch. Această abordare face procesul de training mai stabil și profită din plin de puterea de procesare paralelă a hardware-ului. Pentru a construi manual un minibatch, ar trebui să scrii un loop ca să extragi sample-uri individuale, să le dai stack într-o structură de tip tensor mai mare și să gestionezi edge case-urile, cum ar fi ca ultimul batch să fie mai mic decât restul. DataLoader-ul gestionează toate astea automat. Inițializezi un DataLoader pasându-i obiectul tău Dataset și un parametru numit batch size. Dacă setezi batch size-ul la 64, DataLoader-ul va extrage 64 de itemi distincți din Dataset, îi va consolida într-un singur tensor și îi va servi pe toți deodată. În codul tău, DataLoader-ul se comportă ca un iterable Python standard. Faci un loop peste el. De fiecare dată când loop-ul avansează, DataLoader-ul face yield la următorul batch complet de date și la batch-ul corespunzător de label-uri. De asemenea, îi pasezi un parametru de shuffle. Dacă o rețea neuronală procesează datele de training în exact aceeași secvență de fiecare dată, s-ar putea să memoreze acea secvență specifică în loc să învețe feature-urile reale. Setarea parametrului shuffle pe true îi spune DataLoader-ului să randomizeze ordinea elementelor din dataset la începutul fiecărui epoch. Odată ce s-a făcut yield la fiecare batch și dataset-ul este epuizat, loop-ul se termină. Data viitoare când iterezi peste DataLoader, acesta generează o secvență randomizată complet nouă. Asta e partea care contează. DataLoader-ul acceptă, de asemenea, un parametru pentru numărul de procese worker. Când folosești mai mulți workeri, DataLoader-ul pornește procese CPU în background pentru a aduce datele. Imaginează-ți că dai acele 64 de imagini unei rețele neuronale. În timp ce GPU-ul tău este ocupat să calculeze gradienții pentru batch-ul curent, workerii CPU din background citesc, decodează și fac stack simultan la următoarele 64 de imagini. Până când GPU-ul își termină pasul matematic curent, următorul batch de date așteaptă deja în memorie. GPU-ul nu stă niciodată idle. Un training loop de înaltă performanță izolează realitatea lentă și imprevizibilă a operațiunilor pe disk de realitatea rapidă și structurată a training-ului de model. DataLoader-ul oferă această izolare, transformând o colecție de fișiere standalone într-un pipeline continuu și paralelizat de minibatch-uri. Asta e tot pentru acest episod. Mulțumesc că m-ai ascultat și continuă să construiești!
8

Transformări de date

4m 07s

Descoperă cum să preprocesezi datele brute din mers înainte ca acestea să ajungă în rețeaua ta neuronală. Acoperim transformările torchvision precum ToTensor și funcțiile Lambda personalizate.

Descarcă
Salut, sunt Alex de la DEV STORIES DOT EU. Fundamentele PyTorch, episodul 8 din 18. Rețelele neuronale calculează doar numere, dar datele din lumea reală sunt de obicei o colecție dezordonată de fișiere de imagine brute și categorii de text. Dacă scrii loop-uri manuale pentru a converti fiecare imagine și label înainte de a începe training-ul, codul tău va deveni rapid un haos fragil și greu de citit. Data Transformations sunt mecanismul care rezolvă această problemă, convertind automat datele brute într-un format model-ready exact atunci când este nevoie. Datele rareori ajung pregătite pentru machine learning. Trebuie să le manipulezi într-un format de tensor specific înainte de a le introduce în rețea. PyTorch gestionează acest lucru în mod curat, aplicând transforms on the fly în timpul procesului de data loading. Când inițializezi un dataset, în special în librării precum torchvision, definești aceste modificări folosind două argumente specifice. Folosești argumentul transform exclusiv pentru input features, cum ar fi imaginile brute. Folosești argumentul target transform exclusiv pentru label-uri. Este esențial să le păstrezi separate, deoarece funcționează independent pe jumătăți diferite ale datelor tale. Să analizăm mai întâi input features. Să presupunem că ai un dataset de imagini PIL brute. O rețea neuronală nu poate citi direct un obiect de imagine PIL. Pentru a remedia acest lucru, pasezi un transform torchvision built-in numit ToTensor în argumentul transform. Când dataset-ul încarcă o imagine, ToTensor execută automat doi pași. În primul rând, convertește imaginea PIL într-un float tensor PyTorch. În al doilea rând, scalează valorile intensității pixelilor. Pixelii imaginii brute variază în general de la zero la două sute cincizeci și cinci. Operația ToTensor normalizează aceste valori la un interval floating-point între zero și unu. Dataset-ul aplică această operație strict pe măsură ce fiecare imagine este preluată. Asta acoperă input-urile, dar ce se întâmplă cu output-urile? Label-urile dataset-ului tău ar putea fi simple integers care reprezintă diferite categorii. De exemplu, numărul trei ar putea însemna un câine. Dar pentru a calcula loss-ul în timpul training-ului, modelul tău necesită adesea ca aceste label-uri să fie vectori one-hot encoded, mai degrabă decât integers singulari. Asta înseamnă că ai nevoie de un array în care toate valorile sunt zero, cu excepția indexului care reprezintă clasa corectă, care este setat la unu. Pentru a gestiona o custom logic de acest gen, PyTorch oferă Lambda transforms. Un Lambda transform face wrap la orice funcție user-defined, astfel încât să poată fi aplicată în timpul data loading-ului. Scrii o funcție scurtă care ia label-ul tău integer ca input. În interiorul acelei funcții, creezi un tensor de zerouri care se potrivește cu numărul total de categorii din dataset-ul tău. Apoi, folosești o operațiune PyTorch internă pentru a face scatter la o valoare de unu în indexul specific care corespunde label-ului tău integer. Pasezi această funcție custom într-un Lambda transform, și apoi îl atribui argumentului target transform al dataset-ului tău. Acest lucru creează un pipeline extrem de eficient. Un worker thread extrage o singură înregistrare brută de pe disc. Imaginea atinge argumentul transform, trece prin ToTensor și apare ca un float tensor normalizat. Simultan, categoria integer atinge argumentul target transform, execută funcția ta custom Lambda și se transformă într-un vector one-hot encoded. Ambele părți sunt acum formatate matematic și predate direct modelului tău. Adevărata putere a acestei arhitecturi este separation of concerns. Prin atașarea acestor data transformations direct la definiția dataset-ului, training loop-ul tău propriu-zis rămâne complet orb la realitatea dezordonată a fișierelor brute. Asta e tot pentru acest episod. Mulțumesc pentru audiție și continuă să construiești!
9

Proiectarea rețelelor cu nn.Module

4m 01s

Explorează planul structural al fiecărei rețele neuronale PyTorch. Învață cum să creezi subclase pentru nn.Module, să definești straturile la inițializare și să direcționezi datele în forward pass.

Descarcă
Salut, sunt Alex de la DEV STORIES DOT EU. Fundamentele PyTorch, episodul 9 din 18. Fiecare rețea neuronală din PyTorch, de la un image classifier de bază până la un language model masiv, are exact același blueprint la bază. Dacă nu înțelegi cum organizează acest blueprint datele și logica, vei ajunge să te lupți cu framework-ul la fiecare pas. Proiectarea rețelelor cu nn.Module este felul în care ajungi să stăpânești această structură. nn.Module este clasa de bază pentru toate componentele de rețea neuronală din PyTorch. Acționează ca un container universal. Când construiești un model custom, creezi o clasă care moștenește din nn.Module. Această moștenire oferă automat clasei tale capacitatea de a-și urmări propriii parametri, de a calcula gradienți și de a se integra fără probleme cu restul ecosistemului PyTorch. De asemenea, permite arhitecturi nested. Poți plasa module în interiorul altor module, creând un arbore de layere pe care modulul părinte îl urmărește și îl gestionează ca pe o singură unitate. Gândește-te la configurarea scheletului gol pentru un image classifier complet nou. Construirea acestui schelet necesită definirea a două metode specifice: metoda initialize și metoda forward. PyTorch impune un separation of concerns strict între aceste două etape. Prima este metoda initialize. Gândește-te la ea ca la inventarul tău. Când clasa este instanțiată, această metodă rulează exact o singură dată. O folosești pentru a declara toate layerele individuale și operațiile matematice de care modelul tău va avea nevoie în cele din urmă. Nu procesezi date reale aici. Pur și simplu iei componente structurale de pe raft, le configurezi shape-urile de input și output, și le salvezi ca variabile interne în cadrul clasei tale. Următoarea este metoda forward. Aceasta este linia ta de asamblare activă. Metoda forward preia un tensor de input și dictează exact cum se deplasează acesta prin inventarul pe care tocmai l-ai declarat. Scrii secvența de operații pas cu pas. Iei tensorul imaginii de input, îl pasezi unei operațiuni de flatten, introduci rezultatul într-o serie de dense layers și, în final, returnezi predicțiile de output. Fiecare model custom trebuie să definească această metodă forward pentru a stabili data flow-ul. Asta ne aduce la o capcană comună. Pentru că ai scris explicit logica de data flow în interiorul unei metode numite forward, instinctul natural este să pasezi datele apelând model punct forward. Nu face asta. Trebuie să apelezi modelul direct, ca și cum ar fi o funcție obișnuită, pasând input-ul direct către obiectul model instanțiat. În spate, executarea directă a obiectului model declanșează mai multe hook-uri critice de background de care PyTorch are nevoie pentru a gestiona starea rețelei. Apelarea directă a metodei forward face bypass la aceste hook-uri și va cauza un comportament neașteptat în timpul training loop-ului tău. Odată ce clasa ta este definită și ai creat un obiect model, ai o rețea funcțională. Totuși, by default, PyTorch creează acest obiect și toate weights-urile sale interne în memoria CPU a sistemului tău. Pentru a face training la viteze realiste, trebuie să trimiți această arhitectură către un accelerator. Realizezi asta verificând dacă este disponibil un GPU CUDA sau un silicon specializat, cum ar fi MPS de la Apple, și atribuind acel hardware target unei variabile device. Apoi, apelezi metoda to pe modelul tău, pasând acea variabilă device. Această singură comandă mută imediat toți parametrii inițializați ai modelului din memoria standard în memoria de mare viteză a acceleratorului tău hardware. Trăsătura definitorie a nn.Module este modul în care impune o graniță arhitecturală clară între componentele statice pe care modelul tău le deține în memorie și calea dinamică pe care datele tale o parcurg pentru a fi procesate. Mulțumesc pentru audiție. Aveți grijă de voi, tuturor.
10

Straturi Linear și activări

4m 02s

Privește în interiorul rețelei neuronale. Analizăm modulul nn.Linear și explicăm de ce funcțiile de activare neliniare precum ReLU sunt esențiale din punct de vedere matematic.

Descarcă
Salut, sunt Alex de la DEV STORIES DOT EU. PyTorch Fundamentals, episodul 10 din 18. Dacă stivuiești o sută de layere de rețea neuronală fără un anumit truc matematic, toată rețeaua colapsează matematic într-o singură linie dreaptă. Vinovată este algebra liniară, iar soluția necesită înțelegerea conceptelor de Linear Layers și Activations. O rețea neuronală este, la bază, o secvență de operații matematice pe tensori. Cea mai comună operație este un linear layer, definit în PyTorch ca nn.Linear. Acest modul aplică o transformare afină datelor de intrare. Conține doi tensori interni pe care îi învață în timp: weights și biases. Când datele trec prin el, layer-ul înmulțește input-ul cu matricea de weights și adaugă bias-ul. Ia o imagine standard grayscale de 28 pe 28 de pixeli. Înainte ca un linear layer să o poată procesa, trebuie să dai flatten la grila bidimensională într-un array unidimensional de 784 de numere. Treci acel array de 784 de valori printr-un layer nn.Linear configurat să aibă un output de 512 features. În spate, PyTorch creează o matrice de weights care mapează cele 784 de input-uri la 512 output-uri. Înmulțește valorile pixelilor tăi cu aceste weights, le adună, adaugă un termen de bias pentru a shifta rezultatul și dă ca output 512 numere noi. În timpul procesului de training, PyTorch actualizează continuu aceste weights și biases. Ele formează memoria propriu-zisă a modelului tău. Ai putea presupune că o rețea neuronală deep este doar o secvență lungă de astfel de linear layers stivuite unul după altul. Aici e partea care contează. Dacă pui în chain mai multe operații nn.Linear fără nimic între ele, matematica se simplifică. Matricea A înmulțită cu matricea B este doar o altă matrice, C. Stivuirea a zece linear layers are exact aceeași capacitate matematică precum calcularea unui singur linear layer. Rețeaua ta deep este redusă la o ecuație liniară plată, complet incapabilă să învețe pattern-uri complexe, din lumea reală. Pentru a opri acest colaps matematic, introduci o non-liniaritate imediat după acel linear layer. Acestea se numesc activation functions. Cea mai utilizată activare în PyTorch este nn.ReLU, care vine de la Rectified Linear Unit. După ce acel linear layer își calculează cele 512 output-uri, treci acel tensor direct printr-o funcție ReLU. Logica ReLU este brutal de simplă. Se uită la fiecare număr din tensor. Dacă un număr este mai mic decât zero, ReLU îl schimbă exact în zero. Dacă un număr este zero sau pozitiv, ReLU îl lasă complet neschimbat. Acel singur prag la zero distruge liniaritatea. Împiedică următorul linear layer să se îmbine matematic cu cel precedent. Forțând valorile negative la zero, ReLU creează, de asemenea, sparse representations. Asta înseamnă că doar un subset specific de neuroni se activează pentru un anumit input, făcând rețeaua extrem de eficientă. Data flow-ul este consistent. Imaginea ta flattened intră în linear layer, este transformată de weights și biases, și apoi ajunge la activarea ReLU, unde output-urile negative sunt eliminate. Apoi, poți trece în siguranță acest tensor activat printr-un al doilea linear layer pentru a extrage pattern-uri mai deep și mai abstracte. Un linear layer determină câtă importanță matematică să acorde fiecărui input, dar funcția de activare oferă rețelei geometria reală necesară pentru a învăța formele imprevizibile ale datelor reale. Mersi că ai petrecut câteva minute cu mine. Până data viitoare, numai bine.
11

Containerul nn.Sequential

3m 27s

Eficientizează-ți codul PyTorch folosind containerul nn.Sequential. Învață cum să îmbini straturile curat și să inspectezi parametrii modelului tău.

Descarcă
Salut, sunt Alex de la DEV STORIES DOT EU. Fundamentele PyTorch, episodul 11 din 18. Scrierea de metode forward custom pentru fiecare rețea neurală devine rapid plictisitoare atunci când doar stivuiești layere standard. Nu trebuie întotdeauna să rutezi manual datele de la o funcție la alta. Uneori trebuie doar să prinzi layerele între ele ca pe niște piese de LEGO. Exact asta face containerul nn.Sequential. Containerul nn.Sequential este un pipeline ordonat de module de rețea neurală. Când pasezi date în acest container, datele curg prin modulele interne exact în secvența în care au fost adăugate. Gândește-te la asamblarea unui Multilayer Perceptron standard cu trei layere. În mod normal, ți-ai defini layerele liniare și funcțiile de activare într-o metodă de inițializare, apoi ai scrie o metodă forward custom. În acea metodă forward, ai lua explicit inputul, l-ai pasa layerului unu, l-ai trece printr-o activare ReLU, ai pasa rezultatul către layerul doi, ai aplica un alt ReLU și l-ai da mai departe către layerul final. Cu Sequential, sari complet peste metoda forward. Instanțiezi containerul și pasezi modulele direct în el ca argumente. Îi dai un modul Linear, urmat de un modul ReLU, un al doilea modul Linear, un alt ReLU și un modul Linear final. PyTorch gestionează automat rutarea datelor. Outputul primului modul devine instantaneu inputul celui de-al doilea, continuând automat pe chain. Acest container este extrem de eficient, dar are o limitare strictă. Este strict pentru un data flow liniar, în linie dreaptă. Nu poate gestiona arhitecturi complexe care necesită branching, inputuri multiple sau skip connections. Dacă construiești ceva de genul unui Residual Network, unde datele ocolesc anumite layere și sunt adăugate înapoi ulterior, Sequential nu va funcționa. Pentru orice topologie neliniară, tot trebuie să scrii un modul custom cu o metodă forward dedicată. După ce ai înlănțuit layerele, adesea trebuie să inspectezi ce tocmai ai construit. Fiecare layer din containerul tău Sequential este o subclasă a nn.Module, ceea ce înseamnă că PyTorch înregistrează și face track automat la tot state-ul subiacent. Pentru a vizualiza acest state, folosești metoda named_parameters. Apelarea metodei named_parameters pe modelul tău oferă un iterator peste toate weight-urile și bias-urile din interior. Fiecare element returnat este o pereche simplă: numele parametrului și tensorul parametrului în sine. Deoarece ai folosit un container Sequential fără a denumi explicit layerele, PyTorch generează nume numerice pe baza indexului lor. Vei vedea nume precum zero punct weight pentru weight-urile din primul layer liniar, sau zero punct bias pentru termenii săi de bias. Tensorul însoțitor conține valorile numerice reale, shape-ul matricei și dacă necesită calcul de gradient. Să faci un loop prin named_parameters este modalitatea standard de a-ți verifica arhitectura. Poți printa rapid dimensiunea fiecărei matrice de weight-uri pentru a confirma că dimensiunile de input și output se aliniază perfect pe întregul chain, înainte să începi vreodată să treci date reale prin sistem. Adevărata putere a containerului Sequential combinată cu parameter tracking este că PyTorch absoarbe partea de bookkeeping pentru state management și data routing, lăsându-te să te concentrezi în întregime pe shape-ul rețelei tale. Asta e tot pentru acest episod. Ne auzim data viitoare!
12

Înțelegerea funcțiilor Loss

3m 39s

Înainte ca un AI să poată învăța, trebuie să-și măsoare greșelile. Aprofundăm funcțiile loss din PyTorch, comparând CrossEntropyLoss pentru clasificare și MSELoss pentru regresie.

Descarcă
Salut, sunt Alex de la DEV STORIES DOT EU. Fundamentele PyTorch, episodul 12 din 18. Pentru a învăța o rețea neuronală să fie corectă, trebuie mai întâi să măsori riguros cât de mult greșește. Dacă nu poți cuantifica eșecul, modelul tău nu poate învăța din el. Asta ne aduce la înțelegerea conceptului de Loss Functions. Când o rețea neantrenată procesează date, output-ul ei este în esență o estimare. Un loss function evaluează acea estimare. Măsoară gradul de diferență dintre rezultatul produs de model și adevărul absolut al valorii target. Output-ul unui loss function este întotdeauna un singur număr scalar. Întregul tău proces de training există pentru a împinge acel număr cât mai aproape de zero. Deoarece diferite task-uri de machine learning au definiții diferite pentru a fi greșite, PyTorch oferă mai multe loss functions. Dacă construiești un model de regression pentru a prezice o valoare continuă, cum ar fi temperatura de mâine, măsori distanța dintre estimarea ta și temperatura reală. Pentru asta, folosești Mean Square Error, care în PyTorch se numește nn.MSELoss. Dar la classification e diferit. Să presupunem că ai un model care clasifică imagini cu haine în zece categorii de fashion. Modelul analizează imaginea unei haine și dă ca output zece raw scores, câte unul pentru fiecare categorie posibilă. Aceste raw scores, nenormalizate, se numesc logits. Răspunsul adevărat este doar un singur integer, care reprezintă clasa corectă. Nu poți pur și simplu să scazi un class index dintr-un raw score. În schimb, ai nevoie de o funcție care penalizează modelul pentru că dă scoruri mici clasei corecte și scoruri mari claselor greșite. Pentru classification, tool-ul standard este nn.CrossEntropyLoss. Inițializezi loss function-ul, îi pasezi cele zece raw logits din modelul tău alături de integer label-ul corect, iar ea îți returnează penalizarea scalară. Asta e partea care contează. Există o capcană uriașă aici pentru developeri. În multe manuale de machine learning, o rețea de classification se termină cu un softmax layer. Softmax forțează acele raw logits într-o distribuție de probabilitate ordonată în care toate scorurile adunate dau exact unu. Din această cauză, developerii adaugă adesea manual o operație softmax chiar la finalul modelului lor PyTorch. Dacă folosești nn.CrossEntropyLoss, să faci asta este o greșeală. În PyTorch, nn.CrossEntropyLoss aplică automat intern o funcție LogSoftmax înainte de a calcula negative log likelihood. Este construită să accepte direct raw logits, nenormalizate. Dacă modelul tău dă ca output probabilități pentru că ai aplicat deja softmax, să le pasezi către nn.CrossEntropyLoss înseamnă că aplici matematica de două ori. Asta îți comprimă acei gradients, încetinește drastic procesul de training și distruge capacitatea modelului tău de a învăța eficient. Regula de reținut este că rețeaua ta neuronală ar trebui să dea ca output doar raw numbers. Păstrează output-urile modelului raw, pasează-le direct către nn.CrossEntropyLoss și lasă PyTorch să facă munca grea de a transforma acele logits într-o penalizare semnificativă. Mulțumesc pentru audiție, happy coding tuturor!
13

Optimizers și Gradient Descent

3m 47s

Explorează modul în care optimizer-ul actualizează ponderile modelului pentru a reduce eroarea. Învață dansul crucial în trei pași: zero_grad(), backward() și step().

Descarcă
Salut, sunt Alex de la DEV STORIES DOT EU. Fundamentele PyTorch, episodul 13 din 18. Cel mai frecvent bug în training-ul PyTorch nu sunt datele proaste sau o arhitectură greșită. Este faptul că uiți să cureți calculele matematice vechi, ceea ce face ca rețeaua să scape de sub control. Astăzi vorbim despre optimizers și Gradient Descent, care gestionează exact modul în care modelul tău învață din greșeli. Modelul tău face o predicție, iar tu calculezi loss-ul pentru a vedea cât de mare a fost abaterea. Acum trebuie să ajustezi weights-urile interne ale rețelei neuronale pentru a face următoarea predicție puțin mai precisă. Acest proces de ajustare a parametrilor pentru a minimiza loss-ul se numește optimization. Optimizer-ul este algoritmul specific care guvernează modul în care se modifică acele weights. Pentru a configura un optimizer, trebuie să îi dai două lucruri. În primul rând, îi pasezi un iterable care conține parametrii modelului pe care vrei să-i ajusteze. În al doilea rând, îi oferi un learning rate. Learning rate-ul este un hyperparameter fundamental care controlează magnitudinea modificărilor aplicate pe weights. Dacă learning rate-ul este prea mic, optimizer-ul face pași microscopici, ceea ce face ca training-ul să fie dureros de lent. Dacă learning rate-ul este prea mare, optimizer-ul sare peste valorile optime, ducând la un comportament haotic și imprevizibil. Un algoritm standard pentru acest task este Stochastic Gradient Descent, sau SGD. Acesta evaluează panta funcției de loss și face un step în direcția opusă pentru a coborî spre cea mai mică eroare posibilă. După ce inițializezi optimizer-ul SGD cu parametrii tăi și learning rate-ul, update-urile propriu-zise au loc într-o secvență strictă, în trei pași. Pasul unu este să faci curat. Apelezi comanda zero grad pe optimizer. Aici se ascunde acel bug comun. PyTorch acumulează gradienții by default. Când calculează gradienți noi, nu îi suprascrie pe cei vechi; pur și simplu adaugă noile numere la totalurile existente. Dacă sari peste acest step de zero grad, calculele din batch-ul curent sunt corupte de numerele rămase din batch-ul anterior. Resetează mereu gradienții la zero înainte de a face orice altceva. Pasul doi este calcularea noilor gradienți. Iei valoarea de loss calculată și apelezi comanda backward pe ea. Asta declanșează backpropagation. PyTorch parcurge înapoi arhitectura rețelei. Calculează derivata loss-ului în raport cu fiecare parametru în parte. În esență, determină exact cât de mult a contribuit fiecare weight individual la eroarea generală. Acești gradienți calculați sunt stocați direct în obiectele parametrilor. Pasul trei este aplicarea corecției. Apelezi comanda step pe optimizer. Optimizer-ul analizează gradienții stocați în fiecare parametru în timpul backward pass-ului. Înmulțește acești gradienți cu learning rate-ul pentru a calcula dimensiunea exactă a ajustării, apoi face update la weights-urile reale din memorie. Acest ciclu se repetă pentru fiecare batch. Zero la gradienți, calculează backward loss-ul, dă step la optimizer. Detaliul esențial de reținut este că optimizer-ul face update doar la parametrii care i-au fost dați explicit în timpul setup-ului. Dacă trebuie să dai freeze la un layer din rețea, pur și simplu îi excluzi parametrii atunci când inițializezi optimizer-ul, iar acele weights vor rămâne fixe permanent. Apropo, dacă vrei să susții emisiunea, poți căuta DevStoriesEU pe Patreon. Mulțumesc pentru audiție. Aveți grijă de voi, tuturor.
15

Validare și inferență

3m 53s

Evaluează-ți modelul în mod obiectiv. Învață cum să-ți treci rețeaua în modul de evaluare, să îngheți gradienții și să extragi predicții precise pe date nevăzute.

Descarcă
Salut, sunt Alex de la DEV STORIES DOT EU. PyTorch Fundamentals, episodul 15 din 18. Modelul tău s-ar putea să funcționeze perfect pe datele de training, dar singurul test adevărat pentru un AI este modul în care gestionează necunoscutul. Dacă te bazezi exclusiv pe feedback-ul pe care îl vede optimizer-ul tău, s-ar putea să construiești doar o bancă de memorie foarte scumpă. Pentru a vedea dacă modelul tău chiar generalizează în lumea reală, ai nevoie de validare și inferență. În timpul fazei de training, te uiți la training loss. Acest număr există pentru a ghida optimizer-ul intern. Forțează modelul să își ajusteze weight-urile până când eroarea matematică se micșorează. Dar un training loss scăzut nu înseamnă că ai un model bun. Înseamnă pur și simplu că modelul este foarte bun la a răspunde la întrebări pe care le-a văzut deja. Validation accuracy este o metrică complet separată care le spune oamenilor dacă modelul poate face predicții corecte pe date complet noi. Pentru a obține această metrică, trebuie să rulezi un validation loop pe un test dataset dedicat. Înainte de a introduce o singură bucată de test data în rețea, trebuie să schimbi starea modelului. Faci asta apelând metoda eval pe obiectul modelului tău. Apelarea metodei eval trece rețeaua în evaluation mode. Anumite layere interne se comportă diferit în timpul training-ului față de cum o fac în timpul inferenței. Apelarea metodei eval le forțează să își blocheze comportamentul, astfel încât predicțiile tale să rămână consistente. Dacă sari peste acest pas, rezultatele testelor tale vor fi fundamental nesigure. Asta acoperă starea modelului. Apoi, trebuie să controlezi motorul în sine prin dezactivarea gradient tracking-ului. Faci asta prin încadrarea codului tău de validare într-un context manager no grad. În timpul training-ului, PyTorch construiește constant un computational graph în memorie, stocând istoricul fiecărei operațiuni, astfel încât să poată calcula gradienții mai târziu. Într-un validation loop, ai terminat complet cu training-ul. Nu vrei să actualizezi weight-urile. Blocul no grad îi spune lui PyTorch să oprească tracking-ul istoricului. Asta este partea care contează. Dezactivarea tracking-ului previne actualizările accidentale ale modelului tău, dar eliberează și o cantitate masivă de memorie și accelerează drastic calculul. În interiorul acelui bloc no grad, logica efectivă este simplă. Iterezi prin test dataset-ul tău în batch-uri. Pentru fiecare batch, treci datele de input prin model. Modelul calculează forward pass-ul și returnează predicțiile raw. Dacă faci clasificare, modelul nu scoate un text label clar. În schimb, generează o listă de scoruri numerice pentru absolut fiecare categorie pe care o cunoaște. Pentru a afla ce categorie a ales de fapt modelul, ai nevoie de funcția argmax. Argmax se uită la lista de scoruri raw și găsește cel mai mare număr. Apoi returnează poziția de index a acelui cel mai mare scor. Acel index este class prediction-ul tău ales. Odată ce ai predicțiile modelului, le compari direct cu label-urile reale furnizate de test dataset. Numeri exact câte predicții se potrivesc cu label-urile reale. Păstrezi un total curent al acestor potriviri corecte din toate batch-urile. Când loop-ul se termină, împarți numărul total de predicții corecte la numărul total de elemente din test dataset. Rezultatul este procentul tău final de accuracy. Training loop-ul îți forțează modelul să facă fit pe datele istorice, dar constrângerile stricte ale validation loop-ului dovedesc dacă acel model este de fapt util. Mulțumesc că m-ați ascultat. Aveți grijă de voi toți.
16

Salvarea și încărcarea modelelor

3m 35s

Nu-ți pierde progresul obținut cu greu! Discutăm cele mai sigure modalități de a serializa ponderile modelului tău folosind state_dict și de a le încărca înapoi în siguranță.

Descarcă
Salut, sunt Alex de la DEV STORIES DOT EU. PyTorch Fundamentals, episodul 16 din 18. Antrenarea unui image classifier timp de cincizeci de epoci poate dura săptămâni și poate consuma mii de dolari în compute. Totuși, în momentul în care scriptul tău Python își termină execuția, toate acele pattern-uri obținute cu greu dispar complet din memorie. Pentru a proteja această investiție, ai nevoie de o modalitate de a-ți persista progresul pe disk. Exact asta rezolvă salvarea și încărcarea modelelor. În interiorul fiecărui model PyTorch există un dicționar intern numit state dict. Acest dicționar mapează fiecare layer al rețelei tale la tensorii parametrilor corespunzători. Acesta conține weights-urile și biases-urile reale pe care modelul tău le-a învățat în timpul antrenamentului. Structura modelului este doar cod, dar state dict-ul este inteligența. Pentru a persista modelul, extragi acest dicționar și îl scrii într-un fișier. Faci asta folosind o funcție numită torch dot save. Îi pasezi două lucruri. În primul rând, state dict-ul modelului tău. În al doilea rând, file path-ul unde vrei să-l stochezi, care folosește în mod tradițional o extensie dot pth. Într-o singură linie de cod, run-ul tău de antrenament de cincizeci de epoci este stocat în siguranță pe hard drive ca un singur fișier care nu conține decât date raw de tensori. S-ar putea să vezi exemple online care sar complet peste state dict și pasează obiectul modelului direct către torch dot save. Nu face asta. Salvarea întregului model se bazează foarte mult pe serializarea pickle din Python. Asta leagă fișierul salvat de structura exactă de directoare și de definițiile de clasă prezente atunci când a fost creat fișierul. Dacă ulterior îți refactorizezi codul sau muți un fișier, modelul va da fail la încărcare. Să rămâi la state dict este mult mai sigur și semnificativ mai robust, pentru că salvezi doar datele, nu și codul. Când vine momentul să dai deploy la classifier pentru inference în producție, trebuie să inversezi procesul. Pentru că ai salvat doar weights-urile, PyTorch trebuie să știe cum arată structura rețelei. Începi prin a instanția o versiune complet blank a clasei modelului tău. Asta îți oferă shell-ul arhitectural. Apoi, apelezi torch dot load și îi dai file path-ul tău pentru a citi dicționarul înapoi în memorie. Când apelezi torch dot load, există un best practice modern crucial pe care trebuie să-l urmezi. Pasează mereu argumentul weights only setat pe true. Fișierele pickle din Python pot conține cod executabil arbitrar. Dacă descarci un model pre-trained de pe internet și îl încarci orbește, ar putea rula scripturi malițioase pe mașina ta. Setarea weights only pe true restricționează loader-ul să deserializeze doar tensori standard PyTorch, menținându-ți sistemul în siguranță. În cele din urmă, cu modelul tău blank pregătit și dicționarul securizat încărcat, apelezi load state dict pe model și îi pasezi dicționarul. PyTorch mapează weights-urile încărcate la layer-ele corespunzătoare din shell-ul blank. Modelul tău este acum complet restaurat și gata să facă predicții. Nu-ți lăsa niciodată investiția de antrenament pe seama unui obiect serializat fragil; separă mereu arhitectura din codul tău de parametrii învățați de pe disk. Mersi că ai stat cu mine. Sper că ai învățat ceva nou.
17

Accelerarea vitezei cu torch.compile

3m 18s

Deblochează funcția definitorie a PyTorch 2.0. Învață cum decoratorul torch.compile face JIT-compiles codului tău Python în kernel-uri optimizate pentru accelerări masive.

Descarcă
Salut, sunt Alex de la DEV STORIES DOT EU. Fundamentele PyTorch, episodul 17 din 18. Petreci săptămâni întregi ajustând arhitectura unui model pentru un speedup de cinci la sută. Dar, de multe ori, adevăratul bottleneck nu este matematica. Ci Python în sine, și transferurile constante și ineficiente de date dintre memorie și GPU. Rezolvarea acestei probleme nu necesită rescrierea întregului codebase. Astăzi, vedem cum să creștem masiv viteza cu torch dot compile. Introdus în PyTorch 2.0, acest feature îți trece codul de la execuția standard la un workflow extrem de optimizat. O presupunere comună e că accelerarea PyTorch pentru producție înseamnă scrierea de kernel-uri C plus plus custom sau schimbarea fundamentală a arhitecturii. Nu este cazul. Nu schimbi nimic în interiorul modelului tău. Pur și simplu faci wrap la modelul existent într-un singur function call. Pentru a înțelege de ce asta creează un salt masiv de performanță, trebuie să te uiți la cum rulează PyTorch în mod normal. PyTorch standard funcționează în eager mode. Execută exact ce îi spui, exact când îi ceri, câte o operațiune pe rând. Dacă codul tău îi spune GPU-ului să adune doi tensori, să înmulțească rezultatul cu un alt tensor și să aplice o funcție de activare, eager mode le tratează ca pe trei evenimente izolate. Pentru fiecare pas, GPU-ul citește datele din memoria sa principală, face calculele și scrie rezultatul intermediar înapoi. Memory bandwidth-ul GPU-ului este limitat. Acest transfer constant de date durează mult mai mult decât calculele propriu-zise. Când îți treci modelul prin funcția compile, PyTorch schimbă tactica. Folosește un tool intern numit TorchDynamo pentru a-ți captura operațiunile într-un computation graph înainte de a le executa. Analizând secvența în ansamblu, găsește ineficiențele. Apoi, folosește un backend compiler pentru a genera o versiune nouă, puternic optimizată, a operațiunilor tale. Tehnica principală pe care o folosește este kernel fusion. În loc să citească și să scrie în memorie de trei ori separat, codul compilat îmbină acești pași. GPU-ul citește datele o singură dată, le păstrează în cele mai rapide registre interne, efectuează adunarea, înmulțirea și activarea una după alta, și apoi scrie rezultatul final o singură dată. Acel overhead din Python dispare, iar bottleneck-ul de memorie este evitat. Implementarea este simplă. Instanțiezi modelul ca de obicei. Apoi, apelezi torch dot compile, îi dai ca parametru modelul, și atribui output-ul unei noi variabile. Îți rutezi datele prin această versiune compilată. Dacă TorchDynamo întâlnește un construct Python obscur pe care nu-l poate optimiza în siguranță, nu-ți strică programul. Pur și simplu lasă acea mică secțiune în standard eager mode și compilează restul. Când faci un benchmark la asta, fii atent la prima rulare. Prima trecere durează semnificativ mai mult, deoarece compilarea propriu-zisă are loc exact când sosesc primele date. Dar la a doua trecere, timpul de inference scade drastic. Nu mai trebuie să alegi între flexibilitatea din eager mode în timpul dezvoltării și viteza brută a unui backend compilat în producție. Asta e tot pentru acest episod. Mulțumesc că m-ai ascultat, și continuă să construiești!
18

Compilatoare și Graph Breaks

4m 01s

Pătrunde sub capota compilatorului PyTorch. Explorăm graph breaks, fluxul de control dinamic și de ce torch.compile reușește acolo unde sistemele vechi au eșuat.

Descarcă
Salut, sunt Alex de la DEV STORIES DOT EU. Fundamentele PyTorch, episodul 18 din 18. Compilatoarele AI mai vechi cereau trasee de execuție perfect previzibile, eșuând spectaculos dacă includeai structuri Python complexe și dinamice în modelul tău. Modificai o singură instrucțiune condițională și întregul proces de compilare crăpa. PyTorch doi punct zero gestionează exact același cod arbitrar fără să comenteze. Motorul din spatele acestei flexibilități se bazează pe modul în care compilatorul gestionează graph break-urile. Dacă ai lucrat cu tool-ul legacy de compilare, TorchScript, știi că cerea structuri de cod rigide. TorchScript se baza pe static typing strict și pe o execuție previzibilă. Dacă modelul tău avea un control flow extrem de dinamic, se baza pe dicționare Python standard sau apela librării externe non-tensor, TorchScript îl respingea de multe ori. Inginerii trebuiau frecvent să rescrie porțiuni semnificative din arhitectura modelului doar pentru a satisface compilatorul. PyTorch doi punct zero abordează asta complet diferit. În loc să ceară cod static de la bun început, compilatorul nativ îți analizează dinamic execuția Python. Acesta capturează toate operațiile matematice pe care le poate optimiza în siguranță și le împachetează într-un computational graph extrem de eficient. Inevitabil, compilatorul va da peste cod pe care nu-l poate mapa ușor la o structură de graf optimizată. Când dă de această logică imprevizibilă, declanșează un graph break. Un graph break nu este o eroare și nu este un crash. Este pur și simplu un mecanism de fallback. Înseamnă că compilatorul predă elegant controlul înapoi către eager execution din PyTorch standard, pentru acel segment specific de cod. Gândește-te la o funcție în care rulezi o serie de înmulțiri grele de matrice, urmate de un if-statement în Python care verifică valoarea medie a unui tensor pentru a decide următoarea operație. Acea condiție este dependentă de date. Traseul de execuție este complet necunoscut până în momentul exact în care valorile tensorului sunt calculate la runtime. Când faci trace la această funcție cu compilatorul, el analizează flow-ul. Ia înmulțirile de matrice care au loc înaintea condiției și le compilează într-un sub-graph rapid și optimizat. Apoi, se lovește de acel if-statement complicat. Pentru că nu poate prezice rezultatul, creează un graph break. Compilatorul lasă Python-ul standard să execute condiția în eager mode. Odată ce condiția este evaluată și traseul este ales, compilatorul preia din nou controlul, luând operațiile rămase și compilându-le într-un al doilea sub-graph optimizat. Sistemul îți segmentează automat codul. Obții insule compilate de matematică rapidă, separate de punți standard de Python. Modelul tău continuă să ruleze fără probleme. Asta e partea care contează. Probabil vei vedea log-uri de performanță care indică graph break-urile din arhitectura ta. Deși, în general, vrei să le minimizezi pentru a stoarce viteza maximă de execuție, ele există pur și simplu ca o plasă de siguranță. Ele se asigură că codul tău produce mereu rezultatul matematic corect, chiar dacă nu fiecare linie poate fi fuzionată într-un singur kernel. Principala schimbare de design în compilarea modernă din PyTorch este prioritizarea execuției cursive în detrimentul optimizării totale, asigurându-se că motorul se adaptează la logica ta arbitrară, în loc să-ți forțeze logica să se adapteze la motor. Aici se încheie seria noastră despre PyTorch. Te încurajez cu tărie să explorezi documentația oficială, să încerci aceste tool-uri de compilare hands-on și să vizitezi DEV STORIES DOT EU pentru a sugera subiecte pentru seriile viitoare. Aș vrea să-mi iau un moment să-ți mulțumesc că ne-ai ascultat — ne ajută foarte mult. Să ai o zi minunată!