Ghid simplu pentru unitățile logice aritmetice neuronale (NALU): explicație, intuiție și cod

Inginerii de cercetare de la DeepMind, inclusiv un cunoscut cercetător AI și autor al cărții Grokking Deep Learning, Andrew Trask a publicat o lucrare impresionantă pe un model de rețea neuronală care poate învăța funcții numerice simple până la complexe, cu o mare capacitate de extrapolare (generalizare).

În acest post vă voi explica NALU, arhitectura, componentele și semnificația sa în rețelele neuronale tradiționale. Intenția principală din spatele acestui post este de a oferi explicații simple și intuitive despre NALU (atât cu concepte, cât și cu cod), care pot fi înțelese de cercetători, ingineri și studenți care au o cunoaștere limitată a rețelelor neuronale și a învățării profunde.

Notă: Recomand recomandator cititorilor să citească lucrarea originală pentru o înțelegere mai detaliată a subiectului. Lucrarea poate fi descărcată de aici.

Unde nu reușesc rețelele neuronale?

Această imagine este preluată din acest post mediu

Rețelele neuronale, în teorie, sunt foarte bune aproximatoare ale funcțiilor. Acestea pot învăța aproape întotdeauna orice relație semnificativă între intrări (date sau caracteristici) și ieșiri (etichete sau ținte). Prin urmare, acestea sunt folosite într-o mare varietate de aplicații, de la detectarea obiectelor și clasificare la conversație la vorbire la text, la agenți de joc inteligent care pot bate jucătorii campioni ai lumii umane. Există multe modele de rețele neuronale eficiente, care satisfac nevoile diverse de astfel de aplicații, cum ar fi rețelele neuronale convoluționale (CNN), rețelele neuronale recurente (RNN), codificatoare auto etc.

Cu toate acestea, potrivit autorilor lucrării, le lipsește abilitatea de bază, ceea ce pare banal pentru un om sau pentru albine! Aceasta este capacitatea de a număra / manipula numerele și, de asemenea, de a extrapola relația numerică dintr-un model numeric observabil. În lucrare, se arată că rețelele neuronale standard se luptă să învețe chiar și o funcție de identitate (o funcție a cărei intrare și ieșire este identică; f (x) = x), care este cea mai simplă relație numerică. Imaginea de mai jos arată eroarea medie pătrată (MSE) a diverselor NN-uri instruite pentru a învăța o astfel de funcție de identitate.

Imaginea arată MSE pentru o rețea Neurală standard care are aceeași arhitectură antrenată folosind funcții de activare diferite (non-liniaritate) în straturile ascunse

De ce nu reușesc?

Motivul principal pentru care NN-urile nu reușesc să învețe o astfel de reprezentare numerică este utilizarea funcțiilor non-liniare de activare în straturile ascunse ale rețelei. Astfel de funcții de activare sunt cruciale pentru a învăța relația abstractă non-liniară dintre intrări și etichete, dar nu reușesc mizerabil când vine vorba de a învăța reprezentarea numerică în afara intervalului de numere văzute în datele de instruire. Prin urmare, astfel de rețele sunt foarte bune pentru a memora schema numerică văzută în setul de antrenament, dar nu reușesc să extrapoleze bine această reprezentare.

Este ca memorarea unui răspuns sau a unui subiect fără a înțelege conceptul care stă la baza examenului. Făcând acest lucru, se poate efectua foarte bine dacă întrebările similare sunt puse la examen, cu toate acestea, în cazul întrebărilor răsucite sunt puse concepute pentru a testa cunoștințele și nu capacitatea de memorare a unui candidat.

Gravitatea acestui eșec corespunde direct gradului de neliniaritate în cadrul funcției de activare aleasă. Din imaginea de mai sus, se poate vedea clar că funcțiile neliniare cu restricții dure, precum Tanh și Sigmoid, au o capacitate foarte mică de a generaliza bine decât funcția non-liniară cu restricții moi, cum ar fi PReLU și ELU.

Soluție: Acumulator neuronal (NAC)

Acumulatorul neural (NAC) constituie baza modelului NALU. NAC este un model (unitate) simplu, dar eficient de rețea neuronală, care susține capacitatea de a învăța adunarea și scăderea - ceea ce este o proprietate de dorit pentru a învăța funcțiile liniare în mod eficient.

NAC este un strat special de liniaritate ai cărui parametri de greutate au restricțiile de a avea singurele valori 1, 0 sau -1. Prin constrângerea valorilor de greutate într-o asemenea manieră, împiedică stratul să schimbe scara datelor de intrare și rămân consecvente în toată rețeaua, indiferent de câte straturi sunt stivuite împreună. Prin urmare, rezultatul va fi combinația liniară a vectorului de intrare care poate reprezenta cu ușurință operațiuni de adunare și scădere.

Intuiție: Pentru a înțelege acest fapt, să luăm în considerare următoarele exemple de straturi NN care realizează operația aritmetică liniară pe intrări.

Ilustrație pentru a explica faptul că straturile de rețea Neurală fără părtinire și cu greutăți valorile -1, 0 sau 1 pot învăța extrapolarea liniară.

Așa cum se arată în straturile NN de mai sus, rețeaua poate învăța să extrapoleze funcții aritmetice simple precum adunarea și scăderea (y = x1 + x2 și y = x1-x2) prin restricționarea parametrilor de greutate la -1, 0 și 1.

Notă: Așa cum se arată în diagramele de rețea de mai sus, NAC nu conține niciun parametru de parțialitate (b) și nici o neliniaritate aplicată la ieșirea unităților de straturi ascunse.

Deoarece constrângerea cu privire la parametrii de greutate în NAC este greu de învățat de către rețeaua neuronală standard, autorii au descris formulă foarte utilă pentru a învăța astfel de valori de parametri restrânși folosind parametrii standard (nerestricționat) W_hat și M_hat. Acești parametri sunt ca orice parametri standard de greutate NN, care pot fi inițializați la întâmplare și pot fi învățați pe parcursul procesului de formare. Formula pentru a obține W în termeni de W_hat și M_hat este dată mai jos:

Formula indică produsul înțelept între două matrici

Utilizând ecuația de mai sus pentru a calcula parametrii de greutate în rețea, garantează că valoarea acestor parametri va fi în intervalul [-1,1] cu mai înclinați spre -1, 0 și 1. De asemenea, această ecuație este o ecuație diferențiată (ale cărui instrumente derivate pot fi calculate cu privire la parametrii de greutate). Prin urmare, va fi mai ușor pentru NAC să învețe W folosind coborârea gradientului și propagarea spatelui. Mai jos este diagrama arhitecturală a unei unități NAC.

Arhitectura NAC pentru a învăța funcții numerice simple (liniare)

Implementarea NAC în python folosind Tensorflow

După cum ne putem imagina, NAC este un model NN simplu, cu puține modificări. Mai jos am arătat implementarea simplă a unui singur strat NAC în python folosind biblioteca Tensorflow și Numpy.

import numpy ca np
import tensorflow ca tf
# Definiți un acumulator neuronal (NAC) pentru adunare / scădere -> Util pentru a învăța operația de adăugare / scădere

def nac_simple_single_layer (x_in, out_units):
    „““
    Atribute:
        x_in -> Tensor de intrare
        out_units -> numărul de unități de ieșire

    Întoarcere:
       y_out -> Tensor de ieșire cu forma menționată
       W -> Matricea de greutate a stratului
    „““
# Obțineți numărul de funcții de intrare (numere)
    in_features = x_in.shape [1]

    # definește W_hat și M_hat

    W_hat = tf.get_variable (forma = [in_features, out_units],
    initializator = tf.initializers.random_uniform (minval = -2, maxval = 2),
    trainable = True, name = "W_hat")

    M_hat = tf.get_variable (forma = [in_shape, out_units],
    initializator = tf.initializers.random_uniform (minval = -2, maxval = 2),
    trainable = True, name = "M_hat")

    # Ia W folosind formula
    W = tf.nn.tanh (W_hat) * tf.nn.sigmoid (M_hat)

    y_out = tf.matmul (x_in, W)

    returnați Y_out, W

În codul de mai sus, am folosit inițializarea uniformă la întâmplare pentru parametrii trainabili W_hat și M_hat, dar se poate folosi orice tehnică recomandată de inițiere a greutății pentru acești parametri. Pentru codul complet de lucru, vă rugăm să consultați repoziția mea GitHub menționată la sfârșitul acestui post.

Trecând dincolo de adunare și scădere: NAC pentru funcții numerice complexe

Deși modelul de rețea neuronală simplă menționat mai sus este capabil să învețe funcții aritmetice de bază precum adunarea și scăderea, este de dorit să aibă capacitatea de a învăța operațiuni aritmetice mai complexe, cum ar fi funcțiile de înmulțire, divizare și putere.

Mai jos este arhitectura modificată a NAC care este în măsură să învețe funcții numerice mai complexe folosind spațiul jurnal (valori și exponenți logaritmici) pentru parametrii de greutate. Observă că modul în care acest NAC este diferit de cel menționat mai întâi în postare.

Arhitectura NAC pentru a învăța o funcție numerică mai complexă

Așa cum se arată în diagrama de mai sus, această celulă aplică funcția jurnal la datele de intrare înainte de înmulțirea matricei cu matricea de greutate W și apoi aplică o funcție exponențială pe matricea rezultată. Formula de obținere a producției este dată în ecuația de mai jos.

Ecuația de ieșire a NAC complex prezentată mai sus. Epsilon aici este o valoare foarte mică pentru a evita situația jurnal (0) în timpul antrenamentului

Prin urmare, totul este la fel în ceea ce privește mecanismul de bază al NAC simplu și NAC complex, inclusiv formula pentru greutăți W limitate în termeni de W_hat și M_hat. Singura diferență este că NAC complex aplică spațiu de jurnal pe intrarea și ieșirea stratului.

Implementarea Python a NAC complex:

La fel ca în cazul arhitecturilor ambelor NAC, implementarea piton a NAC complex este aproape aceeași, cu excepția modificării menționate în formula tensorului de ieșire. Mai jos este codul pentru astfel de NAC.

# definiți un NAC complex în spațiul jurnal -> pentru funcții aritmetice mai complexe, cum ar fi înmulțirea, divizarea și puterea

def nac_complex_single_layer (x_in, out_units, epsilon = 0.000001):

    „““
    : param x_in: vector de caracteristică de intrare
    : param out_units: numărul de unități de ieșire ale celulei
    : param epsilon: valoare mică pentru a evita jurnalul (0) în rezultatul de ieșire
    : return m: tensor de ieșire
    : retur W: matrice de greutate asociată
    
    „““

    in_features = x_in.shape [1]

    W_hat = tf.get_variable (forma = [in_shape, out_units],
    initializator = tf.initializers.random_uniform (minval = -2, maxval = 2),
    trainable = True, name = "W_hat")

    M_hat = tf.get_variable (forma = [în_făcări, out_units],
    initializator = tf.initializers.random_uniform (minval = -2, maxval = 2),
    trainable = True, name = "M_hat")
    # Obțineți matricea de parametri fără restricții W
    W = tf.nn.tanh (W_hat) * tf.nn.sigmoid (M_hat)

    # Caracteristică de intrare Express în spațiul jurnal pentru a învăța funcții complexe
    x_modified = tf.log (tf.abs (x_in) + epsilon)

    m = tf.exp (tf.matmul (x_modified, W))

    întoarce m, W

Încă o dată, pentru codul complet de lucru, vă rugăm să consultați repoziția mea GitHub menționată la sfârșitul acestui post.

Împreună: Unități logice de aritmetică neurală (NALU)

Până acum, ne putem imagina că peste două modele NAC combinate împreună pot învăța aproape toate operațiunile aritmetice. Aceasta este ideea cheie din spatele NALU care cuprinde combinația ponderată a unui NAC simplu și a unui NAC complex menționat mai sus, controlat de un semnal de poartă învățat. Astfel, NAC constituie blocul de bază pentru NALU. Deci, dacă ați înțeles corect NAC, NALU este foarte ușor de înțeles. Dacă nu o faceți, vă rugăm să vă faceți timp și să parcurgeți din nou ambele explicații ale NAC Imaginea de mai jos descrie arhitectura NALU.

Diagrama adnotată a NALU

Așa cum se arată în imaginea de mai sus, în NALU ambele NAC (celule violet) sunt interpolate (combinate) de un control învățat al porții sigmoide (celulă portocalie) astfel încât ieșirea fiecărui NAC poate fi activată sau dezactivată de poartă pe baza funcției numerice pe care încearcă să antreneze rețeaua pentru.

După cum am menționat mai sus, NAC simplu calculează funcția de acumulare, astfel încât este responsabil pentru stocarea operațiunilor liniare (adunare și scădere) ale NALU. În timp ce NAC complex este responsabil să îndeplinească funcțiile sale numerice mai complexe, cum ar fi funcțiile de înmulțire, divizare și putere. Ieșirea celulelor subiacente într-un NALU poate fi reprezentată matematic după cum urmează:

NAC simplu: a = W X
NAC complex: m = exp (W jurnal (| X | + e))
Unde, W = tanh (W_hat) * sigmoid (M_hat)
Celula de poartă: g = sigmoid (GX) # În cazul în care G este matrice de parametri trainable standard
# Și în final, ieșirea NALU
NALU: y = g * a + (1-g) * m # Unde * este produsul înțelept

În formula de mai sus a NALU, putem spune că dacă poarta de ieșire g = 0, atunci rețeaua va învăța doar funcții complexe, dar nu și cele simple. În schimb, dacă g = 1, atunci rețeaua va învăța doar funcții aditive și nu cele complexe. Prin urmare, NALU în totalitate poate învăța orice funcții numerice constând în funcții de înmulțire, adunare, scădere, divizare și putere, astfel încât să se extrapoleze bine la numerele din afara gamei de numere care au fost văzute în timpul antrenamentului.

Implementarea Python a NALU:

În implementarea NALU, vom folosi atât NAC simplu, cât și complex, definit în fragmentele de cod anterioare.

def nalu (x_in, out_units, epsilon = 0.000001, get_weights = False):
    „““
    : param x_in: vector de caracteristică de intrare
    : param out_units: numărul de unități de ieșire ale celulei
    : param epsilon: valoare mică pentru a evita jurnalul (0) în rezultatul de ieșire
    : param get_weights: adevărat dacă doriți să obțineți ponderile modelului
                        în schimb
    : retur: tensor de ieșire
    : retur: matricea de greutate a porții
    : revenire: matricea de greutate NAC1 (simplă NAC)
    : revenire: matricea de greutate NAC2 (complex NAC)
    „““

    in_features = x_in.shape [1]

    # Obține tensorul de ieșire de la NAC simplu
    a, W_simple = nac_simple_single_layer (x_in, out_units)

    # Obține tensorul de ieșire de la NAC complex
    m, W_complex = nac_complex_single_layer (x_in, out_units, epsilon = epsilon)

    # Strat semnal poartă
    G = tf.get_variable (forma = [in_facere, ieșiri_unități],
    inițiator = tf.random_normal_initializer (stddev = 1.0),
    trainable = True, name = "Gate_weights")

    g = tf.nn.sigmoid (tf.matmul (x_in, G))

    y_out = g * a + (1 - g) * m

    if (get_weights):
        returnează y_out, G, W_simple, W_complex
    altceva:
        întoarceți-vă

Din nou, în codul de mai sus, am folosit inițializarea normală la întâmplare pentru parametrii porții G, dar se poate folosi orice tehnică de inițializare recomandată a greutății.

Ibelieve NALU este o descoperire modernă în AI și în special în rețelele neuronale care pare foarte promițătoare. Acestea pot deschide uși pentru multe aplicații, care par a fi dificil pentru NN-urile standard.

Autorii au arătat diverse experimente și rezultate care implementează NALU în diferite domenii ale aplicațiilor de rețea neuronală din hârtie, de la sarcini simple de învățare a funcțiilor aritmetice până la numărarea numărului de cifre scrise de mână în serii furnizate de imagini MNIST pentru a face învățarea rețelei să evalueze programele de calculator!

Rezultatele sunt uimitoare și demonstrează că NALU excelează pentru a generaliza bine în aproape toate sarcinile care implică reprezentare numerică decât modelele NN standard. Recomand cititorilor să arunce o privire asupra acestor experimente și a rezultatelor sale pentru a obține o mai bună înțelegere a modului în care NALU poate fi util în unele sarcini numerice interesante.

Cu toate acestea, este puțin probabil ca NAC sau NALU să fie soluția perfectă pentru fiecare sarcină. Mai degrabă, ei demonstrează o strategie de proiectare generală pentru crearea de modele care sunt destinate unei clase țintă de funcții numerice.

Mai jos este linkul către depozitul meu GitHub care arată implementarea completă a fragmentelor de cod afișate în această postare.

https://github.com/faizan2786/nalu_implementation

Sunteți bineveniți să încercați diverse funcții pentru a testa modelul meu folosind diferite hiperprametre pentru a regla rețeaua.

Anunță-mă dacă ai întrebări sau gânduri la această postare în comentariile de mai jos și voi încerca tot posibilul să le abordez.

PS: Aceasta este prima mea postare pe blog pe orice subiect. Așadar, orice recomandare, sugestie și sfaturi viitoare, tehnice și non-tehnice despre scrierea mea sunt binevenite.