Il presente articolo è stato tradotto automaticamente.

Esecuzione di test

Retropropagazione di reti neurali per programmatori

James McCaffrey

Scarica il codice di esempio

James McCaffreyUna rete neurale artificiale può essere pensata come un meta-funzione che accetta un numero fisso di input numerico e produce un numero fisso di output numerici. Nella maggior parte delle situazioni, una rete neurale ha uno strato di neuroni nascosti dove ogni neurone nascosto completamente è collegato con i neuroni di input e i neuroni di output. Associato a ciascun individuo nascosto neurone e ogni neurone di output individuali sono un insieme di valori di peso e un singolo valore cosiddetto bias. I pesi e le distorsioni determinano i valori di output per un dato insieme di valori di input.

Quando le reti neurali sono utilizzate per modellare un set di dati esistenti che previsioni possono essere fatto su nuovi dati, la sfida principale è trovare il set di valori di peso e pregiudizi che generano output che meglio corrispondono ai dati esistenti. La tecnica più comune per la stima dei pregiudizi e pesi ottimale della rete neurale viene chiamata retro-propagazione. Anche se ci sono molte ottime referenze che descrivono la matematica complessa che sottostanno alla retro-propagazione, ci sono pochissime guide disponibili per i programmatori che spiegano chiaramente come programmare l'algoritmo di retro-propagazione. Questo articolo spiega come implementare retro-propagazione. Io uso il linguaggio C#, ma non si dovrebbe avere alcun problema di refactoring di codice qui presentato in altre lingue.

Il modo migliore per vedere dove sono diretto è di dare un'occhiata allo screenshot di un programma demo in Figura 1. Il programma demo crea una rete neurale che ha tre neuroni di input, un livello nascosto con quattro neuroni, e due neuroni di output. Reti neurali con un singolo strato nascosto serve due funzioni di attivazione. In molte situazioni, però, le due funzioni di attivazione sono gli stessi, in genere la funzione sigmoidale. Ma in questa demo, al fine di illustrare la relazione tra le funzioni di attivazione e retro-propagazione, utilizzare le funzioni di attivazione differenti: la funzione sigmoidea per i calcoli di ingresso di nascosto e la funzione tanh (tangente iperbolica) per i calcoli di nascosto-uscita.

Back-Propagation Algorithm in Action
Figura 1 algoritmo di retro-propagazione in azione

A 3-4-2 collegato rete neurale richiede 3 * 4 + 4 * 2 = 20 peso valori e 4++ 2 = 6 valori di polarizzazione per un totale di 26 pesi e pregiudizi. Questi pesi e pregiudizi vengono inizializzati su valori più o meno arbitrari. I tre valori di input fittizi sono impostati su 1.0, 2.0 e 3.0. Con il peso iniziale, bias e valori di input, i valori iniziali di output vengono calcolati dalla rete neurale a essere {0.7225,-0.8779}. Il programma demo assume arbitrariamente che i due corretti output valori sono {-0.8500, 0.7500}. L'obiettivo dell'algoritmo retro-propagazione è di trovare un nuovo set di pesi e pregiudizi che generano uscite che sono molto vicini i valori corretti per ingressi {1.0, 2.0, 3.0}.

Retro-propagazione richiede due parametri liberi. Il tasso di apprendimento, di solito dato l'eta lettera greca nella letteratura di retro-propagazione, controlla quanto velocemente l'algoritmo converge ad una valutazione finale. Lo slancio, l'alfa della lettera greca, di solito dato aiuta l'algoritmo di retro-propagazione di evitare situazioni in cui l'algoritmo oscilla e mai converge ad una valutazione finale. Il programma demo imposta la velocità di apprendimento di 0,90 e lo slancio di 0,04. In genere questi valori si trovano per prova ed errore.

Trovare il miglior set di pesi e pregiudizi per una rete neurale è talvolta chiamato la rete di formazione. Formazione con retro-propagazione è un processo iterativo. A ogni iterazione, retro-propagazione calcola un nuovo set di valori rete neurale peso e pregiudizi che, in teoria, generare valori di output che sono più vicini ai valori bersaglio. Dopo la prima iterazione di formazione del programma demo, l'algoritmo di retro-propagazione trovato nuovi valori di peso e la parzialità che ha generato le nuove uscite di {-0.8932,-0.8006}. Il nuovo primo valore di output di-0.8932 era molto più vicina per il valore di output di destinazione primo di-0.8500. Il secondo nuovo valore di output di-0.8006 era ancora lontano dal valore di destinazione del 0.7500.

Il processo di formazione può essere terminato in una varietà di modi. Il programma demo scorre la formazione fino a quando la somma delle differenze assolute tra i valori di output e i valori di destinazione è < = 0,01 o formazione raggiunge 1.000 iterazioni. Nella demo, dopo sei iterazioni di formazione, retro-propagazione trovato una serie di reti neurali valori di peso e la parzialità che ha generato le uscite di {-0.8423, 0.7481}, che erano molto vicino alla {-0.8500, 0.7500} desiderato i valori di destinazione.

Questo articolo presuppone che hai competenze di programmazione di livello esperto e che avete una conoscenza di base delle reti neurali. (Per informazioni di base sulle reti neurali, vedere il mio articolo di maggio 2012, "Dive in Neural Networks," a msdn.microsoft.com/magazine/hh975375.) Il codice per il programma demo mostrato in Figura 1 è un po' troppo lunga per presentare in questo articolo, quindi mi ci concentro sulla spiegando le parti chiave dell'algoritmo. Il codice sorgente completo per il programma demo è disponibile a archive.msdn.microsoft.com/mag201210TestRun.

Definizione di una classe di rete neurale

Codifica una rete neurale che utilizza retro-propagazione si presta bene a un approccio object-oriented. La definizione della classe utilizzata per il programma demo è elencata in Figura 2.

Figura 2 classe di rete neurale

 

class NeuralNetwork
{
  private int numInput;
  private int numHidden;
  private int numOutput;
  // 15 input, output, weight, bias, and other arrays here
  public NeuralNetwork(int numInput, 
    int numHidden, int numOutput) {...}
  public void UpdateWeights(double[] tValues, 
    double eta, double alpha) {...}
  public void SetWeights(double[] weights) {...}
  public double[] GetWeights() {...}
  public double[] ComputeOutputs(double[] xValues) {...}
  private static double SigmoidFunction(double x)
  {
    if (x < -45.0) return 0.0;
    else if (x > 45.0) return 1.0;
    else return 1.0 / (1.0 + Math.Exp(-x));
  }
  private static double HyperTanFunction(double x)
  {
    if (x < -10.0) return -1.0;
    else if (x > 10.0) return 1.0;
    else return Math.Tanh(x);
  }
}

NumOutput, numInput, numHidden e membri campi definisce le caratteristiche dell'architettura di rete neurale. Oltre un semplice costruttore, la classe ha quattro metodi accessibili pubblicamente e due metodi di supporto. Metodo UpdateWeights contiene tutta la logica dell'algoritmo retro-propagazione. SetWeights metodo accetta una matrice di pesi e pregiudizi e la copia di tali valori u­fuo nelle matrici di membri. Metodo GetWeights esegue la retromarcia oper­ation copiando i pesi e le distorsioni in una sola matrice e restituendo la matrice. Metodo ComputeOutputs determina i valori di output di rete neurale utilizzando i valori di input, peso e pregiudizi correnti.

Metodo SigmoidFunction viene utilizzato come funzione di attivazione ingresso nascosto. Accetta un valore reale (tipo doppio in C#) e restituisce un valore compreso tra 0,0 e 1,0. Metodo HyperTanFunction anche accetta un valore reale, ma restituisce un valore tra -1.0 e 1,0. Il linguaggio c# ha una funzione di tangente iperbolica incorporata, Tanh, ma se si sta utilizzando un linguaggio che non ha una funzione nativa tanh, dovrete codice uno da zero.

Creazione di matrici

Uno dei tasti di programmazione con successo un algoritmo di retro-propagazione di rete neurale è di comprendere appieno le matrici utilizzate per memorizzare i valori di peso e di bias, memorizzare diversi tipi di valori di input e outpui, memorizzare i valori da un'iterazione precedente dell'algoritmo e memorizzare calcoli gratta e Vinci. Il diagramma in grande Figura 3 contiene tutte le informazioni è necessario conoscere per capire come programmare retro-propagazione. La reazione iniziale a Figura 3 rischia di essere qualcosa sulla falsariga di "dimenticarlo — questo è troppo complicato." Appendere lì. Retro-propagazione non è banale, ma una volta capito il diagramma sarete in grado di implementare retro-propagazione utilizzando qualsiasi linguaggio di programmazione.

The Back-Propagation Algorithm
Figura 3 l'algoritmo di retro-propagazione

Figura 3 ha ingressi primari e uscite ai bordi della figura, ma anche diversi input locale e i valori di output che si verificano all'interno del diagramma. Non dovrebbe sottovalutare la difficoltà di codifica di una rete neurale e la necessità di mantenere i nomi e i significati di tutti questi ingressi e uscite chiare. In base alla mia esperienza, un diagramma come quello di Figura 3 è assolutamente essenziale.

Le prime cinque delle 15 matrici utilizzate nella definizione di rete neurale descritta in Figura 2 affrontare i livelli di input nascosto e sono:

 

public class NeuralNetwork
{
  // Declare numInput, numHidden, numOutput
  private double[] inputs;
  private double[][] ihWeights;
  private double[] ihSums;
  private double[] ihBiases;
  private double[] ihOutputs;
...

La prima serie, denominata ingressi, contiene i valori di input numerici. Questi valori vengono tipicamente direttamente da qualche fonte di dati normalizzati ad esempio un file di testo. Il costruttore di NeuralNetwork crea un'istanza di dati di input come:

this.inputs = new double[numInput];

Matrice ihWeights (ingresso nascosto pesi) è una matrice bidimensionale virtuale implementata come una matrice di matrici.Il primo indice indica il neurone di input e il secondo indice il neurone nascosto.La matrice viene creata un'istanza dal costruttore come:

 

this.ihWeights = Helpers.MakeMatrix(numInput, numHidden);

Qui, gli helper è una classe di utilità dei metodi statici che consentono di semplificare la classe di rete neurale:

public static double[][] MakeMatrix(int rows, int cols)
{
  double[][] result = new double[rows][];
  for (int i = 0; i < rows; ++i)
    result[i] = new double[cols];
  return result;
}

IhSums matrice è una matrice vuota che viene utilizzata per contenere un calcolo intermedio nel metodo ComputeOutputs. La matrice contiene valori che diventeranno gli ingressi locali per i neuroni nascosti e viene creata un'istanza come:

this.ihSums = new double[numHidden];

Matrice ihBiases contiene i valori di bias per i neuroni nascosti. I valori del peso di rete neurale sono costanti che vengono applicati moltiplicando li con un valore di input locale. I valori di bias vengono aggiunti a una somma intermedia per produrre un valore di output locale, che diventa l'input locale al livello successivo. Viene creata un'istanza di matrice ihBiases come:

this.ihBiases = new double[numHidden];

Matrice ihOutputs contiene i valori che vengono emessi dai neuroni nascosti-strato (che diventano gli ingressi livello di output).

Le quattro matrici nella classe NeuralNetwork tengono i valori relativi allo strato nascosto-uscita:

private double[][] hoWeights;
private double[] hoSums;
private double[] hoBiases;
private double[] outputs;

Questi quattro matrici vengono creata un'istanza nel costruttore come:

this.hoWeights = Helpers.MakeMatrix(numHidden, numOutput);
this.hoSums = new double[numOutput];
this.hoBiases = new double[numOutput];
this.outputs = new double[numOutput];

La classe di rete neurale ha sei matrici che sono direttamente correlate all'algoritmo di retro-propagazione. Le prime due matrici contengono valori chiamati le pendenze per i neuroni di output e nascosto-strato. Un gradiente è un valore che descrive indirettamente quanto fuori, e in che direzione (positivo o negativo), uscite locali sono relativi output di destinazione. Di pendenza è utilizzati per calcolare i valori di delta, che vengono aggiunti al peso attuale e i valori di bias per produrre nuovi, migliori pesi e pregiudizi. C'è un valore di sfumato per ogni neurone strato nascosto e ogni neurone del livello di output. Le matrici vengono dichiarate come:

private double[] oGrads; // Output gradients
private double[] hGrads; // Hidden gradients

Le matrici sono istanziate nel costruttore come:

this.oGrads = new double[numOutput];
this.hGrads = new double[numHidden];

Le matrici di quattro finali in classe NeuralNetwork tengono i Delta (non gradienti) dalla precedente iterazione del ciclo di formazione.Questi precedenti Delta è necessarie se si utilizza il meccanismo di slancio per impedire la propagazione posteriore convergenza.Ritengo essenziale la quantità di moto, ma se si decide di implementare la quantità di moto non è possibile omettere queste matrici.Sono dichiarati come:

 

private double[][] ihPrevWeightsDelta;  // For momentum
private double[] ihPrevBiasesDelta;
private double[][] hoPrevWeightsDelta;
private double[] hoPrevBiasesDelta;

Queste matrici vengono create istanze come:

ihPrevWeightsDelta = Helpers.MakeMatrix(numInput, numHidden);
ihPrevBiasesDelta = new double[numHidden];
hoPrevWeightsDelta = Helpers.MakeMatrix(numHidden, numOutput);
hoPrevBiasesDelta = new double[numOutput];

Uscite di calcolo

Ogni iterazione del ciclo di formazione indicato Figura 1 ha due parti. Nella prima parte, uscite sono calcolate utilizzando gli ingressi di corrente primari, pesi e pregiudizi. Nella seconda parte, retro-propagazione viene utilizzata per modificare i pesi e le distorsioni. Il diagramma in Figura 3 illustra entrambe le parti del processo di formazione.

Lavorando da sinistra a destro, ingressi x0, X1 e x 2 vengono assegnati valori di 1.0, 2.0 e 3.0. Questi valori di input principali andare nei neuroni di strato di input e vengono emessi senza modifiche. Neuroni di input layer possono modificare l'input, come normalizzare i valori per essere all'interno di un determinato intervallo, nella maggior parte dei casi tale trasformazione avviene esternamente. Per questo motivo, diagrammi di rete neurale utilizzano spesso rettangoli o quadrati scatole per i neuroni di input per indicare che essi non sono elaborazione neuroni nello stesso senso che i neuroni di strato nascosto e il livello di output. Inoltre, questo riguarda la terminologia usata. In alcuni casi, la rete neurale mostrato in Figura3 sarebbe chiamato una rete di tre strati, ma perché il livello di input non esegue l'elaborazione, la rete neurale mostrata è talvolta chiamata una doppio strato di rete.

Successivamente, ciascuno dei neuroni di strato nascosto calcola un locale ingresso e un'uscita del locale. Ad esempio, il neurone nascosto più in basso, con l'indice [3], calcola la somma zero come (1.0)(0.4)+(2.0)(0.8)+(3.0)(1.2) = 5.6. La somma di zero è il prodotto della somma degli tre ingressi volte il peso associato ingresso nascosto. I valori di sopra di ogni freccia sono i pesi. Successivamente, il valore di bias,-7.0, viene aggiunto alla somma zero per produrre un locale ingresso valore di 5.6 + (-7.0) =-1.40. Quindi viene applicata la funzione di attivazione ingresso di nascosto a questo valore di input intermedio per produrre il valore di output locale del neurone. In questo caso, la funzione di attivazione è la funzione sigmoidale, così la produzione locale è 1 / (1 + exp(-(-1.40))) = 0.20.

I neuroni del livello di output per calcolare loro input e output allo stesso modo. Per esempio, in Figura 3, il neurone più in basso del livello di output con indice [1] calcola la somma zero come (0.86)(1.4)+(0.17)(1.6)+(0.98)(1.8)+(0.20)(2.0) = 3.73. Il bias associato viene aggiunto per dare l'input locale: 3.73 + (-5.0) = -1.37. E per dare l'output primario viene applicata la funzione di attivazione: Tanh(-1.37) =-0.88. Se si esamina il codice per ComputeOutputs, vedrai che il metodo calcola uscite esattamente come ho appena descritto.

Retro-propagazione

Anche se la matematica dietro la teoria della propagazione posteriore è abbastanza complicata, una volta che sai quali sono i risultati di matematica, non è troppo difficile attuazione retro-propagazione. Retro-propagazione inizia lavorando da destra a sinistra nel diagramma illustrato in Figura 3. Il primo passo è calcolare i valori di pendenza per ogni neurone del livello di output. Ricordiamo che il gradiente è un valore che ha informazioni riguardo la grandezza e la direzione di un errore. Le pendenze per i neuroni di strato di output sono calcolate in modo diverso dalle pendenze per i neuroni di strato nascosto.

Il gradiente di un neurone del livello di output è uguale al valore di destinazione (desiderato) meno il valore di uscita calcolata, volte il derivato di calcolo della funzione di attivazione a livello di output valutato al valore di uscita calcolata. Ad esempio, il valore di sfumato del neurone più in basso del livello di output in Figura 3, con indice [1], è calcolata come:

(0.75 – (-0.88)) * (1 – (-0.88)) * (1 + (-0.88)) = 0.37

Il 0,75 è il valore desiderato. Il-0.88 è il valore di uscita calcolata dal calcolo passo avanti. Ricordiamo che in questo esempio la funzione di attivazione a livello di output è la funzione tanh. È la derivata di calcolo di Tanh (1 - tanh(x)) * (1 + tanh(x)). L'analisi matematica è un po ' complicato, ma, in definitiva, la pendenza di un neurone del livello di output di calcolo è dato dalla formula qui descritta.

Il gradiente di un neurone di strato nascosto è uguale al derivato di calcolo della funzione di attivazione del livello nascosto valutato all'uscita del locale del neurone volte la somma del prodotto delle uscite primarie volte loro pesi nascosti-uscita associati. Per esempio, in Figura 3, è il gradiente del neurone nascosto-strato più in basso con l'indice [3]:

(0.20)(1 – 0.20) * [ (-0.76)(1.9) + (0.37)(2.0) ] = -0.03

Se chiamiamo la funzione sigmoidale g (x), si scopre che la derivata di calcolo della funzione sigmoidea è g (x) * (1 - g(x)). Ricordiamo che questo esempio viene utilizzata la funzione sigmoidea per la funzione di attivazione ingresso nascosto. Il 0,20 Ecco l'output locale dal neurone. Il-0.76 e 0,37 sono le sfumature dei neuroni a livello di output e il 1.9 e 2.0 sono i pesi di nascosto-uscita associati due gradienti di livello di output.

Calcolo del peso e Bias Delta

Dopo tutte le sfumature del livello di output e strato nascosto gradienti sono stati calcolati, il passo successivo nell'algoritmo di retro-propagazione è utilizzare i valori di pendenza per calcolare i valori di delta per ogni valore di peso e di bias. A differenza di sfumature, che devono essere calcolate destra a sinistra, i valori di delta possono essere calcolati in qualsiasi ordine. Il valore di delta per peso o bias è uguale a volte eta il gradiente associato con il peso o pregiudizi, a volte il valore di input associato al peso o pregiudizi. Ad esempio, il valore di delta per il peso di ingresso nascosto da neuroni di input [2] al neurone nascosto [3] è:

Delta i-h peso [2] [3] = eta * gradiente nascosti [3] * ingresso [2]
= 0.90 * (-0.11) * 3.0
= -0.297

Il 0.90 è eta, che controlla quanto velocemente il retro-propagazione impara. Valori più grandi di eta producono grandi cambiamenti nel delta, con il rischio di superamento di una buona risposta. Il-0.11 valore è il gradiente per neurone nascosto [3]. Il valore 3.0 è il valore di input per ingresso neurone [2]. In termini di diagramma in Figura 3, se un peso è rappresentato come una freccia da un neurone a altro, per calcolare il delta per un peso particolare, si utilizza il valore di sfumato del neurone ha sottolineato sulla destra e il valore di input del neurone con puntato da sinistra.

Calcolo del Delta per i valori di bias, si noti che perché una somma intermedia vengono aggiunti semplicemente i valori di bias, non hanno nessun valore di input associato. Quindi, per calcolare il delta per un valore di bias si può omettere il termine valore di input complessivamente, o utilizzare un valore fittizio 1,0 come forma di documentazione. Per esempio, in Figura 3, il pregiudizio nascosto-strato inferiore ha valore-7.0. Il delta per quel valore di bias è:

0.90 * gradiente di neurone ha sottolineato * 1.0
= 0.90 * (-0.11) * 1.0
= 0.099

Aggiunta di un termine di quantità di moto

Dopo che tutti i valori di peso e bias delta sono stati calcolati, è possibile aggiornare ogni peso e bias semplicemente aggiungendo il valore delta associato. Tuttavia, esperienza con reti neurali ha dimostrato che con determinati insiemi di dati, l'algoritmo di retro-propagazione può oscillare, ripetutamente superamento e quindi undershooting il valore di destinazione e mai convergenti ad un ultimo gruppo di peso e le stime di sbieco. Una tecnica per la riduzione di questa tendenza è aggiungere a ogni nuovo peso e pregiudizi di un termine supplementare chiamato slancio. Lo slancio per un peso (o bias) è solo qualche piccolo valore (come 0.4 nel programma demo) volte il valore del delta del precedente per il peso. Con slancio aggiunge una piccola quantità di complessità all'algoritmo di propagazione indietro perché i valori di Delta precedenti devono essere conservati. La matematica dietro perché questa tecnica impedisce l'oscillazione è sottile, ma il risultato è semplice.

Per riassumere, per aggiornare un peso (o bias) mediante retro-propagazione, il primo passo è calcolare le pendenze per tutti i neuroni del livello di output. Il secondo passo è quello di calcolare le pendenze per tutti i neuroni di strato nascosto. Il terzo passo è calcolare il Delta per tutti i pesi utilizzando eta, il tasso di apprendimento. Il quarto passo è aggiungere i delta di ogni peso. Il quinto passo è quello di aggiungere un termine di quantità di moto di ogni peso.

Codifica con Visual Studio 2012

La spiegazione di retro-propagazione presentato in questo articolo, insieme con il codice di esempio, dovrebbe darvi informazioni sufficienti per comprendere e l'algoritmo di retro-propagazione di codice. Retro-propagazione è solo una delle numerose tecniche che possono essere utilizzati per stimare il peso migliore e i valori di bias per un set di dati. Rispetto alle alternative come particle swarm optimization e algoritmi di ottimizzazione evolutiva, retro-propagazione tende ad essere più veloce. Ma retro-propagazione ha svantaggi. Non può essere utilizzato con reti neurali che utilizzano funzioni di attivazione non differenziabile. Determinazione dei valori buoni per imparare i parametri di velocità e quantità di moto è più arte che scienza e può richiedere molto tempo.

Ci sono diversi argomenti importanti che questo articolo non affronta, in particolare come trattare con più elementi di dati di destinazione. Spiegherò questo concetto e altre tecniche di rete neurale articoli in futuro.

Quando ho codificato il programma demo per questo articolo, ho usato la versione beta di Visual Studio 2012. Anche se molte delle nuove funzionalità di Visual Studio 2012 sono correlati a Windows 8 apps, volevo vedere come Visual Studio 2012 gestito il buon vecchio applicazioni console. Sono stato piacevolmente sorpreso che sono non ero spiacevolmente sorpreso da una delle nuove funzionalità di Visual Studio 2012. Il mio passaggio al Visual Studio 2012 era senza sforzo. Anche se non ho fatto uso della nuova funzionalità in Visual Studio 2012 Async, avrebbe potuto essere utile durante il calcolo di tutti i valori di delta per ogni peso e la parzialità. Ho provato la nuova funzionalità di gerarchia di chiamata e trovato utile e intuitiva. Mie impressioni iniziali del Visual Studio 2012 erano favorevoli, e ho intenzione di transizione ad esso, non appena sono in grado.

Dr.James McCaffrey funziona per Volt Information Sciences Inc., dove gestisce la formazione tecnica per gli ingegneri software della Microsoft di Redmond, Washington, campus. Si è occupato di diversi prodotti Microsoft, inclusi Internet Explorer e MSN Search. Egli è l'autore di ".NET Test Automation Recipes" (Apress, 2006) e può essere raggiunto a jammc@microsoft.com.

Grazie all'esperto tecnica seguente per la revisione di questo articolo: Dan Liebling