Februar 2018

Band 33, Nummer 2

Machine Learning: Deep Neural Network-Klassifizierer mit CNTK

Von James McCaffrey

Die Microsoft Cognitive Toolkit-Bibliothek (CNTK) ist eine leistungsfähige Sammlung von Funktionen, mit denen Sie Machine Learning-Vorhersagesysteme (ML) erstellen können. Ich habe in der Ausgabe aus Juli 2017 eine Einführung in Version 2 vorgestellt (msdn.com/magazine/mt784662). In diesem Artikel werde ich zeigen, wie CNTK verwendet wird, um einen Klassifizierer für Deep Neural Networks zu erstellen. Ich empfehle Ihnen, sich den Screenshot in Abbildung 1 anzusehen. Er zeigt, um was es in diesem Artikel geht.

Sortenvorhersagedemo für Weizensaatgut
Abbildung 1: Sortenvorhersagedemo für Weizensaatgut

Die CNTK-Bibliothek ist aus Leistungsgründen in C++ geschrieben, aber der gebräuchlichste Weg, die Bibliotheksfunktionen aufzurufen, ist die Verwendung der CNTK Python-Sprach-API. Ich habe das Demoprogramm aufgerufen, indem ich den folgenden Befehl in einer gewöhnlichen Windows 10-Befehlsshell ausgegeben habe:

> python seeds_dnn.py

Das Ziel des Demoprogramms ist es, ein Deep Neural Network zu schaffen, das die Weizensaatgutsorte vorhersagen kann. Hinter den Kulissen verwendet das Demoprogramm einen Satz von Trainingsdaten, der folgendermaßen aussieht:

|properties 15.26 14.84 ... 5.22 |variety 1 0 0
|properties 14.88 14.57 ... 4.95 |variety 1 0 0
...
|properties 17.63 15.98 ... 6.06 |variety 0 1 0
...
|properties 10.59 12.41 ... 4.79 |variety 0 0 1

Die Trainingsdaten umfassen 150 Elemente. Jede Zeile stellt eine von drei Sorten Weizensaatgut dar: „Kama“, „Rosa“ oder „Canadian“. Die ersten sieben numerischen Werte auf jeder Zeile sind die Prädiktorwerte, die in der Terminologie von Machine Learning oft als Attribute oder Features bezeichnet werden. Die Prädiktoren sind Saatgutfläche, Umfang, Kompaktheit, Länge, Breite, Asymmetriekoeffizient und Kerbenlänge. Das vorherzusagende Element (oft als Klasse oder Label bezeichnet) füllt die letzten drei Spalten und wird als 1 0 0 für Kama, 0 1 0 für Rosa und 0 0 1 für Canadian codiert.

Das Demoprogramm verwendet außerdem einen Testdatensatz von 60 Elementen, 20 Elemente von jeder Saatgutsorte. Die Testdaten weisen das gleiche Format wie die Trainingsdaten auf.

Das Demoprogramm erstellt ein 7-(4-4-4)-3 tiefes neuronales Netzwerk. Das Netzwerk wird in Abbildung 2 dargestellt. Es sind sieben Eingabeknoten (einer für jeden Prädiktorwert), drei verborgene Schichten mit jeweils vier Verarbeitungsknoten und drei Ausgabeknoten, die den drei möglichen kodierten Weizensaatgutsorten entsprechen, vorhanden.

Struktur des Deep Neural Network
Abbildung 2: Struktur des Deep Neural Network

Das Demoprogramm trainiert das Netzwerk mit 5.000 Batches zu je 10 Elementen unter Verwendung des SGD-Algorithmus (Stochastic Gradient Descent, stochastisches Gradientenverfahren). Nachdem das Vorhersagemodell trainiert wurde, wird es auf den Testdatensatz mit 60 Elementen angewendet. Das Modell erreichte eine Genauigkeit von 78,33 Prozent, was bedeutet, dass 47 der 60 Testelemente richtig vorhergesagt wurden.

Das Demoprogramm schließt mit einer Vorhersage für ein unbekanntes Weizensaatgut ab. Die sieben Eingabewerte sind (17,6, 15,9, 0,8, 6,2, 3,5, 4,1, 6,1). Die berechneten Rohwerte des Ausgabeknotens sind (1,0530, 2,5276, -3,6578), und die zugehörigen Wahrscheinlichkeitswerte des Ausgabeknotens sind (0,1859, 0,8124, 0,0017). Da der Mittelwert am größten ist, wird die Ausgabe (0, 1, 0) zugeordnet, also der Sorte Rosa.

 Dieser Artikel geht davon aus, dass Sie über mittlere oder fortgeschrittene Programmierkenntnisse mit einer Sprache der C-Familie verfügen und dass Sie mit neuronalen Netzwerken vertraut sind. Unabhängig von Ihrem Hintergrund sollten Sie aber in der Lage sein, diesem Artikel ohne allzu große Schwierigkeiten zu folgen. Der gesamte Quellcode für das seeds_dnn.py-Programm wird in diesem Artikel vorgestellt. Der Code und die zugehörigen Trainings- und Testdatendateien sind auch im Dateidownload verfügbar, der diesen Artikel begleitet.

Installieren von CNTK v2

Da CNTK v2 relativ neu ist, sind Sie möglicherweise mit dem Installationsvorgang nicht vertraut. Kurz gesagt: Sie installieren zuerst eine Python-Sprachdistribution (ich empfehle dringend die Anaconda-Distribution), die die Python-Kernsprache und die erforderlichen Python-Pakete enthält, dann installieren Sie CNTK als zusätzliches Python-Paket. Mit anderen Worten: CNTK ist keine eigenständige Installation.

Während ich diesen Artikel schreibe, ist die aktuelle Version von CNTK v2.3. Da sich CNTK in einer intensiven Entwicklungsphase befindet, könnte es zu dem Zeitpunkt, an dem Sie diesen Artikel lesen, durchaus eine neuere Version geben. Ich habe die Anaconda-Distribution Version 4.1.1 verwendet (die Python Version 3.5.2, NumPy Version 1.11.1 und SciPy Version 0.17.1 enthält). Nach der Installation von Anaconda habe ich die reine CPU-Version von CNTK mithilfe des Hilfsprogramms pip installiert. Die Installation von CNTK kann ein wenig kompliziert sein, wenn Sie mit der Versionsverwaltungskompatibilität unvorsichtig sind, aber die CNTK-Dokumentation beschreibt den Installationsvorgang im Detail.

Verstehen der Daten

Die Erstellung der meisten Machine Learning-Systeme beginnt mit dem zeitaufwändigen und oft lästigen Einrichten der Trainings- und Testdatendateien. Sie finden den Weizensaatgut-Rohdatensatz unter bit.ly/2idhoRK. Die aus 210 Elementen bestehenden Rohdaten (mit Tabstopptrennzeichen) sehen folgendermaßen aus:

14.11  14.1   0.8911  5.42  3.302  2.7  5      1
16.63  15.46  0.8747  6.053 3.465  2.04 5.877  1

Ich habe ein Hilfsprogramm geschrieben, um eine Datei in einem Format zu generieren, das von CNTK leicht verarbeitet werden kann. Die sich ergebende Datei mit 210 Elementen sieht folgendermaßen aus:

|properties 14.1100 14.1000 ... 5.0000 |variety 1 0 0
|properties 16.6300 15.4600 ... 5.8770 |variety 1 0 0

Das Hilfsprogramm hat ein führendes Tag „|properties“ hinzugefügt, um den Speicherort der Features zu identifizieren, und ein Tag „|variety“, um den Speicherort der vorherzusagenden Klasse zu identifizieren. Die Klassenrohwerte wurden 1-aus-N codiert (manchmal auch als One-Hot-Codierung bezeichnet), Tabstopps wurden durch einzelne Leerzeichen ersetzt, und alle Prädiktorwerte wurden auf genau vier Dezimalstellen formatiert.

In den meisten Fällen möchten Sie numerische Prädiktorwerte normalisieren, sodass sie alle ungefähr den gleichen Bereich aufweisen. Ich habe diese Daten nicht normalisiert, um diesen Artikel etwas einfacher zu halten. Zwei gebräuchliche Formen der Normalisierung sind die Z-Faktor-Normalisierung und die Min-Max-Normalisierung. Im Allgemeinen sollten Sie in Nicht-Demoszenarien Ihre Prädiktorwerte normalisieren.

Dann habe ich ein anderes Hilfsprogramm geschrieben, das die 210 Elemente umfassende Datendatei im CNTK-Format verwendet, um eine 150 Elemente umfassende Trainingsdatei mit dem Namen „seeds_train_data.txt“ (die ersten 50 Elemente jeder Sorte) und eine 60 Elemente umfassende Testdatei mit dem Namen „seeds_test_data.txt“ (die letzten 20 Elemente jeder Sorte) zu generieren.

Da es sieben Prädiktorvariablen gibt, ist es nicht realisierbar, ein vollständiges Diagramm der Daten zu erstellen. Eine grobe Vorstellung von der Struktur der Daten können Sie sich aber durch die grafische Darstellung der Teildaten in Abbildung 3 verschaffen. Ich habe nur die Vorhersagewerte für den Saatgutumfang und die Saatgutkompaktheit des 60 Elemente umfassenden Testdatensatzes verwendet.

Partielles Diagramm der Testdaten
Abbildung 3: Partielles Diagramm der Testdaten

Deep Neural Network-Demoprogramm

Ich habe den Editor verwendet, um das Demoprogramm zu schreiben. Ich mag den Editor, aber die meisten meiner Kollegen bevorzugen einen der vielen exzellenten Python-Editoren, die verfügbar sind. Besonders ansprechend ist der kostenlose Visual Studio Code-Editor mit dem Add-In für die Python-Sprache. Der vollständige Quellcode des Demoprogramms (mit einigen kleinen Bearbeitungen, um Platz zu sparen) wird in Abbildung 4 gezeigt. Beachten Sie, dass das umgekehrte Schrägstrichzeichen von Python für die Zeilenfortsetzung verwendet wird.

Abbildung 4: Vollständiges Saatgutklassifizierungs-Demoprogramm

# seeds_dnn.py
# classify wheat seed variety
import numpy as np
import cntk as C
def create_reader(path, is_training, input_dim, output_dim):
  strm_x = C.io.StreamDef(field='properties',
    shape=input_dim, is_sparse=False)
  strm_y = C.io.StreamDef(field='variety',
    shape=output_dim, is_sparse=False)
  streams = C.io.StreamDefs(x_src=strm_x,
    y_src=strm_y)
  deserial = C.io.CTFDeserializer(path, streams)
  sweeps = C.io.INFINITELY_REPEAT if is_training else 1
  mb_source = C.io.MinibatchSource(deserial,
    randomize=is_training, max_sweeps=sweeps)
  return mb_source
def main():
  print("\nBegin wheat seed classification demo  \n")
  print("Using CNTK verson = " + str(C.__version__) + "\n")
  input_dim = 7
  hidden_dim = 4
  output_dim = 3
  train_file = ".\\Data\\seeds_train_data.txt"
  test_file = ".\\Data\\seeds_test_data.txt"
  # 1. create network and model
  X = C.ops.input_variable(input_dim, np.float32)
  Y = C.ops.input_variable(output_dim, np.float32)
  print("Creating a 7-(4-4-4)-3 tanh softmax NN for seed data ")
  with C.layers.default_options(init= \
    C.initializer.normal(scale=0.1, seed=2)):
    h1 = C.layers.Dense(hidden_dim, activation=C.ops.tanh,
      name='hidLayer1')(X)
    h2 = C.layers.Dense(hidden_dim, activation=C.ops.tanh,
      name='hidLayer2')(h1)
    h3 = C.layers.Dense(hidden_dim, activation=C.ops.tanh,
      name='hidLayer3')(h2)
    oLayer = C.layers.Dense(output_dim, activation=None,
      name='outLayer')(h3)
  nnet = oLayer
  model = C.softmax(nnet)
  # 2. create learner and trainer
  print("Creating a cross entropy, SGD with LR=0.01, \
    batch=10 Trainer \n")
  tr_loss = C.cross_entropy_with_softmax(nnet, Y)
  tr_clas = C.classification_error(nnet, Y)
  learn_rate = 0.01
  learner = C.sgd(nnet.parameters, learn_rate)
  trainer = C.Trainer(nnet, (tr_loss, tr_clas), [learner])
  max_iter = 5000  # maximum training iterations
  batch_size = 10   # mini-batch size
  # 3. create data reader
  rdr = create_reader(train_file, True, input_dim,
    output_dim)
  my_input_map = {
    X : rdr.streams.x_src,
    Y : rdr.streams.y_src
  }
  # 4. train
  print("Starting training \n")
  for i in range(0, max_iter):
    curr_batch = rdr.next_minibatch(batch_size,
      input_map=my_input_map)
    trainer.train_minibatch(curr_batch)
    if i % 1000 == 0:
      mcee = trainer.previous_minibatch_loss_average
      pmea = trainer.previous_minibatch_evaluation_average
      macc = (1.0 - pmea) * 100
      print("batch %6d: mean loss = %0.4f, \
        mean accuracy = %0.2f%% " % (i,mcee, macc))
  print("\nTraining complete")
  # 5. evaluate model on the test data
  print("\nEvaluating test data \n")
  rdr = create_reader(test_file, False, input_dim, output_dim)
  my_input_map = {
    X : rdr.streams.x_src,
    Y : rdr.streams.y_src
  }
  numTest = 60
  allTest = rdr.next_minibatch(numTest, input_map=my_input_map)
  acc = (1.0 - trainer.test_minibatch(allTest)) * 100
  print("Classification accuracy on the \
    60 test items = %0.2f%%" % acc)
  # (could save model here)
  # 6. use trained model to make prediction
  np.set_printoptions(precision = 4)
  unknown = np.array([[17.6, 15.9, 0.8, 6.2, 3.5, 4.1, 6.1]],
    dtype=np.float32)
  print("\nPredicting variety for (non-normalized) seed features: ")
  print(unknown[0])
  raw_out = nnet.eval({X: unknown})
  print("\nRaw output values are: ")
  for i in range(len(raw_out[0])):
    print("%0.4f " % raw_out[0][i])
  pred_prob = model.eval({X: unknown})
  print("\nPrediction probabilities are: ")
  for i in range(len(pred_prob[0])):
    print("%0.4f " % pred_prob[0][i])
  print("\nEnd demo \n ")
# main()
if __name__ == "__main__":
  main()

Die Demo beginnt mit dem Importieren der benötigten NumPy- und CNTK-Pakete und dem Zuweisen der Kurzaliase „np“ und „C“ zu diesen. Die Funktion create_reader ist ein vom Programm definiertes Hilfsprogramm, mit dem Trainingsdaten (wenn der Parameter is_training auf TRUE festgelegt ist) oder Testdaten (wenn is_training auf FALSE festgelegt ist) gelesen werden können.

Sie können die Funktion create_reader auch als Bausteincode für neuronale Klassifikationsprobleme verwenden. Die einzigen Dinge, die Sie unter den meisten Umständen ändern müssen, sind die beiden Zeichenfolgenwerte der Feldargumente in den Aufrufen der StreamDef-Funktion („properties“ und „varieties“ in der Demo).

Die gesamte Programmsteuerungslogik ist in einer einzigen Hauptfunktion enthalten. Der normale Fehlerüberprüfungscode wurde vollständig entfernt, um die Größe der Demo klein zu halten und die Hauptideen klar herauszuarbeiten. Beachten Sie, dass ich statt der üblichen vier Leerzeichen einen Einzug von zwei Leerzeichen verwendet habe, um Platz zu sparen.

Erstellen des Netzwerks und des Modells

Die Hauptfunktion beginnt mit dem Einrichten der Dimensionen der neuronalen Netzwerkarchitektur:

def main():
  print("Begin wheat seed classification demo")
  print("Using CNTK verson = " + str(C.__version__) )
  input_dim = 7
  hidden_dim = 4
  output_dim = 3
...

Da CNTK sich in einer rasanten Entwicklung befindet, empfiehlt es sich, die verwendete Version auszugeben oder zu kommentieren. Die Demo weist drei verborgene Schichten auf, die jeweils vier Knoten besitzen. Die Anzahl der verborgenen Schichten und die Anzahl der Knoten in jeder Schicht müssen durch Versuch und Irrtum ermittelt werden. Sie können auch in jeder Schicht eine unterschiedliche Anzahl von Knoten verwenden. Zum Beispiel würde hidden_dim = [10, 8, 10, 12] einem Deep Network mit vier verborgenen Schichten mit jeweils 10, 8, 10 bzw. 12 Knoten entsprechen.

Im nächsten Schritt wird der Speicherort der Trainings- und Testdatendateien festgelegt, und die Netzwerkein- und -ausgabevektoren werden erstellt:

train_file = ".\\Data\\seeds_train_data.txt"
test_file = ".\\Data\\seeds_test_data.txt"
# 1. create network and model
X = C.ops.input_variable(input_dim, np.float32)
Y = C.ops.input_variable(output_dim, np.float32)

Beachten Sie, dass ich die Trainings- und Testdateien in einem separaten Unterverzeichnis „Data“ gespeichert habe. Dies ist eine gängige Praxis, da Sie während der Modellerstellung oft viele verschiedene Datendateien verwenden. Die Verwendung des Datentyps np.float32 ist weitaus verbreiteter als die Verwendung des Datentyps np.float64, da die zusätzliche Genauigkeit, die durch die Verwendung von 64 Bit erreicht wird, in der Regel nicht die Leistungseinbußen wert ist, mit denen Sie rechnen müssen.

Als nächstes wird das Netzwerk erstellt:

print("Creating a 7-(4-4-4)-3 NN for seed data ")
with C.layers.default_options(init= \
  C.initializer.normal(scale=0.1, seed=2)):
  h1 = C.layers.Dense(hidden_dim,
    activation=C.ops.tanh, name='hidLayer1')(X)
  h2 = C.layers.Dense(hidden_dim, activation=C.ops.tanh,
    name='hidLayer2')(h1)
  h3 = C.layers.Dense(hidden_dim, activation=C.ops.tanh,
    name='hidLayer3')(h2)
  oLayer = C.layers.Dense(output_dim, activation=None,
    name='outLayer')(h3)
nnet = oLayer
model = C.softmax(nnet)

Hier passiert eine Menge. Die with-Anweisung von Python ist eine Abkürzungssyntax, um eine Sammlung allgemeiner Werte auf mehrere Schichten eines Netzwerks anzuwenden. Hier erhalten alle Gewichtungen einen Gaußschen (Glockenkurve) Zufallswert mit einer Standardabweichung von 0,1 und einem Mittelwert von 0. Durch das Festlegen eines Startwerts wird die Reproduzierbarkeit gewährleistet. CNTK unterstützt eine Vielzahl von Initialisierungsalgorithmen, darunter „uniform“, „glorot“, „he“ und „xavier“. Deep Neural Networks reagieren oft überraschend empfindlich auf die Wahl des Initialisierungsalgorithmus, sodass bei einem Fehlschlagen des Trainings als eine der ersten Maßnahmen ein alternativer Initialisierungsalgorithmus ausprobiert werden sollte.

Die drei verborgenen Schichten werden mit der Dense-Funktion definiert, die so genannt wird, weil jeder Knoten vollständig mit den Knoten in den Schichten davor und danach verbunden ist. Die verwendete Syntax kann verwirrend sein. Hier fungiert X als Eingabe für die verborgene Schicht h1. Die Schicht h1 dient als Eingabe für die verborgene Schicht h2 und so weiter.

Beachten Sie, dass die Ausgabeschicht keine Aktivierungsfunktion verwendet, sodass die Ausgabeknoten Werte aufweisen, die nicht notwendigerweise 1 ergeben. Wenn Sie über Erfahrungen mit anderen neuronalen Netzwerkbibliotheken verfügen, bedarf dies einer Erklärung. Bei vielen anderen neuronalen Bibliotheken würden Sie die Softmax-Aktivierung für die Ausgabeschicht verwenden, sodass der Ausgabewert immer 1 ergibt und als Wahrscheinlichkeit interpretiert werden kann. Während des Trainings würden Sie dann die Kreuzentropie-Fehlerfunktion (auch als „Log Loss“ bezeichnet) verwenden. Dafür ist eine Reihe von Werten erforderlich, deren Summe 1 ergibt.

Etwas überraschend verfügt CNTK v2.3 jedoch nicht über eine grundlegende Kreuzentropie-Fehlerfunktion für das Training. Stattdessen verfügt CNTK über eine Kreuzentropie mit Softmax-Funktion. Das bedeutet, dass während des Trainings die Werte der Ausgabeknoten mithilfe von Softmax spontan in Wahrscheinlichkeiten umgewandelt werden, um einen Fehlerterm zu berechnen.

Mit CNTK trainieren Sie also ein Deep Network für Ausgabeknoten-Rohwerte, wenn Sie aber Vorhersagen treffen, müssen Sie (wenn Sie Prognosewahrscheinlichkeiten wünschen, wie es normalerweise der Fall ist) die Funktion Softmax explizit anwenden. Der Ansatz der Demo besteht darin, für das nnet-Objekt zu trainieren (keine Aktivierung in der Ausgabeschicht), aber ein zusätzliches model-Objekt mit angewendeter Softmax-Funktion zu erstellen, das beim Treffen von Vorhersagen verwendet werden kann.

Jetzt ist es tatsächlich möglich, die Softmax-Aktivierung für die Ausgabeschicht zu verwenden und während des Trainings die Kreuzentropie mit Softmax anzuwenden. Dieser Ansatz führt dazu, dass Softmax zwei Mal angewendet wird, zuerst auf die Ausgaberohwerte und dann erneut auf die normalisierten Ausgabeknotenwerte. Wie sich herausstellt, funktioniert dieser Ansatz zwar, aber aus recht komplexen technischen Gründen ist das Training nicht sehr effizient.

Das Verketten verborgener Schichten ist bis zu einem gewissen Punkt möglich. Für sehr tiefe Netzwerke unterstützt CNTK eine Metafunktion namens Sequential, die eine Abkürzungssyntax für die Erstellung von mehrschichtigen Netzwerken bereitstellt. Die CNTK-Bibliothek verfügt außerdem über eine Dropout-Funktion, mit deren Hilfe eine Überanpassung des Modells verhindert werden kann. Um z.B. Dropout zur ersten verborgenen Schicht hinzuzufügen, können Sie den Democode wie folgt ändern:

h1 = C.layers.Dense(hidden_dim, activation=C.ops.tanh,
  name='hidLayer1')(X)
d1 = C.layers.Dropout(0.50, name='drop1')(h1)
h2 = C.layers.Dense(hidden_dim, activation=C.ops.tanh,
  name='hidLayer2')(d1)
h3 = C.layers.Dense(hidden_dim, activation=C.ops.tanh,
  name='hidLayer3')(h2)
oLayer = C.layers.Dense(output_dim, activation=None,
  name='outLayer')(h3)

Viele meiner Kollegen ziehen es vor, Sequential immer zu verwenden, selbst bei tiefen neuronalen Netzen, die nur wenige verborgene Schichten aufweisen. Ich bevorzuge die manuelle Verkettung, aber das ist nur eine Frage des persönlichen Stils.

Trainieren des Netzwerks

Nach der Erstellung eines neuronalen Netzwerks und Modells erstellt das Demoprogramm ein Learner-Objekt und ein Trainer-Objekt:

print("Creating a Trainer \n")
tr_loss = C.cross_entropy_with_softmax(nnet, Y)
tr_clas = C.classification_error(nnet, Y)
learn_rate = 0.01
learner = C.sgd(nnet.parameters, learn_rate)
trainer = C.Trainer(nnet, (tr_loss, tr_clas), [learner])

Sie können sich einen Lerner als einen Algorithmus und einen Trainer als ein Objekt vorstellen, das den Lerner-Algorithmus verwendet. Das Objekt tr_loss („training loss“, Trainingsverlust") definiert, wie Fehler zwischen den vom Netzwerk berechneten Ausgabewerten und den bekannten richtigen Ausgabewerten in den Trainingsdaten gemessen werden. Für die Klassifizierung wird fast immer Kreuzentropie verwendet, aber CNTK unterstützt mehrere Alternativen. Der Teil „with_softmax“ des Funktionsnamens gibt an, dass die Funktion Ausgabeknoten-Rohwerte erwartet und nicht Werte, die mit Softmax normalisiert wurden. Aus diesem Grund verwendet die Ausgabeschicht keine Aktivierungsfunktion.

Das tr_clas-Objekt („training classification error“, Trainigsklassifizierungsfehler) definiert, wie die Anzahl der richtigen und falschen Vorhersagen während des Trainings berechnet wird. CNTK definiert eine Bibliotheksfunktion für Klassifizierungsfehler (Prozentsatz der fehlerhaften Vorhersagen) und nicht eine Klassifizierungsgenauigkeitsfunktion, die von einigen anderen Bibliotheken verwendet wird. Es gibt also zwei Arten von Fehlern, die während des Trainings berechnet werden. Der tr_loss-Fehler wird verwendet, um die Gewichtungen und Bias anzupassen. tr_clas wird für die Überwachung der Vorhersagegenauigkeit verwendet.

Das Learner-Objekt verwendet den SGD-Algorithmus mit einer konstanten Lernrate, die auf 0,01 festgelegt ist. SGD ist der einfachste Trainingsalgorithmus, aber selten der leistungsstärkste. CNTK unterstützt eine große Anzahl von Lerner-Algorithmen, von denen einige sehr komplex sind. Als Faustregel empfehle ich, mit SGD zu beginnen und nur dann exotischere Algorithmen auszuprobieren, wenn das Training fehlschlägt. Der Adam-Algorithmus (Adam ist hier kein Akronym) ist normalerweise meine zweite Wahl.

Beachten Sie die ungewöhnliche Syntax zum Erstellen eines Trainer-Objekts. Die beiden Objekte der Verlustfunktion werden als Python-Tupel übergeben, was durch die Klammern angegeben wird. Das Learner-Objekt wird jedoch als Python-Liste übergeben, die durch eckige Klammern gekennzeichnet ist. Sie können mehrere Leaner-Objekte an einen Trainer übergeben, obwohl das Demoprogramm nur ein einziges Objekt übergibt.

Der folgende Code führt das Training tatsächlich aus:

for i in range(0, max_iter):
  curr_batch = rdr.next_minibatch(batch_size,
    input_map=my_input_map)
  trainer.train_minibatch(curr_batch)
  if i % 1000 == 0:
    mcee = trainer.previous_minibatch_loss_average
    pmea = trainer.previous_minibatch_evaluation_average
    macc = (1.0 - pmea) * 100
    print("batch %6d: mean loss = %0.4f, \
      mean accuracy = %0.2f%% " % (i, mcee, macc))

Es ist wichtig, den Trainingsfortschritt zu überwachen, da das Training häufig fehlschlägt. Hier wird alle 1.000 Iterationen der durchschnittliche Kreuzentropiefehler des soeben verwendeten Batches von 10 Trainingselementen angezeigt. Die Demo zeigt die durchschnittliche Klassifizierungsgenauigkeit (Prozentsatz der richtigen Vorhersagen für die aktuellen 10 Elemente), die meiner Meinung nach eine natürlichere Metrik ist als der Klassifizierungsfehler (Prozentsatz der falschen Vorhersagen).

Speichern des trainierten Modells

Da nur 150 Trainingselemente vorhanden sind, kann das neuronale Demonetzwerk innerhalb weniger Sekunden trainiert werden. Aber in Nicht-Demoszenarien kann das Trainieren eines sehr tiefen neuronalen Netzwerks Stunden, Tage oder sogar noch länger dauern. Nach dem Training möchten Sie Ihr Modell wahrscheinlich speichern, damit Sie es nicht von Grund auf neu trainieren müssen. Das Speichern und Laden eines trainierten CNTK-Modells ist sehr einfach. Zum Speichern können Sie dem Demoprogramm z.B. solchen Code hinzufügen:

mdl = ".\\Models\\seed_dnn.model"
model.save(mdl, format=C.ModelFormat.CNTKv2)

Das erste Argument, das an die Speicherfunktion übergeben wird, ist nur ein Dateiname, ggf. mit Pfadangabe. Es ist keine bestimmte Dateierweiterung erforderlich, aber die Verwendung von „MODEL“ ist sinnvoll. Der Formatparameter weist den Standardwert ModelFormat.CNTKv2 auf, so dass er ausgelassen werden kann. Eine Alternative ist die Verwendung des neuen Open Neural Network Exchange-Formats (ONNX).

Erinnern Sie sich daran, dass das Demoprogramm sowohl ein nnet-Objekt (ohne Softmax für die Ausgabe) als auch ein Modellobjekt (mit Softmax) erstellt hat. Normalerweise möchten Sie die Softmax-Version eines trainierten Modells speichern, aber Sie können auch das Nicht-Softmax-Objekt speichern, wenn Sie möchten.

Nachdem ein Modell gespeichert wurde, können Sie es wie folgt in den Arbeitsspeicher laden:

model = C.ops.functions.Function.Load(".\\Models\\seed_dnn.model")

Anschließend kann das Modell so verwendet werden, als ob es gerade erst trainiert worden wäre. Beachten Sie, dass ein wenig Asymmetrie in den Aufrufen zum Speichern und Laden vorhanden ist: save ist eine Methode für ein Function-Objekt, und load ist eine statische Methode aus der Function-Klasse.

Zusammenfassung

Viele Klassifizierungsprobleme können mithilfe eines einfachen FNN (Feed-Forward Neural Network) mit einer einzigen verborgenen Schicht gelöst werden. Theoretisch kann ein FNN jedes Problem verarbeiten, das ein tiefes neuronales Netzwerk bewältigen kann. In der Praxis ist es jedoch manchmal einfacher, ein tiefes neuronales Netz zu trainieren als ein FNN. Die mathematische Grundlage für diese Ideen wird als universelles Approximationstheorem (oder manchmal auch als Cybenko-Theorem) bezeichnet.

Wenn die Klassifizierung neuronaler Netze neu für Sie ist, kann die Anzahl der Entscheidungen, die Sie treffen müssen, einschüchternd wirken. Sie müssen sich für die Anzahl der verborgenen Schichten, die Anzahl der Knoten in jeder Schicht, ein Initialisierungsschema und eine Aktivierungsfunktion für jede verborgene Schicht, einen Trainingsalgorithmus und die Parameter des Trainingsalgorithmus wie Lernrate und den Momentumterm entscheiden. Mit etwas Übung werden Sie jedoch schnell eine Reihe von Faustregeln für die Arten von Problemen entwickeln, mit denen Sie zu tun haben.


Dr. James McCaffreyist in Redmond (Washington) für Microsoft Research tätig. Er hat an verschiedenen Microsoft-Produkten mitgearbeitet, unter anderem an Internet Explorer und Bing. Dr. McCaffrey erreichen Sie unter jamccaff@microsoft.com.

Unser Dank gilt den folgenden technischen Experten von Microsoft für die Durchsicht dieses Artikels: Chris Lee, Ricky Loynd, Kenneth Tran


Diesen Artikel im MSDN Magazine-Forum diskutieren