5.4. Camadas Personalizadas¶ Open the notebook in SageMaker Studio Lab
Um fator por trás do sucesso do Deep Learning é a disponibilidade de uma ampla gama de camadas que pode ser composto de maneiras criativas projetar arquiteturas adequadas para uma ampla variedade de tarefas. Por exemplo, os pesquisadores inventaram camadas especificamente para lidar com imagens, texto, loop sobre dados sequenciais, e realizando programação dinâmica. Mais cedo ou mais tarde, você encontrará ou inventará uma camada que ainda não existe na estrutura de Deep Learning. Nesses casos, você deve construir uma camada personalizada. Nesta seção, mostramos como.
5.4.1. Camadas Sem Parâmetros¶
Para começar, construímos uma camada personalizada que não possui
parâmetros próprios. Isso deve parecer familiar, se você se lembra de
nosso introdução ao bloco em Section 5.1. A
seguinte classe CenteredLayer
simplesmente subtrai a média de sua
entrada. Para construí-lo, simplesmente precisamos herdar da classe da
camada base e implementar a função de propagação direta.
from mxnet import np, npx
from mxnet.gluon import nn
npx.set_np()
class CenteredLayer(nn.Block):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def forward(self, X):
return X - X.mean()
import torch
from torch import nn
from torch.nn import functional as F
class CenteredLayer(nn.Module):
def __init__(self):
super().__init__()
def forward(self, X):
return X - X.mean()
import tensorflow as tf
class CenteredLayer(tf.keras.Model):
def __init__(self):
super().__init__()
def call(self, inputs):
return inputs - tf.reduce_mean(inputs)
Vamos verificar se nossa camada funciona conforme o esperado, alimentando alguns dados por meio dela.
layer = CenteredLayer()
layer(np.array([1, 2, 3, 4, 5]))
array([-2., -1., 0., 1., 2.])
layer = CenteredLayer()
layer(torch.FloatTensor([1, 2, 3, 4, 5]))
tensor([-2., -1., 0., 1., 2.])
layer = CenteredLayer()
layer(tf.constant([1, 2, 3, 4, 5]))
<tf.Tensor: shape=(5,), dtype=int32, numpy=array([-2, -1, 0, 1, 2], dtype=int32)>
Agora podemos incorporar nossa camada como um componente na construção de modelos mais complexos.
net = nn.Sequential()
net.add(nn.Dense(128), CenteredLayer())
net.initialize()
net = nn.Sequential(nn.Linear(8, 128), CenteredLayer())
net = tf.keras.Sequential([tf.keras.layers.Dense(128), CenteredLayer()])
Como uma verificação extra de sanidade, podemos enviar dados aleatórios através da rede e verificar se a média é de fato 0. Porque estamos lidando com números de ponto flutuante, ainda podemos ver um número muito pequeno diferente de zero devido à quantização.
Y = net(np.random.uniform(size=(4, 8)))
Y.mean()
array(3.783498e-10)
Y = net(torch.rand(4, 8))
Y.mean()
tensor(-4.1910e-09, grad_fn=<MeanBackward0>)
Y = net(tf.random.uniform((4, 8)))
tf.reduce_mean(Y)
<tf.Tensor: shape=(), dtype=float32, numpy=-1.8626451e-09>
5.4.2. Camadas com Parâmetros¶
Agora que sabemos como definir camadas simples, vamos prosseguir para a definição de camadas com parâmetros que pode ser ajustado por meio de treinamento. Podemos usar funções integradas para criar parâmetros, que fornecem algumas funcionalidades básicas de manutenção. Em particular, eles governam o acesso, inicialização, compartilhar, salvar e carregar parâmetros do modelo. Dessa forma, entre outros benefícios, não precisaremos escrever rotinas de serialização personalizadas para cada camada personalizada.
Agora, vamos implementar nossa própria versão da camada totalmente
conectada. Lembre-se de que esta camada requer dois parâmetros, um para
representar o peso e outro para o viés. Nesta implementação, preparamos
a ativação do ReLU como padrão. Esta camada requer a entrada de
argumentos: in_units
e units
, que denotam o número de entradas e
saídas, respectivamente.
class MyDense(nn.Block):
def __init__(self, units, in_units, **kwargs):
super().__init__(**kwargs)
self.weight = self.params.get('weight', shape=(in_units, units))
self.bias = self.params.get('bias', shape=(units,))
def forward(self, x):
linear = np.dot(x, self.weight.data(ctx=x.ctx)) + self.bias.data(
ctx=x.ctx)
return npx.relu(linear)
class MyLinear(nn.Module):
def __init__(self, in_units, units):
super().__init__()
self.weight = nn.Parameter(torch.randn(in_units, units))
self.bias = nn.Parameter(torch.randn(units,))
def forward(self, X):
linear = torch.matmul(X, self.weight.data) + self.bias.data
return F.relu(linear)
class MyDense(tf.keras.Model):
def __init__(self, units):
super().__init__()
self.units = units
def build(self, X_shape):
self.weight = self.add_weight(name='weight',
shape=[X_shape[-1], self.units],
initializer=tf.random_normal_initializer())
self.bias = self.add_weight(
name='bias', shape=[self.units],
initializer=tf.zeros_initializer())
def call(self, X):
linear = tf.matmul(X, self.weight) + self.bias
return tf.nn.relu(linear)
Em seguida, instanciamos a classe MyDense
e acessar seus parâmetros
de modelo.
dense = MyDense(units=3, in_units=5)
dense.params
mydense0_ (
Parameter mydense0_weight (shape=(5, 3), dtype=<class 'numpy.float32'>)
Parameter mydense0_bias (shape=(3,), dtype=<class 'numpy.float32'>)
)
dense = MyLinear(5, 3)
dense.weight
Parameter containing:
tensor([[-0.4359, -0.8619, -0.4170],
[-0.3191, 0.3732, 0.7508],
[-0.1494, -0.8253, -0.7071],
[ 1.0940, -0.4332, -2.2712],
[ 1.9598, -0.2122, 1.8529]], requires_grad=True)
dense = MyDense(3)
dense(tf.random.uniform((2, 5)))
dense.get_weights()
[array([[-0.03506669, 0.01943431, -0.00358615],
[ 0.02064196, -0.07825888, -0.00893933],
[ 0.00577814, 0.0454763 , 0.02938056],
[-0.03217198, -0.05240436, -0.00833275],
[ 0.05816463, 0.02635984, -0.05368067]], dtype=float32),
array([0., 0., 0.], dtype=float32)]
Podemos realizar cálculos de propagação direta usando camadas personalizadas.
dense.initialize()
dense(np.random.uniform(size=(2, 5)))
array([[0. , 0.01633355, 0. ],
[0. , 0.01581812, 0. ]])
dense(torch.rand(2, 5))
tensor([[1.1498, 0.0000, 0.0000],
[1.9590, 0.0000, 0.0000]])
dense(tf.random.uniform((2, 5)))
<tf.Tensor: shape=(2, 3), dtype=float32, numpy=
array([[0.01058663, 0. , 0. ],
[0. , 0. , 0. ]], dtype=float32)>
Também podemos construir modelos usando camadas personalizadas. Assim que tivermos isso, podemos usá-lo como a camada totalmente conectada integrada.
net = nn.Sequential()
net.add(MyDense(8, in_units=64),
MyDense(1, in_units=8))
net.initialize()
net(np.random.uniform(size=(2, 64)))
array([[0.06508517],
[0.0615553 ]])
net = nn.Sequential(MyLinear(64, 8), MyLinear(8, 1))
net(torch.rand(2, 64))
tensor([[0.],
[0.]])
net = tf.keras.models.Sequential([MyDense(8), MyDense(1)])
net(tf.random.uniform((2, 64)))
<tf.Tensor: shape=(2, 1), dtype=float32, numpy=
array([[0.],
[0.]], dtype=float32)>
5.4.3. Sumário¶
Podemos projetar camadas personalizadas por meio da classe de camada básica. Isso nos permite definir novas camadas flexíveis que se comportam de maneira diferente de quaisquer camadas existentes na biblioteca.
Uma vez definidas, as camadas personalizadas podem ser chamadas em contextos e arquiteturas arbitrários.
As camadas podem ter parâmetros locais, que podem ser criados por meio de funções integradas.