[PYTHON] Probieren Sie das MXNet-Tutorial (2) aus: Symbol-Neuralnet-Diagramm und automatische Differenzierung

Ein Memo, das MXNet Tutorial der Reihe nach durchläuft (geben Sie Ihr Bestes und fahren Sie bis zum Ende fort ...)

Symbol

Da wissenschaftliche Berechnungen nur mit dem NDArray im vorherigen Abschnitt möglich sind, sind dies nicht alle Berechnungen? Kann angehoben werden.

MXNet bietet eine Symbol-API, mit der Sie symbolisch schreiben können. Definieren Sie beim symbolischen Schreiben zuerst das Berechnungsdiagramm, anstatt Schritt für Schritt Berechnungen zu schreiben. Das Diagramm enthält einen Eingabe- / Ausgabe-Platzhalter, der kompiliert und dann ausgeführt wird, indem eine Funktion angegeben wird, die ein NDArray ausgibt ... Die Symbol-API wird für die Netzwerkeinstellungen von Caffe und den symbolischen Schreibstil von Theano verwendet. Ähnlich.

Symbolisch = deklarativ, fast synonym mit

Ein weiterer Vorteil des symbolischen Ansatzes ist die Optimierung. Wenn Sie es unbedingt schreiben, wissen Sie nicht, was Sie für jede Berechnung benötigen. Beim symbolischen Schreiben ist die Ausgabe vordefiniert, sodass Sie den Speicher in der Mitte neu zuweisen und sofort berechnen können. Außerdem ist der Speicherbedarf selbst im selben Netzwerk geringer.

Welche Schreibweise [hier] beschrieben wird (https://mxnet.incubator.apache.org/architecture/program_model.html)

In diesem Kapitel wird vorerst die Symbol-API beschrieben Eine grafische Beschreibung finden Sie hier (http: // localhost: 8888 /? Token = ebb31e9b782c15683f581f6b99d7cfecc115e4774c59cf7c).

Erstellen eines Basissymbols

Grundberechnung

So drücken Sie "a + b" aus. Erstellen Sie zunächst einen Platzhalter mit "mx.sym.Variable" (geben Sie beim Erstellen einen Namen an). Verbinden Sie sie anschließend mit +, um "c" zu definieren, das automatisch benannt wird.

import mxnet as mx
a = mx.sym.Variable('a')  #Fehler beim Herausziehen von a
b = mx.sym.Variable('b')
c = a + b
(a, b, c)  # _

OUT


    (<Symbol a>, <Symbol b>, <Symbol _plus0>)

Die meisten NDArray-Vorgänge gelten auch für Symble.

#Multiplikation für jedes Element
d = a * b
#Matrix Produkt
e = mx.sym.dot(a, b)   
#Verformung
f = mx.sym.Reshape(d+e, shape=(1,4))  
#Übertragung
g = mx.sym.broadcast_to(f, shape=(2,4))  
mx.viz.plot_network(symbol=g)  #Netzwerkvisualisierung

output_4_0.png

Geben Sie eine Eingabe mit "Binden" und bewerten Sie (Details später)

Grundlegendes neuronales Netz

Auch für neuronale Netzschichten vorgesehen. Beschreibungsbeispiel eines zweischichtigen, vollständig verbundenen Netzwerks.

#Das Ausgabediagramm kann sein
net = mx.sym.Variable('data')
net = mx.sym.FullyConnected(data=net, name='fc1', num_hidden=128)
net = mx.sym.Activation(data=net, name='relu1', act_type="relu")
net = mx.sym.FullyConnected(data=net, name='fc2', num_hidden=10)
net = mx.sym.SoftmaxOutput(data=net, name='out')
mx.viz.plot_network(net, shape={'data':(100,200)})

output_7_0.png

Jedes Symbol hat einen eindeutigen Namen. Sowohl NDArray als auch Symbol repräsentieren einen einzelnen Tensor und die Operatoren repräsentieren Berechnungen zwischen Tensoren. Der Bediener verwendet Symbol (oder NDArray) als Eingabe und empfängt und gibt in einigen Fällen Hyperparameter wie die Anzahl der ausgeblendeten Ebenen (hidden_num) und die Art der Aktivierungsfunktion ( act_type) aus.

Sie können das Symbol auch als Funktion mit mehreren Argumenten anzeigen und die Liste der Argumente mit diesem Funktionsaufruf anzeigen.

net.list_arguments()

OUT


    ['data', 'fc1_weight', 'fc1_bias', 'fc2_weight', 'fc2_bias', 'out_label']
mx.sym.Variable('data')

OUT


    <Symbol data>

Die folgenden Parameter und Eingaben sind für jedes Symbol erforderlich

--data: Daten, die für die Variable data eingegeben werden sollen --fc1_weight, fc1_bias: Gewicht und Vorspannung der ersten vollständig verbundenen Schicht fc1 --fc2_weight, fc2_bias: Gewicht und Vorspannung der ersten vollständig verbundenen Schicht fc2 --out_label: Etikett für Verlust erforderlich

Kann auch explizit deklariert werden

net = mx.symbol.Variable('data')
w = mx.symbol.Variable('myweight')
net = mx.symbol.FullyConnected(data=net, weight=w, name='fc1', num_hidden=128)
net.list_arguments()

OUT


    ['data', 'myweight', 'fc1_bias']

Im obigen Beispiel gibt es drei Eingänge für "Daten", "Gewicht" und "Vorspannung" für "Vollständig verbunden".

Ist geschrieben, aber es scheint keine Verzerrung im Code zu geben, und außerdem wird die Abkürzung sym nicht verwendet ...

Komplexere Konstruktion

MXNet bietet Symbole, die für Ebenen optimiert sind, die üblicherweise beim Deep Learning verwendet werden. Neue Operatoren können auch in Python definiert werden

Im folgenden Beispiel wird für jedes Element ein Symbol hinzugefügt und dann an die vollständig verbundene Ebene übergeben.

lhs = mx.symbol.Variable('data1')
rhs = mx.symbol.Variable('data2')
net = mx.symbol.FullyConnected(data=lhs + rhs, name='fc1', num_hidden=128)
"""Ist das nicht ein normaler Betrieb?"""
net.list_arguments()

OUT


    ['data1', 'data2', 'fc1_weight', 'fc1_bias']

Es ist nicht nur eine unidirektionale Konstruktion, sondern auch eine flexiblere Konstruktion möglich

data = mx.symbol.Variable('data')
net1 = mx.symbol.FullyConnected(data=data, name='fc1', num_hidden=10)
print(net1.list_arguments())
net2 = mx.symbol.Variable('data2')
net2 = mx.symbol.FullyConnected(data=net2, name='fc2', num_hidden=10)
composed = net2(data2=net1, name='composed')  #Verwenden Sie net als Funktion
print(composed.list_arguments())

OUT


    ['data', 'fc1_weight', 'fc1_bias']
    ['data', 'fc1_weight', 'fc1_bias', 'fc2_weight', 'fc2_bias']

In diesem Beispiel wird net2 als eine Funktion indiziert, die ein vorhandenes net1 übernimmt, und als Ergebnis wird projiziert sowohl net1- als auch net2-Argumente haben.

Symbol Wenn Sie ein gemeinsames Präfix anhängen möchten, können Sie "Präfix" verwenden

data = mx.sym.Variable("data")
net = data
n_layer = 2
for i in range(n_layer):
    with mx.name.Prefix("layer%d_" % (i + 1)): #Präfixgewährung
        net = mx.sym.FullyConnected(data=net, name="fc", num_hidden=100)
net.list_arguments()

OUT


    ['data',
     'layer1_fc_weight',
     'layer1_fc_bias',
     'layer2_fc_weight',
     'layer2_fc_bias']

Aufbau eines modularen tiefen neuronalen Netzwerks

Es ist schwierig, tiefe Netzwerke wie Google Inception zu schreiben. Daher wird es modularisiert und wiederverwendet.

Im folgenden Beispiel wird zuerst die Funktionsfunktion (Faltung, Generieren einer Charge von Batch Normalize, ReLU) definiert.

# Output may vary
def ConvFactory(data, num_filter, kernel, stride=(1,1), pad=(0, 0), name=None, suffix=''):
    conv = mx.symbol.Convolution(data=data, num_filter=num_filter, kernel=kernel, stride=stride, pad=pad, name='conv_%s%s' %(name, suffix))
    bn = mx.symbol.BatchNorm(data=conv, name='bn_%s%s' %(name, suffix))
    act = mx.symbol.Activation(data=bn, act_type='relu', name='relu_%s%s' %(name, suffix))
    return act
#Definieren Sie eine Einheit: Faltung → Chargennorm (Normalisierung für jede Charge) → Aktivierung mit ReLU

prev = mx.symbol.Variable(name="Previos Output")
conv_comp = ConvFactory(data=prev, num_filter=64, kernel=(7,7), stride=(2, 2)) #Schieben Sie den 7x7-Filter mit Schritt 2, ohne Polsterung=>11 mal 11
shape = {"Previos Output" : (128, 3, 28, 28)}
mx.viz.plot_network(symbol=conv_comp, shape=shape)

output_24_0.png

Verwenden Sie diese Option, um eine Inception zu erstellen

# @@@ AUTOTEST_OUTPUT_IGNORED_CELL
def InceptionFactoryA(data, num_1x1, num_3x3red, num_3x3, num_d3x3red, num_d3x3, pool, proj, name):
    # 1x1
    c1x1 = ConvFactory(data=data, num_filter=num_1x1, kernel=(1, 1), name=('%s_1x1' % name))
    # 3x3 reduce + 3x3
    c3x3r = ConvFactory(data=data, num_filter=num_3x3red, kernel=(1, 1), name=('%s_3x3' % name), suffix='_reduce')
    c3x3 = ConvFactory(data=c3x3r, num_filter=num_3x3, kernel=(3, 3), pad=(1, 1), name=('%s_3x3' % name))
    # double 3x3 reduce + double 3x3
    cd3x3r = ConvFactory(data=data, num_filter=num_d3x3red, kernel=(1, 1), name=('%s_double_3x3' % name), suffix='_reduce')
    cd3x3 = ConvFactory(data=cd3x3r, num_filter=num_d3x3, kernel=(3, 3), pad=(1, 1), name=('%s_double_3x3_0' % name))
    cd3x3 = ConvFactory(data=cd3x3, num_filter=num_d3x3, kernel=(3, 3), pad=(1, 1), name=('%s_double_3x3_1' % name))
    # pool + proj
    pooling = mx.symbol.Pooling(data=data, kernel=(3, 3), stride=(1, 1), pad=(1, 1), pool_type=pool, name=('%s_pool_%s_pool' % (pool, name)))
    cproj = ConvFactory(data=pooling, num_filter=proj, kernel=(1, 1), name=('%s_proj' %  name))
    # concat
    concat = mx.symbol.Concat(*[c1x1, c3x3, cd3x3, cproj], name='ch_concat_%s_chconcat' % name)
    return concat
prev = mx.symbol.Variable(name="Previos Output")
in3a = InceptionFactoryA(prev, 64, 64, 64, 64, 96, "avg", 32, name="in3a")
mx.viz.plot_network(symbol=in3a, shape=shape)

output_26_0.png

Ein Beispiel für die Fertigstellung ist hier.

Gruppierung mehrerer Simbols

Beim Aufbau eines neuronalen Netzes mit mehreren Verlustschichten ist eine Gruppierung mit mxnet.sym.Group möglich

net = mx.sym.Variable('data')
fc1 = mx.sym.FullyConnected(data=net, name='fc1', num_hidden=128)
net = mx.sym.Activation(data=fc1, name='relu1', act_type="relu")
out1 = mx.sym.SoftmaxOutput(data=net, name='softmax')
out2 = mx.sym.LinearRegressionOutput(data=net, name='regression')
group = mx.sym.Group([out1, out2])
group.list_outputs()

OUT


    ['softmax_output', 'regression_output']

Beziehung zu NDArray

NDArray bietet eine zwingende Schnittstelle, und Berechnungen werden Aussage für Aussage ausgewertet. Symbol steht der deklarativen Programmierung nahe, deklariert zuerst die Rechenstruktur und wertet dann die Daten aus. Nah an regulären Ausdrücken und SQL.

Vorteile von NDArray

--Einfach

Vorteile von Symbol

Bedienung des Symbols

Der Unterschied zwischen Symbol und NDArray ist wie oben erwähnt, aber Symbol kann auch direkt manipuliert werden. Beachten Sie jedoch, dass es meistens im .module -Paket enthalten ist.

Shape Inference Für jedes Symbol können Argumente, zusätzliche Informationen und Ausgaben abgerufen werden. Die Ausgabeform und der Symboltyp können aus der Eingabeform und dem Argumenttyp geschätzt werden, was die Speicherzuordnung erleichtert. ..

#Es ist leicht zu vergessen, aber c= a + b

arg_name = c.list_arguments()  #Name eingeben
out_name = c.list_outputs()    #Ausgabename
#Schätzen Sie die Form der Ausgabe aus der Eingabe
arg_shape, out_shape, _ = c.infer_shape(a=(2,3), b=(2,3))
#Schätzen Sie den Ausgabetyp aus der Eingabe
arg_type, out_type, _ = c.infer_type(a='float32', b='float32')
print({'input' : dict(zip(arg_name, arg_shape)),
 'output' : dict(zip(out_name, out_shape))})
print({'input' : dict(zip(arg_name, arg_type)),
 'output' : dict(zip(out_name, out_type))})

OUT


    {'output': {'_plus0_output': (2, 3)}, 'input': {'b': (2, 3), 'a': (2, 3)}}
    {'output': {'_plus0_output': <class 'numpy.float32'>}, 'input': {'b': <class 'numpy.float32'>, 'a': <class 'numpy.float32'>}}

Datenbindung und Auswertung

Sie müssen Daten als Argument angeben, um das Symbolc auszuwerten. Verwenden Sie dazu die Methode "bind". Diese Methode gibt einen Extruder zurück, wenn Sie ein Wörterbuch übergeben, das den Kontext abbildet und wertvolle Namen an NDArray freigibt. Vom Exeutor kann die Auswertung mit der "Forward" -Methode ausgeführt werden, und das Ergebnis kann aus dem "Output" -Attribut abgerufen werden.

ex = c.bind(ctx=mx.cpu(), args={'a' : mx.nd.ones([2,3]), 
                                'b' : mx.nd.ones([2,3])})

ex.forward()
print('number of outputs = %d\nthe first output = \n%s' % (
           len(ex.outputs), ex.outputs[0].asnumpy()))

OUT


    number of outputs = 1
    the first output = 
    [[ 2.  2.  2.]
     [ 2.  2.  2.]]

Das gleiche Symbol kann mit unterschiedlichen Kontexten (GPU) und unterschiedlichen Daten ausgewertet werden

ex_gpu = c.bind(ctx=mx.gpu(), args={'a' : mx.nd.ones([3,4], mx.gpu())*2,
                                    'b' : mx.nd.ones([3,4], mx.gpu())*3})
ex_gpu.forward()
ex_gpu.outputs[0].asnumpy()

OUT


    array([[ 5.,  5.,  5.,  5.],
           [ 5.,  5.,  5.,  5.],
           [ 5.,  5.,  5.,  5.]], dtype=float32)

Eine Auswertung durch "eval" ist ebenfalls möglich, dies ist ein Bündel von "bind" und "forward"

ex = c.eval(ctx = mx.cpu(), a = mx.nd.ones([2,3]), b = mx.nd.ones([2,3]))
print('number of outputs = %d\nthe first output = \n%s' % (
            len(ex), ex[0].asnumpy()))

OUT


    number of outputs = 1
    the first output = 
    [[ 2.  2.  2.]
     [ 2.  2.  2.]]

Laden und speichern

Wie NDArray kann es eingelegt und gespeichert und geladen werden. Symbol ist jedoch ein Diagramm, und das Diagramm besteht aus kontinuierlichen Berechnungen. Da diese implizit durch das Ausgabesymbol dargestellt werden, serialisieren Sie den Graphen des Ausgabesymbols. Die Serialisierung mit JSON verbessert die Lesbarkeit, verwenden Sie jedoch "tojson".

print(c.tojson())
c.save('symbol-c.json')
c2 = mx.symbol.load('symbol-c.json')
c.tojson() == c2.tojson()

OUT


    {
      "nodes": [
        {
          "op": "null", 
          "name": "a", 
          "inputs": []
        }, 
        {
          "op": "null", 
          "name": "b", 
          "inputs": []
        }, 
        {
          "op": "elemwise_add", 
          "name": "_plus0", 
          "inputs": [[0, 0, 0], [1, 0, 0]]
        }
      ], 
      "arg_nodes": [0, 1], 
      "node_row_ptr": [0, 1, 2, 3], 
      "heads": [[2, 0, 0]], 
      "attrs": {"mxnet_version": ["int", 1000]}
    }

    True

Benutzerdefiniertes Symbol

Operationen wie "mx.sym.Convolution", "mx.sym.Reshape" sind aus Leistungsgründen in C ++ implementiert. Mit MXNet können Sie auch neue arithmetische Module mit Sprachen wie Python erstellen. Weitere Informationen finden Sie unter hier.

Es fühlt sich an, als würde man Softmax erben und im Vordergrund implementieren

Erweiterte Verwendung

Typ gegossen

Normalerweise 32-Bit-Dezimalpunkt, aber es ist auch möglich, einen weniger genauen Typ zum Beschleunigen zu verwenden. Führen Sie die Typkonvertierung mit mx.sym.Cast durch

a = mx.sym.Variable('data')
b = mx.sym.Cast(data=a, dtype='float16')
arg, out, _ = b.infer_type(data='float32')
print({'input':arg, 'output':out})

c = mx.sym.Cast(data=a, dtype='uint8')
arg, out, _ = c.infer_type(data='int32')
print({'input':arg, 'output':out})

OUT


    {'output': [<class 'numpy.float16'>], 'input': [<class 'numpy.float32'>]}
    {'output': [<class 'numpy.uint8'>], 'input': [<class 'numpy.int32'>]}

Variablenfreigabe

Das Teilen zwischen Symbolen ist möglich, indem Symbole an dasselbe Array gebunden werden

a = mx.sym.Variable('a')
b = mx.sym.Variable('b')
c = mx.sym.Variable('c')
d = a + b * c

data = mx.nd.ones((2,3))*2
ex = d.bind(ctx=mx.cpu(), args={'a':data, 'b':data, 'c':data})  #Daten als Eingabewert freigeben
ex.forward()
ex.outputs[0].asnumpy()

OUT


    array([[ 6.,  6.,  6.],
           [ 6.,  6.,  6.]], dtype=float32)

Sie haben noch nicht entschieden, wie Sie das Modul unterwegs aufrufen sollen? Ich hatte den Eindruck, dass es noch nicht als Tutorial entschieden wurde (genannt sym oder symbol).

Als nächstes folgt der Modulplan.

Recommended Posts

Probieren Sie das MXNet-Tutorial (2) aus: Symbol-Neuralnet-Diagramm und automatische Differenzierung
[PyTorch Tutorial ②] Autograd: Automatische Differenzierung
Einführung in die Thano-Funktionsdefinition und automatische Differenzierung