Un mémo qui passe par MXNet Tutorial dans l'ordre (faites de votre mieux et passez à la fin ...)
--Cette fois, le deuxième Symbole - Graphiques du réseau neuronal et auto-différenciation.
Symbol
Puisque les calculs scientifiques sont possibles avec juste le NDArray
dans la section précédente, n'est-ce pas tous les calculs effectués? La question peut se poser.
MXNet fournit une API Symbol qui vous permet d'écrire symboliquement. Dans l'écriture symbolique, au lieu d'écrire des calculs étape par étape, définissez d'abord le graphe de calcul. Le graphique contient un espace réservé d'entrée / sortie, qui est compilé puis exécuté en donnant une fonction qui génère un NDArray ... L'API Symbol est utilisée pour les paramètres réseau de Caffe et le style d'écriture symbolique de Theano. Similaire.
Symbolique = déclaratif, presque synonyme de
Un autre avantage de l'approche symbolique est l'optimisation. Si vous l'écrivez impérativement, vous ne savez pas ce dont vous aurez besoin pour chaque calcul. En écriture symbolique, la sortie est prédéfinie, vous pouvez donc réallouer la mémoire au milieu et calculer immédiatement. En outre, la mémoire requise est plus petite même sur le même réseau.
La manière d'écrire est discutée ici
Pour l'instant, ce chapitre décrit l'API Symbol Voir ici pour une description graphique (http: // localhost: 8888 /? Token = ebb31e9b782c15683f581f6b99d7cfecc115e4774c59cf7c)
Comment exprimer «a + b» Tout d'abord, créez un espace réservé avec «mx.sym.Variable» (donnez un nom lors de la création).
Ensuite, connectez-les avec + pour définir c
, qui est automatiquement nommé.
import mxnet as mx
a = mx.sym.Variable('a') #Erreur lors du retrait d'un
b = mx.sym.Variable('b')
c = a + b
(a, b, c) # _Plus0 et c sont automatiquement nommés
OUT
(<Symbol a>, <Symbol b>, <Symbol _plus0>)
La plupart des opérations NDArray sont également applicables à Symble.
#Multiplication pour chaque élément
d = a * b
#Produit matriciel
e = mx.sym.dot(a, b)
#Déformation
f = mx.sym.Reshape(d+e, shape=(1,4))
#diffuser
g = mx.sym.broadcast_to(f, shape=(2,4))
mx.viz.plot_network(symbol=g) #Visualisation du réseau
Donnez une entrée avec bind
et évaluez (détails plus tard)
Également prévu pour les couches de réseau neuronal. Exemple de description d'un réseau entièrement connecté à deux couches.
#Le graphique de sortie peut être
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)})
Chaque symbole a un nom unique. NDArray et Symbol représentent tous deux un seul tenseur et les opérateurs représentent les calculs entre les tenseurs. L'opérateur prend Symbol (ou NDArray) comme entrée, et dans certains cas reçoit et sort des hyperparamètres tels que le nombre de couches cachées (hidden_num
) et le type de fonction d'activation (ʻact_type`).
Vous pouvez également voir le symbole comme une fonction avec plusieurs arguments, et vous pouvez voir la liste des arguments avec cet appel de fonction.
net.list_arguments()
OUT
['data', 'fc1_weight', 'fc1_bias', 'fc2_weight', 'fc2_bias', 'out_label']
mx.sym.Variable('data')
OUT
<Symbol data>
Les paramètres et entrées suivants sont nécessaires pour chaque symbole
--data
: Données à saisir pour la variable data
--fc1_weight
, fc1_bias
: poids et biais de la première couche entièrement connectée fc1
--fc2_weight
, fc2_bias
: poids et biais de la première couche entièrement connectée fc2
--ʻOut_label`: étiquette requise pour la perte
Peut également être déclaré explicitement
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']
Dans l'exemple ci-dessus, il y a trois entrées pour «données», «poids» et «biais» à «entièrement connecté».
Est écrit, mais il ne semble y avoir aucun biais dans le code, et de plus, l'abréviation sym n'est pas utilisée ...
MXNet fournit des symboles optimisés pour les couches couramment utilisées dans l'apprentissage en profondeur. De nouveaux opérateurs peuvent également être définis en Python
Dans l'exemple suivant, Symbol est ajouté pour chaque élément, puis transmis à la couche entièrement connectée.
lhs = mx.symbol.Variable('data1')
rhs = mx.symbol.Variable('data2')
net = mx.symbol.FullyConnected(data=lhs + rhs, name='fc1', num_hidden=128)
"""N'est-ce pas une opération normale?"""
net.list_arguments()
OUT
['data1', 'data2', 'fc1_weight', 'fc1_bias']
Non seulement une construction unidirectionnelle, mais également une construction plus flexible est possible
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') #Utiliser net comme fonction
print(composed.list_arguments())
OUT
['data', 'fc1_weight', 'fc1_bias']
['data', 'fc1_weight', 'fc1_bias', 'fc2_weight', 'fc2_bias']
Dans cet exemple, net2 est indexé en tant que fonction qui prend un net1 existant et, par conséquent, projeté aura les arguments net1 et net2.
symbole Vous pouvez utiliser Prefix
si vous souhaitez attacher un préfixe commun
data = mx.sym.Variable("data")
net = data
n_layer = 2
for i in range(n_layer):
with mx.name.Prefix("layer%d_" % (i + 1)): #Subvention de préfixe
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']
Il est difficile d'écrire des réseaux profonds comme Google Inception. Par conséquent, il est modulaire et réutilisé.
Dans l'exemple suivant, la fonction fuctory (convolution, générer un lot de Batch Normalize, ReLU) est définie en premier.
# 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
#Définir une unité: convolution → norme du lot (normalisation pour chaque lot) → activation avec ReLU
prev = mx.symbol.Variable(name="Previos Output")
conv_comp = ConvFactory(data=prev, num_filter=64, kernel=(7,7), stride=(2, 2)) #Filtre Slide 7x7 avec Stride 2, sans rembourrage=>11 fois 11
shape = {"Previos Output" : (128, 3, 28, 28)}
mx.viz.plot_network(symbol=conv_comp, shape=shape)
Utilisez ceci pour créer une Inception
# @@@ 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)
Un exemple de ce qui est terminé est ici
Lors de la construction d'un réseau neuronal avec plusieurs couches de perte, le regroupement est possible avec mxnet.sym.Group
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
fournit une interface impérative, et les calculs sont évalués instruction par instruction.
«Symbol» est proche de la programmation déclarative, déclarant d'abord la structure de calcul puis évaluant les données. Proche des expressions régulières et du SQL.
Avantages de NDArray
--Facile --Facile à utiliser les fonctionnalités des langages de programmation tels que for, if-else et des bibliothèques telles que NumPy --Facile à effectuer un débogage étape par étape
Avantages du symbole
La différence entre Symbol et NDArray est comme mentionné ci-dessus, mais Symbol peut également être manipulé directement.
Cependant, gardez à l'esprit qu'il est principalement emballé dans le package .module
.
Shape Inference Des arguments, des informations supplémentaires et une sortie peuvent être obtenus pour chaque symbole. La forme de sortie et le type de symbole peuvent être estimés à partir de la forme d'entrée et du type d'argument, ce qui facilite l'allocation de mémoire. ..
#C'est facile à oublier, mais c= a + b
arg_name = c.list_arguments() #Nom d'entrée
out_name = c.list_outputs() #Nom de sortie
#Estimer la forme de la sortie de l'entrée
arg_shape, out_shape, _ = c.infer_shape(a=(2,3), b=(2,3))
#Estimer le type de sortie à partir de l'entrée
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'>}}
Vous devez donner des données comme argument pour évaluer le symbolec
.
Pour ce faire, utilisez la méthode bind
. Il s'agit d'une méthode qui renvoie un extrudeur lorsque vous transmettez un dictionnaire qui mappe le contexte, les noms précieux libres et NDArray.
A partir de l'exeuteur, l'évaluation peut être exécutée avec la méthode forward
, et le résultat peut être récupéré à partir de l'attribut ʻoutput`.
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.]]
Le même symbole peut être évalué avec différents contextes (GPU) et différentes données
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)
L'évaluation par "eval" est également possible, c'est un ensemble de "bind" et "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.]]
Comme NDArray, il peut être pickle et enregistrer et charger.
Cependant, Symbol est un graphique et le graphique se compose de calculs continus. Comme ils sont implicitement représentés par le symbole de sortie, sérialisez le graphique du symbole de sortie.
La sérialisation avec JSON améliore la lisibilité, mais utilisez tojson
pour cela.
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
Des opérations telles que mx.sym.Convolution
, mx.sym.Reshape
sont implémentées en C ++ pour les performances.
MXNet vous permet également de créer de nouveaux modules arithmétiques en utilisant des langages comme Python, voir ici pour plus d'informations.
On a l'impression d'hériter de Softmax et de l'implémenter au premier plan
Normalement, il s'agit d'un point décimal 32 bits, mais il est également possible d'utiliser un type moins précis pour accélérer.
Effectuer la conversion de type avec mx.sym.Cast
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'>]}
Le partage entre les symboles est possible en liant les symboles au même tableau
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}) #Partager les données en tant que valeur d'entrée
ex.forward()
ex.outputs[0].asnumpy()
OUT
array([[ 6., 6., 6.],
[ 6., 6., 6.]], dtype=float32)
--Apprentissage en Python 3 + Ubuntu, environnement GPU ――Je n'ai pas traduit toute la phrase en japonais, juste un mémo ――Depuis que je viens de modifier la sortie de Jupyter, la mise en page s'effondre ...
Vous n'avez pas décidé comment appeler le module en cours de route? (Appelé sym ou symbole) J'ai eu l'impression que ce n'était pas encore décidé comme un tutoriel.
Vient ensuite le plan du module.