Agosto 2017

Volume 32 Numero 8

Il presente articolo è stato tradotto automaticamente.

Esecuzione di test - I/O per reti neurali profonde tramite C#

Da James McCaffrey | 2017 agosto

James McCaffrey
Molti dei recenti miglioramenti nell'apprendimento (esecuzione di stime utilizzando i dati) può essere soggetta usando le reti neurali. Ad esempio il riconoscimento vocale Cortana Microsoft e Apple Siri e il riconoscimento di immagini che consente di abilitare self-Guida di automobili.

Il termine rete neurali profonde (DNN) è generale e sono disponibili diverse varianti specifiche, tra cui reti neurali ricorrente (RNNs) e le reti neurali convolutional (CNNs). La forma più semplice di un DNN, che illustrati in questo articolo, non è un nome speciale, pertanto si farà riferimento a esso come un DNN.

In questo articolo verrà presentate DNNs per avere un programma demo concreta per sperimentare, che consentirà di comprendere documentazione su DNNs. Non sarà presente codice che può essere utilizzato direttamente in un sistema di produzione, ma il codice può essere esteso per creare un sistema, come verrà illustrato. Anche se non si prevede di implementare un DNN, è possibile trovare la spiegazione del loro funzionamento interessanti per fine a se stessa.

Un DNN meglio viene spiegata in modo visivo. Si osservi la Figura 1. La rete completa ha due nodi di input, a sinistra, con attivi val (1.0, 2.0). Esistono tre nodi di output a destra, con i valori (0.3269, 0.3333, 0.3398). È possibile considerare un DNN come una funzione matematica complessa che in genere due o più valori di input numerici accetta e restituisce uno o più valori di output numerica.

Una rete neurale profonde Basic
Figura 1 base Deep Neural Network

Il DNN illustrato potrebbe corrispondere a un problema in cui l'obiettivo è per stimare l'affiliazione politica di terze parti (Democrat, Republican, gli altri) di una persona in base all'età e reddito, in cui i valori di input vengono ridimensionati in qualche modo. Se Democrat viene codificato come (1, 0,0) e Republican viene codificato come (0, 1,0) e l'altra viene codificata come (0, 0,1), quindi il DNN in figura 1 altri stima per un utente con durata = 1.0 e income = 2.0 perché l'ultimo valore di output (0.3398) è il più grande.

Una rete neurale regolari ha un solo livello nascosto di nodi di elaborazione. Un DNN ha due o più livelli nascosti e in grado di gestire i problemi di stima molto difficile. Tipi di DNNs, ad esempio RNNs e CNNs, dispongono anche di più livelli di nodi di elaborazione, ma più complesso di architetture di connessione, nonché.

Il DNN in figura 1 prevede tre livelli nascosti di nodi di elaborazione. Il primo livello nascosto con quattro nodi, secondo e terzi livelli nascosti sono due nodi. Ogni freccia lungo da sinistra a destra rappresenta una costante numerica chiamata un peso. Se i nodi sono zero indicizzato con [0] nella parte superiore della figura, il peso di connessione di input [0] in hid-den [0] [0] (livello 0, nodo 0) ha valore 0,01 e il peso di connessione di input [1] per nascosti [0] [3] (livello 0, il nodo 3) ha valore 0,08 e così via. Sono disponibili i valori di peso 26 nodi.

Ognuna delle otto nascosto e tre nodi di output è una piccola freccia che rappresenta una costante numerica chiamata una distorsione. Output [1] è il valore della deviazione di 0,36 ai ricavi, ad esempio, nascosti [2] [0] ha valore distorsione 0,33. Non tutti i pesi e i valori di compensazione sono contrassegnati nel diagramma, ma poiché i valori sono sequenziali compreso tra 0,01 e 0.37, è possibile determinare facilmente il valore di un peso non con l'etichetta o una distorsione.

Nelle sezioni che seguono si illustrano il funzionamento del meccanismo di input / output DNN e Mostra come implementarlo. Il programma demo è codificato tramite c#, ma non dovrebbe essere troppo difficile refactoring del codice in un altro linguaggio, ad esempio Python o JavaScript, se si desidera eseguire questa operazione. Il programma demo è troppo lungo per presentare interamente in questo articolo, ma il programma completo è disponibile nel download del codice associato.

Il programma Demo

Un buon metodo per visualizzare in questo articolo è a due punte consiste nell'esaminare nella schermata del programma demo in figura 2. La dimostrazione corrisponde al DNN nel figura 1 e viene illustrato il meccanismo di input / output visualizzando i valori dei 13 nodi nella rete. Il codice demo che ha generato l'output inizia con il codice illustrato in figura 3.

Esecuzione di Demo base Deep Neural Network
Figura 2 base Deep Neural Network Demo esecuzione

Figura 3 inizio del codice di generazione di Output

using System;
namespace DeepNetInputOutput
{
  class DeepInputOutputProgram
  {
    static void Main(string[] args)
    {
      Console.WriteLine("Begin deep net IO demo");
      Console.WriteLine("Creating a 2-(4-2-2)-3 deep network");
      int numInput = 2;
      int[] numHidden = new int[] { 4, 2, 2 };
      int numOutput = 3;
      DeepNet dn = new DeepNet(numInput, numHidden, numOutput);

Si noti che il programma demo utilizza solo semplice in c# con nessuno spazio dei nomi ad eccezione di sistema. Il DNN viene creato passando il numero di nodi in ogni livello a un costruttore di classe definito dal programma DeepNet. Il numero di livelli nascosti, 3, viene passato in modo implicito come il numero di elementi nella matrice numHidden. Un progetto alternativo consiste nel passare il numero di livelli nascosti in modo esplicito.

I valori di 26 pesi e le 11 pregiudizi vengono impostati come illustrato di seguito:

int nw = DeepNet.NumWeights(numInput, numHidden, numOutput);
Console.WriteLine("Setting weights and biases to 0.01 to " +
  (nw/100.0).ToString("F2") );
double[] wts = new double[nw];
for (int i = 0; i < wts.Length; ++i)
  wts[i] = (i + 1) * 0.01; 
dn.SetWeights(wts);

Il numero totale di pesi e i pregiudizi viene calcolato usando un metodo di classe statica NumWeights. Se si fa riferimento al nuovo figura 1, è possibile notare che ogni nodo è connessa a tutti i nodi nel livello a destra, il numero di pesi (2 * 4) + (4 * 2) + (2 * 2) + (2 * 3) = 8 + 8 + 4 + 6 = 26. Poiché non esiste una differenza per nodo nascosto e di output di copertura, il numero totale di pregiudizi è 4 + 2 + 2 + 3 = 11.

Viene creata un'istanza di una matrice denominata wts con 37 celle e quindi i valori vengono impostati su 0,01 tramite 0.37. Questi valori vengono inseriti nell'oggetto DeepNet utilizzando il metodo SetWeights. In un DNN realistico, non demo, sarebbe possibile determinare i valori dei pesi e pregiudizi usando un set di dati che dispongono di valori di input e valori di output corretto noto. Si tratta di training della rete. L'algoritmo più comune di training viene chiamato retropropagazione.

Il metodo principale del programma demo termina con:

...
    Console.WriteLine("Computing output for [1.0, 2.0] ");
    double[] xValues = new double[] { 1.0, 2.0 };
    dn.ComputeOutputs(xValues);
    dn.Dump(false); 
    Console.WriteLine("End demo");
    Console.ReadLine();
  } // Main
} // Class Program

Metodo ComputeOutputs accetta una matrice di valori di input e quindi utilizza il meccanismo di input / output, in seguito a breve, per calcolare e archiviare i valori dei nodi di output. Il metodo di supporto di Dump vengono visualizzati i valori dei 13 nodi e indica che l'argomento "false" per non visualizzare i valori dei 37 pesi e pregiudizi.

Il meccanismo di Input / Output

Il meccanismo di input / output per un DNN meglio è illustrato un esempio concreto. Il primo passaggio è utilizzare i valori in nodi di input per calcolare i valori dei nodi di primo livello nascosto. Il valore del nodo nascosto superiore nel primo livello nascosto è:
Tanh ((1.0)(0.01) (2.0)(0.05) + 0,27) =
Tanh(0.38) = 0.3627

In parole, "calcolare la somma dei prodotti per ogni nodo di input e il peso associato, il valore della distorsione, quindi richiedere la tangente iperbolica della somma." La tangente iperbolica, tanh abbreviato, viene chiamata la funzione di attivazione. Funzione tanh accetta qualsiasi valore di infinito negativo verso l'infinito positivo e restituisce un valore compreso tra -1,0 e + 1.0. Funzioni di attivazione alternative includono la logistica sigmoidale e rettificate lineare (ReLU) funzioni, che sono importanti all'esterno dell'ambito di questo articolo.

I valori dei nodi nei livelli nascosti rimanenti vengono calcolati in modo analogo. Ad esempio, è [1] [0] nascosto:
Tanh ((0.3627)(0.09) + (0.3969)(0.11) + (0.4301)(0.13) (0.4621)(0.15) + 0,31) =
Tanh(0.5115) = 0.4711
E nascosti [2] [0] è:
Tanh ((0.4711)(0.17) (0.4915)(0.19) + 0,33) =
Tanh(0.5035) = 0.4649

I valori dei nodi di output vengono calcolati utilizzando una funzione di attivazione differente, denominata softmax. Il passaggio preliminare, pre-attivazione di somma dei prodotti più distorsione è lo stesso:
pre-attivazione di output [0] =
(.4649)(0.21) + (0.4801)(0.24) + 0.35 =
0.5628
pre-attivazione di output [1] =
(.4649)(0.22) + (0.4801)(0.25) + 0.36 =
0.5823
pre-attivazione di output [2] =
(.4649)(0.23) + (0.4801)(0.26) + 0.37 =
0.6017

Softmax di tre valori arbitrari, x, y, y è:
softmax(x) = e ^ x / (e ^ x + e ^ y + e ^ z)
softmax(y) = e ^ y / (e ^ x + e ^ y + e ^ z)
softmax(z) = e ^ z / (e ^ x + e ^ y + e ^ z)

dove e è il numero di Eulero, circa 2.718282. In questo caso, per il DNN in figura 1, i valori di output finale sono:

output [0] = e ^ 0.5628 / (e ^ 0.5628 + e ^ 0.5823 + e ^ 0.6017) = 0.3269
output [1] = e ^ 0.5823 / (e ^ 0.5628 + e ^ 0.5823 + e ^ 0.6017) = 0.3333
output di [2] = e ^ 0.6017 / (e ^ 0.5628 + e ^ 0.5823 + e ^ 0.6017) = 0.3398

Lo scopo della funzione di attivazione softmax è per assegnare i valori di output per la somma 1.0 in modo che possono essere interpretati come probabilità e una mappa a un valore categorico. In questo esempio, perché il terzo valore di output è il più grande, indipendentemente dal valore di categoria che è stato codificato come (0, 0,1) sarebbe la categoria per gli input stimata = (1.0, 2.0).

Implementazione di una classe DeepNet

Per creare il programma demo, avviato Visual Studio e selezionato il modello di applicazione Console c# e denominato DeepNetInputOutput. Si usa Visual Studio 2015, ma la demo senza dipendenze significativo .NET, pertanto funzionerà qualsiasi versione di Visual Studio.

Dopo aver caricato il codice del modello, nella finestra Esplora soluzioni, I pulsante destro del mouse sul file Program.cs e rinominato il DeepNetInputOutputProgram.cs più descrittivi e Visual Studio rinominare automaticamente classe Program per me è consentito. Nella parte superiore della finestra dell'editor, è stato eliminato tutti non necessari tramite istruzioni, lasciando solo quella che fa riferimento a spazio dei nomi System.

Demo DNN implementato come una classe denominata DeepNet. La definizione della classe inizia con:

public class DeepNet
{
  public static Random rnd; 
  public int nInput; 
  public int[] nHidden; 
  public int nOutput; 
  public int nLayers; 
...

Tutti i membri di classe vengono dichiarati con ambito pubblico per motivi di semplicità. Membro dell'oggetto Random statico denominato rnd è utilizzato dalla classe DeepNet per inizializzare i pesi e i pregiudizi a piccoli valori casuali (che vengono quindi sovrascritti con i valori 0,01 per 0.37). NOuput e nInput membri sono il numero di nodi di input e outpui. HHidden membro di matrice contiene il numero di nodi in ogni livello nascosto, in modo viene indicato il numero di livelli nascosti dalla proprietà lunghezza della matrice, che viene archiviata nel membro nLayers per motivi di praticità. La definizione della classe continua:

public double[] iNodes;
public double [][] hNodes;
public double[] oNodes;

Un'implementazione di rete neurale deep presenta numerose opzioni di progettazione. ONodes e matrice membri inode contenere l'input e output valori, come previsto. Matrice di matrici membro hNodes contiene i valori di nodo nascosto. Un progetto alternativo consiste nell'archiviare tutti i nodi in una singola struttura di matrice di matrici nnNodes, dove la demo nnNodes [0] è una matrice di valori di nodo di input e nnNodes [4] è una matrice di valori di nodo di output.

I pesi relativi al nodo a nodo vengono archiviati utilizzando queste strutture di dati:

public double[][] ihWeights; 
public double[][][] hhWeights;
public double[][] hoWeights;

Membro ihWeights è una matrice di matrice di matrici di tipo che contiene i pesi di input in-first-nascosto-livello. Membro hoWeights è una matrice di matrice di matrici di tipo che contiene i pesi connessione l'ultimo nodi del livello nascosto ai nodi di output. Membro hhWeights è una matrice in cui ogni cella punta a una matrice di matrici di matrici che contiene i pesi nascosto a nascosto. Ad esempio, hhWeights [0] [3] [1] contiene i pesi connessione nodo nascosto [3] nel livello nascosto [0] a [1] di nodo nascosto nel livello nascosto [0 + 1]. Queste strutture di dati sono il nucleo del meccanismo di input / output DNN e difficile. Un diagramma concettuale di essi viene visualizzato figura 4.

Pesi e le strutture di dati pregiudizi
Figura 4 pesi e le strutture di dati pregiudizi

I membri della classe ultime due contengano pregiudizi il nodo nascosto e i pregiudizi nodo di output:

public double[][] hBiases;
public double[] oBiases;

Quanto qualsiasi sistema software che con, DNNs hanno molte progettazioni di struttura di dati alternativi e con sketch di queste strutture di dati è essenziale quando si scrive codice di input / output.

Calcolare il numero di pesi e i pregiudizi

Per impostare i valori di pesi e i pregiudizi, è necessario sapere quanti pesi e pregiudizi non esiste. Il programma demo implementa il metodo statico NumWeights per calcolare e restituire il numero. Tenere presente che la rete di 2-3-(4-2-2) demo abbia (2 * 4) + (4 * 2) + (2 * 2) + (2 * 3) = 26 pesi e 4 + 2 + 2 + 3 = 11 pregiudizi. Il codice nel metodo NumWeights, che calcola il numero di input per nascosto, nascosto per nascosto e nascosto nell'output è:

int ihWts = numInput * numHidden[0];
int hhWts = 0;
for (int j = 0; j < numHidden.Length - 1; ++j) {
  int rows = numHidden[j];
  int cols = numHidden[j + 1];
  hhWts += rows * cols;
}
int hoWts = numHidden[numHidden.Length - 1] * numOutput;

Anziché restituire il numero totale di pesi e i pregiudizi come metodo che numweights, si potrebbe voler provare a restituire il numero di pesi e i pregiudizi separatamente, in una matrice di interi di due celle.

Pregiudizi e pesi di impostazione

In genere, un DNN non demo inizializzare tutti i pesi e i pregiudizi di valori casuali di piccole dimensioni. Il programma demo imposta i 26 pesi 0,26 tramite 0,01 e i pregiudizi per 0,27 tramite 0.37 utilizzando il metodo di classe SetWeights. La definizione inizia con:

public void SetWeights(double[] wts)
{
  int nw = NumWeights(this.nInput, this.nHidden, this.nOutput);
  if (wts.Length != nw)
    throw new Exception("Bad wts[] length in SetWeights()");
  int ptr = 0;
...

Parametro di input wts contiene i valori per i pesi e i pregiudizi e si presuppone che abbia la lunghezza corretta. Variabile ptr punti nella matrice wts. Il programma demo è poco errori per mantenere i concetti principali più chiare possibili. I pesi di input in-first-nascosto-livello vengono impostati come illustrato di seguito:

for (int i = 0; i < nInput; ++i) 
  for (int j = 0; j < hNodes[0].Length; ++j) 
    ihWeights[i][j] = wts[ptr++];

Successivamente, aver impostato i pesi nascosto per nascosto:

for (int h = 0; h < nLayers - 1; ++h) 
  for (int j = 0; j < nHidden[h]; ++j)  // From 
    for (int jj = 0; jj < nHidden[h+1]; ++jj)  // To 
      hhWeights[h][j][jj] = wts[ptr++];

Se non si è abituati a lavorare con le matrici multidimensionali, l'indicizzazione può essere molto complessa. Un diagramma delle strutture di dati pesi e i pregiudizi è essenziale (nonché, per me, comunque). Aver impostato i pesi last-nascosto-layer-a-output simile al seguente:

int hi = this.nLayers - 1;
for (int j = 0; j < this.nHidden[hi]; ++j)
  for (int k = 0; k < this.nOutput; ++k)
    hoWeights[j][k] = wts[ptr++];

Questo codice Usa il fatto che se sono presenti nLayers nascosto (3 nella demo), quindi l'indice dell'ultimo livello nascosto è nLayers-1. Metodo SetWeights conclude impostando i pregiudizi nodi nascosti e i pregiudizi nodo di output:

... 
  for (int h = 0; h < nLayers; ++h) 
    for (int j = 0; j < this.nHidden[h]; ++j)
      hBiases[h][j] = wts[ptr++];

  for (int k = 0; k < nOutput; ++k)
    oBiases[k] = wts[ptr++];
}

Calcolare i valori di Output

La definizione di metodo di classe ComputeOutputs inizia con:

public double[] ComputeOutputs(double[] xValues)
{
  for (int i = 0; i < nInput; ++i) 
    iNodes[i] = xValues[i];
...

I valori di input sono in xValues di parametro matrice. Classe membro nInput contiene il numero di nodi di input e viene impostata nel costruttore della classe. I primi valori nInput in xValues vengono copiati in nodi di input, in modo xValues si presuppone che abbia almeno nInput valori nelle celle del primo. Successivamente, i valori correnti nel file nascosto e nodi di output vengono azzerati-out:

for (int h = 0; h < nLayers; ++h)
  for (int j = 0; j < nHidden[h]; ++j)
    hNodes[h][j] = 0.0;
 
for (int k = 0; k < nOutput; ++k)
  oNodes[k] = 0.0;

L'idea è che la somma dei termini di prodotti verrà accumulata direttamente nei nodi nascosti e di output, in modo questi nodi devono essere reimpostati in modo esplicito su 0.0 per ogni chiamata al metodo. Un'alternativa consiste nel dichiarare e utilizzare matrici locali con nomi quali hSums [] e [] oSums. Successivamente, vengono calcolati i valori dei nodi di primo livello nascosto:

for (int j = 0; j < nHidden[0]; ++j) {
  for (int i = 0; i < nInput; ++i)
    hNodes[0][j] += ihWeights[i][j] * iNodes[i];
  hNodes[0][j] += hBiases[0][j];  // Add the bias
  hNodes[0][j] = Math.Tanh(hNodes[0][j]);  // Activation
}

Il codice è sostanzialmente un mapping uno-a-uno del meccanismo descritto in precedenza. L'elemento predefinito che Math. tanh viene utilizzato per l'attivazione del nodo nascosto. Come già accennato, importante alternative sono la funzione sigmoidale logistica e le funzioni rettificato unità lineare (ReLU), che verrà descritta in un altro articolo. Successivamente, vengono calcolati i rimanenti nodi di livello nascosto:

for (int h = 1; h < nLayers; ++h) {
  for (int j = 0; j < nHidden[h]; ++j) {
    for (int jj = 0; jj < nHidden[h-1]; ++jj) 
      hNodes[h][j] += hhWeights[h-1][jj][j] * hNodes[h-1][jj];
    hNodes[h][j] += hBiases[h][j];
    hNodes[h][j] = Math.Tanh(hNodes[h][j]);
  }
}

Questa è la parte complessa del programma demo, soprattutto a causa di più indici di matrice necessarie. Successivamente, pre-l'attivazione di somma dei prodotti vengono calcolati per i nodi di output:

for (int k = 0; k < nOutput; ++k) {
  for (int j = 0; j < nHidden[nLayers - 1]; ++j)
    oNodes[k] += hoWeights[j][k] * hNodes[nLayers - 1][j];
   oNodes[k] += oBiases[k];  // Add bias 
}

Metodo ComputeOutputs conclude applicando la funzione di attivazione softmax, restituendo i valori di output calcolata in una matrice separata:

...     
  double[] retResult = Softmax(oNodes); 
  for (int k = 0; k < nOutput; ++k)
    oNodes[k] = retResult[k];
  return retResult; 
}

Il metodo Softmax è un helper statici. Vedere il codice associato di download per i dettagli. Si noti che poiché softmax attivazione richiede tutti i valori che verranno attivati (nel termine denominatore), risulta più efficiente per calcolare tutti i valori di softmax in una sola volta anziché separatamente. I valori di output finale vengono archiviati in nodi di output e vengono restituiti anche separatamente per la chiamata di praticità.

Conclusioni

Si è verificato attività di ricerca enormi e molte reti correlate a neurali profonde successi negli ultimi anni. Specializzati DNNs, ad esempio reti neurali convolutional, reti neurali ricorrente, reti neurali LSTM e reti neurali residue sono molto potenti ma molto complessi. Ritengo, comprendere come base DNNs operano è essenziale per comprendere le varianti più complesse.

In un altro articolo, verrà illustrato in dettaglio come utilizzare l'algoritmo retropropagazione (senza dubbio il più famoso e importanti l'algoritmo di apprendimento) per eseguire il training di una base DNN. Retropropagazione o almeno una forma di, viene utilizzata per formare la maggior parte delle varianti DNN, troppo. Questa spiegazione verrà introdotto il concetto di sfumatura fuoco, che a sua volta spiegano la progettazione e motivazione di molte del DNNs ora utilizzati per i sistemi di stima sofisticate.


Ripristino di emergenza. James McCaffreyfunziona per Microsoft Research Redmond, WA Ha lavorato su diversi prodotti Microsoft, tra cui Internet Explorer e Bing. Dr. McCaffrey può essere raggiunto al jamccaff@microsoft.com.

Grazie per i seguenti esperti Microsoft che ha revisionato in questo articolo: Li Deng, Pingjun Hu, Po Sen Huang, Li Kirk, Alan Liu, Ricky Loynd, Baochen Sun, Ezio Alboni Turbell.


Viene illustrato in questo articolo nel forum di MSDN Magazine