Dies ist der Artikel zum 9. Tag des TensorFlow-Adventskalenders 2016.
TensorFlow wurde im November 2015 veröffentlicht, aber die Funktion "Namensraum" wurde von Anfang an unterstützt. Dies wird in der Grafikvisualisierung mit TensorBoard verwendet, aber natürlich nicht nur dafür. Namespaces sind sehr nützlich für die Verwaltung von Bezeichnern. Die starke Unterstützung von "Namespace" erinnert mich an C ++, aber ich werde es aus dem C ++ - Lehrbuch (Selbststudium C ++) zitieren.
Der Zweck von Namespaces besteht darin, die Namen von Bezeichnern zu lokalisieren und Namenskonflikte zu vermeiden. In der C ++ - Programmierumgebung haben sich die Namen von Variablen, Funktionen und Klassen weiter vermehrt. Vor dem Aufkommen von Namespaces konkurrierten alle diese Namen um Platz innerhalb des globalen Namespace, was zu vielen Konflikten führte.
Auf der anderen Seite hat Pythons variabler Bereich nur das Minimum an lokalem, globalem (global
), + α ( nichtlokal
), also ein Google-Ingenieur, der den Kernteil von TensorFlow in C ++ geschrieben hat Es erscheint naheliegend, über die Implementierung der Namespace-Unterstützung auf C ++ - Ebene in TensorFlow nachzudenken.
In MLP (Multi-Layer Perceptron), das nicht viele Schichten im neuronalen Netzwerk hat, ist die Namensverwaltung kein Problem, aber für tiefe CNNs und große Modelle in RNNs ist auch eine Gewichtsverteilung möglich, sodass geeignete Variablen verwendet werden können. Ein Umfang ist erforderlich. In Anbetracht des Scale-Ups (ich habe wenig Erfahrung mit mir selbst) muss ich den Code auch auf verteilte Umgebungen wie Multi-Device (GPU) und Cluster anwenden. Auch hier brauchen wir einen variablen Bereich.
In diesem Artikel möchte ich die zugehörige API bestätigen, um den "Namensraum" von TensorFlow genau zu verstehen. (Die Programmierumgebung ist TensorFlow 0.11.0, Python 3.5.2, Ubuntu 16.04LTS.)
Wenn Sie das Dokument richtig lesen, ist der Umfang der Variablen nicht schwierig. Wenn Sie es jedoch "mehrdeutig" verstehen, werden die folgenden Punkte erfasst.
Ich werde die Antwort zuerst schreiben, aber die Antwort auf Frage 1 lautet, dass tf.name_scope () eine allgemeinere Bereichsdefinition ist und tf.variable_scope () eine dedizierte Bereichsdefinition zum Verwalten von Variablen (Bezeichnern) ist. Es wird. Die Antwort auf Frage 2 lautet außerdem, dass tf.Variable () eine primitivere (niedrigere) Variablendefinition ist, während tf.get_variable () eine (höhere) Variable ist, die den Variablenbereich berücksichtigt. Es wird eine Definition. (Das zugehörige Dokument TesorFlow - "HOW TO" - "Sharing Variable" erläutert die Beziehung zwischen gemeinsam genutzten Variablen im Detail.)
Im Folgenden möchte ich den Code verschieben und die Details untersuchen.
# tf.name_scope
with tf.name_scope("my_scope"):
v1 = tf.get_variable("var1", [1], dtype=tf.float32)
v2 = tf.Variable(1, name="var2", dtype=tf.float32)
a = tf.add(v1, v2)
print(v1.name) # var1:0
print(v2.name) # my_scope/var2:0
print(a.name) # my_scope/Add:0
Zuerst habe ich "tf.name_scope ()" verwendet, um die darin enthaltenen Variablen zu definieren. Der von TensorFlow verwaltete Bezeichner wird in der zweiten Hälfte ausgegeben, die Ausgabe wird jedoch als Kommentar rechts von der print-Anweisung angezeigt. Der Bereich von "my_scope" ist für die durch "tf.Variable ()" und die Additionsoperation a definierte Variable v2 richtig definiert. Andererseits hat v1, definiert durch "tf.get_varible ()", den Bereich brillant ignoriert.
# tf.variable_scope
with tf.variable_scope("my_scope"):
v1 = tf.get_variable("var1", [1], dtype=tf.float32)
v2 = tf.Variable(1, name="var2", dtype=tf.float32)
a = tf.add(v1, v2)
print(v1.name) # my_scope/var1:0
print(v2.name) # my_scope_1/var2:0 ...Der Bereichsname wurde aktualisiert.
print(a.name) # my_scope_1/Add:0 ...Der Bereichsname nach dem Update wird beibehalten.
Dann habe ich tf.variable_sope ()
verwendet. (Beachten Sie, dass sich das vorherige und das aktuelle Snippet kontinuierlich bewegen.)
Der durch "tf.get_variable ()" definierte Bezeichner der Variablen v1 wurde erstellt, wobei "my_scope" wie beabsichtigt an den Variablennamen angehängt wurde. Außerdem haben die Variable v2 darunter und die Operation a "my_scope_1" hinzugefügt (trotz der Tatsache, dass es sich um tf.variable_scope ("my_scope") handelt). Der Grund dafür ist, dass es "my_scope" (im Anfangszustand des Programms) hätte geben sollen, aber es ist automatisch, da derselbe Bezeichner ("my_scope / var2: 0") bereits im vorherigen Code-Snippet verwendet wurde. Dies liegt daran, dass ich auf "my_scope_1" aktualisiert habe. (Die Anweisung "a = tf.add (v1, v2)" nach der Aktualisierung des Bereichsnamens ("my_scope" -> "my_scope_1") scheint diesen Bereich ("my_scope_1") beizubehalten.)
Es wird kompliziert, also werde ich es ein wenig klären.
--tf.name_scope () ist eine allgemeine Definition des Namensbereichs. (Wie Sie wissen, verwendet die Ausgabe an TensorBoard diese Bezeichnereinstellung.) --tf.variable_scope () ist eine Bereichsdefinition, die für die Variablenverwaltung verwendet wird. (Funktionsname variable_scope ist wie es ist ...) --tf.get_variable () definiert Variablen beim Verwalten von Variablennamen-IDs (neu oder doppelt?). Stellen Sie sicher, dass Sie es als Set mit tf.variable_scope () verwenden.
In den beiden obigen Ausschnitten war die Situation aufgrund des Experiments kompliziert, aber es ist nicht besonders schwierig, wenn Sie dem Grundprinzip folgen, dass "tf.get_variable () als Menge mit tf.variable_scope () verwendet wird".
Lassen Sie uns nun sehen, wie gemeinsam genutzte Variablen mit tf.get_variable () verwendet werden.
with tf.variable_scope("my_scope2"):
v4_init = tf.constant_initializer([4.])
v4 = tf.get_variable("var4", shape=[1], initializer=v4_init)
print(v4.name) # my_scope2/var4:0
Zunächst haben wir im Bereich "my_scope2" die Variable v4 definiert. tf.get_variable ()
gibt einen Variableninitialisierer zum Definieren einer Variablen an. Hier haben wir einen konstanten Initialisierer verwendet, um eine Anweisung zu erstellen, die 4. in v4 enthält. Für den Bezeichner von TensorFlow wurde im ersten Argument "var4" angegeben.
Versuchen Sie als Nächstes, eine Variable mit demselben Bezeichner zuzuweisen.
with tf.variable_scope("my_scope2"):
v5 = tf.get_variable("var4", shape=[1], initializer=v4_init)
ValueError: Variable my_scope2/var4 already exists, disallowed. Did you mean to set reuse=True in VarScope? Originally defined at:
File "name_scope_ex.py", line 47, in <module>
v4 = tf.get_variable("var4", shape=[1], initializer=v4_init)
Wie geplant. Ein ValueError ist aufgetreten. Der Fehler lautet "Ist es nicht seltsam, Variablen mit demselben Bezeichner zu verwenden?" Die Neuzuweisung von Variablen ** unter Verwendung derselben Kennung verwendet die Option "Wiederverwendung" wie folgt.
with tf.variable_scope("my_scope2", reuse=True):
v5 = tf.get_variable("var4", shape=[1])
print(v5.name) # my_scope2/var4:0
Alternativ können Sie auch Folgendes tun.
with tf.variable_scope("my_scope2"):
tf.get_variable_scope().reuse_variables()
v5 = tf.get_variable("var4", shape=[1])
print(v5.name) # my_scope2/var4:0
Bisher haben wir die Grundfunktionen von "tf.variable_scope ()" und "tf.get_variable ()" bestätigt.
Schauen wir uns nun ein Beispiel für die Verwendung gemeinsam genutzter Variablen an, jedoch im Dokument TensorFlow - Sharing Variable. , Bitte beachten Sie das folgende Verwendungsbeispiel.
Da es sich bei beiden um eine beträchtliche Menge an Code handelt, möchte ich diesmal die Gewichtsverteilung eines von diesen abweichenden Selbstcodierers (im Folgenden als Autoencoder bezeichnet) übernehmen. Die Codierungsseite / Decodierungsseite des Autoencoders kann wie folgt ausgedrückt werden.
y = f(\textbf{W}x + \textbf{b}) \\
\hat{x} = \tilde{f}(\tilde{\textbf{W}}y + \tilde{\textbf{b}})
Die folgende Gewichtsverteilung kann in einem solchen symmetrischen Autoencoder verwendet werden.
\tilde{\textbf{W} } = \textbf{W} ^{\mathrm{T}}
Lassen Sie uns das obige Konfigurationsnetzwerk mit gemeinsam genutzten Variablen von TensorFlow implementieren. Definieren Sie zunächst die Encoder-Klasse.
# Encoder Layer
class Encoder(object):
def __init__(self, input, n_in, n_out, vs_enc='encoder'):
self.input = input
with tf.variable_scope(vs_enc):
weight_init = tf.truncated_normal_initializer(mean=0.0, stddev=0.05)
W = tf.get_variable('W', [n_in, n_out], initializer=weight_init)
bias_init = tf.constant_initializer(value=0.0)
b = tf.get_variable('b', [n_out], initializer=bias_init)
self.w = W
self.b = b
def output(self):
linarg = tf.matmul(self.input, self.w) + self.b
self.output = tf.sigmoid(linarg)
return self.output
Der Variablenbereich wird mit der Option "vs_enc" angegeben und festgelegt, und "W" wird mit "tf.get_variable ()" definiert. Als nächstes folgt die Decoder-Klasse, die wie folgt lautet.
# Decoder Layer
class Decoder(object):
def __init__(self, input, n_in, n_out, vs_dec='decoder'):
self.input = input
if vs_dec == 'decoder': # independent weight
with tf.variable_scope(vs_dec):
weight_init = tf.truncated_normal_initializer(mean=0.0, stddev=0.05)
W = tf.get_variable('W', [n_in, n_out], initializer=weight_init)
else: # weight sharing (tying)
with tf.variable_scope(vs_dec, reuse=True): # set reuse option
W = tf.get_variable('W', [n_out, n_in])
W = tf.transpose(W)
with tf.variable_scope('decoder'): # in all case, need new bias
bias_init = tf.constant_initializer(value=0.0)
b = tf.get_variable('b', [n_out], initializer=bias_init)
self.w = W
self.b = b
def output(self):
linarg = tf.matmul(self.input, self.w) + self.b
self.output = tf.sigmoid(linarg)
return self.output
Das meiste davon ist dasselbe wie die Encoder-Klasse, aber die Definitionsanweisung der Variablen W wird durch Verzweigung verarbeitet. Der Netzwerkdefinitionsteil lautet wie folgt.
# make neural network model
def make_model(x):
enc_layer = Encoder(x, 784, 625, vs_enc='encoder')
enc_out = enc_layer.output()
dec_layer = Decoder(enc_out, 625, 784, vs_dec='encoder')
dec_out = dec_layer.output()
return enc_out, dec_out
Wenn Sie beim Erstellen eines Decoder-Objekts "vs_dec =" Decoder "angeben oder diese Option weglassen, wird eine neue Gewichtsvariable" W "zugewiesen, die vom Encoder wie oben" vs_dec = "Encoder" zugewiesen wird. Wenn derselbe Variablenbereich verwendet wurde, wurde die Gewichtsvariable implementiert, so dass "W" als gemeinsam genutzte Variable wiederverwendet wird. (Transponieren Sie bei der Wiederverwendung "W" entsprechend dem Netzwerk.)
Ein Beispiel für die Ausführung einer Berechnung mit MNIST-Daten wird gezeigt. Wenn zunächst keine Gewichtsverteilung durchgeführt wird, ist das Ergebnis wie folgt.
Training...
step, loss = 0: 0.732
step, loss = 1000: 0.271
step, loss = 2000: 0.261
step, loss = 3000: 0.240
step, loss = 4000: 0.234
step, loss = 5000: 0.229
step, loss = 6000: 0.219
step, loss = 7000: 0.197
step, loss = 8000: 0.195
step, loss = 9000: 0.193
step, loss = 10000: 0.189
loss (test) = 0.183986
Wenn die Gewichtsverteilung durchgeführt wird, ist dies wie folgt.
Training...
step, loss = 0: 0.707
step, loss = 1000: 0.233
step, loss = 2000: 0.215
step, loss = 3000: 0.194
step, loss = 4000: 0.186
step, loss = 5000: 0.174
step, loss = 6000: 0.167
step, loss = 7000: 0.154
step, loss = 8000: 0.159
step, loss = 9000: 0.152
step, loss = 10000: 0.152
loss (test) = 0.147831
Aufgrund der Einstellung zur Gewichtsverteilung nimmt der Verlust (Kreuzentropie) bei gleicher Anzahl von Lernvorgängen schneller ab. Da der Freiheitsgrad des Netzwerks etwa die Hälfte beträgt, kann gesagt werden, dass das Ergebnis wie erwartet ist.
Betrachten wir ein weiteres etwas kompliziertes Modell. (Obwohl es nicht so kompliziert ist ...) MNIST wird verwendet, damit die Daten wie oben behandelt werden. Dieses Mal werden wir eine Klassifizierung für mehrere Klassen durchführen. Als Klassifikator verwendeten wir MLP (Multi-Layer Perceptron) mit 2 versteckten Schichten und 1 Ausgabeebene. Die folgende Abbildung zeigt ein Diagramm von TensorBoard.
Fig. Graph of 2 MLP networks
(Ich bin mit TensorBoard nicht vertraut. Bitte nehmen Sie es als grobes Bild.)
Definieren Sie zunächst die Klassen der verborgenen Ebene (vollständig verbundene Ebene) und der Ausgabeebene.
# Full-connected Layer
class FullConnected(object):
def __init__(self, input, n_in, n_out, vn=('W', 'b')):
self.input = input
weight_init = tf.truncated_normal_initializer(mean=0.0, stddev=0.05)
bias_init = tf.constant_initializer(value=0.0)
W = tf.get_variable(vn[0], [n_in, n_out], initializer=weight_init)
b = tf.get_variable(vn[1], [n_out], initializer=bias_init)
self.w = W
self.b = b
self.params = [self.w, self.b]
def output(self):
linarg = tf.matmul(self.input, self.w) + self.b
self.output = tf.nn.relu(linarg)
return self.output
#
# Read-out Layer
class ReadOutLayer(object):
def __init__(self, input, n_in, n_out, vn=('W', 'b')):
self.input = input
weight_init = tf.random_normal_initializer(mean=0.0, stddev=0.05)
bias_init = tf.constant_initializer(value=0.0)
W = tf.get_variable(vn[0], [n_in, n_out], initializer=weight_init)
b = tf.get_variable(vn[1], [n_out], initializer=bias_init)
self.w = W
self.b = b
self.params = [self.w, self.b]
def output(self):
linarg = tf.matmul(self.input, self.w) + self.b
self.output = tf.nn.softmax(linarg)
return self.output
Der Variablenname wird als Option des Klassenkonstruktors festgelegt, aber die Variablenfreigabeoperation wird hier nicht ausgeführt. Als nächstes definiert der Teil das Netzwerk wie folgt.
# Create the model
def mk_NN_model(scope='mlp', reuse=False):
'''
args.:
scope : variable scope ID of networks
reuse : reuse flag of weights/biases
'''
with tf.variable_scope(scope, reuse=reuse):
hidden1 = FullConnected(x, 784, 625, vn=('W_hid_1','b_hid_1'))
h1out = hidden1.output()
hidden2 = FullConnected(h1out, 625, 625, vn=('W_hid_2','b_hid_2'))
h2out = hidden2.output()
readout = ReadOutLayer(h2out, 625, 10, vn=('W_RO', 'b_RO'))
y_pred = readout.output()
cross_entropy = -tf.reduce_sum(y_*tf.log(y_pred))
# Regularization terms (weight decay)
L2_sqr = tf.nn.l2_loss(hidden1.w) + tf.nn.l2_loss(hidden2.w)
lambda_2 = 0.01
# the loss and accuracy
with tf.name_scope('loss'):
loss = cross_entropy + lambda_2 * L2_sqr
with tf.name_scope('accuracy'):
correct_prediction = tf.equal(tf.argmax(y_pred,1), tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
return y_pred, loss, accuracy
Diese Funktion wird angegeben, um den Variablenbereich "Bereich" und das Variablenfreigabeflag "Wiederverwendung" als Optionen zu verwenden. In zwei MLP-Netzwerken richtet die Gewichtsverteilung die Bereichsnamen aus und setzt das Flag "Wiederverwendung" wie folgt:
y_pred1, loss1, accuracy1 = mk_NN_model(scope='mlp1')
y_pred2, loss2, accuracy2 = mk_NN_model(scope='mlp1', reuse=True)
Wenn Sie keine Gewichte teilen möchten, stellen Sie Folgendes ein. (Obwohl es eine natürliche Syntax ist ...)
y_pred1, loss1, accuracy1 = mk_NN_model(scope='mlp1')
y_pred2, loss2, accuracy2 = mk_NN_model(scope='mlp2')
Die folgenden zwei Fälle wurden als Berechnungsexperimente durchgeführt.
Da ich mit Gewichtsverteilung experimentieren wollte, müssen die Anzahl der Schichten und die Anzahl der Einheiten der beiden Klassifikatoren gleich sein. Da jedoch genau derselbe Klassifikator langweilig ist, unterscheidet sich der Optimierer und die Lernrate wird fein eingestellt.
Erstens ist das Ausführungsergebnis von Fall Nr. 1 wie folgt.
Training...
Network No.1 :
step, loss, accurary = 0: 178.722, 0.470
step, loss, accurary = 1000: 22.757, 0.950
step, loss, accurary = 2000: 15.717, 0.990
step, loss, accurary = 3000: 10.343, 1.000
step, loss, accurary = 4000: 9.234, 1.000
step, loss, accurary = 5000: 8.950, 1.000
Network No.2 :
step, loss, accurary = 0: 14.552, 0.980
step, loss, accurary = 1000: 7.353, 1.000
step, loss, accurary = 2000: 5.806, 1.000
step, loss, accurary = 3000: 5.171, 1.000
step, loss, accurary = 4000: 5.043, 1.000
step, loss, accurary = 5000: 4.499, 1.000
accuracy1 = 0.9757
accuracy2 = 0.9744
Beachten Sie, dass der Verlust zu Beginn des Lernens von Netzwerk Nr. 2 leicht zunimmt, jedoch erheblich kleiner ist als der Wert zu Beginn des Lernens Nr. 1. Dies zeigt an, dass infolge der Gewichtsverteilung die Parameter (die das Lernergebnis von Nr. 1 erben) am Anfang von Nr. 2 begannen. Die endgültige Klassifizierungsgenauigkeit "Genauigkeit2 = 0,9744" verbesserte sich jedoch nicht von "Genauigkeit1", und es wurde festgestellt, dass dieses "lernähnliche Ensemble" ein Fehler war.
Wenn Sie darüber nachdenken, befinden Sie sich natürlich in einer Situation, in der Sie denselben Klassifikator in zwei Lernsitzungen verwenden. Da die Trainingsdaten einfach in zwei Teile geteilt und geliefert wurden, kann nicht erwartet werden, dass dies die Genauigkeit des Ensembles verbessert.
Das Ergebnis der Durchführung des richtigen Ensembles mit der unabhängigen Klassifiziererkonfiguration von Fall Nr. 2 ist wie folgt.
Training...
Network No.1 :
step, loss, accurary = 0: 178.722, 0.470
step, loss, accurary = 1000: 15.329, 0.990
step, loss, accurary = 2000: 12.242, 0.990
step, loss, accurary = 3000: 10.827, 1.000
step, loss, accurary = 4000: 10.167, 0.990
step, loss, accurary = 5000: 8.178, 1.000
Network No.2 :
step, loss, accurary = 0: 192.382, 0.570
step, loss, accurary = 1000: 10.037, 0.990
step, loss, accurary = 2000: 7.590, 1.000
step, loss, accurary = 3000: 5.855, 1.000
step, loss, accurary = 4000: 4.678, 1.000
step, loss, accurary = 5000: 4.693, 1.000
accuracy1 = 0.9751
accuracy2 = 0.9756
accuracy (model averaged) = 0.9810
Wie erwartet ist die Genauigkeit (korrekte Antwortrate), die für jeden Klassifikator bei etwa 0,975 lag, mit 0,980 gemäß dem Modelldurchschnitt etwas besser.
(Der diesmal erstellte Code wurde auf [Gist] hochgeladen (https://gist.github.com/tomokishii/42146ecc450c9e7228c3bdd1ccb9e408).)
Es scheint ein wenig abseits der ausgetretenen Pfade zu sein, aber ich hoffe, Sie haben eine Vorstellung davon, wie Sie den Gültigkeitsbereich von Variablen und gemeinsam genutzte Variablen verwenden. Bei kleineren Modellen müssen Sie wahrscheinlich keine variablen Bereiche zum Verwalten von Variablen verwenden. Bei größeren Modellen möchten Sie möglicherweise variable Bereiche und gemeinsam genutzte Variablen verwenden. Dies ist eine Funktion von TensorFlow, die in anderen Deep Learning Frameworks nicht häufig zu finden ist. Verwenden Sie sie daher bitte!
Recommended Posts