Vom Lösen von Gleichungen bis zum Deep Learning: Ein TensorFlow-Python-Tutorial
Veröffentlicht: 2022-03-11In letzter Zeit gab es einige bemerkenswerte Entwicklungen in der Welt der künstlichen Intelligenz, von viel publizierten Fortschritten bei selbstfahrenden Autos bis hin zu Maschinen, die jetzt Chopin-Imitationen komponieren oder einfach nur wirklich gut in Videospielen sind.
Im Mittelpunkt dieser Fortschritte stehen eine Reihe von Tools, mit denen Deep Learning und andere Modelle für maschinelles Lernen abgeleitet werden können, darunter Torch, Caffe und Theano. Seit Google Brain jedoch im November 2015 mit seinem eigenen Framework TensorFlow Open Source wurde, haben wir gesehen, wie die Popularität dieser Softwarebibliothek zum beliebtesten Deep-Learning-Framework geworden ist.
Warum ist das passiert? Zu den Gründen gehören die Fülle an verfügbarer Unterstützung und Dokumentation, die Produktionsreife, die einfache Verteilung von Berechnungen auf eine Reihe von Geräten und ein hervorragendes Visualisierungstool: TensorBoard.
Letztendlich gelingt es TensorFlow, ein umfassendes und flexibles Set an technischen Features mit großer Benutzerfreundlichkeit zu kombinieren.
In diesem Artikel lernen Sie die Mechanik dieses Tools kennen, indem Sie es verwenden, um ein allgemeines numerisches Problem zu lösen, das weit außerhalb dessen liegt, was maschinelles Lernen normalerweise beinhaltet, bevor Sie seine Verwendung im Deep Learning mit einer einfachen Implementierung eines neuronalen Netzwerks vorstellen.
Bevor Sie beginnen
Grundlegende Kenntnisse in Methoden des maschinellen Lernens werden vorausgesetzt. Wenn Sie aufholen müssen, lesen Sie diesen sehr nützlichen Beitrag.
Da wir die Python-API demonstrieren werden, ist ein Verständnis von Numpy ebenfalls von Vorteil.
Um TensorFlow einzurichten, folgen Sie bitte den Anweisungen hier.
Wenn Sie Windows verwenden, sollten Sie beachten, dass Sie zum Zeitpunkt des Schreibens Python 3.4+ und nicht 2.7 verwenden müssen.
Wenn Sie dann fertig sind, sollten Sie in der Lage sein, die Bibliothek zu importieren mit:
import tensorflow as tf
Schritt 1 von 2 zu einer TensorFlow-Lösung: Erstellen Sie ein Diagramm
Die Konstruktion von TensorFlow-Programmen besteht im Allgemeinen aus zwei Hauptschritten, von denen der erste darin besteht, einen Berechnungsgraphen zu erstellen, der die Berechnungen beschreibt, die Sie ausführen möchten, aber sie nicht wirklich ausführt oder irgendwelche Werte enthält.
Wie bei jedem Graphen haben wir Knoten und Kanten. Die Kanten stellen Tensoren dar, wobei ein Tensor ein n-dimensionales Array darstellt. Zum Beispiel ist ein Tensor mit Dimension (oder Rang in TensorFlow-Sprache) 0 ein Skalar, Rang 1 ein Vektor, Rang 2 eine Matrix und so weiter.
Knoten stellen Operationen dar, die einen Ausgabetensor erzeugen, wobei bei Bedarf Tensoren als Eingaben verwendet werden. Solche Operationen umfassen Additionen ( tf.add
), Matrixmultiplikationen ( tf.matmul
) und auch die Erstellung von Konstanten ( tf.constant
).
Kombinieren wir also einige davon für unsere erste Grafik.
a = tf.constant([2.5, 2]) b = tf.constant([3, 6], dtype=tf.float32) total = tf.add(a, b)
Hier haben wir drei Operationen erstellt, zwei davon zum Erstellen konstanter 1-d-Arrays.
Die Datentypen werden aus dem übergebenen Argument values abgeleitet, oder Sie können diese mit dem Argument dtype
. Wenn ich dies nicht für b
getan hätte, wäre ein int32
abgeleitet und ein Fehler ausgegeben worden, da tf.add
versucht hätte, eine Addition für zwei verschiedene Typen zu definieren.
Schritt 2 von 2 zu einer TensorFlow-Lösung: Ausführen der Operationen
Das Diagramm ist definiert, aber um tatsächlich Berechnungen damit (oder einem Teil davon) durchzuführen, müssen wir eine TensorFlow-Sitzung einrichten.
sess = tf.Session()
Wenn wir alternativ eine Sitzung in einer interaktiven Shell wie IPython ausführen, verwenden wir:
sess = tf.InteractiveSession()
Die run
-Methode für das Sitzungsobjekt ist eine Möglichkeit, einen Tensor auszuwerten.
Um die oben definierte Additionsberechnung auszuwerten, übergeben wir daher „total“, den abzurufenden Tensor, der die Ausgabe der tf.add
.
print(sess.run(total)) # [ 5.5 8. ]
An dieser Stelle stellen wir die Variable-Klasse von TensorFlow vor. Während Konstanten ein fester Bestandteil der Graphdefinition sind, können Variablen aktualisiert werden. Der Klassenkonstruktor benötigt einen Anfangswert, aber selbst dann benötigen Variablen eine Operation, um sie explizit zu initialisieren, bevor andere Operationen an ihnen ausgeführt werden.
Variablen halten den Status des Diagramms in einer bestimmten Sitzung, daher sollten wir beobachten, was bei mehreren Sitzungen passiert, die dasselbe Diagramm verwenden, um die Variablen besser zu verstehen.
# Create a variable with an initial value of 1 some_var = tf.Variable(1) # Create op to run variable initializers init_op = tf.global_variables_initializer() # Create an op to replace the value held by some_var to 3 assign_op = some_var.assign(3) # Set up two instances of a session sess1 = tf.Session() sess2 = tf.Session() # Initialize variables in both sessions sess1.run(init_op) sess2.run(init_op) print(sess1.run(some_var)) # Outputs 1 # Change some_var in session1 sess1.run(assign_op) print(sess1.run(some_var)) # Outputs 3 print(sess2.run(some_var)) # Outputs 1 # Close sessions sess1.close() sess2.close()
Wir haben das Diagramm und zwei Sitzungen eingerichtet.
Nachdem wir die Initialisierung in beiden Sitzungen ausgeführt haben (wenn wir dies nicht ausführen und dann die Variable auswerten, ist ein Fehler aufgetreten), führen wir die Assign-Operation nur in einer Sitzung aus. Wie man sieht, bleibt der Variablenwert bestehen, aber nicht über Sitzungen hinweg.
Den Graphen füttern, um numerische Probleme zu lösen
Ein weiteres wichtiges Konzept von TensorFlow ist der Platzhalter. Während Variablen den Zustand halten, werden Platzhalter verwendet, um zu definieren, welche Eingaben das Diagramm erwarten kann, sowie ihren Datentyp (und optional ihre Form). Dann können wir über diese Platzhalter Daten in den Graphen einspeisen, wenn wir die Berechnung durchführen.
Der TensorFlow-Graph ähnelt allmählich den neuronalen Netzen, die wir schließlich trainieren wollen, aber vorher wollen wir die Konzepte verwenden, um ein allgemeines numerisches Problem aus der Finanzwelt zu lösen.
Angenommen, wir möchten y
in einer Gleichung wie dieser finden:
für ein gegebenes v
(mit C
und P
konstant).
Dies ist eine Formel zur Berechnung der Verfallrendite ( y
) einer Anleihe mit Marktwert v
, Kapital P
und Coupon C
, die halbjährlich gezahlt wird, jedoch mit diskontierten Cashflows und kontinuierlicher Aufzinsung.
Wir müssen eine Gleichung wie diese im Grunde mit Versuch und Irrtum lösen, und wir werden die Bisektionsmethode wählen, um unseren Endwert für y
auf Null zu setzen.
Zuerst werden wir dieses Problem als TensorFlow-Graph modellieren.
C
und P
sind feste Konstanten und bilden einen Teil der Definition unseres Graphen. Wir möchten einen Prozess haben, der die unteren und oberen Grenzen von y
verfeinert. Daher sind diese Grenzen (mit a
und b
bezeichnet) gute Kandidaten für Variablen, die nach jeder Schätzung von y
(als Mittelpunkt von a
und b
angenommen) geändert werden müssen.
# Specify the values our constant ops will output C = tf.constant(5.0) P = tf.constant(100.0) # We specify the initial values that our lower and upper bounds will be when initialised. # Obviously the ultimate success of this algorithm depends on decent start points a = tf.Variable(-10.0) b = tf.Variable(10.0) # We expect a floating number to be inserted into the graph v_target = tf.placeholder("float") # Remember the following operations are definitions, # none are carried out until an operation is evaluated in a session! y = (a+b)/2 v_guess = C*tf.exp(-0.5*y) + C*tf.exp(-y) + C*tf.exp(-1.5*y) + (C + P)*tf.exp(-2*y) # Operations to set temporary values (a_ and b_) intended to be the next values of a and b. # eg if the guess results in av greater than the target v, # we will set a_ to be the current value of y a_ = tf.where(v_guess > v_target, y, a) b_ = tf.where(v_guess < v_target, y, b) # The last stage of our graph is to assign the two temporary vals to our variables step = tf.group( a.assign(a_), b.assign(b_) )
Wir haben jetzt also eine Liste von Operationen und Variablen, von denen jede für eine bestimmte Sitzung ausgewertet werden kann. Einige dieser Operationen sind auf die Ausführung anderer Operationen angewiesen, sodass beispielsweise das Ausführen von v_guess
eine Kettenreaktion auslöst, damit andere Tensoren wie C
und P
zuerst ausgewertet werden.

Einige dieser Operationen beruhen auf einem Platzhalter, für den ein Wert angegeben werden muss. Wie geben wir diesen Wert also tatsächlich ein?
Dies erfolgt über das Argument feed_dict
in der run
-Funktion selbst.
Wenn wir a_
auswerten wollen, setzen wir den Wert für unseren Platzhalter v_target
, etwa so:
sess.run(a_, feed_dict={v_target: 100})
gibt uns 0,0.
Setzen Sie ein v_target
von 130 ein und wir erhalten -10,0.
Es ist unsere „Schritt“-Operation, die tatsächlich erfordert, dass alle anderen Operationen als Voraussetzung ausgeführt werden, und tatsächlich den gesamten Graphen ausführt. Es ist auch eine Operation, die den tatsächlichen Zustand während unserer Sitzung tatsächlich ändert. Je öfter wir also den Schritt ausführen, desto mehr verschieben wir unsere Variablen a
und b
inkrementell in Richtung des tatsächlichen Werts von y
.
Nehmen wir also an, unser Wert für v
in unserer Gleichung ist gleich 95. Lassen Sie uns eine Sitzung einrichten und unser Diagramm 100 Mal darauf ausführen.
# Set up a session and initialize variables sess = tf.Session() tf.global_variables_initializer().run() # Run the step operation (and therefore whole graph) 100 times for i in range (100): sess.run(step, feed_dict={v_target:95})
Wenn wir jetzt den y
-Tensor auswerten, erhalten wir so etwas wie eine wünschenswerte Antwort
print(sess.run(y)) # 0.125163
Neuronale Netze
Nachdem wir nun die Mechanik von TensorFlow verstanden haben, können wir dies mit einigen zusätzlichen maschinellen Lernoperationen, die in TensorFlow integriert sind, zusammenbringen, um ein einfaches neuronales Netzwerk zu trainieren.
Hier möchten wir Datenpunkte in einem 2D-Koordinatensystem klassifizieren, je nachdem, ob sie in eine bestimmte Region fallen – einen Kreis mit Radius 0,5, der am Ursprung zentriert ist.
Natürlich kann dies konkret verifiziert werden, indem einfach nach einem bestimmten Punkt (a,b)
gesucht wird, wenn a^2 + b^2 < 0.5
, aber für die Zwecke dieses Experiments zum maschinellen Lernen möchten wir stattdessen a übergeben Trainingssatz: Eine Reihe von zufälligen Punkten und ob sie in unsere beabsichtigte Region fallen. Hier ist eine Möglichkeit, dies zu erstellen:
import numpy as np NO_OF_RANDOM_POINTS = 100 CIRCLE_RADIUS = 0.5 random_spots = np.random.rand(NO_OF_RANDOM_POINTS, 2) * 2 - 1 is_inside_circle = (np.power(random_spots[:,0],2) + np.power(random_spots[:,1],2) < CIRCLE_RADIUS).astype(int)
Wir erstellen ein neuronales Netzwerk mit den folgenden Eigenschaften:
- Es besteht aus einer Eingabeschicht mit zwei Knoten, in die wir unsere Reihe von zweidimensionalen Vektoren einspeisen, die in „random_spots“ enthalten sind. Dies wird durch einen Platzhalter dargestellt, der auf die Trainingsdaten wartet.
- Die Ausgabeschicht wird auch zwei Knoten haben, also müssen wir unsere Reihe von Trainingslabels („is_inside_circle“) in einen Platzhalter für einen Skalar einspeisen und diese Werte dann in einen One-Hot-zweidimensionalen Vektor umwandeln.
- Wir haben eine verborgene Schicht, die aus drei Knoten besteht, daher müssen wir Variablen für unsere Gewichtungsmatrizen und Bias-Vektoren verwenden, da dies die Werte sind, die bei der Durchführung des Trainings verfeinert werden müssen.
INPUT_LAYER_SIZE = 2 HIDDEN_LAYER_SIZE = 3 OUTPUT_LAYER_SIZE = 2 # Starting values for weights and biases are drawn randomly and uniformly from [-1, 1] # For example W1 is a matrix of shape 2x3 W1 = tf.Variable(tf.random_uniform([INPUT_LAYER_SIZE, HIDDEN_LAYER_SIZE], -1, 1)) b1 = tf.Variable(tf.random_uniform([HIDDEN_LAYER_SIZE], -1, 1)) W2 = tf.Variable(tf.random_uniform([HIDDEN_LAYER_SIZE, OUTPUT_LAYER_SIZE], -1, 1)) b2 = tf.Variable(tf.random_uniform([OUTPUT_LAYER_SIZE], -1, 1)) # Specifying that the placeholder X can expect a matrix of 2 columns (but any number of rows) # representing random spots X = tf.placeholder(tf.float32, [None, INPUT_LAYER_SIZE]) # Placeholder Y can expect integers representing whether corresponding point is in the circle # or not (no shape specified) Y = tf.placeholder(tf.uint8) # An op to convert to a one hot vector onehot_output = tf.one_hot(Y, OUTPUT_LAYER_SIZE)
Um die Definition unseres Graphen zu vervollständigen, definieren wir einige Operationen, die uns helfen werden, die Variablen zu trainieren, um einen besseren Klassifikator zu erreichen. Dazu gehören die Matrixberechnungen, Aktivierungsfunktionen und der Optimierer.
LEARNING_RATE = 0.01 # Op to perform matrix calculation X*W1 + b1 hidden_layer = tf.add(tf.matmul(X, W1), b1) # Use sigmoid activation function on the outcome activated_hidden_layer = tf.sigmoid(hidden_layer) # Apply next weights and bias (W2, b2) to hidden layer and then apply softmax function # to get our output layer (each vector adding up to 1) output_layer = tf.nn.softmax(tf.add(tf.matmul(activated_hidden_layer, W2), b2)) # Calculate cross entropy for our loss function loss = -tf.reduce_sum(onehot_output * tf.log(output_layer)) # Use gradient descent optimizer at specified learning rate to minimize value given by loss tensor train_step = tf.train.GradientDescentOptimizer(LEARNING_RATE).minimize(loss)
Nachdem Sie unser Diagramm eingerichtet haben, ist es an der Zeit, eine Sitzung einzurichten und den „train_step“ auszuführen (der auch alle erforderlichen Operationen ausführt). Einige dieser Operationen verwenden Platzhalter, daher müssen Werte für diese angegeben werden. Dieser Trainingsschritt stellt eine Epoche in unserem Lernalgorithmus dar und wird als solche über die Anzahl der Epochen geschleift, die wir ausführen möchten. Wir können andere Teile des Diagramms ausführen, wie z. B. den „Verlust“-Tensor zu Informationszwecken.
EPOCH_COUNT = 1000 sess = tf.Session() tf.global_variables_initializer().run() for i in range(EPOCH_COUNT): if i%100 == 0: print('Loss after %d runs: %f' % (i, sess.run(loss, feed_dict={X: random_spots, Y: is_inside_circle}))) sess.run(train_step, feed_dict={X: random_spots, Y: is_inside_circle}) print('Final loss after %d runs: %f' % (i, sess.run(loss, feed_dict={X: random_spots, Y: is_inside_circle})))
Sobald wir den Algorithmus trainiert haben, können wir einen Punkt eingeben und die Ausgabe des neuronalen Netzwerks wie folgt erhalten:
sess.run(output_layer, feed_dict={X: [[1, 1]]}) # Hopefully something close to [1, 0] sess.run(output_layer, feed_dict={X: [[0, 0]]}) # Hopefully something close to [0, 1]
Wir können den Punkt als außerhalb des Kreises klassifizieren, wenn das erste Element des Ausgabevektors größer als 0,5 ist, andernfalls innerhalb.
Indem wir den output_layer
Tensor für viele Punkte ausführen, können wir uns ein Bild davon machen, wie sich der Lernende die Region vorstellt, die die positiv klassifizierten Punkte enthält. Es lohnt sich, mit der Größe des Trainingssatzes, der Lernrate und anderen Parametern herumzuspielen, um zu sehen, wie nahe wir an den beabsichtigten Kreis herankommen.
Lernrate: 0,01
Epochen: 1000
Lernrate: 0,01
Epochen: 1000
Lernrate: 0,01
Epochen: 10000
Lernrate: 0,001
Epochen: 10000
Einpacken
Dies ist eine gute Lehre, dass ein erhöhter Trainingssatz oder eine erhöhte Epochenmenge keine Garantie für einen guten Lerner ist – die Lernrate sollte entsprechend angepasst werden.
Hoffentlich haben Ihnen diese Demonstrationen einen guten Einblick in die Kernprinzipien von TensorFlow gegeben und eine solide Grundlage für die Implementierung komplexerer Techniken geschaffen.
Wir haben Konzepte wie das Tensorboard oder das Training unserer Modelle über GPUs hinweg nicht behandelt, aber diese werden in der TensorFlow-Dokumentation gut behandelt. In der Dokumentation finden Sie eine Reihe von Rezepten, die Ihnen dabei helfen können, mit diesem leistungsstarken Framework spannende Deep-Learning-Aufgaben zu bewältigen!