top of page
Rechercher

Come creare una rete neurale da zero in Python - Math & Code

  • Photo du rédacteur: Literature & Cie
    Literature & Cie
  • 14 janv. 2021
  • 8 min de lecture

In questo articolo, cerco di spiegarvi in modo completo e matematico come funziona una semplice rete neurale a 2 livelli, codificandone una da zero in Python. Questo articolo è scritto tanto per voi per aiutarvi a capire il dietro le quinte di un algoritmo così popolare, come per me di avere un foglio trucco che spiega con le mie parole come funziona una rete neurale.


In primo luogo, studiamo la logica dietro l'elaborazione di questi algoritmi e l'intuizione matematica dietro di loro. Poi, ci immergiamo nella codifica di una rete neurale (mescolando linee di codice Python ed equazioni matematiche). Alla fine, immaginiamo come potremmo generalizzare il nostro modello e renderlo più adattabile per risolvere problemi complessi della vita reale.


Perché abbiamo bisogno di reti neurali ?


L'apprendimento automatico può essere definito come tutte le tecniche utilizzate per addestrare un computer a svolgere un compito senza essere esplicitamente codificato per farlo. Sebbene questa definizione possa sembrare esoterica, può essere facilmente compresa.

Diciamo che abbiamo una semplice equazione come segue :


y = f(x)


In questa equazione, x è una data variabile e y è una variabile dipendente da x. f è una funzione sconosciuta. Ad esempio, in una macchina a guida autonoma x potrebbe essere l'ambiente circostante una macchina (le altre auto, i segni di circolazione, il colore dei semafori...) e y come la macchina si comporta rispetto a tali elementi. Nell'apprendimento automatico, dato che non conosciamo f, usiamo metodi statistici per avvicinarci il più possibile.


f può avere molte forme. La più semplice è quando f è lineare. In questo caso, f assomiglia a :

y = ax + b


Dove a e b sono numeri reali che ci avviciniamo grazie a metodi statistici. In particolare, usiamo il famoso metodo di regressione lineare per determinare i due coefficienti a e b.


E se f non fosse lineare ?


Come possiamo sapere quale forma ha ? È esponenziale ? Quadratico ? Per la maggior parte delle funzioni, infatti, non possiamo saperlo.


Qui, il trucco viene da un teorema dimostrato da Kurt Hornik chiamato Teorema di Approssimazione Universale :



Per noi profani della matematica, dice semplicemente che sommando abbastanza funzioni lineari e trasformandole con la stessa funzione non lineare, possiamo approssimare qualsiasi funzione f.


In poche parole, selezionando i giusti pesi e bias, possiamo approssimare qualsiasi funzione non lineare. E questo è un grosso problema perché nel nostro mondo molte relazioni tra un input e un output non sono lineari.


Costruire una rete neurale da zero


Ora che abbiamo un'idea di come si usa una rete neurale, proviamo a codificarne una. Per maggiore chiarezza, scomponiamo il funzionamento di una rete neurale in diverse fasi :


- la struttura

- la propagazione degli alimenti per animali

- la funzione di perdita

- la retropropagazione del gradiente e le equazioni matematiche sottostanti

  • funzionamento della nostra rete neurale


La struttura


In questo articolo, creeremo solo una rete neurale a due livelli, ma l'idea rimane la stessa per la rete neurale a più di due livelli.


Come potete vedere nell'immagine, la nostra rete neurale è composta da tre livelli : un livello di input, uno nascosto e uno di output.


NB : Di solito omettiamo il livello di input, da cui il nome « rete neurale a due livelli ».


Le connessioni fatte tra l'input e gli strati nascosti, così come tra gli strati nascosti e quelli di output sono chiamati pesi.


Per calcolare i valori nelle cellule (le chiamiamo attivazioni), usiamo un'equazione matematica che conosciamo bene :


a = φ (xW + b)

Dove a è l'attivazione, x l'input, W i pesi, b i bias e φ una funzione non lineare chiamata funzione di attivazione (si potrebbe anche sentire non-linearità).


La funzione di attivazione crea non-linearità nel nostro modello. Dovreste richiamare qui il teorema universale. Effettivamente, troviamo la stessa struttura. Quindi, per creare la struttura della nostra rete neurale, abbiamo bisogno di :


  • un input x (i globuli rossi nella figura sopra)

  • un prodotto obiettivo y

  • uno strato di output (le caselle verdi nella figura sopra)

  • vettori di peso

  • vettori di bias

Nel seguente frammento di codice, definiamo la funzione __init__ della nostra classe Neuralnetwork.

Si può anche osservare un tasso di apprendimento. Presumo familiarità con i concetti di base di apprendimento automatico. Se non sai qual è il tasso di apprendimento, ti consiglio il famoso corso di Machine Learning di Andrew Ng alla Stanford University che puoi trovare su Coursera.


Come potete vedere, inizializzo i miei pesi a caso con piccoli numeri poiché implementeremo un algoritmo di discesa gradiente in pochi minuti per ottimizzare i nostri pesi e le nostre distorsioni. Non possiamo impostare i nostri pesi a zero, poiché il modello non aggiornerebbe le equazioni di discesa del gradiente. Se volete saperne di più, leggete https://machinelearningmastery.com/why-initialize-a-neural-network-with-random-weights/ .


Inoltre, creiamo un vettore weight1 che ha una forma (numero di righe del nostro livello di input, numero di righe dello strato nascosto). Allo stesso modo, il vettore weight2 ha una forma (numero di righe del livello nascosto, numero di righe del livello di output). Lo si può vedere chiaramente nell'immagine qui sopra dove le matrici di peso creano le connessioni tra i livelli.


La propagazione dei mangimi


Per addestrare una rete neurale, ci sono tre passi fondamentali.


In primo luogo, si calcola tutte le attivazioni. In secondo luogo, si calcola l'errore. In terzo luogo si ottimizza i pesi per ridurre al minimo l'errore del modello. Fate questi tre punti iteratively alcune centinaia di volte (li denominiamo epoche), per sviluppare un modello che è abbastanza esatto.



La propagazione feedforward ha lo scopo di calcolare le attivazioni. Ricorda, prima calcoliamo l'equazione vettorializzata z = x*W+b e poi applichiamo la funzione di attivazione φ.


La funzione di attivazione che abbiamo scelto è la funzione sigmoide. Ha notevoli proprietà, di cui una particolarmente utile, i suoi derivati danno l'equazione :



Come vedrete, questa proprietà ci aiuterà molto a semplificare i nostri calcoli. Un'altra proprietà interessante è che i pesi sono numeri reali tra 0 e 1. Così, abbiamo numeri abbastanza piccoli nella nostra rete neurale, e non rischiamo di avere una rete neurale lenta a causa di calcoli pesanti.


NB: Attualmente quasi tutte le reti utilizzano la funzione Relu (Rectified Linear Unit) come funzione principale di attivazione, ad eccezione dell'ultimo strato.


Le nostre attivazioni sono calcolate come segue: a = sigmoid(z).


Da qui il codice :


La funzione di perdita


Ora che abbiamo definito il processo di base del nostro algoritmo, abbiamo bisogno di qualcosa per calcolare il nostro errore attraverso la rete. Ricordate, una rete neurale è un processo in tre fasi. Primo, calcoliamo un output, secondo un errore e, infine, minimizziamo l'errore. Per calcolare l'errore, usiamo una funzione di perdita. Esistono un sacco di funzioni di perdita, qui prendiamo la funzione di perdita di cross-entropia, che è :



Se vuoi saperne di più sull'origine di questa funzione e avere un'intuizione, vai a controllare l'entropia di Shannon : https://en.wiktionary.org/wiki/Shannon_entropy


Per calcolare la perdita ad ogni passo del nostro processo di back-propagazione, chiameremo questa funzione.



NB : Per problemi di regressione (cioè problemi in cui sono previsti valori continui), viene spesso utilizzata la funzione di perdita dell'errore al quadrato medio. Preferiamo la funzione di cross-entropia poiché abbiamo a che fare con un problema di classificazione. La funzione di cross-entropia penalizza pesantemente le previsioni discrete errate con un'alta sicurezza, rendendola quindi una soluzione molto migliore per i problemi di classificazione rispetto alla funzione di errore quadrata media.



Il processo di propagazione posteriore e le equazioni matematiche sottostanti

Ora arriva la parte matematica. In poche parole, l'intera idea della nostra rete neurale è quello di minimizzare la funzione di perdita trovando i pesi e le distorsioni ottimali. Per farlo, usiamo un algoritmo di ottimizzazione chiamato algoritmo di discesa a gradiente.


Data una funzione J, una matrice di peso W e un tasso di apprendimento α, possiamo minimizzare la nostra funzione calcolando iterativamente :


Più specificamente, ∂J/∂W è il gradiente della funzione di perdita.


Ma prima di tuffarci nella matematica, facciamo un'intuizione sulla regola della catena. Intuitivamente, quello che stiamo cercando di calcolare è quanto w1 e w2 influenzano la nostra funzione di perdita, che è l'errore della nostra rete. Tuttavia, come potete vedere nell'espressione della funzione di perdita, non c'è una relazione diretta tra la nostra funzione di perdita J e i nostri pesi. J è definito rispetto a un target e a un output. Quindi, dobbiamo "incatenare" tutti i nostri risultati per ricavare la variazione di J indotta da una variazione di w1. Effettivamente, in primo luogo deriveremo la variazione della nostra uscita indotta da una variazione dei pesi ed allora deriveremo la variazione della nostra funzione di perdita indotta da una variazione della nostra uscita. E concatenando tutto insieme, possiamo trovare la variazione di J indotta da una variazione di w1.


Le seguenti equazioni matematiche mostrano come ricavare il gradiente usando la regola della catena.


Il primo consiste nel ricavare il gradiente dell'errore rispetto ad ogni peso collegando lo strato nascosto allo strato di output. Questi pesi sono i coefficienti nella nostra matrice pesante2 che abbiamo creato in precedenza.


Dove z2 è il prodotto puntiforme delle attivazioni nascoste a1 e dei pesi che collegano lo strato nascosto allo strato di output z2 = a1 * w2 + b2.


Allora, possiamo esaminare ogni fattore. In primo luogo :



Abbiamo derivato la funzione di cross-entropia rispetto all'output.


Secondo :

Ricordiamo che l'output = sigmoid(z2). Inoltre, non dimenticare la proprietà specifica dei derivati della funzione sigmoide che ho spiegato sopra.


Terzo :



Poiché z2 = a1 * w2 + b2


Combinando le 3 equazioni di cui sopra, alla fine otteniamo :

Per il secondo passo, dobbiamo calcolare il gradiente dell'errore rispetto ad ogni peso che collega il livello di input al livello nascosto (o dovremmo avere una rete neurale più grande, gli strati nascosti tra di loro). Questa volta, i pesi sono i coefficienti del peso della matrice t1 che abbiamo creato precedentemente nel metodo __init__ nella nostra classe Neuralnetwork.


Per farlo, usiamo i calcoli che abbiamo fatto prima. Come potete vedere chiaramente, stiamo concatenando il risultato poiché stiamo combinando ogni calcolo che abbiamo fatto per arrivare finalmente a pesare1. In una rete neurale più grande, faremmo esattamente lo stesso, con più matrici di peso da calcolare.


Allo stesso modo, prendiamo z1 = w1 * x + b1 la somma ponderata dell'input della nostra rete neurale e a1 = sigmoid(z1) le attivazioni dello strato nascosto.


Iniziamo dicendo che :

Eccoci di nuovo, studiamo a fondo le derivate che compongono ∂J/∂W1.

Otteniamo quanto sopra seguendo i calcoli che abbiamo fatto in precedenza.


Da z2 = a1 * w2 + b2.

Di nuovo, ricordate i derivati di una funzione sigmoide.

Da z1 = w1 *x + b1.


Quindi combinando tutto, otteniamo :

Infine, dobbiamo aggiornare i pregiudizi della nostra rete neurale. L'idea di base rimane la stessa, abbiamo bisogno di calcolare il gradiente della funzione di perdita rispetto a b2 e b1 e aggiornare i nostri pregiudizi. Eccoci di nuovo, ma questa volta i calcoli sono molto più semplici. Prendiamo un vettore bi che rappresenta il bias per il livello i, usiamo di nuovo la regola della catena.


Ora abbiamo :

Ma :

Poiché z(i) = a(i-1) * w(i) + b(i)


Quindi, abbiamo ottenuto :

Pertanto, il codice finale :

Come potete vedere, ho anche incluso nel codice il tasso di apprendimento, secondo l'algoritmo Gradient Descent. Allora dobbiamo aggiornare i nostri pesi attuali.


Far funzionare la nostra rete neurale


Ora che abbiamo una rete neurale completamente funzionante con i processi di feedforward e back-propagation implementati, possiamo creare un'istanza della nostra classe di Rete Neurale e permettergli di aggiornare i suoi pesi e distorsioni per un numero predefinito di volte (le epoche).


Qui, 1500 volte potrebbe essere un buon inizio per avere una rete neurale abbastanza performante.

Alimentiamo la nostra rete neurale con un insieme di addestramento, qui possiamo passare una tabella che ci dà i dati booleani circa il fattore chiave che conduce una persona ad avere un diabete. Facciamo in modo che la nostra rete neurale impari gli schemi che predicono se una persona ha o meno il diabete.


Trovate sotto l'intero codice e in bonus, una funzione per tracciare l'evoluzione dei costi rispetto al numero di epoche :

Se tracciamo l'evoluzione della nostra perdita, otteniamo una forma curva, che è quello che vogliamo. Significa che la rete neurale sta imparando correttamente, non essendo bloccata in Optima locale ma progressivamente raggiungendo il minimo globale.

Come potete vedere, la nostra funzione di perdita è minimizzata quando l'algoritmo raggiunge l'800 epoca. Quindi, possiamo abbassare il nostro numero di epoche correre, per ottimizzare la nostra rete neurale.


Dove andare da qui ?


Ora che avete una chiara comprensione di come funziona una rete neurale, potete generalizzare la rete neurale cambiando il numero di neuroni per ogni strato e aggiungendo anche altri strati nascosti.


È inoltre possibile implementare tassi di apprendimento ciclici in quanto è un modo semplice per ottenere modelli di apprendimento automatico all'avanguardia, come indicato nel corso fast.ai che consiglio vivamente.


Spero vi sia piaciuto questo articolo; potete trovare sotto tutti i riferimenti sorprendenti che ho usato. Tutti i crediti a loro. Se avete domande o osservazioni, non esitate a commentare qui sotto. Sono tutt'altro che un esperto di machine learning e eventuali correzioni o suggerimenti sono i benvenuti !


Fonti e referenze













 
 
 

Posts récents

Voir tout

Commentaires


Post: Blog2_Post

Formulaire d'abonnement

Merci pour votre envoi !

©2020 par Literature & Cie. Créé avec Wix.com

bottom of page