Home » Intelligenza Artificiale » 📋 URL
L’utilizzo dei Neural Network ha permesso all’intelligenza artificiale di cercare di replicare l’intelligenza biologica ottenuta tramite i neuroni e le interazioni tra più neuroni.
Il deep learning parte dunque dal tentativo di rispecchiare il computer biologico umano tramite un’entità comparabile al neurone: il percettrone (anche se poi, de facto, lo chiameremo spesso neurone anch’esso). Nella sua forma generale un percettrone è composto dalle seguenti parti:
All’interno di esso viene utilizzata una funzione di attivazione, che permette effettivamente al percettrone di imparare dai dati e proporre un output.
La funzione di attivazione più semplice che possiamo avere è la seguente
a_j=g\left[\sum_{i=0}^{n}w_{i,j}\cdot a_i\right]
A parole: ogni output a_j è determinato dalla somma di tutti di input a_0,\,\dots,\,a_i,\,\dots,\,a_n moltiplicati ognuno per il peso w_{i,j} che che connette l’input i con l’output j. Dopo aver trovato il valore di tutti gli input moltiplicati per il proprio peso sul totale si utilizza la funzione g(\cdot) che, in base al problema, dovrà effettivamente restituire il valore a_j in base alle regole che sono state stabilite per lo specifico problema.
Nota: utilizziamo, tra gli input, un valore di bias che ci permetta di (1) prevenire l’overfitting e (2) migliorare l’accuratezza dell’agente. Nell’immagine precedente, il valore di bias è a_0=1 e viene usato, ad esempio, per aggiustare i bounday decisionali (si veda l’esempio di implementazione delle funzioni logiche).
Due semplici modi per definire la funzione g(\cdot) sono i seguenti
Dove vediamo, a sinistra, una funzione a gradino con punti non derivabili, mentre a destra abbiamo la versione equivalente, derivabile però in ogni suo punto (perlopiù è D^{\infty}), cosa che sarà utile nell’esecuzione degli algoritmi che vedremo in seguito.
Naturalmente, nel caso in cui si può avere a che fare con delle reti più complesse nelle quali non sono presenti soltanto 2 input. In generale potrebbe essere presente un numero arbitrario di input a seconda del problema. Nel caso di una funzione a 3 input (dunque nello spazio tridimensionale), il corrispondente della curva logistica è
Nota: con più di 3 input si ottengono degli spazi n-dimensionali che non rappresenteremo graficamente, ma possono essere utilizzati per separare efficientemente dei dati che diamo in input all’algoritmo nel caso in cui essi non possano essere separati da funzioni in una o due variabili.
L’utilizzo di funzioni con \text{span} maggiore rispetto a quello dei dati viene utilizzato quando vogliamo separare dei punti per i quali una separazione all’interno dello stesso spazio vettoriale necessiterebbe l’utilizzo di funzioni molto complesse come nell’immagine (c) seguente
In tale caso è possibile utilizzare una funzione che passi “sotto” (o eventualmente “sopra”) ai punti neri ma “sopra” (”sotto”) quelli bianchi.
È facile immaginare che tramite l’utilizzo di un solo percettrone non si riescono a risolvere dei problemi particolarmente complessi. Introduciamo dunque i multilayer (feed-forward) Neural Network, tramite i quali siamo in grado di costruire delle funzioni di attivazione molto più complesse rispetto ai semplici casi precedenti. Immaginiamo dunque di costruire una rete a 2 input con 1 output e 1 hidden layer come in figura
Tramite il passaggio nell’hidden layer siamo in grado di costruire una nuova funzione di attivazione non lineare che riesce a risolvere dei problemi più complessi grazie alla sovrapposizione (pesata) delle diverse funzioni ai attivazione dei singoli percettroni.
Nota: spesso si possono vedere anche delle rappresentazioni simili (ed equivalenti!) per i Neural Network come nella figura seguente, utilizzati in alcuni casi invece della rappresentazione sopra.
Esercizio: calcolare il valore di h_{\bf w}({\bf x})=a_5, ovvero l’input visto dal nodo 5 considerando come input \fbox{1}=x_1 e \fbox{2}=x_2 (in riferimento all’immagine (alpha)) e considerando la presenza di pesi di bias del tipo w_{0,i}, dove il valore di i rappresenta il nodo a cui tale peso fa riferimento, dunque il peso w_{0,3} è il peso di bias applicato al nodo 3.
Soluzione
\begin{aligned}a_5&=g(w_{0,5}+a_3\cdot w_{3,5}+a_4\cdot w_{4,5})\\[10pt]&=g(w_{0,5}+w_{3,5}\cdot g(w_{0,3}+x_1\cdot w_{1,3}+x_2\cdot w_{2,3})+w_{4,5}\cdot g(w_{0,4}+x_1\cdot w_{1,4}+x_2\cdot w_{2,4}))\\[10pt]&=h_{\bf w}({\bf x})\end{aligned}
Tramite un network di questo tipo possiamo creare delle funzioni più complesse come le seguenti
E, in generale, grazie a questo tipo di rete siamo in grado di rappresentare una qualunque funzione—anche se, di fatto, stiamo creando in realtà un’approssimazione dell’andamento vero e proprio della funzione.
Altre funzioni di attivazione spesso usate
Oltre alle funzioni di attivazione già viste (gradino e sigmoidea, ovvero versione smooth del gradino) vengono spesso utilizzate le seguenti funzioni:
- ReLU (Rectifier Linear Unit):
\text{ReLU}(x)=\max(0,\,x)=\begin{cases}0&x\leq0\\x&x\geq0\end{cases} - Softplus (smooth ReLU):
\text{Softplus}(x)=\log\left(1+e^{x}\right) - Tangente iperbolica (l’espressione con gli esponenziali utilizzata non è standard, ma è equivalente ad essa):
\tanh(x)=\frac{e^{2x}-1}{e^{2x}+1}
Come calcolare i pesi ottimali per un network
Usiamo, ad esempio, la funzione loss L_2 (errore quadratico). Dobbiamo cercare di minimizzare l’errore commesso dall’algoritmo nel calcolo dell’output corretto, dunque dobbiamo cercare di minimizzare la funzione di loss tramite la tecnica iterativa della back-propagation. Cerchiamo quindi di decomporre l’errore a partire dall’output, ovvero cerchiamo di procedere ”all’indietro” per capire quanto hanno contribuito i diversi layer alla creazione di tale errore. In questo modo possiamo andare ad aggiustare i pesi che rendono sbagliato l’output e lasciare invariati quelli che invece non lo causano. Naturalmente, nella ricerca degli errori, i percettroni con un peso maggiore sull’output finale sono quelli che con più probabilità causano gli errori.
Per una spiegazione più approfondita fare riferimento alla pagina sul calcolo dei pesi per un Neural Network.
Codifica degli input
Per codificare gli input di un Neural Network possiamo utilizzare diversi metodi a seconda del tipo di valore che vogliamo codificare. Per esempio
- Per valori booleani: assegniamo 0 e 1
- Per valori numerici: assegniamo i valori così come sono—o eventualmente li scaliamo per riportarli all’interno di un determinato range
- Per valori categorici: utilizziamo la codifica one-hot tramite cui costruiamo una tabella in cui inseriamo tutti i possibili attributi che il problema può presentare, e assegniamo il valore 1 all’attributo che stiamo rappresentando e 0 agli altri. Ad esempio
La funzione di loss log-negativo
La maggior parte delle applicazioni deep learning utilizzano come funzione di loss la log-negativo (negative log likelihood) grazie alla sua caratteristica di dare molto peso alle predizioni sbagliate, invitando così l’algoritmo a commettere meno errori. Utilizziamo questa funzione quando vogliamo ottenere una sola probabilità in output {\bf w}^*
\begin{aligned}{\bf w}^* &= \argmin_{\bf w}\left[-\sum_j \log(P_{\bf w}({\bf y}_j \mid {\bf x}_j))\right] \\[10pt] &= \argmax_{\bf w}\left[\sum_j \log(P_{\bf w}({\bf y}_j \mid {\bf x}_j))\right] \\[10pt] &= \fbox{$\displaystyle\argmax_{\bf w}\left[\prod_j P_{\bf w}({\bf y}_j \mid {\bf x}_j)\right]$}\end{aligned}
Nota: questo tipo di funzione di loss riesce a diminuire l’entropia incrociata. L’entropia incrociata rappresenta la differenza tra le predizioni e gli effettivi valori corretti che dovrebbero essere predetti. Se l’entropia incrociata diminuisce significa che l’algoritmo sta diventando più accurato nelle sue previsioni.
In poche parole
✅ I Neural Network sono una branchia dell’intelligenza artificiale che cerca di imitare il funzionamento del cervello umano tramite degli algoritmi. Si basano sul concetto dei neuroni biologici e utilizzano dunque il corrispondente compotazionale—il percettrone—come unità fondamentali di elaborazione. I percettroni sono composti da un numero arbitrario di input, dei pesi che determinano l’influenza del singolo input sul risultato finale, e una funzione di attivazione che riesca effettivamente a predere le decisioni.
✅ All’interno di un percettrone, i valori di input vengono moltiplicati per i pesi e sommati insieme, includendo anche un eventuale peso di bias. La funzione di attivazione attraverso cui tali input vengono elaborati può essere semplice, come una funzione gradino, oppure più complessa, formata da una funzione più complessa in sé, oppure tramite la sovrapposizione di più funzioni semplici.
✅ I Neural Network possono essere costituiti da più percettroni collegati tra loro, formando così degli strati (layer): più ne sono presenti e più il network viene definito “profondo” (deep).
✅ I layer interni al network vengono definiti hidden layer, i quali consentono di fatto la creazione di funzioni di attivazione non lineari e risolvere problemi più complessi.
✅ Per addestrare un Neural Network, si cerca di minimizzare l’errore commesso dall’algoritmo rispetto agli output desiderati (nel supervised learning), oppure si cerca di ottenere gli output corretti sulla base di qualche altra metrica (unsupervised learning). In ogni caso, l’addestramento e la ricerca dei pesi ottimali per i diversi input del network (anche gli input interni tra i diversi hidden layer) venogno calcolati grazie all’utilizzo di funzione di loss, che permette di quantificare numericamente gli errori che l’algoritmo commette.
✅ Una funzione spesso utilizzata come funzione di loss è la log-negativo, tramite cui è possibile assegnare un peso che tende velocemente verso infinito per errori molto gravi.
✅ Per codificare gli input, si possono utilizzare diverse metodologie, come l’assegnazione di valori booleani, numerici o la codifica one-hot per le variabili categoriche.