Apache MXNet - Gluon
Otro paquete MXNet Python más importante es Gluon. En este capítulo, discutiremos este paquete. Gluon proporciona una API clara, concisa y simple para proyectos DL. Permite a Apache MXNet crear prototipos, construir y entrenar modelos DL sin perder la velocidad de entrenamiento.
Bloques
Los bloques forman la base de diseños de redes más complejos. En una red neuronal, a medida que aumenta la complejidad de la red neuronal, debemos pasar del diseño de capas únicas de neuronas a capas completas. Por ejemplo, el diseño de NN como ResNet-152 tiene un grado muy justo de regularidad al consistir enblocks de capas repetidas.
Ejemplo
En el ejemplo que se muestra a continuación, escribiremos el código de un bloque simple, es decir, bloque para un perceptrón multicapa.
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
Esto produce la siguiente salida:
[[ 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)>
Pasos necesarios para pasar de definir capas a definir bloques de una o más capas:
Step 1 - Bloquear toma los datos como entrada.
Step 2- Ahora, los bloques almacenarán el estado en forma de parámetros. Por ejemplo, en el ejemplo de codificación anterior, el bloque contiene dos capas ocultas y necesitamos un lugar para almacenar parámetros para él.
Step 3- El siguiente bloque invocará la función hacia adelante para realizar la propagación hacia adelante. También se denomina cálculo directo. Como parte de la primera llamada de desvío, los bloques inicializan los parámetros de forma diferida.
Step 4- Por fin los bloques invocarán la función hacia atrás y calcularán el gradiente con referencia a su entrada. Normalmente, este paso se realiza automáticamente.
Bloque secuencial
Un bloque secuencial es un tipo especial de bloque en el que los datos fluyen a través de una secuencia de bloques. En esto, cada bloque se aplica a la salida de uno antes y el primer bloque se aplica a los datos de entrada en sí.
Veamos como sequential trabajos de clase -
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 salida se da a continuación:
[[ 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)>
Bloque personalizado
Podemos ir fácilmente más allá de la concatenación con bloques secuenciales como se definió anteriormente. Pero, si quisiéramos hacer personalizaciones, entonces elBlockclass también nos proporciona la funcionalidad requerida. La clase de bloque tiene un constructor de modelo proporcionado en el módulo nn. Podemos heredar ese constructor de modelo para definir el modelo que queremos.
En el siguiente ejemplo, el MLP class anula el __init__ y funciones de avance de la clase Block.
Veamos cómo funciona.
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
Cuando ejecute el código, verá el siguiente resultado:
[[ 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)>
Capas personalizadas
La API Gluon de Apache MXNet viene con un número modesto de capas predefinidas. Pero aún en algún momento, podemos encontrar que se necesita una nueva capa. Podemos agregar fácilmente una nueva capa en Gluon API. En esta sección, veremos cómo podemos crear una nueva capa desde cero.
La capa personalizada más sencilla
Para crear una nueva capa en Gluon API, debemos crear una clase heredada de la clase Block que proporciona la funcionalidad más básica. Podemos heredar todas las capas predefinidas de él directamente o mediante otras subclases.
Para crear la nueva capa, el único método de instancia que se debe implementar es forward (self, x). Este método define qué va a hacer exactamente nuestra capa durante la propagación hacia adelante. Como se discutió anteriormente también, el paso de retropropagación para bloques lo realizará Apache MXNet automáticamente.
Ejemplo
En el siguiente ejemplo, definiremos una nueva capa. También implementaremosforward() método para normalizar los datos de entrada ajustándolos a un rango 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
Al ejecutar el programa anterior, obtendrá el siguiente resultado:
[[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)>
Hibridación
Puede definirse como un proceso utilizado por Apache MXNet para crear un gráfico simbólico de un cálculo directo. La hibridación permite que MXNet aumente el rendimiento de la computación optimizando el gráfico simbólico computacional. En lugar de heredar directamente deBlock, de hecho, podemos encontrar que al implementar capas existentes, un bloque hereda de un HybridBlock.
Las siguientes son las razones de esto:
Allows us to write custom layers: HybridBlock nos permite escribir capas personalizadas que se pueden usar en programación imperativa y simbólica tanto.
Increase computation performance- HybridBlock optimiza el gráfico simbólico computacional que permite a MXNet aumentar el rendimiento de los cálculos.
Ejemplo
En este ejemplo, reescribiremos nuestra capa de ejemplo, creada anteriormente, usando 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 salida se indica a continuación:
[0. 0.2 0.4 0.6 0.8 1. ]
<NDArray 6 @cpu(0)>
La hibridación no tiene nada que ver con la computación en GPU y se pueden entrenar redes híbridas y no híbridas tanto en CPU como en GPU.
Diferencia entre Block y HybridBlock
Si comparamos el Block Clase y HybridBlock, veremos eso HybridBlock ya tiene su forward() método implementado. HybridBlock define un hybrid_forward()método que debe implementarse al crear las capas. El argumento F crea la principal diferencia entreforward() y hybrid_forward(). En la comunidad MXNet, el argumento F se conoce como backend. F puede referirse amxnet.ndarray API (usado para programación imperativa) o mxnet.symbol API (usado para programación simbólica).
¿Cómo agregar una capa personalizada a una red?
En lugar de utilizar capas personalizadas por separado, estas capas se utilizan con capas predefinidas. Podemos usar cualquieraSequential o HybridSequentialcontenedores desde una red neuronal secuencial. Como se discutió anteriormente también,Sequential contenedor heredado de Block y HybridSequential heredar de HybridBlock respectivamente.
Ejemplo
En el siguiente ejemplo, crearemos una red neuronal simple con una capa personalizada. La salida deDense (5) La capa será la entrada de NormalizationHybridLayer. La salida deNormalizationHybridLayer se convertirá en la entrada de Dense (1) capa.
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
Verá la siguiente salida:
[[-1.1272651]
[-1.2299833]
[-1.0662932]
[-1.1805027]
[-1.3382034]
[-1.2081106]
[-1.1263978]
[-1.2524893]
[-1.1044774]
[-1.316593 ]]
<NDArray 10x1 @cpu(0)>
Parámetros de capa personalizados
En una red neuronal, una capa tiene un conjunto de parámetros asociados. A veces los llamamos pesos, que es el estado interno de una capa. Estos parámetros juegan diferentes roles:
A veces, estos son los que queremos aprender durante el paso de propagación hacia atrás.
A veces, estas son solo constantes que queremos usar durante el pase hacia adelante.
Si hablamos del concepto de programación, estos parámetros (pesos) de un bloque se almacenan y se accede a través de ParameterDict clase que ayuda a inicializarlos, actualizarlos, guardarlos y cargarlos.
Ejemplo
En el siguiente ejemplo, definiremos dos conjuntos de parámetros siguientes:
Parameter weights- Es entrenable y su forma se desconoce durante la fase de construcción. Se deducirá de la primera ejecución de propagación hacia adelante.
Parameter scale- Esta es una constante cuyo valor no cambia. A diferencia de los pesos de los parámetros, su forma se define durante la construcción.
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