Finalità

In questo articolo (spero il primo di una piccola serie), vedremo come implementare una rete neurale in Visual Basic .NET, ovvero un modello capace di processare dei dati in input, aggiustando la sua meccanica interna per imparare a produrre un risultato desiderato. Vedremo più avanti cosa significhi questo. Il presente articolo si concentrerà sulle definizioni generali relative alle reti neurali ed ai loro comportamenti, offrendo una loro semplice implementazione che il lettore sarà libero di testare. Nel paragrafo finale, scriveremo una rete neurale che sia capace di scambiare tra loro due variabili.

Introduzione

Una prima definizione


Il termine "rete neurale" è tipicamente usato in riferimento ad una rete, o circuito, costituita da neuroni. Possiamo differenziare due tipologie di reti neurali, ovvero: a) biologiche e b) artificiali. Naturalmente, parlando qui di sviluppo software, nel proseguo dell'articolo ci riferiremo alla seconda tipologia, ma questo genere di implementazioni attingono comunque il proprio modello base, nonché ispirazione, dalla loro controparte naturale; tenendo conto di ciò, può essere utile considerare il funzionamento di ciò che intendiamo quando parliamo di reti neurali biologiche.

Reti neurali naturali


Si tratta di reti costituite di neuroni biologici, tipiche delle creature viventi. I neuroni sono interconnessi tra loro nel sistema nervoso periferico o in quello centrale. Nelle neuroscienza, i gruppi di neuroni vengono identificati dalle funzioni fisiologiche che eseguono.

Reti neurali artificiali


Le reti artificiali sono i modelli matematici, implementabili attraverso sistemi elettronici ed informatici, che mimano il funzionamento di una rete biologica. Si tratta, in parole semplici, di avere un certo set di neuroni artificiali adatti a risolvere un particolare problema nel campo dell'intelligenza artificiale. Allo stesso modo di quelle naturali, una rete artificiale può "apprendere", attraverso il tempo e le prove, la natura di un problema, divenendo sempre più capace ed efficiente nella sua risoluzione.

Neuroni


Dopo questa breve premessa, diventa quindi ovvio che, in una rete, sia essa naturale o artificiale, l'entità conosciuta come "neurone" assume un'importanza capitale, in quanto ricevente degli input di rete e, in qualche modo, responsabile della corretta elaborazione dei dati, che produrranno un risultato. Pensiamo al nostro cervello: si tratta di un meraviglioso supercomputer dotato di circa 86*10^9 neuroni. Un numero strabiliante di entità che scambiano tra di loro informazioni in maniera costante, che corrono su 10^14 sinapsi. Come abbiamo detto, i modelli artificiali cercano di catturare e riprodurre il funzionamento di base di un neurone, che è basato su tre parti principali:
  • Soma, o corpo cellulare
  • Assone, la linea di uscita di un neurone
  • Dendrita, linea di ingresso di un neurone, che riceve i dati da altri assoni attraverso le sinapsi
Il some esegue una somma pesata dei segnali in input, verificando l'eventuale eccedenza di un dato limite. Nel caso tale condizione si verifichi, il neurone viene attivato (risultando in un'azione potenziale), mentre permane in uno stato di quiete in caso contrario. Un modello artificiale cerca di replicare tali sottoparti, attraverso la creazione di un array di entità interconnesse capace di auto-tararsi sulla base degli input ricevuti, controllando costantemente i risultati prodotti rispetto ad un output atteso.

L'apprendimento di una rete neurale

Tipicamente, la teoria delle reti neurali identifica 3 metodi principali attraverso cui una rete apprende (dove, con "apprende" intenderemo da qui in avanti il processo attraverso cui la rete stessa si modifica per rendersi in grado di produrre un certo risultato a fronte di un input specifico). Riguardo all'implementazione in Visual Basic, ci riferiremo ad uno solo di questi metodi, ma è comunque utile introdurre la totalità dei paradigmi in gioco, in modo da avere una panoramica più completa. Affinchè una rete neurale possa apprendere, deve essere istruita (o allenata). L'allenamento di una rete può essere supervisionato, quando disponiamo di tuple di valori input/output. Attraverso di essi, una rete può imparare a costruire le relazioni che esistono tra i neuroni. Un altro metodo è il training non supervisionato, che si basa su algoritmi appositi, i quali modificheranno i pesi della rete in base ai dati in ingresso, risultando in reti che apprenderanno a raggruppare informazioni secondo metodi probabilistici. L'ultimo metodo è l'apprendimento rinforzato, che non si basa su dati da presentare alla rete, ma su algoritmi di esplorazione che produrranno input da sottoporre ad un agente, il quale verificherà l'impatto dei dati stessi sulla rete, tentando di identificare le performance di quest'ultima nella risoluzione di un dato problema. In questo articolo ci soffermeremo sul primo caso presentato, ovvero il training supervisionato.

Training supervisionato


Consideriamo questo metodo da una prospettiva più ravvicinata. Cosa significa addestrare una rete mediante supervisione? Come abbiamo visto, ciò riguarda prevalentemente il presentare alla rete dei set di dati in ingresso e attesi in uscita. Supponiamo di voler insegnare alla nostra rete come eseguire la somma di due numeri. In questo caso, seguendo il paradigma dell'addestramento supervisionato, dovremo sottoporre alla nostra rete dei dati di ingresso (diciamo [1;5]), ma anche renderle noto ciò che attendiamo come risultato (nel nostro caso, [6]). Quindi, un particolare algoritmo dovrà essere applicato per poter valutare lo stato corrente della rete, aggiustandola sulla base dei dati introdotti. Tale algoritmo, usato nel nostro esempio, è detto "di retropropagazione".

Retropropagazione


La retropropagazione degli errori è una tecnica secondo cui si inizializza la rete (tipicamente con valori casuali, che andremo ad assegnare ai pesi di ciascun neurone), procedendo poi nell'inoltrare i segnali in input e confrontando il risultato di rete con i valori attesi. A questo punto si calcola la deviazione tra l'effettivo prodotto e quello atteso, ottenendo un fattore delta che deve essere retropropagato ai nostri neuroni, per aggiustarne i valori iniziali in rispetto all'entità degli errori calcolati. Attraverso la ripetizione del processo, molti set di valori input/output verranno presentati alla rete, che ogni volta confronterà i dati reali con quelli ideali. In un dato tempo, questo tipo di operazione produrrà output sempre più precisi, calibrando il peso di ogni componente della rete, e raffinando di conseguenza la sua abilità di processare i dati ricevuti. Per un'estensiva spiegazione della retropropagazione, cfr. la sezione Bibliografia, al termine dell'articolo.



Creare una rete neurale

Dopo aver visto alcuni concetti preliminare sulle reti neurali, dovremo essere in grado di sviluppare un modello che risponda ai paradigmi illustrati. Senza eccedere in spiegazioni di tipo matematico, che non sono necessarie a meno di non volere approfondire ulteriormente il tema trattato, procederemo passo passo nella codifica di una rete semplice ma funzionale, che testeremo quando terminata. La prima cosa che dobbiamo considerare è la struttura di una rete neurale: sappiamo che è organizzata in neuroni, e che i neuroni sono interconnessi tra loro. Ma ignoriamo come.

Layer


Ed è a questo punto che fanno la loro comparsa i layers. Un layer è un gruppo di neuroni che condividono funzioni simili. Per esempio, pensiamo al punto di ingresso di una rete neurale, dove sono disposti i neuroni che riceveranno gli input. Quello sarà il layer di ingresso, lo strato che raggruppa tutti i neuroni dalla funzione simile, in questo caso limitata alla ricezione e trasmissione dei dati. Avremo sicuramente un layer di output, costituito dai neuroni che ricevono il risultato di elaborazioni pregresse. Tra questi layers, inoltre, ve ne possono risiedere un certo numero di altre tipologie, detti "nascosti" in quanto l'utente non deve potervi avere accesso diretto. Il numero di layer nascosti, così come il numero di neuroni che costituiscono ogni strato, dipende pesantemente dalla natura e complessità del problema che desideriamo risolvere. Per riassumere, ogni rete sarà costituita da strati, ciascuno dei quali conterrà un certo numero predeterminato di neuroni.

Neuroni e dendriti


Dal punto di vista artificiale, possiamo concepire un neurono come un'entità che espone un certo valore, il quale verrà aggiustato durante le iterazioni di addestramento, e legato ad altri neuroni attraverso i dendriti, che nel nostro caso saranno rappresentati da sotto-entità con un peso iniziale casuale. Il processo di training consisterà nel valorizzare i neuroni dello strato di input, i quali trasmetteranno il proprio valore agli strati superiori attraverso i dendriti. Negli strati più alti avverrà la stessa cosa, fino a che non sarà raggiunto lo strato di output. Infine, calcoleremo il delta tra l'output corrente e quello atteso, ripercorrendo la rete per aggiustare i pesi dei dendriti, il valore dei neuroni, e ciascun valore di deviazione, per correggere la rete stessa. Verrà quindi eseguito un nuovo round di addestramento.

Preparazione delle classi di rete

Avendo visto come una rete neurale sia strutturata, possiamo ora scrivere alcune classi attraverso cui gestiremo le varie entità di rete. Nello snippet che segue, delineerò le classi Dendrite, Neuron e Layer, che utilizzeremo congiuntamente nella stesura della classe NeuralNetwork 

Classe Dendrite

Public Class Dendrite
    Dim _weight As Double
 
    Property Weight As Double
        Get
            Return _weight
        End Get
        Set(value As Double)
            _weight = value
        End Set
    End Property
 
    Public Sub New()
        Me.Weight = r.NextDouble()
    End Sub
End Class

Per prima cosa, la classe Dendrite: come si noterà, è costituita da una sola proprietà, denominata Weight. Nell'inizializzare un Dendrite, un valore casuale viene attribuito alla proprietà Weight. Il tipo della proprietà Weight è Double, in quanto i nostri valori di input saranno sempre un valore compreso tra zero ed uno, necessitando quindi di una precisione consistente quanto alle cifre decimali. Vedremo il perchè a breve. Non sono necessarie né previste altre proprietà o funzioni per questa classe.

Classe Neuron

Public Class Neuron
    Dim _dendrites As New List(Of Dendrite)
    Dim _dendriteCount As Integer
    Dim _bias As Double
    Dim _value As Double
    Dim _delta As Double
 
    Public Property Dendrites As List(Of Dendrite)
        Get
            Return _dendrites
        End Get
        Set(value As List(Of Dendrite))
            _dendrites = value
        End Set
    End Property
 
    Public Property Bias As Double
        Get
            Return _bias
        End Get
        Set(value As Double)
            _bias = value
        End Set
    End Property
 
    Public Property Value As Double
        Get
            Return _value
        End Get
        Set(value As Double)
            _value = value
        End Set
    End Property
 
    Public Property Delta As Double
        Get
            Return _delta
        End Get
        Set(value As Double)
            _delta = value
        End Set
    End Property
 
    Public ReadOnly Property DendriteCount As Integer
        Get
            Return _dendrites.Count
        End Get
    End Property
 
    Public Sub New()
        Me.Bias = r.NextDouble()
    End Sub
End Class

Successivamente, la classe Neuron. Come facilmente intuibile, esporrà una proprietà valore, Value, di tipo Double, per le stesse motivazioni viste sopra, ed una serie di potenziali Dendrite, il cui numero dipenderà dal numero di neuroni del layer a cui l'attuale neurone verrà connesso. Avremo quindi una proprietà Dendrite, una DendriteCount per la restituzione del numero di Dendrite, e due proprietà che ci serviranno durante il processo di ricalibrazione, ovvero Bias e Delta.

Classe Layer

Public Class Layer
    Dim _neurons As New List(Of Neuron)
    Dim _neuronCount As Integer
 
    Public Property Neurons As List(Of Neuron)
        Get
            Return _neurons
        End Get
        Set(value As List(Of Neuron))
            _neurons = value
        End Set
    End Property
 
    Public ReadOnly Property NeuronCount As Integer
        Get
            Return _neurons.Count
        End Get
    End Property
 
    Public Sub New(neuronNum As Integer)
        _neuronCount = neuronNum
    End Sub
End Class

In ultimo, la classe Layer, che è semplicemente un contenitore per un array di Neuron. Chiamandone il metodo New, l'utente dovrà indicare da quanti neuroni uno strato sia costituito. Vedremo nella prossima sezione come queste classi interagiscano nel contesto di una rete neurale completa.

Classe NeuralNetwork

La nostra classe NeuralNetwork può essere vista come una lista di strati (ciascuno dei quali eredità le proprietà delle classi sottostanti, ovvero neuroni e dendriti). Una rete neurale deve essere eseguita e addestrata, di conseguenza avremo sicuramente dei metodi utili a tale fine. L'inizializzazione della rete deve specificare, tra le altre cose, un parametro che chiameremo "tasso di apprendimento", ossia una variabile che andrà ad incidere nel ricalcolo dei pesi. Come il nome fa presupporre, il tasso di apprendimento (o learning rate) è un fattore che determina la velocità di apprendimento di una rete. Dal momento che si tratta di un fattore correttivo, il suo valore deve essere scelto accuratamente: se è troppo elevato, ma vi è una grossa presenza di possibilità di input, una rete potrebbe non apprendere correttamente. In generale, una buona abitudine è quella di impostare un tasso relativamente piccolo, per incrementarlo se la ricalibrazione della rete dovesse essere troppo lenta.

Vediamo un'implementazione della classe NeuralNetwork quasi completa:

Public Class NeuralNetwork
    Dim _layers As New List(Of Layer)
    Dim _learningRate As Double
 
    Public Property Layers As List(Of Layer)
        Get
            Return _layers
        End Get
        Set(value As List(Of Layer))
            _layers = value
        End Set
    End Property
 
    Public Property LearningRate As Double
        Get
            Return _learningRate
        End Get
        Set(value As Double)
            _learningRate = value
        End Set
    End Property
 
    Public ReadOnly Property LayerCount As Integer
        Get
            Return _layers.Count
        End Get
    End Property
 
    Sub New(LearningRate As Double, nLayers As List(Of Integer))
        If nLayers.Count < 2 Then Exit Sub
 
        Me.LearningRate = LearningRate
 
        For ii As Integer = 0 To nLayers.Count - 1
 
            Dim l As Layer = New Layer(nLayers(ii) - 1)
            Me.Layers.Add(l)
 
            For jj As Integer = 0 To nLayers(ii) - 1
                l.Neurons.Add(New Neuron())
            Next
 
            For Each n As Neuron In l.Neurons
                If ii = 0 Then n.Bias = 0
 
                If ii > 0 Then
                    For k As Integer = 0 To nLayers(ii - 1) - 1
                        n.Dendrites.Add(New Dendrite)
                    Next
                End If
 
            Next
 
        Next
    End Sub
 
    Function Execute(inputs As List(Of Double)) As List(Of Double)
        If inputs.Count <> Me.Layers(0).NeuronCount Then
            Return Nothing
        End If
 
        For ii As Integer = 0 To Me.LayerCount - 1
            Dim curLayer As Layer = Me.Layers(ii)
 
            For jj As Integer = 0 To curLayer.NeuronCount - 1
                Dim curNeuron As Neuron = curLayer.Neurons(jj)
 
                If ii = 0 Then
                    curNeuron.Value = inputs(jj)
                Else
                    curNeuron.Value = 0
                    For k = 0 To Me.Layers(ii - 1).NeuronCount - 1
                        curNeuron.Value = curNeuron.Value + Me.Layers(ii - 1).Neurons(k).Value * curNeuron.Dendrites(k).Weight
                    Next k
 
                    curNeuron.Value = Sigmoid(curNeuron.Value + curNeuron.Bias)
                End If
 
            Next
        Next
 
        Dim outputs As New List(Of Double)
        Dim la As Layer = Me.Layers(Me.LayerCount - 1)
        For ii As Integer = 0 To la.NeuronCount - 1
            outputs.Add(la.Neurons(ii).Value)
        Next
 
        Return outputs
    End Function
 
    Public Function Train(inputs As List(Of Double), outputs As List(Of Double)) As Boolean
        If inputs.Count <> Me.Layers(0).NeuronCount Or outputs.Count <> Me.Layers(Me.LayerCount - 1).NeuronCount Then
            Return False
        End If
 
        Execute(inputs)
 
        For ii = 0 To Me.Layers(Me.LayerCount - 1).NeuronCount - 1
            Dim curNeuron As Neuron = Me.Layers(Me.LayerCount - 1).Neurons(ii)
 
            curNeuron.Delta = curNeuron.Value * (1 - curNeuron.Value) * (outputs(ii) - curNeuron.Value)
 
            For jj = Me.LayerCount - 2 To 1 Step -1
                For kk = 0 To Me.Layers(jj).NeuronCount - 1
                    Dim iNeuron As Neuron = Me.Layers(jj).Neurons(kk)
 
                    iNeuron.Delta = iNeuron.Value *
                                    (1 - iNeuron.Value) * Me.Layers(jj + 1).Neurons(ii).Dendrites(kk).Weight *
                                    Me.Layers(jj + 1).Neurons(ii).Delta
                Next kk
            Next jj
        Next ii
 
 
        For ii = Me.LayerCount - 1 To 0 Step -1
            For jj = 0 To Me.Layers(ii).NeuronCount - 1
                Dim iNeuron As Neuron = Me.Layers(ii).Neurons(jj)
                iNeuron.Bias = iNeuron.Bias + (Me.LearningRate * iNeuron.Delta)
 
                For kk = 0 To iNeuron.DendriteCount - 1
                    iNeuron.Dendrites(kk).Weight = iNeuron.Dendrites(kk).Weight + (Me.LearningRate * Me.Layers(ii - 1).Neurons(kk).Value * iNeuron.Delta)
                Next kk
            Next jj
        Next ii
 
        Return True
    End Function
 
End Class


Metodo New()


Quando avremo inizializzato la nostra rete, essa richiederà un tasso di apprendimento, ed una lista di Layers. Processando tale lista, si nota come ciascun layer risulti nella generazione di neuroni e dendriti, assegnati ai rispettivi genitori. Chiamando il metodo New() dei neuroni e dei dendriti, avverrà un'assegnazione casuale dei loro valori iniziali e dei loro pesi. Se i layer passati sono meno di uno, la routine uscirà dal flusso, in quanto una rete neurale deve essere dotata di almeno due layer, input ed output.

Sub New(LearningRate As Double, nLayers As List(Of Integer))
    If nLayers.Count < 2 Then Exit Sub
    Me.LearningRate = LearningRate
 
    For ii As Integer = 0 To nLayers.Count - 1
        Dim l As Layer = New Layer(nLayers(ii) - 1)
        Me.Layers.Add(l)
        For jj As Integer = 0 To nLayers(ii) - 1
            l.Neurons.Add(New Neuron())
        Next
        For Each n As Neuron In l.Neurons
            If ii = 0 Then n.Bias = 0
            If ii > 0 Then
                For k As Integer = 0 To nLayers(ii - 1) - 1
                    n.Dendrites.Add(New Dendrite)
                Next
            End If
        Next
    Next
End Sub

Funzione Execute()


Come precedentemente detto, una rete deve possedere una funzione attraverso cui processare i dati di input, facendo in modo che essi vengono veicolati nella rete, raggiungendo lo strato di output. La funzione seguente realizza tale obiettivo. Per prima cosa, verificheremo la correttezza dei dati in ingresso: se il numero degli input differisce dal numero di neuroni del layer, la funzione non potrà essere eseguita.  Ciascun neurone dovrà essere inizializzato. Per il primo layer, quello di input, assegneremo semplicemente la proprietà Value dei neuroni, in qualità di ingresso. Per gli altri layers, calcoleremo la somma pesata data dal valore attuale del neurone, più il valore del neurone del layer precedente, moltiplicato per il peso del dendrita. In ultimo, eseguiremo sul valore così calcolato la funzione sigmoide, che analizzeremo più sotto. Processando tutti i layers, il layer di uscita riceverà un risultato, che è il parametro che la nostra funzione restituirà al controllo del flusso di programma, sotto forma di List(Of Double).

Function Execute(inputs As List(Of Double)) As List(Of Double)
    If inputs.Count <> Me.Layers(0).NeuronCount Then
        Return Nothing
    End If
    For ii As Integer = 0 To Me.LayerCount - 1
        Dim curLayer As Layer = Me.Layers(ii)
        For jj As Integer = 0 To curLayer.NeuronCount - 1
            Dim curNeuron As Neuron = curLayer.Neurons(jj)
            If ii = 0 Then
                curNeuron.Value = inputs(jj)
            Else
                curNeuron.Value = 0
                For k = 0 To Me.Layers(ii - 1).NeuronCount - 1
                    curNeuron.Value = curNeuron.Value + Me.Layers(ii - 1).Neurons(k).Value * curNeuron.Dendrites(k).Weight
                Next k
                curNeuron.Value = Sigmoid(curNeuron.Value + curNeuron.Bias)
            End If
        Next
    Next
 
    Dim outputs As New List(Of Double)
    Dim la As Layer = Me.Layers(Me.LayerCount - 1)
    For ii As Integer = 0 To la.NeuronCount - 1
        outputs.Add(la.Neurons(ii).Value)
    Next
    Return outputs
End Function


Funzione Sigmoid()


La sigmoide è una funzione matematica nota per la sua tipica forma ad "S". È definita per ciascun valore reale. Utilizziamo questa funzione nella programmazione di reti neurali a causa della sua differenziabilità - requisito della retropropagazione - ed inoltre perché introduce non-linearità nella rete stessa (rende cioè la rete in grado di apprendere la correlazione tra input che non producono combinazioni lineari). In più, per ciascun valore reale, la funzione sigmoide ritorna un valore tra zero ed uno (escluso il limite superiore). Questa funzione ha dunque caratteristiche che la rendono particolarmente adatta alla retropropagazione.



Funzione Train()


Una rete viene inizializzata con valori casuali, di conseguenza - senza ricalibrazione - i valori di ritorno saranno anch'essi piuttosto casuali. Senza una procedura di addestramento, una rete appena creata sarà quasi inutile. Definiamo, con il termine addestramento, il processo attraverso cui una rete neurale viene mantenuta attiva, continuando a fornirle input e confrontando i suoi risultati con quelli che si attendono. Trovando le differenze tra questi valori, procederemo nella ricalibrazione di ciascun peso e valore sulla rete, avvicinando sempre di più la rete alla produzione di quanto desideriamo.
In codice VB, qualcosa di questo tipo:

Public Function Train(inputs As List(Of Double), outputs As List(Of Double)) As Boolean
    If inputs.Count <> Me.Layers(0).NeuronCount Or outputs.Count <> Me.Layers(Me.LayerCount - 1).NeuronCount Then
        Return False
    End If
 
    Execute(inputs)
 
    For ii = 0 To Me.Layers(Me.LayerCount - 1).NeuronCount - 1
        Dim curNeuron As Neuron = Me.Layers(Me.LayerCount - 1).Neurons(ii)
        curNeuron.Delta = curNeuron.Value * (1 - curNeuron.Value) * (outputs(ii) - curNeuron.Value)
        For jj = Me.LayerCount - 2 To 1 Step -1
            For kk = 0 To Me.Layers(jj).NeuronCount - 1
                Dim iNeuron As Neuron = Me.Layers(jj).Neurons(kk)
                iNeuron.Delta = iNeuron.Value *
                                (1 - iNeuron.Value) * Me.Layers(jj + 1).Neurons(ii).Dendrites(kk).Weight *
                                Me.Layers(jj + 1).Neurons(ii).Delta
            Next kk
        Next jj
    Next ii
 
    For ii = Me.LayerCount - 1 To 0 Step -1
        For jj = 0 To Me.Layers(ii).NeuronCount - 1
            Dim iNeuron As Neuron = Me.Layers(ii).Neurons(jj)
            iNeuron.Bias = iNeuron.Bias + (Me.LearningRate * iNeuron.Delta)
            For kk = 0 To iNeuron.DendriteCount - 1
                iNeuron.Dendrites(kk).Weight = iNeuron.Dendrites(kk).Weight + (Me.LearningRate * Me.Layers(ii - 1).Neurons(kk).Value * iNeuron.Delta)
            Next kk
        Next jj
    Next ii
 
    Return True
End Function

Come sempre, verifichiamo la correttezza degli input, avviando poi la rete chiamando il metodo Execute(). Quindi, partendo dall'ultimo layer, processeremo ogni neurone e dendrita, correggendo ciascun valore applicando la differenza tra outputs. La stessa cosa faremo sui pesi dei dendriti, introducendo il tasso di apprendimento della rete, come visto sopra. Al termine di un round di addestramento (o, più realisticamente, al completamento di molte centinaia di round), inizieremo ad osservare che gli output prodotti dalla rete saranno sempre più precisi.

Una applicazione di test

In questa sezione vedremo una semplice applicazione di test, che utilizzeremo per creare una rete semplice, la cui finalità sarà l'esecuzione di un banale swap di variabili. Nel proseguo, considereremo una rete avente tre layers: uno strato di ingresso, costituito da due neuroni; un layer nascosto con quattro neuroni; uno strato di uscita con due neuroni. Ciò che ci aspettiamo dalla rete, una volta che sarà abbastanza addestrata, è la capacità di invertire i valori che le vengono presentati o, in altre parole, di trasferire il valore del neurone di input #1 sul neurone di output #2, e viceversa.

Nella versione scaricabile della classe NeuralNetwork troverete un paio di metodi qui non discussi, che si riferiscono al rendering grafico ed al debug testuale della rete. Li utilizzeremo in questo esempio, e potrete consultarli scaricando il sorgente allegato.

Inizializzare la rete


Utilizzando le nostre classi, una rete neurale potrà essere inizializzata semplicemente dichiarandola, e passandole un tasso di apprendimento e layers iniziali. Per esempio, nell'applicazione di test è visibile uno snippet del genere:

Dim network As NeuralNetwork
 
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    Dim layerList As New List(Of Integer)
    With layerList
        .Add(2)
        .Add(4)
        .Add(2)
    End With
 
    network = New NeuralNetwork(21.5, layerList)
End Sub

Ho definito la mia rete globalmente rispetto al Form di avvio, poi - durante l'evento Load() - ho creato i layers di cui necessito, passandoli all'inizializzatore di rete. Ciò risulterà nel popolamento dei layer stessi mediante la creazione dei neuroni specificati, con ciascun elemento inizializzato in modo casuale quanto a valori, delta, bias, e pesi.

Eseguire una rete neurale


Avviare e addestrare una rete sono anch'esse procedure molto semplici. Tutto ciò che richiedono è l'esecuzione del metodo adatto, passando un set di valori di input/output. Nel caso del metodo Execute(), per lanciare la rete, potremmo ad esempio scrivere:

Dim inputs As New List(Of Double)
inputs.Add(txtIn01.Text)
inputs.Add(txtIn02.Text)
 
Dim ots As List(Of Double) = network.Execute(inputs)
txtOt01.Text = ots(0)
txtOt02.Text = ots(1)

Dove txtIn01, txtIn02, txtOt01, txtOt02 sono dei TextBoxes presenti sul Form. Ciò che abbiamo fatto con il codice di cui sopra è stato semplicemente di prendere il valore di due TextBoxes come input, scrivendo poi in un'altra coppia di TextBoxes i valori ritornati. La stessa procedura si applica in caso di addestramento.



Scambiare due variabili


Nel video che segue, si può vedere una semplice sessione di addestramento. Il programma in esecuzione è lo stesso che è possibile scaricare al termine di questo articolo. Come si può notare, iniziando da valori casuali, la rete inizierà progressivamente ad apprendere come invertire i segnali che riceve. I valori di zero ed uno non vanno intesi come numeri precisi, bensì come "segnali di una certa intensità". Osservando i risultati dell'addestramento, si potrà notare come l'intensità dei valori crescerà verso uno, o decrescerà verso zero, senza mai toccare del tutto gli estremi, ma rappresentando una loro tendenza. Di conseguenza, al termine di una intensa sessione di addestramento, vedremo come, nel caso di una coppia in input del tipo [0;1], desiderando l'output [1;0], riceveremo un risultato simile a [0.999999998; 0.00000000015], dove i segnali di partenza sono rappresentazioni, per intensità, dei valori conducivi ad essi più vicini, comportandosi quindi come un vero neurone, la cui eccitabilità ed attivazione dipendono da soglie energetiche.


Codice sorgente

Il codice sorgente fin qui discusso può essere scaricato tramite il link: https://code.msdn.microsoft.com/Basis-of-Neural-Networks-a379549c

Bibliografia

Altre lingue

Il presente articolo è disponibile nelle seguenti localizzazioni: