Apache MXNet - Gluon
Un autre package MXNet Python le plus important est Gluon. Dans ce chapitre, nous discuterons de ce package. Gluon fournit une API claire, concise et simple pour les projets DL. Il permet à Apache MXNet de prototyper, de créer et de former des modèles DL sans perdre la vitesse de formation.
Blocs
Les blocs constituent la base de conceptions de réseaux plus complexes. Dans un réseau de neurones, à mesure que la complexité du réseau de neurones augmente, nous devons passer de la conception de couches uniques à des couches entières de neurones. Par exemple, la conception NN comme ResNet-152 a un très bon degré de régularité en se composant deblocks de couches répétées.
Exemple
Dans l'exemple donné ci-dessous, nous écrirons du code un bloc simple, à savoir un bloc pour un perceptron multicouche.
from mxnet import nd
from mxnet.gluon import nn
x = nd.random.uniform(shape=(2, 20))
N_net = nn.Sequential()
N_net.add(nn.Dense(256, activation='relu'))
N_net.add(nn.Dense(10))
N_net.initialize()
N_net(x)
Output
Cela produit la sortie suivante:
[[ 0.09543004 0.04614332 -0.00286655 -0.07790346 -0.05130241 0.02942038
0.08696645 -0.0190793 -0.04122177 0.05088576]
[ 0.0769287 0.03099706 0.00856576 -0.044672 -0.06926838 0.09132431
0.06786592 -0.06187843 -0.03436674 0.04234696]]
<NDArray 2x10 @cpu(0)>
Étapes nécessaires pour passer de la définition des couches à la définition des blocs d'une ou plusieurs couches -
Step 1 - Bloquer prendre les données comme entrée.
Step 2- Désormais, les blocs stockent l'état sous forme de paramètres. Par exemple, dans l'exemple de codage ci-dessus, le bloc contient deux couches cachées et nous avons besoin d'un emplacement pour stocker les paramètres.
Step 3- Le bloc suivant invoquera la fonction avant pour effectuer la propagation avant. Il est également appelé calcul direct. Dans le cadre du premier appel de transfert, les blocs initialisent les paramètres de manière paresseuse.
Step 4- Enfin, les blocs invoqueront la fonction arrière et calculeront le gradient en référence à leur entrée. En règle générale, cette étape est effectuée automatiquement.
Bloc séquentiel
Un bloc séquentiel est un type particulier de bloc dans lequel les données circulent à travers une séquence de blocs. En cela, chaque bloc appliqué à la sortie d'un bloc précédent, le premier bloc étant appliqué aux données d'entrée elles-mêmes.
Voyons comment sequential travaux de classe -
from mxnet import nd
from mxnet.gluon import nn
class MySequential(nn.Block):
def __init__(self, **kwargs):
super(MySequential, self).__init__(**kwargs)
def add(self, block):
self._children[block.name] = block
def forward(self, x):
for block in self._children.values():
x = block(x)
return x
x = nd.random.uniform(shape=(2, 20))
N_net = MySequential()
N_net.add(nn.Dense(256, activation
='relu'))
N_net.add(nn.Dense(10))
N_net.initialize()
N_net(x)
Output
La sortie est donnée ici -
[[ 0.09543004 0.04614332 -0.00286655 -0.07790346 -0.05130241 0.02942038
0.08696645 -0.0190793 -0.04122177 0.05088576]
[ 0.0769287 0.03099706 0.00856576 -0.044672 -0.06926838 0.09132431
0.06786592 -0.06187843 -0.03436674 0.04234696]]
<NDArray 2x10 @cpu(0)>
Bloc personnalisé
On peut facilement aller au-delà de la concaténation avec bloc séquentiel tel que défini ci-dessus. Mais, si nous voulons faire des personnalisations, alors leBlockclass nous fournit également les fonctionnalités requises. La classe Block a un constructeur de modèle fourni dans le module nn. Nous pouvons hériter de ce constructeur de modèle pour définir le modèle que nous voulons.
Dans l'exemple suivant, le MLP class remplace le __init__ et les fonctions avancées de la classe Block.
Voyons comment cela fonctionne.
class MLP(nn.Block):
def __init__(self, **kwargs):
super(MLP, self).__init__(**kwargs)
self.hidden = nn.Dense(256, activation='relu') # Hidden layer
self.output = nn.Dense(10) # Output layer
def forward(self, x):
hidden_out = self.hidden(x)
return self.output(hidden_out)
x = nd.random.uniform(shape=(2, 20))
N_net = MLP()
N_net.initialize()
N_net(x)
Output
Lorsque vous exécutez le code, vous verrez la sortie suivante:
[[ 0.07787763 0.00216403 0.01682201 0.03059879 -0.00702019 0.01668715
0.04822846 0.0039432 -0.09300035 -0.04494302]
[ 0.08891078 -0.00625484 -0.01619131 0.0380718 -0.01451489 0.02006172
0.0303478 0.02463485 -0.07605448 -0.04389168]]
<NDArray 2x10 @cpu(0)>
Calques personnalisés
L'API Gluon d'Apache MXNet est fournie avec un nombre modeste de couches prédéfinies. Mais encore à un moment donné, nous pouvons constater qu'une nouvelle couche est nécessaire. Nous pouvons facilement ajouter une nouvelle couche dans l'API Gluon. Dans cette section, nous verrons comment créer un nouveau calque à partir de zéro.
Le calque personnalisé le plus simple
Pour créer une nouvelle couche dans l'API Gluon, nous devons créer une classe hérite de la classe Block qui fournit les fonctionnalités les plus basiques. Nous pouvons en hériter toutes les couches prédéfinies directement ou via d'autres sous-classes.
Pour créer la nouvelle couche, la seule méthode d'instance à implémenter est forward (self, x). Cette méthode définit ce que notre couche va faire exactement pendant la propagation vers l'avant. Comme indiqué précédemment, le passage de rétro-propagation des blocs sera effectué automatiquement par Apache MXNet lui-même.
Exemple
Dans l'exemple ci-dessous, nous allons définir un nouveau calque. Nous mettrons également en œuvreforward() méthode pour normaliser les données d'entrée en les ajustant dans une plage de [0, 1].
from __future__ import print_function
import mxnet as mx
from mxnet import nd, gluon, autograd
from mxnet.gluon.nn import Dense
mx.random.seed(1)
class NormalizationLayer(gluon.Block):
def __init__(self):
super(NormalizationLayer, self).__init__()
def forward(self, x):
return (x - nd.min(x)) / (nd.max(x) - nd.min(x))
x = nd.random.uniform(shape=(2, 20))
N_net = NormalizationLayer()
N_net.initialize()
N_net(x)
Output
En exécutant le programme ci-dessus, vous obtiendrez le résultat suivant -
[[0.5216355 0.03835821 0.02284337 0.5945146 0.17334817 0.69329053
0.7782702 1. 0.5508242 0. 0.07058554 0.3677264
0.4366546 0.44362497 0.7192635 0.37616986 0.6728799 0.7032008
0.46907538 0.63514024]
[0.9157533 0.7667402 0.08980197 0.03593295 0.16176797 0.27679572
0.07331014 0.3905285 0.6513384 0.02713427 0.05523694 0.12147208
0.45582628 0.8139887 0.91629887 0.36665893 0.07873632 0.78268915
0.63404864 0.46638715]]
<NDArray 2x20 @cpu(0)>
L'hybridation
Il peut être défini comme un processus utilisé par Apache MXNet pour créer un graphe symbolique d'un calcul direct. L'hybridation permet à MXNet d'augmenter les performances de calcul en optimisant le graphe symbolique de calcul. Plutôt que d'hériter directement deBlock, en fait, nous pouvons constater que lors de l'implémentation de couches existantes, un bloc hérite d'un HybridBlock.
Voici les raisons à cela -
Allows us to write custom layers: HybridBlock nous permet d'écrire des couches personnalisées qui peuvent ensuite être utilisées dans la programmation impérative et symbolique.
Increase computation performance- HybridBlock optimise le graphe symbolique de calcul qui permet à MXNet d'augmenter les performances de calcul.
Exemple
Dans cet exemple, nous réécrirons notre exemple de couche, créé ci-dessus, en utilisant HybridBlock:
class NormalizationHybridLayer(gluon.HybridBlock):
def __init__(self):
super(NormalizationHybridLayer, self).__init__()
def hybrid_forward(self, F, x):
return F.broadcast_div(F.broadcast_sub(x, F.min(x)), (F.broadcast_sub(F.max(x), F.min(x))))
layer_hybd = NormalizationHybridLayer()
layer_hybd(nd.array([1, 2, 3, 4, 5, 6], ctx=mx.cpu()))
Output
La sortie est indiquée ci-dessous:
[0. 0.2 0.4 0.6 0.8 1. ]
<NDArray 6 @cpu(0)>
L'hybridation n'a rien à voir avec le calcul sur GPU et on peut former des réseaux hybrides ou non hybrides sur CPU et GPU.
Différence entre Block et HybridBlock
Si nous comparons les Block Classe et HybridBlock, nous verrons que HybridBlock a déjà son forward() méthode mise en œuvre. HybridBlock définit un hybrid_forward()méthode qui doit être implémentée lors de la création des couches. L'argument F crée la principale différence entreforward() et hybrid_forward(). Dans la communauté MXNet, l'argument F est appelé backend. F peut désigner soitmxnet.ndarray API (utilisé pour la programmation impérative) ou mxnet.symbol API (utilisé pour la programmation symbolique).
Comment ajouter une couche personnalisée à un réseau?
Au lieu d'utiliser des calques personnalisés séparément, ces calques sont utilisés avec des calques prédéfinis. Nous pouvons utiliser soitSequential ou HybridSequentialconteneurs à partir d'un réseau neuronal séquentiel. Comme indiqué précédemment,Sequential conteneur hérite de Block et HybridSequential hériter de HybridBlock respectivement.
Exemple
Dans l'exemple ci-dessous, nous allons créer un réseau de neurones simple avec une couche personnalisée. La sortie deDense (5) couche sera l'entrée de NormalizationHybridLayer. La sortie deNormalizationHybridLayer deviendra l'entrée de Dense (1) couche.
net = gluon.nn.HybridSequential()
with net.name_scope():
net.add(Dense(5))
net.add(NormalizationHybridLayer())
net.add(Dense(1))
net.initialize(mx.init.Xavier(magnitude=2.24))
net.hybridize()
input = nd.random_uniform(low=-10, high=10, shape=(10, 2))
net(input)
Output
Vous verrez la sortie suivante -
[[-1.1272651]
[-1.2299833]
[-1.0662932]
[-1.1805027]
[-1.3382034]
[-1.2081106]
[-1.1263978]
[-1.2524893]
[-1.1044774]
[-1.316593 ]]
<NDArray 10x1 @cpu(0)>
Paramètres de calque personnalisés
Dans un réseau neuronal, une couche est associée à un ensemble de paramètres. Nous les référons parfois comme des poids, qui est l'état interne d'une couche. Ces paramètres jouent différents rôles -
Parfois, ce sont ceux que nous voulons apprendre lors de l'étape de rétropropagation.
Parfois, ce ne sont que des constantes que nous voulons utiliser lors de la passe avant.
Si nous parlons du concept de programmation, ces paramètres (poids) d'un bloc sont stockés et accessibles via ParameterDict classe qui facilite leur initialisation, leur mise à jour, leur sauvegarde et leur chargement.
Exemple
Dans l'exemple ci-dessous, nous définirons deux ensembles de paramètres suivants -
Parameter weights- Ceci peut être entraîné et sa forme est inconnue pendant la phase de construction. Il sera déduit lors du premier passage de propagation vers l'avant.
Parameter scale- C'est une constante dont la valeur ne change pas. Contrairement aux poids des paramètres, sa forme est définie lors de la construction.
class NormalizationHybridLayer(gluon.HybridBlock):
def __init__(self, hidden_units, scales):
super(NormalizationHybridLayer, self).__init__()
with self.name_scope():
self.weights = self.params.get('weights',
shape=(hidden_units, 0),
allow_deferred_init=True)
self.scales = self.params.get('scales',
shape=scales.shape,
init=mx.init.Constant(scales.asnumpy()),
differentiable=False)
def hybrid_forward(self, F, x, weights, scales):
normalized_data = F.broadcast_div(F.broadcast_sub(x, F.min(x)),
(F.broadcast_sub(F.max(x), F.min(x))))
weighted_data = F.FullyConnected(normalized_data, weights, num_hidden=self.weights.shape[0], no_bias=True)
scaled_data = F.broadcast_mul(scales, weighted_data)
return scaled_data