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).
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
Geben Sie eine Eingabe mit "Binden" und bewerten Sie (Details später)
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)})
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 ...
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']
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)
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)
Ein Beispiel für die Fertigstellung ist hier.
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']
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
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'>}}
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.]]
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
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
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'>]}
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.