[PYTHON] Verstehen Sie den TensorFlow-Namespace und die gemeinsam genutzten Mastervariablen

Intro

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.)

Fangpunkt für den variablen Bereich von TensorFlow

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.

Shared Variable Example-Autoencoder

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.

Etwas komplexer Modellbeispiel-Klassifikator mit zwei MLPs

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 mnist_2nets_60.png

(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.

  1. Die Trainingsdaten werden zweigeteilt und den beiden Klassifikatoren "mlp1" und "mlp2" zugeführt. Die beiden Klassifikatoren legen die Gewichtsverteilung fest. Wird seriell mit 'mlp1'training' und 'mlp2' Training durchgeführt. Klassifizieren Sie die Testdaten anhand der endgültigen Parameter.
  2. Die Trainingsdaten werden zweigeteilt und den beiden Klassifikatoren "mlp1" und "mlp2" zugeführt. 'mlp1' und 'mlp2' sind unabhängige (nicht gemeinsam genutzte) Netzwerke, und jeder lernt. Die Testdaten werden auf jeden Klassifizierer angewendet, und das Ergebnis wird gemittelt, um das endgültige Klassifizierungsergebnis zu erhalten.

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

Verstehen Sie den TensorFlow-Namespace und die gemeinsam genutzten Mastervariablen
Wählen Sie die erforderlichen Variablen in TensorFlow aus und speichern / wiederherstellen
Verstehen Sie den Unterschied zwischen der kumulativen Zuordnung zu Variablen und der kumulativen Zuordnung zu Objekten
Verstehen Sie den Entscheidungsbaum und klassifizieren Sie Dokumente
Überprüfen von Methoden und Variablen mithilfe der Bibliothek siehe
Untersuchen Sie die Beziehung zwischen TensorFlow und Keras in der Übergangszeit
Verstehen Sie die Exponentialverteilung sorgfältig und zeichnen Sie in Python
Visualisieren Sie Daten und erfassen Sie gleichzeitig die Korrelation
Zeichnen und verstehen Sie die multivariate Normalverteilung in Python
Verstehe die Poisson-Distribution sorgfältig und zeichne in Python