Acesta este al treilea și ultimul tutorial despre realizarea „NLP From Scratch”, unde ne scriem propriile clase și funcții pentru a preprocesa datele pentru a realiza sarcinile noastre de modelare NLP. Sperăm că, după finalizarea acestui tutorial, veți continua să aflați cum torchtext poate gestiona o mare parte din această preprocesare pentru dvs. în cele trei tutoriale imediat următoare.

În acest proiect vom preda o rețea neuronală pentru a traduce din franceză în engleză.

… La diferite grade de succes.

Acest lucru este posibil prin ideea simplă, dar puternică, a rețelei secvență în secvență, în care două rețele neuronale recurente funcționează împreună pentru a transforma o secvență în alta. O rețea de codificare condensează o secvență de intrare într-un vector, iar o rețea de decodare desfășoară acel vector într-o nouă secvență.

scratch

Pentru a îmbunătăți acest model, vom folosi un mecanism de atenție, care permite decodorului să învețe să se concentreze pe un anumit interval al secvenței de intrare.

Lectură recomandată:

Presupun că ați instalat cel puțin PyTorch, cunoașteți Python și înțelegeți tensorii:

  • https://pytorch.org/ Pentru instrucțiuni de instalare
  • Învățare profundă cu PyTorch: un Blitz de 60 de minute pentru a începe cu PyTorch în general
  • Învățarea PyTorch cu exemple pentru o imagine de ansamblu largă și profundă
  • PyTorch pentru foștii utilizatori Torch dacă sunteți un fost utilizator Lua Torch

De asemenea, ar fi util să știm despre rețelele Secvență în secvență și cum funcționează:

Veți găsi, de asemenea, tutoriale anterioare despre NLP From Scratch: Clasificarea numelor cu un RNN la nivel de caracter și NLP From Scratch: Generarea de nume cu un RNN la nivel de caracter util, deoarece aceste concepte sunt foarte asemănătoare cu modelele Encoder și respectiv Decoder.

Și pentru mai multe, citiți lucrările care au introdus aceste subiecte:

Cerințe

Se încarcă fișiere de date

Datele pentru acest proiect sunt un set de mii de perechi de traduceri din engleză în franceză.

Această întrebare din Open Data Stack Exchange m-a indicat spre site-ul de traducere deschis https://tatoeba.org/ care are descărcări disponibile la https://tatoeba.org/eng/downloads - și mai bine, cineva a făcut munca suplimentară de divizare perechi de limbi în fișiere text individuale aici: https://www.manythings.org/anki/

Perechile din engleză în franceză sunt prea mari pentru a fi incluse în repo, așa că descărcați pe data/eng-fra.txt înainte de a continua. Fișierul este o listă separată de tabele de perechi de traduceri:

Descărcați datele de aici și extrageți-le în directorul curent.

Similar cu codificarea caracterelor folosită în tutorialele RNN la nivel de caractere, vom reprezenta fiecare cuvânt într-o limbă ca un vector cu un singur fierbinte sau vector gigant de zerouri, cu excepția unuia singur (la indexul cuvântului). Comparativ cu zecile de caractere care ar putea exista într-o limbă, există multe alte cuvinte, deci vectorul de codificare este mult mai mare. Cu toate acestea, vom înșela puțin și vom tăia datele pentru a folosi doar câteva mii de cuvinte pe limbă.

Vom avea nevoie de un index unic pe cuvânt pentru a-l folosi ca intrări și ținte ale rețelelor mai târziu. Pentru a urmări toate acestea, vom folosi o clasă de ajutor numită Lang care are dicționare cuvânt → index (word2index) și index → ​​word (index2word), precum și un număr al fiecărui cuvânt word2count de utilizat pentru a înlocui ulterior cuvintele rare.

Fișierele sunt toate în Unicode, pentru a simplifica, vom transforma caracterele Unicode în ASCII, vom face totul cu litere mici și vom tăia cele mai multe punctuații.

Pentru a citi fișierul de date, vom împărți fișierul în linii, apoi le vom împărți în perechi. Fișierele sunt toate engleză → Altă limbă, așa că dacă dorim să traducem din altă limbă → engleză, am adăugat steagul invers pentru a inversa perechile.

Deoarece există o mulțime de propoziții de exemplu și dorim să instruim ceva rapid, vom reduce setul de date doar la propoziții relativ scurte și simple. Aici lungimea maximă este de 10 cuvinte (care include punctuația de încheiere) și trecem la propoziții care se traduc în forma „Eu sunt” sau „El este” etc. (contabilizarea apostrofelor înlocuite anterior).

Procesul complet de pregătire a datelor este:

  • Citiți fișierul text și împărțiți-l în linii, împărțiți liniile în perechi
  • Normalizați textul, filtrați după lungime și conținut
  • Faceți liste de cuvinte din propoziții în perechi

Modelele Seq2Seq

O rețea neuronală recurentă sau RNN este o rețea care funcționează pe o secvență și își folosește propria ieșire ca intrare pentru pașii următori.

O rețea de secvență la secvență sau rețea seq2seq sau rețea de decodare a codificatorului este un model format din două RNN-uri numite codificator și decodor. Codificatorul citește o secvență de intrare și scoate un singur vector, iar decodorul citește acel vector pentru a produce o secvență de ieșire.

Spre deosebire de predicția secvenței cu un singur RNN, unde fiecare intrare corespunde unei ieșiri, modelul seq2seq ne eliberează de lungimea și ordinea secvenței, ceea ce îl face ideal pentru traducerea între două limbi.

Luați în considerare propoziția „Je ne suis pas le chat noir” → „Eu nu sunt pisica neagră”. Majoritatea cuvintelor din propoziția de intrare au o traducere directă în propoziția de ieșire, dar sunt în ordine ușor diferite, de ex. „Chat noir” și „pisică neagră”. Datorită construcției „ne/pas”, mai există încă un cuvânt în propoziția de intrare. Ar fi dificil să se producă o traducere corectă direct din secvența cuvintelor introduse.

Cu un model seq2seq, codificatorul creează un singur vector care, în cazul ideal, codifică „semnificația” secvenței de intrare într-un singur vector - un singur punct într-un spațiu dimensional N al propozițiilor.

Codificatorul¶

Codificatorul unei rețele seq2seq este un RNN care transmite o anumită valoare pentru fiecare cuvânt din propoziția de intrare. Pentru fiecare cuvânt de intrare codificatorul scoate un vector și o stare ascunsă și folosește starea ascunsă pentru următorul cuvânt de intrare.

Decodor¶

Decodorul este un alt RNN care ia vectorul (codurile) de ieșire a codificatorului și scoate o secvență de cuvinte pentru a crea traducerea.

Decodor simplu¶

În cel mai simplu decodificator seq2seq folosim doar ultima ieșire a codificatorului. Această ultimă ieșire este uneori numită vectorul context, deoarece codifică contextul din întreaga secvență. Acest vector context este folosit ca starea inițială ascunsă a decodificatorului.

La fiecare pas al decodificării, decodificatorul primește un simbol de intrare și o stare ascunsă. Jetonul de intrare inițial este jetonul de începere a șirului, iar prima stare ascunsă este vectorul context (ultima stare ascunsă a codificatorului).

Vă încurajez să vă antrenați și să observați rezultatele acestui model, dar pentru a economisi spațiu vom merge direct pentru aur și vom introduce mecanismul de atenție.

Decodor de atenție¶

Dacă numai vectorul context este trecut între codificator și decodor, acel vector unic poartă sarcina codificării întregii propoziții.

Atenția permite rețelei decodorului să „se concentreze” pe o parte diferită a ieșirilor codificatorului pentru fiecare pas al ieșirilor proprii ale decodorului. Mai întâi calculăm un set de greutăți de atenție. Acestea vor fi înmulțite cu vectorii de ieșire a codificatorului pentru a crea o combinație ponderată. Rezultatul (numit attn_applied în cod) ar trebui să conțină informații despre acea parte specifică a secvenței de intrare și astfel să ajute decodorul să aleagă cuvintele de ieșire potrivite.

Calcularea greutăților de atenție se face cu un alt strat de feed-forward attn, folosind intrările și starea ascunsă a decodorului ca intrări. Deoarece există propoziții de toate dimensiunile în datele de instruire, pentru a crea și a antrena acest strat, trebuie să alegem o lungime maximă a frazei (lungimea de intrare, pentru ieșirile codificatorului) la care se poate aplica. Propozițiile cu lungimea maximă vor folosi toate greutățile de atenție, în timp ce propozițiile mai scurte vor folosi doar primele.

Există alte forme de atenție care acționează în jurul limitării lungimii utilizând o abordare a poziției relative. Citiți despre „atenția locală” în Abordări eficiente ale traducerii automate neuronale bazate pe atenție.

Instruire¶

Pregătirea datelor de instruire¶

Pentru antrenament, pentru fiecare pereche vom avea nevoie de un tensor de intrare (indexuri ale cuvintelor din propoziția de intrare) și tensor țintă (indexuri ale cuvintelor din propoziția țintă). În timp ce creăm acești vectori, vom adăuga simbolul EOS la ambele secvențe.

Instruirea modelului¶

Pentru antrenament, executăm propoziția de intrare prin codificator și ținem evidența fiecărei ieșiri și a celei mai recente stări ascunse. Apoi decodificatorul primește simbolul ca primă intrare, iar ultima stare ascunsă a codificatorului ca primă stare ascunsă.

„Forțarea profesorului” este conceptul de a utiliza ieșirile țintă reale ca fiecare intrare următoare, în loc de a folosi presupunerea decodorului ca următoare intrare. Folosirea forțării profesorului face ca aceasta să convergă mai repede, dar atunci când rețeaua instruită este exploatată, aceasta poate prezenta instabilitate.

Puteți observa rezultatele rețelelor forțate de profesori care citesc cu o gramatică coerentă, dar rătăcesc departe de traducerea corectă - intuitiv a învățat să reprezinte gramatica de ieșire și poate „prelua” semnificația odată ce profesorul îi spune primele cuvinte, dar nu a învățat corect cum să creeze propoziția din traducere.

Datorită libertății oferite de autogradul PyTorch, putem alege aleatoriu să folosim forțarea profesorului sau nu cu o declarație simplă if. Ridicați teacher_forcing_ratio în sus pentru a folosi mai mult.

Aceasta este o funcție de asistență pentru imprimarea timpului scurs și a timpului rămas estimat, având în vedere timpul actual și procentul%.

Întregul proces de formare arată astfel:

  • Porniți un cronometru
  • Inițializați optimizatorii și criteriul
  • Creați un set de perechi de antrenament
  • Porniți matricea de pierderi goale pentru reprezentare

Apoi apelăm trenul de multe ori și ocazional imprimăm progresul (% exemple, timp până acum, timp estimat) și pierderea medie.

Trasarea rezultatelor¶

Plotarea se face cu matplotlib, utilizând matricea de valori de pierdere plot_losses salvate în timpul antrenamentului.

Evaluare¶

Evaluarea este în mare parte aceeași cu antrenamentul, dar nu există ținte, așa că pur și simplu alimentăm predicțiile decodorului înapoi la sine pentru fiecare pas. De fiecare dată când prezice un cuvânt, îl adăugăm la șirul de ieșire și, dacă prezice jetonul EOS, ne oprim aici. De asemenea, stocăm ieșirile de atenție ale decodorului pentru afișare ulterioară.

Putem evalua propoziții aleatorii din setul de antrenament și putem imprima intrarea, ținta și rezultatul pentru a face câteva judecăți subiective de calitate:

Instruire și evaluare¶

Cu toate aceste funcții de asistență la locul lor (pare a fi o muncă suplimentară, dar face mai ușoară rularea mai multor experimente) putem inițializa o rețea și putem începe antrenamentul.

Amintiți-vă că propozițiile introduse au fost puternic filtrate. Pentru acest mic set de date putem folosi rețele relativ mici de 256 de noduri ascunse și un singur strat GRU. După aproximativ 40 de minute pe un procesor MacBook, vom obține câteva rezultate rezonabile.

Dacă rulați acest notebook, puteți antrena, întrerupe nucleul, evalua și continua antrenamentul mai târziu. Comentează liniile unde sunt inițializate codificatorul și decodificatorul și rulează din nou trainIters.

Vizualizarea atenției¶

O proprietate utilă a mecanismului de atenție este rezultatele sale extrem de interpretabile. Deoarece este folosit pentru a cântări ieșirile specifice de codificare ale secvenței de intrare, ne putem imagina căutăm unde este concentrată cel mai mult rețeaua la fiecare pas.

Puteți rula pur și simplu plt.matshow (atenții) pentru a vedea ieșirea atenției afișată ca o matrice, coloanele fiind pași de intrare și rândurile fiind pași de ieșire:

Pentru o experiență de vizionare mai bună, vom face munca suplimentară de a adăuga axe și etichete:

Exerciții

  • Încercați cu un alt set de date
    • O altă pereche lingvistică
    • Om → Mașină (de exemplu, comenzi IOT)
    • Chat → Răspuns
    • Întrebare → Răspuns
  • Înlocuiți încorporările cu încorporări de cuvinte pre-antrenate, cum ar fi word2vec sau GloVe
  • Încercați cu mai multe straturi, mai multe unități ascunse și mai multe propoziții. Comparați timpul de antrenament și rezultatele.
  • Dacă utilizați un fișier de traducere în care perechile au două din aceeași frază (eu sunt test \ t sunt test), îl puteți folosi ca un autoencoder. Incearca asta:
    • Antrenează-te ca autoencoder
    • Salvați doar rețeaua Encoder
    • Instruiți un nou decodor pentru traducere de acolo

Durata totală de rulare a scriptului: (30 minute 44.151 secunde)