[PYTHON] Essayez le didacticiel MXNet (2): graphique Symbol-Neuralnet et différenciation automatique

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)

Construire un symbole de base

Calcul de base

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

output_4_0.png

Donnez une entrée avec bind et évaluez (détails plus tard)

Réseau neuronal de base

É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)})

output_7_0.png

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

Construction plus complexe

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']

Construire un réseau neuronal profond modulaire

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)

output_24_0.png

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)

output_26_0.png

Un exemple de ce qui est terminé est ici

Regroupement de plusieurs Simbols

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']

Relation avec NDArray

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

Fonctionnement 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'>}}

Liaison et évaluation des données

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

Charger et enregistrer

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

Symbole personnalisé

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

Utilisation avancée

Type fonte

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'>]}

Partage variable

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.

Recommended Posts

Essayez le didacticiel MXNet (2): graphique Symbol-Neuralnet et différenciation automatique
[Tutoriel PyTorch ②] Autograd: différenciation automatique
Introduction à la définition de la fonction Thano et à la différenciation automatique