Un tutorial de învățare profundă: de la perceptroni la rețele profunde

Publicat: 2022-03-11

În ultimii ani, a existat o renaștere în domeniul inteligenței artificiale. Este răspândit dincolo de lumea academică, jucători importanți precum Google, Microsoft și Facebook creându-și propriile echipe de cercetare și făcând câteva achiziții impresionante.

Unele dintre acestea pot fi atribuite abundenței de date brute generate de utilizatorii rețelelor sociale, dintre care multe trebuie analizate, creșterii soluțiilor avansate de știință a datelor, precum și puterii de calcul ieftine disponibile prin GPGPU.

Dar dincolo de aceste fenomene, această renaștere a fost alimentată în mare măsură de o nouă tendință în AI, în special în învățarea automată, cunoscută sub numele de „învățare profundă”. În acest tutorial, vă voi prezenta conceptele și algoritmii cheie din spatele învățării profunde, începând cu cea mai simplă unitate de compoziție și construind conceptele de învățare automată în Java.

(Pentru dezvăluire completă: sunt, de asemenea, autorul unei biblioteci de învățare profundă Java, disponibilă aici, iar exemplele din acest articol sunt implementate folosind biblioteca de mai sus. Dacă vă place, o puteți susține dându-i o stea pe GitHub , pentru care aș fi recunoscător. Instrucțiunile de utilizare sunt disponibile pe pagina de pornire.)

Un tutorial de treizeci de secunde despre învățarea automată

În cazul în care nu sunteți familiarizat, consultați această introducere în învățarea automată:

Procedura generală este următoarea:

  1. Avem un algoritm care are câteva exemple etichetate, să spunem 10 imagini cu câini cu eticheta 1 („Câine”) și 10 imagini cu alte lucruri cu eticheta 0 („Nu câine”) – rețineți că rămânem în principal. la clasificare binară supravegheată pentru acest post.
  2. Algoritmul „învață” să identifice imaginile câinilor și, atunci când este alimentat cu o nouă imagine, speră să producă eticheta corectă (1 dacă este o imagine a unui câine și 0 în caz contrar).

Această setare este incredibil de generală: datele tale ar putea fi simptome și etichetele tale boli; sau datele dvs. ar putea fi imagini cu caractere scrise de mână și etichetele dvs. pe caracterele reale pe care le reprezintă.

Perceptroni: algoritmi timpurii de învățare profundă

Unul dintre cei mai timpurii algoritmi de antrenament supravegheat este cel al perceptronului, un bloc de bază al rețelei neuronale.

Să presupunem că avem n puncte în plan, etichetate „0” și „1”. Ni se oferă un nou punct și vrem să-i ghicim eticheta (aceasta este asemănătoare cu scenariul „Câine” și „Nu câine” de mai sus). Cum o facem?

O abordare ar putea fi să te uiți la cel mai apropiat vecin și să returnezi eticheta punctului respectiv. Dar un mod puțin mai inteligent de a proceda în acest sens ar fi să alegeți o linie care separă cel mai bine datele etichetate și să o utilizați ca clasificator.

O reprezentare a datelor de intrare în raport cu un clasificator liniar este o abordare de bază a învățării profunde.

În acest caz, fiecare bucată de date de intrare ar fi reprezentată ca un vector x = ( x_1, x_2 ) iar funcția noastră ar fi ceva de genul „'0' dacă este sub linie, '1' dacă este deasupra".

Pentru a reprezenta acest lucru matematic, să fie definit separatorul nostru printr-un vector de greutăți w și un offset vertical (sau bias) b . Apoi, funcția noastră ar combina intrările și ponderile cu o funcție de transfer a sumei ponderate:

funcția de transfer a sumei ponderate

Rezultatul acestei funcții de transfer va fi apoi introdus într-o funcție de activare pentru a produce o etichetare. În exemplul de mai sus, funcția noastră de activare a fost o limită de prag (de exemplu, 1 dacă este mai mare decât o anumită valoare):

rezultat al acestei funcții de transfer

Antrenarea Perceptronului

Antrenamentul perceptronului constă în alimentarea cu mai multe mostre de antrenament și în calcularea ieșirii pentru fiecare dintre ele. După fiecare probă, ponderile w sunt ajustate astfel încât să se minimizeze eroarea de ieșire , definită ca diferența dintre ieșirile dorite (țintă) și cele reale . Există și alte funcții de eroare, cum ar fi eroarea pătratică medie, dar principiul de bază al antrenamentului rămâne același.

Dezavantaje un singur perceptron

Abordarea cu un singur perceptron a învățării profunde are un dezavantaj major: poate învăța doar funcții separabile liniar. Cât de mare este acest dezavantaj? Luați XOR, o funcție relativ simplă și observați că nu poate fi clasificată printr-un separator liniar (observați încercarea eșuată, mai jos):

Dezavantajul acestei abordări de învățare profundă este că unele funcții nu pot fi clasificate printr-un separator liniar.

Pentru a rezolva această problemă, va trebui să folosim un perceptron multistrat, cunoscut și sub numele de rețea neuronală feedforward: de fapt, vom compune o grămadă de acești perceptroni împreună pentru a crea un mecanism mai puternic de învățare.

Rețele neuronale feedforward pentru învățare profundă

O rețea neuronală este de fapt doar o compoziție de perceptroni, conectați în moduri diferite și care funcționează pe diferite funcții de activare.

Învățarea profundă în rețea neutră cu feedforward este o abordare mai complexă decât perceptronii unici.

Pentru început, ne vom uita la rețeaua neuronală feedforward, care are următoarele proprietăți:

  • O intrare, o ieșire și unul sau mai multe straturi ascunse . Figura de mai sus prezintă o rețea cu un strat de intrare de 3 unități, un strat ascuns de 4 unități și un strat de ieșire cu 2 unități (termenii unități și neuroni sunt interschimbabili).
  • Fiecare unitate este un singur perceptron ca cel descris mai sus.
  • Unitățile stratului de intrare servesc ca intrări pentru unitățile stratului ascuns, în timp ce unitățile stratului ascuns sunt intrări pentru stratul de ieșire.
  • Fiecare conexiune dintre doi neuroni are o pondere w (similară cu greutățile perceptronului).
  • Fiecare unitate a stratului t este de obicei conectată la fiecare unitate a stratului anterior t - 1 (deși le puteți deconecta setând greutatea lor la 0).
  • Pentru a procesa datele de intrare, „prindeți” vectorul de intrare la stratul de intrare, setând valorile vectorului ca „ieșiri” pentru fiecare dintre unitățile de intrare. În acest caz particular, rețeaua poate procesa un vector de intrare tridimensional (din cauza celor 3 unități de intrare). De exemplu, dacă vectorul dvs. de intrare este [7, 1, 2], atunci ați seta ieșirea unității de intrare superioare la 7, unitatea din mijloc la 1 și așa mai departe. Aceste valori sunt apoi propagate înainte către unitățile ascunse folosind funcția de transfer a sumei ponderate pentru fiecare unitate ascunsă (de unde termenul de propagare înainte), care la rândul lor calculează ieșirile (funcția de activare).
  • Stratul de ieșire calculează ieșirile sale în același mod ca stratul ascuns. Rezultatul stratului de ieșire este ieșirea rețelei.

Dincolo de liniaritate

Ce se întâmplă dacă fiecare dintre perceptronii noștri are voie să folosească doar o funcție de activare liniară? Apoi, rezultatul final al rețelei noastre va fi în continuare o funcție liniară a intrărilor, doar ajustată cu o mulțime de greutăți diferite pe care le colectează în întreaga rețea. Cu alte cuvinte, o compoziție liniară a unui grup de funcții liniare este încă doar o funcție liniară. Dacă suntem restricționați la funcții de activare liniară, atunci rețeaua neuronală feedforward nu este mai puternică decât perceptronul, indiferent de câte straturi are.

O compoziție liniară a unui grup de funcții liniare este încă doar o funcție liniară, așa că majoritatea rețelelor neuronale folosesc funcții de activare neliniare.

Din acest motiv, majoritatea rețelelor neuronale folosesc funcții de activare neliniare, cum ar fi logistica, tanh, binară sau redresor. Fără ele, rețeaua poate învăța doar funcții care sunt combinații liniare ale intrărilor sale.

Antrenarea perceptronilor

Cel mai comun algoritm de învățare profundă pentru antrenamentul supravegheat al perceptronilor multistrat este cunoscut sub numele de backpropagation. Procedura de baza:

  1. Un eșantion de antrenament este prezentat și propagat prin rețea.
  2. Se calculează eroarea de ieșire, de obicei eroarea pătratică medie:

    eroare pătrată medie

    Unde t este valoarea țintă și y este ieșirea reală a rețelei. Alte calcule de eroare sunt, de asemenea, acceptabile, dar MSE este o alegere bună.

  3. Eroarea de rețea este minimizată folosind o metodă numită coborâre a gradientului stocastic.

    Coborâre în gradient

    Coborârea gradientului este universală, dar în cazul rețelelor neuronale, acesta ar fi un grafic al erorii de antrenament în funcție de parametrii de intrare. Valoarea optimă pentru fiecare pondere este aceea la care eroarea atinge un minim global . În timpul fazei de antrenament, greutățile sunt actualizate în pași mici (după fiecare eșantion de antrenament sau un mini-lot de mai multe mostre) astfel încât să încerce întotdeauna să atingă minimul global - dar aceasta nu este o sarcină ușoară, deoarece dvs. ajung adesea în minime locale, ca cea din dreapta. De exemplu, dacă greutatea are o valoare de 0,6, aceasta trebuie schimbată spre 0,4.

    Această cifră reprezintă cel mai simplu caz, acela în care eroarea depinde de un singur parametru. Cu toate acestea, eroarea de rețea depinde de fiecare greutate a rețelei, iar funcția de eroare este mult, mult mai complexă.

    Din fericire, retropropagarea oferă o metodă de actualizare a fiecărei greutăți dintre doi neuroni în ceea ce privește eroarea de ieșire. Derivarea în sine este destul de complicată, dar actualizarea greutății pentru un anumit nod are următoarea formă (simpluă):

    formular exemplu

    Unde E este eroarea de ieșire și w_i este greutatea intrării i către neuron.

    În esență, scopul este să se deplaseze în direcția gradientului în raport cu greutatea i . Termenul cheie este, desigur, derivata erorii, care nu este întotdeauna ușor de calculat: cum ați găsi această derivată pentru o greutate aleatorie a unui nod ascuns aleatoriu în mijlocul unei rețele mari?

    Răspunsul: prin retropropagare. Erorile sunt mai întâi calculate la unitățile de ieșire în care formula este destul de simplă (pe baza diferenței dintre valorile țintă și cele prezise) și apoi propagate înapoi prin rețea într-un mod inteligent, permițându-ne să ne actualizăm eficient greutățile în timpul antrenamentului și (sperăm) să ajungă la minim.

Strat ascuns

Stratul ascuns prezintă un interes deosebit. Prin teorema de aproximare universală, o rețea cu un singur strat ascuns cu un număr finit de neuroni poate fi antrenată pentru a aproxima o funcție arbitrar aleatorie. Cu alte cuvinte, un singur strat ascuns este suficient de puternic pentru a învăța orice funcție. Acestea fiind spuse, adesea învățăm mai bine în practică cu mai multe straturi ascunse (adică, plase mai adânci).

Stratul ascuns este locul în care rețeaua stochează reprezentarea abstractă internă a datelor de antrenament.

Stratul ascuns este locul în care rețeaua stochează reprezentarea abstractă internă a datelor de antrenament, similar modului în care un creier uman (analogie foarte simplificată) are o reprezentare internă a lumii reale. Mergând mai departe în tutorial, vom analiza diferite moduri de a juca cu stratul ascuns.

Un exemplu de rețea

Puteți vedea o rețea neuronală de tip feedforward simplă (4-2-3 straturi) care clasifică setul de date IRIS implementat în Java aici prin metoda testMLPSigmoidBP . Setul de date conține trei clase de plante de iris cu caracteristici precum lungimea sepalului, lungimea petalei etc. Rețeaua este furnizată 50 de mostre per clasă. Caracteristicile sunt fixate la unitățile de intrare, în timp ce fiecare unitate de ieșire corespunde unei singure clase a setului de date: „1/0/0” indică faptul că instalația este din clasa Setosa, „0/1/0” indică Versicolour și „ 0/0/1” indică Virginica). Eroarea de clasificare este 2/150 (adică clasifică greșit 2 mostre din 150).

Problema cu rețelele mari

O rețea neuronală poate avea mai mult de un strat ascuns: în acest caz, straturile superioare „construiesc” noi abstracții peste straturile anterioare. Și, așa cum am menționat anterior, puteți învăța adesea mai bine în practică cu rețele mai mari.

Cu toate acestea, creșterea numărului de straturi ascunse duce la două probleme cunoscute:

  1. Gradienți care dispar: pe măsură ce adăugăm din ce în ce mai multe straturi ascunse, propagarea inversă devine din ce în ce mai puțin utilă în transmiterea informațiilor către straturile inferioare. De fapt, pe măsură ce informația este transmisă înapoi, gradienții încep să dispară și să devină mici în raport cu greutățile rețelelor.
  2. Suprafitting: poate problema centrală în învățarea automată. Pe scurt, supraajustarea descrie fenomenul de potrivire prea strânsă a datelor de antrenament, poate cu ipoteze prea complexe. Într-un astfel de caz, cursantul tău ajunge să se potrivească foarte bine cu datele de antrenament, dar va funcționa mult, mult mai slab pe exemple reale.

Să ne uităm la câțiva algoritmi de învățare profundă pentru a aborda aceste probleme.

Autoencodere

Majoritatea cursurilor introductive de învățare automată tind să se oprească cu rețelele neuronale feedforward. Dar spațiul plaselor posibile este mult mai bogat – așa că haideți să continuăm.

Un autoencoder este de obicei o rețea neuronală feedforward care urmărește să învețe o reprezentare (codificare) comprimată și distribuită a unui set de date.

Un autoencoder este o rețea neuronală de învățare profundă care își propune să învețe o anumită reprezentare a unui set de date.

Conceptual, rețeaua este antrenată să „recreeze” intrarea, adică datele de intrare și țintă sunt aceleași. Cu alte cuvinte: încercați să scoateți același lucru pe care l-ați introdus, dar comprimat într-un fel. Aceasta este o abordare confuză, așa că să ne uităm la un exemplu.

Comprimarea intrării: imagini în tonuri de gri

Să spunem că datele de antrenament constau din imagini de 28x28 în tonuri de gri și valoarea fiecărui pixel este fixată la un neuron al stratului de intrare (adică, stratul de intrare va avea 784 de neuroni). Apoi, stratul de ieșire ar avea același număr de unități (784) ca și stratul de intrare, iar valoarea țintă pentru fiecare unitate de ieșire ar fi valoarea în tonuri de gri a unui pixel al imaginii.

Intuiția din spatele acestei arhitecturi este că rețeaua nu va învăța o „mapping” între datele de antrenament și etichetele sale, ci va învăța în schimb structura internă și caracteristicile datelor în sine. (Din acest motiv, stratul ascuns este numit și detector de caracteristici .) De obicei, numărul de unități ascunse este mai mic decât straturile de intrare/ieșire, ceea ce obligă rețeaua să învețe doar cele mai importante caracteristici și realizează o reducere a dimensionalității.

Vrem câteva noduri mici în mijloc pentru a învăța datele la nivel conceptual, producând o reprezentare compactă.

De fapt, vrem câteva noduri mici în mijloc pentru a învăța cu adevărat datele la nivel conceptual, producând o reprezentare compactă care surprinde într-un fel caracteristicile de bază ale intrării noastre.

Boala de gripă

Pentru a demonstra în continuare codificatoarele automate, să ne uităm la încă o aplicație.

În acest caz, vom folosi un set de date simplu constând din simptome de gripă (credit pentru această postare de blog pentru idee). Dacă sunteți interesat, codul pentru acest exemplu poate fi găsit în metoda testAEBackpropagation .

Iată cum se defalcă setul de date:

  • Există șase caracteristici de intrare binară.
  • Primele trei sunt simptome ale bolii. De exemplu, 1 0 0 0 0 0 indică faptul că acest pacient are o temperatură ridicată, în timp ce 0 1 0 0 0 0 indică tuse, 1 1 0 0 0 0 indică tuse și temperatură ridicată etc.
  • Ultimele trei caracteristici sunt „contra” simptome; atunci când un pacient are una dintre acestea, este mai puțin probabil să fie bolnav. De exemplu, 0 0 0 1 0 0 indică faptul că acest pacient are un vaccin antigripal. Este posibil să existe combinații ale celor două seturi de caracteristici: 0 1 0 1 0 0 indică un pacient vaccinat cu tuse și așa mai departe.

Vom considera un pacient bolnav atunci când are cel puțin două din primele trei caracteristici și sănătos dacă are cel puțin două dintre cele trei (cu legăturile rupându-se în favoarea pacienților sănătoși), de exemplu:

  • 111000, 101000, 110000, 011000, 011100 = bolnav
  • 000111, 001110, 000101, 000011, 000110 = sănătos

Vom antrena un autoencoder (folosind backpropagation) cu șase unități de intrare și șase unități de ieșire, dar doar două unități ascunse .

După câteva sute de iterații, observăm că atunci când fiecare dintre eșantioanele „bolnave” este prezentată rețelei de învățare automată, una dintre cele două unități ascunse (aceeași unitate pentru fiecare eșantion „bolnav”) prezintă întotdeauna o valoare de activare mai mare decât alte. Dimpotrivă, atunci când este prezentată o probă „sănătoasă”, cealaltă unitate ascunsă are o activare mai mare.

Revenind la Machine Learning

În esență, cele două unități ascunse ale noastre au învățat o reprezentare compactă a setului de date despre simptomele gripei. Pentru a vedea cum se leagă acest lucru cu învățarea, revenim la problema supraadaptării. Prin antrenarea rețelei noastre pentru a învăța o reprezentare compactă a datelor, favorizăm o reprezentare mai simplă, mai degrabă decât o ipoteză extrem de complexă care depășește datele de antrenament.

Într-un fel, prin favorizarea acestor reprezentări mai simple, încercăm să învățăm datele într-un sens mai adevărat.

Mașini Boltzmann restricționate

Următorul pas logic este să privim o mașină Boltzmann restricționată (RBM), o rețea neuronală stocastică generativă care poate învăța o distribuție a probabilității pe setul său de intrări .

În învățarea automată, mașinile Botlzmann restricționate sunt compuse din unități vizibile și ascunse.

RBM-urile sunt compuse dintr-un strat ascuns, vizibil și părtinitor. Spre deosebire de rețelele feedforward, conexiunile dintre straturile vizibile și ascunse sunt nedirecționate (valorile pot fi propagate atât în ​​direcția vizibil la ascuns, cât și în direcția ascunsă la vizibil) și complet conectate (fiecare unitate dintr-un anumit strat este conectată la fiecare unitate în următoarea — dacă am permite oricărei unități din orice strat să se conecteze la orice alt strat, atunci am avea o mașină Boltzmann (mai degrabă decât o mașină Boltzmann restricționată ).

RBM standard are unități binare ascunse și vizibile: adică activarea unității este 0 sau 1 sub o distribuție Bernoulli, dar există variante cu alte neliniarități.

În timp ce cercetătorii știu despre RBM de ceva timp, introducerea recentă a algoritmului de antrenament nesupravegheat de divergență contrasttivă a reînnoit interesul.

Divergenta contrastiva

Algoritmul de divergență contrastivă într-un singur pas (CD-1) funcționează astfel:

  1. Faza pozitiva :
    • Un eșantion de intrare v este fixat de stratul de intrare.
    • v este propagat la stratul ascuns într-un mod similar cu rețelele feedforward. Rezultatul activărilor stratului ascuns este h .
  2. Faza negativa :
    • Propagați h înapoi la stratul vizibil cu rezultatul v’ (legăturile dintre straturile vizibil și ascunse sunt nedirecționate și permit astfel mișcarea în ambele direcții).
    • Propagați noul v' înapoi la stratul ascuns cu rezultatul activărilor h' .
  3. Actualizare greutate :

    actualizare de greutate

    Unde a este rata de învățare și v , v' , h , h' și w sunt vectori.

Intuiția din spatele algoritmului este că faza pozitivă ( h dat v ) reflectă reprezentarea internă a rețelei a datelor din lumea reală . Între timp, faza negativă reprezintă o încercare de a recrea datele pe baza acestei reprezentări interne ( v' dat h ). Scopul principal este ca datele generate să fie cât mai aproape de lumea reală și acest lucru se reflectă în formula de actualizare a greutății.

Cu alte cuvinte, rețeaua are o anumită percepție asupra modului în care datele de intrare pot fi reprezentate, așa că încearcă să reproducă datele pe baza acestei percepții. Dacă reproducerea sa nu este suficient de aproape de realitate, face o ajustare și încearcă din nou.

Revenind la gripă

Pentru a demonstra divergența contrastantă, vom folosi același set de date despre simptome ca înainte. Rețeaua de testare este un RBM cu șase unități vizibile și două ascunse. Vom antrena rețeaua folosind divergența contrastantă cu simptomele v fixate pe stratul vizibil. În timpul testării, simptomele sunt din nou prezentate stratului vizibil; apoi, datele sunt propagate la stratul ascuns. Unitățile ascunse reprezintă starea bolnavă/sănătoasă, o arhitectură foarte asemănătoare cu autoencoderul (propagarea datelor din stratul vizibil în stratul ascuns).

După câteva sute de iterații, putem observa același rezultat ca și în cazul autoencoderelor: una dintre unitățile ascunse are o valoare de activare mai mare atunci când este prezentată oricare dintre mostrele „bolnave”, în timp ce cealaltă este întotdeauna mai activă pentru mostrele „sănătoase”.

Puteți vedea acest exemplu în acțiune în metoda testContrastiveDivergence .

Rețele profunde

Acum am demonstrat că straturile ascunse ale codificatoarelor automate și ale RBM-urilor acționează ca detectoare de caracteristici efective; dar este rar să putem folosi aceste funcții direct. De fapt, setul de date de mai sus este mai mult o excepție decât o regulă. În schimb, trebuie să găsim o modalitate de a folosi indirect aceste caracteristici detectate.

Din fericire, s-a descoperit că aceste structuri pot fi stivuite pentru a forma rețele profunde . Aceste rețele pot fi antrenate cu lăcomie, câte un strat, pentru a ajuta la depășirea gradientului care dispare și a problemelor de supraadaptare asociate cu retropropagarea clasică.

Structurile rezultate sunt adesea destul de puternice, producând rezultate impresionante. Luați, de exemplu, faimoasa lucrare „pisica” de la Google, în care folosesc un tip special de codificatoare profunde pentru a „învăța” detectarea feței umane și a pisicilor pe baza datelor neetichetate .

Să aruncăm o privire mai atentă.

Autoencodere stivuite

După cum sugerează și numele, această rețea constă din mai multe autoencodere stivuite.

Autoencoderele stivuite au o serie de intrări, ieșiri și straturi ascunse care contribuie la rezultatele învățării automate.

Stratul ascuns al codificatorului automat t acţionează ca un strat de intrare al codificatorului automat t + 1 . Stratul de intrare al primului autoencoder este stratul de intrare pentru întreaga rețea. Procedura de antrenament lacomă pe straturi funcționează astfel:

  1. Antrenați primul autoencoder ( t=1 , sau conexiunile roșii din figura de mai sus, dar cu un strat de ieșire suplimentar) individual folosind metoda de backpropagation cu toate datele de antrenament disponibile.
  2. Antrenează al doilea autoencoder t=2 (conexiuni verzi). Deoarece stratul de intrare pentru t=2 este stratul ascuns al lui t=1 nu mai suntem interesați de stratul de ieșire al lui t=1 și îl eliminăm din rețea. Antrenamentul începe prin fixarea unei probe de intrare la stratul de intrare al lui t=1 , care este propagat înainte către stratul de ieșire al lui t=2 . În continuare, ponderile (input-hidden și hidden-output) ale lui t=2 sunt actualizate folosind backpropagation. t=2 folosește toate eșantioanele de antrenament, similar cu t=1 .
  3. Repetați procedura anterioară pentru toate straturile (adică, eliminați stratul de ieșire al autoencoderului anterior, înlocuiți-l cu încă un alt autoencoder și antrenați-vă cu propagarea înapoi).
  4. Pașii 1-3 se numesc pre-antrenament și lasă greutățile corect inițializate. Cu toate acestea, nu există nicio mapare între datele de intrare și etichetele de ieșire. De exemplu, dacă rețeaua este antrenată să recunoască imagini ale cifrelor scrise de mână, tot nu este posibilă maparea unităților de la ultimul detector de caracteristici (adică, stratul ascuns al ultimului codificator automat) la tipul de cifră al imaginii. În acest caz, cea mai comună soluție este să adăugați unul sau mai multe straturi complet conectate la ultimul strat (conexiuni albastre). Întreaga rețea poate fi acum privită ca un perceptron multistrat și este antrenată utilizând propagarea inversă (acest pas se mai numește și reglare fină ).

Prin urmare, codificatoarele automate stivuite se referă la a oferi o metodă eficientă de pre-antrenament pentru inițializarea greutăților unei rețele, lăsându-vă cu un perceptron complex, cu mai multe straturi, care este gata de antrenat (sau de reglat fin ).

Rețele de credință profundă

Ca și în cazul autoencoderelor, putem, de asemenea, să stivuim mașinile Boltzmann pentru a crea o clasă cunoscută sub numele de rețele de credință profundă (DBN) .

Rețelele de credință profundă sunt formate dintr-un teanc de mașini Boltzmann.

În acest caz, stratul ascuns al RBM t acţionează ca un strat vizibil pentru RBM t+1 . Stratul de intrare al primului RBM este stratul de intrare pentru întreaga rețea, iar pre-antrenamentul lacom la nivel funcționează astfel:

  1. Antrenează primul RBM t=1 utilizând divergența contrastivă cu toate eșantioanele de antrenament.
  2. Antrenează al doilea RBM t=2 . Deoarece stratul vizibil pentru t=2 este stratul ascuns al lui t=1 , antrenamentul începe prin fixarea eșantionului de intrare la stratul vizibil al lui t=1 , care este propagat înainte către stratul ascuns al lui t=1 . Aceste date servesc apoi pentru a iniția antrenamentul divergenței contrastive pentru t=2 .
  3. Repetați procedura anterioară pentru toate straturile.
  4. Similar cu autoencoderele stivuite, după pre-antrenare, rețeaua poate fi extinsă prin conectarea unuia sau mai multor straturi complet conectate la stratul ascuns RBM final. Acesta formează un perceptron cu mai multe straturi care poate fi apoi reglat fin folosind propagarea inversă.

Această procedură este asemănătoare cu cea a autoencoderelor stivuite, dar cu autoencoderele înlocuite cu RBM și backpropagation înlocuită cu algoritmul de divergență contrastivă.

(Notă: pentru mai multe despre construirea și antrenamentul autoencoderelor stivuite sau rețelelor de credință profundă, consultați exemplul de cod aici.)

Rețele convoluționale

Ca o arhitectură finală de învățare profundă, să aruncăm o privire la rețelele convoluționale, o clasă deosebit de interesantă și specială de rețele feedforward care sunt foarte potrivite pentru recunoașterea imaginilor.

Rețelele convoluționale sunt o clasă specială de rețele feedforward de învățare profundă.
Imagine prin DeepLearning.net

Înainte de a ne uita la structura reală a rețelelor convoluționale, definim mai întâi un filtru de imagine sau o regiune pătrată cu ponderi asociate. Un filtru este aplicat pe întreaga imagine de intrare și veți aplica adesea mai multe filtre. De exemplu, puteți aplica patru filtre 6x6 la o anumită imagine de intrare. Apoi, pixelul de ieșire cu coordonatele 1,1 este suma ponderată a unui pătrat de 6x6 de pixeli de intrare cu colțul din stânga sus 1,1 și a greutăților filtrului (care este, de asemenea, pătrat de 6x6). Pixelul de ieșire 2,1 este rezultatul pătratului de intrare cu colțul din stânga sus 2,1 și așa mai departe.

Cu toate acestea, aceste rețele sunt definite de următoarele proprietăți:

  • Straturile convoluționale aplică un număr de filtre la intrare. De exemplu, primul strat convoluțional al imaginii ar putea avea patru filtre 6x6. Rezultatul unui filtru aplicat peste imagine se numește hărți de caracteristici (FM) și numărul de hărți de caracteristici este egal cu numărul de filtre. Dacă stratul anterior este, de asemenea, convoluțional, filtrele sunt aplicate pe toate FM-urile sale cu greutăți diferite, astfel încât fiecare FM de intrare este conectat la fiecare FM de ieșire. Intuiția din spatele greutăților partajate în imagine este că caracteristicile vor fi detectate indiferent de locația lor, în timp ce multitudinea de filtre permite fiecăruia dintre ele să detecteze un set diferit de caracteristici.
  • Straturile de subeșantionare reduc dimensiunea intrării. De exemplu, dacă intrarea constă dintr-o imagine de 32x32 și stratul are o regiune de subeșantionare de 2x2, valoarea de ieșire ar fi o imagine de 16x16, ceea ce înseamnă că 4 pixeli (fiecare pătrat de 2x2) ai imaginii de intrare sunt combinați într-o singură ieșire. pixel. Există mai multe moduri de subeșantionare, dar cele mai populare sunt pooling maxim, pooling mediu și pooling stocastic.
  • Ultimul strat de subeșantionare (sau convoluțional) este de obicei conectat la unul sau mai multe straturi complet conectate, ultimul dintre acestea reprezentând datele țintă.
  • Antrenamentul se realizează folosind backpropagation modificată care ia în considerare straturile de subeșantionare și actualizează greutățile filtrului convoluțional pe baza tuturor valorilor cărora li se aplică acel filtru.

Puteți vedea câteva exemple de rețele convoluționale antrenate (cu backpropagation) pe setul de date MNIST (imagini în tonuri de gri ale literelor scrise de mână) aici, în special în metodele testLeNet* (aș recomanda testLeNetTiny2 deoarece atinge o rată de eroare scăzută de aproximativ 2% într-o perioadă relativ scurtă de timp). Există, de asemenea, o vizualizare JavaScript frumoasă a unei rețele similare aici.

Implementarea

Acum că am acoperit cele mai comune variante de rețele neuronale, m-am gândit să scriu puțin despre provocările prezentate în timpul implementării acestor structuri de învățare profundă.

În linii mari, scopul meu în crearea unei biblioteci de învățare profundă a fost (și este încă) să construiesc un cadru bazat pe rețele neuronale care să îndeplinească următoarele criterii:

  • O arhitectură comună care este capabilă să reprezinte modele diverse (toate variantele rețelelor neuronale pe care le-am văzut mai sus, de exemplu).
  • Abilitatea de a utiliza diverși algoritmi de antrenament (propagare înapoi, divergență contrastivă etc.).
  • Performanță decentă.

Pentru a satisface aceste cerințe, am adoptat o abordare în trepte (sau modulară) a designului software-ului.

Structura

Să începem cu elementele de bază:

  • NeuralNetworkImpl este clasa de bază pentru toate modelele de rețele neuronale.
  • Fiecare rețea conține un set de straturi.
  • Fiecare strat are o listă de conexiuni, unde o conexiune este o legătură între două straturi, astfel încât rețeaua este un graf aciclic direcționat.

Această structură este suficient de agilă pentru a fi utilizată pentru rețelele clasice de tip feedforward, precum și pentru RBM-uri și arhitecturi mai complexe precum ImageNet.

De asemenea, permite unui strat să facă parte din mai mult de o rețea. De exemplu, straturile dintr-o Rețea Deep Belief sunt, de asemenea, straturi din RBM-urile lor corespunzătoare.

În plus, această arhitectură permite ca un DBN să fie vizualizat ca o listă de RBM-uri stivuite în timpul fazei de pre-instruire și o rețea de feedforward în timpul fazei de reglare fină, ceea ce este atât intuitiv frumos, cât și convenabil din punct de vedere programatic.

Propagarea datelor

Următorul modul se ocupă de propagarea datelor prin rețea, un proces în doi pași:

  1. Determinați ordinea straturilor. De exemplu, pentru a obține rezultatele de la un perceptron multistrat, datele sunt „prinse” la stratul de intrare (prin urmare, acesta este primul strat care trebuie calculat) și propagate până la stratul de ieșire. Pentru a actualiza ponderile în timpul propagării inverse, eroarea de ieșire trebuie să fie propagată prin fiecare strat în ordinea lățimii, începând de la stratul de ieșire. Acest lucru se realizează folosind diverse implementări ale LayerOrderStrategy , care profită de structura grafică a rețelei, folosind diferite metode de traversare a graficului. Unele exemple includ strategia de lățime mai întâi și direcționarea unui anumit strat. Ordinea este de fapt determinată de conexiunile dintre straturi, astfel încât strategiile returnează o listă ordonată de conexiuni.
  2. Calculați valoarea de activare. Fiecare strat are asociat un ConnectionCalculator care preia lista sa de conexiuni (de la pasul anterior) și valorile de intrare (de la alte straturi) și calculează activarea rezultată. De exemplu, într-o rețea de feedforward simplă sigmoidală, ConnectionCalculator al stratului ascuns ia valorile straturilor de intrare și de polarizare (care sunt, respectiv, datele de intrare și o matrice de 1s ) și greutățile dintre unități (în cazul unei conexiuni complet). straturi, greutățile sunt de fapt stocate într-o conexiune FullyConnected ca Matrix ), calculează suma ponderată și alimentează rezultatul în funcția sigmoid. Calculatoarele de conectare implementează o varietate de funcții de transfer (de exemplu, sumă ponderată, convoluțională) și activare (de exemplu, logistică și tanh pentru perceptron multistrat, binară pentru RBM). Cele mai multe dintre ele pot fi executate pe un GPU folosind Aparapi și utilizabile cu antrenament mini-batch.

Calcul GPU cu Aparapi

După cum am menționat mai devreme, unul dintre motivele pentru care rețelele neuronale au revenit în ultimii ani este că metodele lor de antrenament sunt extrem de favorabile paralelismului, permițându-vă să accelerați semnificativ antrenamentul cu ajutorul unui GPGPU. În acest caz, am ales să lucrez cu biblioteca Aparapi pentru a adăuga suport GPU.

Aparapi impune câteva restricții importante asupra calculatoarelor de conectare:

  • Sunt permise doar matricele unidimensionale (și variabilele) de tipuri de date primitive.
  • Numai metodele membre ale clasei Kernel Aparapi în sine pot fi apelate din codul executabil GPU.

Ca atare, majoritatea datelor (greutăți, matrice de intrare și de ieșire) sunt stocate în instanțe Matrix , care folosesc matrice flotant unidimensionale în interior. Toate calculatoarele de conexiune Aparapi folosesc fie AparapiWeightedSum (pentru straturi complet conectate și funcții de intrare cu sumă ponderată), AparapiSubsampling2D (pentru straturi de subeșantionare), fie AparapiConv2D (pentru straturi convoluționale). Unele dintre aceste limitări pot fi depășite prin introducerea arhitecturii sistemelor eterogene. Aparapi also allows to run the same code on both CPU and GPU.

Training

The training module implements various training algorithms. It relies on the previous two modules. For example, BackPropagationTrainer (all the trainers are using the Trainer base class) uses feedforward layer calculator for the feedforward phase and a special breadth-first layer calculator for propagating the error and updating the weights.

My latest work is on Java 8 support and some other improvements, will soon be merged into master.

Concluzie

The aim of this Java deep learning tutorial was to give you a brief introduction to the field of deep learning algorithms, beginning with the most basic unit of composition (the perceptron) and progressing through various effective and popular architectures, like that of the restricted Boltzmann machine.

The ideas behind neural networks have been around for a long time; but today, you can't step foot in the machine learning community without hearing about deep networks or some other take on deep learning. Hype shouldn't be mistaken for justification, but with the advances of GPGPU computing and the impressive progress made by researchers like Geoffrey Hinton, Yoshua Bengio, Yann LeCun and Andrew Ng, the field certainly shows a lot of promise. There's no better time to get familiar and get involved like the present.

Appendix: Resources

If you're interested in learning more, I found the following resources quite helpful during my work:

  • DeepLearning.net: a portal for all things deep learning. It has some nice tutorials, software library and a great reading list.
  • An active Google+ community.
  • Two very good courses: Machine Learning and Neural Networks for Machine Learning, both offered on Coursera.
  • The Stanford neural networks tutorial.
Related: Schooling Flappy Bird: A Reinforcement Learning Tutorial