.. _sec_model_construction:
Camadas e Blocos
================
Quando introduzimos as redes neurais pela primeira vez, focamos em
modelos lineares com uma única saída. Aqui, todo o modelo consiste em
apenas um único neurônio. Observe que um único neurônio (i) leva algum
conjunto de entradas; (ii) gera uma saída escalar correspondente; e
(iii) tem um conjunto de parâmetros associados que podem ser atualizados
para otimizar alguma função objetivo de interesse. Então, quando
começamos a pensar em redes com múltiplas saídas, nós alavancamos a
aritmética vetorizada para caracterizar uma camada inteira de neurônios.
Assim como os neurônios individuais, camadas (i) recebem um conjunto de
entradas, (ii) gerar resultados correspondentes, e (iii) são descritos
por um conjunto de parâmetros ajustáveis. Quando trabalhamos com a
regressão softmax, uma única camada era ela própria o modelo. No
entanto, mesmo quando subsequentemente introduziu MLPs, ainda podemos
pensar no modelo como mantendo esta mesma estrutura básica.
Curiosamente, para MLPs, todo o modelo e suas camadas constituintes
compartilham essa estrutura. Todo o modelo recebe entradas brutas (os
recursos), gera resultados (as previsões), e possui parâmetros (os
parâmetros combinados de todas as camadas constituintes). Da mesma
forma, cada camada individual ingere entradas (fornecido pela camada
anterior) gera saídas (as entradas para a camada subsequente), e possui
um conjunto de parâmetros ajustáveis que são atualizados de acordo com o
sinal que flui para trás da camada subsequente.
Embora você possa pensar que neurônios, camadas e modelos dê-nos
abstrações suficientes para cuidar de nossos negócios, Acontece que
muitas vezes achamos conveniente para falar sobre componentes que são
maior do que uma camada individual mas menor do que o modelo inteiro.
Por exemplo, a arquitetura ResNet-152, que é muito popular na visão
computacional, possui centenas de camadas. Essas camadas consistem em
padrões repetidos de *grupos de camadas*. Implementar uma camada de rede
por vez pode se tornar tedioso. Essa preocupação não é apenas hipotética
— tal padrões de projeto são comuns na prática. A arquitetura ResNet
mencionada acima venceu as competições de visão computacional ImageNet e
COCO 2015 para reconhecimento e detecção :cite:`He.Zhang.Ren.ea.2016`
e continua sendo uma arquitetura indispensável para muitas tarefas de
visão. Arquiteturas semelhantes nas quais as camadas são organizadas em
vários padrões repetidos agora são onipresentes em outros domínios,
incluindo processamento de linguagem natural e fala.
Para implementar essas redes complexas, introduzimos o conceito de uma
rede neural *block*. Um bloco pode descrever uma única camada, um
componente que consiste em várias camadas, ou o próprio modelo inteiro!
Uma vantagem de trabalhar com a abstração de bloco é que eles podem ser
combinados em artefatos maiores, frequentemente recursivamente. Isso é
ilustrado em :numref:`fig_blocks`. Definindo o código para gerar
blocos de complexidade arbitrária sob demanda, podemos escrever código
surpreendentemente compacto e ainda implementar redes neurais complexas.
.. _fig_blocks:
.. figure:: ../img/blocks.svg
Múltiplas camadas são combinadas em blocos, formando padrões
repetitivos de um modelo maior.
Do ponto de vista da programação, um bloco é representado por uma
*classe*. Qualquer subclasse dele deve definir uma função de propagação
direta que transforma sua entrada em saída e deve armazenar todos os
parâmetros necessários. Observe que alguns blocos não requerem nenhum
parâmetro. Finalmente, um bloco deve possuir uma função de
retropropagação, para fins de cálculo de gradientes. Felizmente, devido
a alguma magia dos bastidores fornecido pela diferenciação automática
(introduzido em :numref:`sec_autograd`) ao definir nosso próprio
bloco, só precisamos nos preocupar com os parâmetros e a função de
propagação direta.
Para começar, revisitamos o código que usamos para implementar MLPs
(:numref:`sec_mlp_concise`). O código a seguir gera uma rede com uma
camada oculta totalmente conectada com 256 unidades e ativação ReLU,
seguido por uma camada de saída totalmente conectada com 10 unidades
(sem função de ativação).
.. raw:: html
.. raw:: html
.. code:: python
from mxnet import np, npx
from mxnet.gluon import nn
npx.set_np()
net = nn.Sequential()
net.add(nn.Dense(256, activation='relu'))
net.add(nn.Dense(10))
net.initialize()
X = np.random.uniform(size=(2, 20))
net(X)
.. parsed-literal::
:class: output
array([[ 0.06240274, -0.03268593, 0.02582653, 0.02254181, -0.03728798,
-0.04253785, 0.00540612, -0.01364185, -0.09915454, -0.02272737],
[ 0.02816679, -0.03341204, 0.03565665, 0.02506384, -0.04136416,
-0.04941844, 0.01738529, 0.01081963, -0.09932579, -0.01176296]])
Neste exemplo, nós construímos nosso modelo instanciando um
``nn.Sequential``, atribuindo o objeto retornado à variável ``net``. Em
seguida, chamamos repetidamente sua função ``add``, anexando camadas no
pedido que eles devem ser executados. Em suma, ``nn.Sequential`` define
um tipo especial de\ ``Block``, a classe que apresenta um bloco em
Gluon. Ele mantém uma lista ordenada de ``Block`` constituintes. A
função ``add`` simplesmente facilita a adição de cada ``Bloco``
sucessivo à lista. Observe que cada camada é uma instância da classe
``Dense`` que é uma subclasse de ``Block``. A função de propagação
direta (``forward``) também é notavelmente simples: ele encadeia cada
``Block`` na lista, passando a saída de cada um como entrada para o
próximo. Observe que, até agora, temos invocado nossos modelos através
da construção ``net (X)`` para obter seus resultados. Na verdade, isso é
apenas um atalho para ``net.forward (X)``, um truque Python habilidoso
alcançado via a função ``__call__`` da classe\ ``Block``.
.. raw:: html
.. raw:: html
.. code:: python
import torch
from torch import nn
from torch.nn import functional as F
net = nn.Sequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
X = torch.rand(2, 20)
net(X)
.. parsed-literal::
:class: output
tensor([[-0.0256, 0.1279, 0.0679, 0.0072, -0.2088, 0.0412, -0.2575, -0.0133,
0.0521, 0.0697],
[ 0.0747, 0.1671, 0.1247, -0.1827, -0.1590, 0.0773, -0.2884, -0.1681,
0.0335, 0.2242]], grad_fn=
)
Neste exemplo, nós construímos nosso modelo instanciando um
``nn.Sequential``, com camadas na ordem que eles devem ser executados
passados como argumentos. Em suma, ``nn.Sequential`` define um tipo
especial de ``Module``, a classe que apresenta um bloco em PyTorch. Ele
mantém uma lista ordenada de ``Module`` constituintes. Observe que cada
uma das duas camadas totalmente conectadas é uma instância da classe
``Linear`` que é uma subclasse de ``Module``. A função de propagação
direta (``forward``) também é notavelmente simples: ele encadeia cada
bloco da lista, passando a saída de cada um como entrada para o próximo.
Observe que, até agora, temos invocado nossos modelos através da
construção ``net (X)`` para obter seus resultados. Na verdade, isso é
apenas um atalho para ``net.__call__(X)``.
.. raw:: html
.. raw:: html
.. code:: python
import tensorflow as tf
net = tf.keras.models.Sequential([
tf.keras.layers.Dense(256, activation=tf.nn.relu),
tf.keras.layers.Dense(10),
])
X = tf.random.uniform((2, 20))
net(X)
.. parsed-literal::
:class: output
Neste exemplo, nós construímos nosso modelo instanciando um
``keras.models.Sequential``, com camadas na ordem que eles devem ser
executados passados como argumentos. Em suma, ``Sequential`` define um
tipo especial de\ ``keras.Model``, a classe que apresenta um bloco em
Keras. Ele mantém uma lista ordenada de ``Model`` constituintes. Observe
que cada uma das duas camadas totalmente conectadas é uma instância da
classe ``Dense`` que é uma subclasse de ``Model``. A função de
propagação direta (``call``) também é extremamente simples: ele encadeia
cada bloco da lista, passando a saída de cada um como entrada para o
próximo. Observe que, até agora, temos invocado nossos modelos através
da construção ``net (X)`` para obter seus resultados. Na verdade, isso é
apenas um atalho para ``net.call(X)``, um truque Python habilidoso
alcançado via a função ``__call__`` da classe Block.
.. raw:: html
.. raw:: html
Um Bloco Personalizado
----------------------
Talvez a maneira mais fácil de desenvolver intuição sobre como funciona
um bloco é implementar um nós mesmos. Antes de implementar nosso próprio
bloco personalizado, resumimos brevemente a funcionalidade básica que
cada bloco deve fornecer:
1. Ingerir dados de entrada como argumentos para sua função de
propagação direta.
2. Gere uma saída fazendo com que a função de propagação direta retorne
um valor. Observe que a saída pode ter uma forma diferente da
entrada. Por exemplo, a primeira camada totalmente conectada em nosso
modelo acima ingere uma entrada de dimensão arbitrária, mas retorna
uma saída de dimensão 256.
3. Calcule o gradiente de sua saída em relação à sua entrada, que pode
ser acessado por meio de sua função de retropropagação. Normalmente,
isso acontece automaticamente.
4. Armazene e forneça acesso aos parâmetros necessários para executar o
cálculo de propagação direta.
5. Inicialize os parâmetros do modelo conforme necessário.
No seguinte trecho de código, nós codificamos um bloco do zero
correspondendo a um MLP com uma camada oculta com 256 unidades ocultas,
e uma camada de saída de 10 dimensões. Observe que a classe ``MLP``
abaixo herda a classe que representa um bloco. Vamos contar muito com as
funções da classe pai, fornecendo apenas nosso próprio construtor (a
função ``__init__`` em Python) e a função de propagação direta.
.. raw:: html
.. raw:: html
.. code:: python
class MLP(nn.Block):
# Declare uma camada com parâmetros de modelo.
# Aqui, nós declaramos duas camadas completamente conectadas
def __init__(self, **kwargs):
# Chame o construtor da classe pai `MLP` `Block` para realizar
# as inicializações necessárias. Desta forma, outros argumentos das funções
# também podem ser especificados durante a instalação da classe,
# da mesma forma que os parâmetros do modelo, 'params' (a ser descrito posteriormente)
super().__init__(**kwargs)
self.hidden = nn.Dense(256, activation='relu') # Hidden layer
self.out = nn.Dense(10) # Output layer
# Defina a propagação direta do modelo, ou seja, como retornar
# a saída do modelo requirido baseado na entrada 'X'
def forward(self, X):
return self.out(self.hidden(X))
.. raw:: html
.. raw:: html
.. code:: python
class MLP(nn.Module):
# Declare uma camada com parâmetros de modelo. Aqui, declaramos duas
# camadas totalmente conectadas
def __init__(self):
# Chame o construtor da classe pai `MLP` `Block` para realizar
# a inicialização necessária. Desta forma, outros argumentos de função
# também podem ser especificado durante a instanciação da classe, como
# os parametros do modelo, `params` (a ser descritos posteriormente)
super().__init__()
self.hidden = nn.Linear(20, 256) # Hidden layer
self.out = nn.Linear(256, 10) # Output layer
# Defina a propagação direta do modelo, ou seja, como retornar a
# saída do modelo necessária com base na entrada `X`
def forward(self, X):
# Observe aqui que usamos a versão funcional do ReLU definida no
# módulo nn.functional.
return self.out(F.relu(self.hidden(X)))
.. raw:: html
.. raw:: html
.. code:: python
class MLP(tf.keras.Model):
# Declare uma camada com parâmetros de modelo. Aqui, declaramos duas
# camadas totalmente conectadas
def __init__(self):
# Chame o construtor da classe pai `MLP` `Block` para realizar
# a inicialização necessária. Desta forma, outros argumentos de função
# também podem ser especificados durante a instanciação da classe, como os
# parâmetros do modelo, `params` (a serem descritos mais tarde)
super().__init__()
# Camadas escondidas
self.hidden = tf.keras.layers.Dense(units=256, activation=tf.nn.relu)
self.out = tf.keras.layers.Dense(units=10) # Output layer
# Defina a propagação direta do modelo, ou seja, como retornar a
# saída do modelo necessária com base na entrada `X`
def call(self, X):
return self.out(self.hidden((X)))
.. raw:: html
.. raw:: html
Vamos primeiro nos concentrar na função de propagação direta. Observe
que leva ``X`` como entrada, calcula a representação oculta com a função
de ativação aplicada, e produz seus *logits*. Nesta implementação
``MLP``, ambas as camadas são variáveis de instância. Para ver por que
isso é razoável, imagine instanciando dois MLPs, ``net1`` e\ ``net2``, e
treiná-los em dados diferentes. Naturalmente, esperaríamos que eles para
representar dois modelos aprendidos diferentes.
Nós instanciamos as camadas do MLP no construtor e posteriormente
invocar essas camadas em cada chamada para a função de propagação
direta. Observe alguns detalhes importantes: Primeiro, nossa função
``__init__`` personalizada invoca a função ``__init__`` da classe pai
via ``super().__ init __()`` poupando-nos da dor de reafirmar o código
padrão aplicável à maioria dos blocos. Em seguida, instanciamos nossas
duas camadas totalmente conectadas, atribuindo-os a ``self.hidden``
e\ ``self.out``. Observe que, a menos que implementemos um novo
operador, não precisamos nos preocupar com a função de *backpropagation*
ou inicialização de parâmetro. O sistema irá gerar essas funções
automaticamente. Vamos tentar fazer isso.
.. raw:: html
.. raw:: html
.. code:: python
net = MLP()
net.initialize()
net(X)
.. parsed-literal::
:class: output
array([[-0.03989595, -0.10414709, 0.06799038, 0.05245074, 0.0252606 ,
-0.00640342, 0.04182098, -0.01665318, -0.02067345, -0.07863816],
[-0.03612847, -0.07210435, 0.09159479, 0.07890773, 0.02494171,
-0.01028665, 0.01732427, -0.02843244, 0.03772651, -0.06671703]])
.. raw:: html
.. raw:: html
.. code:: python
net = MLP()
net(X)
.. parsed-literal::
:class: output
tensor([[-0.0627, -0.0958, -0.0792, 0.2375, 0.3284, 0.1038, -0.0707, -0.0726,
-0.0738, 0.0958],
[ 0.0125, -0.1072, 0.0780, 0.1053, 0.2692, 0.1731, -0.0750, -0.1358,
-0.0111, -0.0523]], grad_fn=
)
.. raw:: html
.. raw:: html
.. code:: python
net = MLP()
net(X)
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
Uma virtude fundamental da abstração em bloco é sua versatilidade.
Podemos criar uma subclasse de um bloco para criar camadas (como a
classe de camada totalmente conectada), modelos inteiros (como a classe
``MLP`` acima), ou vários componentes de complexidade intermediária. Nós
exploramos essa versatilidade ao longo dos capítulos seguintes, como ao
abordar redes neurais convolucionais.
O Bloco Sequencial
------------------
Agora podemos dar uma olhada mais de perto em como a classe
``Sequential`` funciona. Lembre-se de que ``Sequential`` foi projetado
para conectar outros blocos em série. Para construir nosso próprio
``MySequential`` simplificado, só precisamos definir duas funções
principais: 1. Uma função para anexar um blocos a uma lista. 2. Uma
função de propagação direta para passar uma entrada através da cadeia de
blocos, na mesma ordem em que foram acrescentados.
A seguinte classe ``MySequential`` oferece o mesmo funcionalidade da
classe ``Sequential`` padrão.
.. raw:: html
.. raw:: html
.. code:: python
class MySequential(nn.Block):
def add(self, block):
# Here, `block` is an instance of a `Block` subclass, and we assume
#
# that it has a unique name. We save it in the member variable
#
# `_children` of the `Block` class, and its type is OrderedDict. When
#
# the `MySequential` instance calls the `initialize` function, the
#
# system automatically initializes all members of `_children`
#
self._children[block.name] = block
def forward(self, X):
# OrderedDict guarantees that members will be traversed in the order
#
# they were added
#
for block in self._children.values():
X = block(X)
return X
A função ``add`` adiciona um único bloco para o dicionário ordenado
``_children``. Você deve estar se perguntando por que todo bloco de
Gluon possui um atributo ``_children`` e por que o usamos em vez de
apenas definir uma lista Python nós mesmos. Resumindo, a principal
vantagem das ``_children`` é que durante a inicialização do parâmetro do
nosso bloco, Gluon sabe olhar dentro do dicionário ``_children`` para
encontrar sub-blocos cujo os parâmetros também precisam ser
inicializados.
.. raw:: html
.. raw:: html
.. code:: python
class MySequential(nn.Module):
def __init__(self, *args):
super().__init__()
for idx, module in enumerate(args):
# Here, `module` is an instance of a `Module` subclass. We save it
#
# in the member variable `_modules` of the `Module` class, and its
#
# type is OrderedDict
#
self._modules[str(idx)] = module
def forward(self, X):
# OrderedDict guarantees that members will be traversed in the order
#
# they were added
#
for block in self._modules.values():
X = block(X)
return X
No método ``__init__``, adicionamos todos os módulos para o dicionário
ordenado ``_modules`` um por um. Você pode se perguntar por que todo
``Module`` possui um atributo ``_modules`` e por que o usamos em vez de
apenas definir uma lista Python nós mesmos. Em suma, a principal
vantagem de ``_modules`` é que durante a inicialização do parâmetro do
nosso módulo, o sistema sabe olhar dentro do ``_modules`` dicionário
para encontrar submódulos cujo os parâmetros também precisam ser
inicializados.
.. raw:: html
.. raw:: html
.. code:: python
class MySequential(tf.keras.Model):
def __init__(self, *args):
super().__init__()
self.modules = []
for block in args:
# Here, `block` is an instance of a `tf.keras.layers.Layer`
#
# subclass
#
self.modules.append(block)
def call(self, X):
for module in self.modules:
X = module(X)
return X
.. raw:: html
.. raw:: html
Quando a função de propagação direta de nosso ``MySequential`` é
invocada, cada bloco adicionado é executado na ordem em que foram
adicionados. Agora podemos reimplementar um MLP usando nossa classe
``MySequential``.
.. raw:: html
.. raw:: html
.. code:: python
net = MySequential()
net.add(nn.Dense(256, activation='relu'))
net.add(nn.Dense(10))
net.initialize()
net(X)
.. parsed-literal::
:class: output
array([[-0.0764568 , -0.01130233, 0.04952145, -0.04651389, -0.04131571,
-0.05884131, -0.06213811, 0.01311471, -0.01379425, -0.02514282],
[-0.05124623, 0.00711232, -0.00155933, -0.07555379, -0.06675334,
-0.01762914, 0.00589085, 0.0144719 , -0.04330775, 0.03317727]])
.. raw:: html
.. raw:: html
.. code:: python
net = MySequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
net(X)
.. parsed-literal::
:class: output
tensor([[-0.1604, 0.0811, -0.1572, 0.0730, 0.1101, -0.1630, 0.0652, 0.0542,
0.3904, 0.3679],
[-0.2220, -0.0184, -0.0323, 0.1314, 0.2649, -0.0249, -0.1741, -0.0846,
0.2679, 0.3209]], grad_fn=
)
.. raw:: html
.. raw:: html
.. code:: python
net = MySequential(
tf.keras.layers.Dense(units=256, activation=tf.nn.relu),
tf.keras.layers.Dense(10))
net(X)
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
Observe que este uso de ``MySequential`` é idêntico ao código que
escrevemos anteriormente para a classe ``Sequential`` (conforme descrito
em :numref:`sec_mlp_concise`).
Execução de Código na Função de Propagação Direta
-------------------------------------------------
A classe ``Sequential`` facilita a construção do modelo, nos permitindo
montar novas arquiteturas sem ter que definir nossa própria classe. No
entanto, nem todas as arquiteturas são cadeias simples. Quando uma maior
flexibilidade é necessária, vamos querer definir nossos próprios blocos.
Por exemplo, podemos querer executar o controle de fluxo do Python
dentro da função de propagação direta. Além disso, podemos querer
realizar operações matemáticas arbitrárias, não simplesmente depender de
camadas de rede neural predefinidas.
Você deve ter notado que até agora, todas as operações em nossas redes
agiram de acordo com as ativações de nossa rede e seus parâmetros. Às
vezes, no entanto, podemos querer incorporar termos que não são
resultado de camadas anteriores nem parâmetros atualizáveis. Chamamos
isso de *parâmetros constantes*. Digamos, por exemplo, que queremos uma
camada que calcula a função
:math:`f(\mathbf{x},\mathbf{w}) = c \cdot \mathbf{w}^\top \mathbf{x}`,
onde :math:`\mathbf{x}` é a entrada, :math:`\mathbf{w}` é nosso
parâmetro, e :math:`c` é alguma constante especificada que não é
atualizado durante a otimização. Portanto, implementamos uma classe
``FixedHiddenMLP`` como a seguir.
.. raw:: html
.. raw:: html
.. code:: python
class FixedHiddenMLP(nn.Block):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# Parâmetros de peso aleatórios criados com a função `get_constant`
# não são atualizados durante o treinamento (ou seja, parâmetros constantes)
self.rand_weight = self.params.get_constant(
'rand_weight', np.random.uniform(size=(20, 20)))
self.dense = nn.Dense(20, activation='relu')
def forward(self, X):
X = self.dense(X)
# Use os parâmetros constantes criados, bem como as funções `relu` e` dot`
X = npx.relu(np.dot(X, self.rand_weight.data()) + 1)
# Reutilize a camada totalmente conectada. Isso é equivalente a compartilhar
# parâmetros com duas camadas totalmente conectadas
X = self.dense(X)
# Control flow
while np.abs(X).sum() > 1:
X /= 2
return X.sum()
.. raw:: html
.. raw:: html
.. code:: python
class FixedHiddenMLP(nn.Module):
def __init__(self):
super().__init__()
# Parâmetros de peso aleatórios que não computarão gradientes e
# portanto, mantem-se constante durante o treinamento
self.rand_weight = torch.rand((20, 20), requires_grad=False)
self.linear = nn.Linear(20, 20)
def forward(self, X):
X = self.linear(X)
# Use os parâmetros constantes criados, bem como as funções `relu` e` mm`
X = F.relu(torch.mm(X, self.rand_weight) + 1)
# Reutilize a camada totalmente conectada. Isso é equivalente a compartilhar
# parâmetros com duas camadas totalmente conectadas
X = self.linear(X)
# Controle de fluxo
while X.abs().sum() > 1:
X /= 2
return X.sum()
.. raw:: html
.. raw:: html
.. code:: python
class FixedHiddenMLP(tf.keras.Model):
def __init__(self):
super().__init__()
self.flatten = tf.keras.layers.Flatten()
# Parâmetros de peso aleatório criados com `tf.constant` não são atualizados
# durante o treinamento (ou seja, parâmetros constantes)
self.rand_weight = tf.constant(tf.random.uniform((20, 20)))
self.dense = tf.keras.layers.Dense(20, activation=tf.nn.relu)
def call(self, inputs):
X = self.flatten(inputs)
# Use os parâmetros constantes criados, bem como as funções `relu` e `matmul`
X = tf.nn.relu(tf.matmul(X, self.rand_weight) + 1)
# Reutilize a camada totalmente conectada. Isso é equivalente a compartilhar
# parâmetros com duas camadas totalmente conectadas
X = self.dense(X)
# Control flow
#
while tf.reduce_sum(tf.math.abs(X)) > 1:
X /= 2
return tf.reduce_sum(X)
.. raw:: html
.. raw:: html
Neste modelo ``FixedHiddenMLP``, implementamos uma camada oculta cujos
pesos (``self.rand_weight``) são inicializados aleatoriamente na
instanciação e daí em diante constantes. Este peso não é um parâmetro do
modelo e, portanto, nunca é atualizado por *backpropagation*. A rede
então passa a saída desta camada “fixa” através de uma camada totalmente
conectada.
Observe que antes de retornar a saída, nosso modelo fez algo incomum.
Executamos um *loop while*, testando na condição de que sua norma
:math:`L_1` seja maior que :math:`1`, e dividindo nosso vetor de
produção por :math:`2` até que satisfizesse a condição. Finalmente,
retornamos a soma das entradas em ``X``. Até onde sabemos, nenhuma rede
neural padrão executa esta operação. Observe que esta operação em
particular pode não ser útil em qualquer tarefa do mundo real. Nosso
objetivo é apenas mostrar como integrar código arbitrário no fluxo de
seu cálculos de rede neural.
.. raw:: html
.. raw:: html
.. code:: python
net = FixedHiddenMLP()
net.initialize()
net(X)
.. parsed-literal::
:class: output
array(0.52637565)
.. raw:: html
.. raw:: html
.. code:: python
net = FixedHiddenMLP()
net(X)
.. parsed-literal::
:class: output
tensor(0.1221, grad_fn=)
.. raw:: html
.. raw:: html
.. code:: python
net = FixedHiddenMLP()
net(X)
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
Podemos misturar e combinar vários maneiras de montar blocos juntos. No
exemplo a seguir, aninhamos blocos de algumas maneiras criativas.
.. raw:: html
.. raw:: html
.. code:: python
class NestMLP(nn.Block):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.net = nn.Sequential()
self.net.add(nn.Dense(64, activation='relu'),
nn.Dense(32, activation='relu'))
self.dense = nn.Dense(16, activation='relu')
def forward(self, X):
return self.dense(self.net(X))
chimera = nn.Sequential()
chimera.add(NestMLP(), nn.Dense(20), FixedHiddenMLP())
chimera.initialize()
chimera(X)
.. parsed-literal::
:class: output
array(0.9772054)
.. raw:: html
.. raw:: html
.. code:: python
class NestMLP(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(nn.Linear(20, 64), nn.ReLU(),
nn.Linear(64, 32), nn.ReLU())
self.linear = nn.Linear(32, 16)
def forward(self, X):
return self.linear(self.net(X))
chimera = nn.Sequential(NestMLP(), nn.Linear(16, 20), FixedHiddenMLP())
chimera(X)
.. parsed-literal::
:class: output
tensor(-0.0955, grad_fn=)
.. raw:: html
.. raw:: html
.. code:: python
class NestMLP(tf.keras.Model):
def __init__(self):
super().__init__()
self.net = tf.keras.Sequential()
self.net.add(tf.keras.layers.Dense(64, activation=tf.nn.relu))
self.net.add(tf.keras.layers.Dense(32, activation=tf.nn.relu))
self.dense = tf.keras.layers.Dense(16, activation=tf.nn.relu)
def call(self, inputs):
return self.dense(self.net(inputs))
chimera = tf.keras.Sequential()
chimera.add(NestMLP())
chimera.add(tf.keras.layers.Dense(20))
chimera.add(FixedHiddenMLP())
chimera(X)
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
Eficiência
----------
.. raw:: html
.. raw:: html
O leitor ávido pode começar a se preocupar sobre a eficiência de algumas
dessas operações. Afinal, temos muitas pesquisas de dicionário, execução
de código e muitas outras coisas Pythônicas ocorrendo no que deveria ser
uma biblioteca de *Deep Learning* de alto desempenho. Os problemas do
`Bloqueio do Interprete
Global `__ do Python
são bem conhecidos. No contexto de *Deep Learning*, podemos nos
preocupar que nossas GPU(s) extremamente rápidas pode ter que esperar
até uma CPU insignificante executa o código Python antes de obter outro
trabalho para ser executado. A melhor maneira de acelerar o Python é
evitá-lo completamente.
Uma maneira de o Gluon fazer isso é permitindo *hibridização*, que será
descrita mais tarde. Aqui, o interpretador Python executa um bloco na
primeira vez que é invocado. O tempo de execução do Gluon registra o que
está acontecendo e, da próxima vez, provoca um curto-circuito nas
chamadas para Python. Isso pode acelerar as coisas consideravelmente em
alguns casos mas é preciso ter cuidado ao controlar o fluxo (como acima)
pois conduz a diferentes ramos em diferentes passagens através da rede.
Recomendamos que o leitor interessado verifique a seção de hibridização
(:numref:`sec_hybridize`) para aprender sobre a compilação depois de
terminar o capítulo atual.
.. raw:: html
.. raw:: html
O leitor ávido pode começar a se preocupar sobre a eficiência de algumas
dessas operações. Afinal, temos muitas pesquisas de dicionário, execução
de código e muitas outras coisas Pythônicas ocorrendo no que deveria ser
uma biblioteca de *Deep Learning* de alto desempenho. Os problemas do
`bloqueio do interpretador
global `__ do Python
são bem conhecidos. No contexto de *Deep Learning*, podemos nos
preocupar que nossas GPU(s) extremamente rápidas pode ter que esperar
até uma CPU insignificante executa o código Python antes de obter outro
trabalho para ser executado.
.. raw:: html
.. raw:: html
O leitor ávido pode começar a se preocupar sobre a eficiência de algumas
dessas operações. Afinal, temos muitas pesquisas de dicionário, execução
de código e muitas outras coisas Pythônicas ocorrendo no que deveria ser
uma biblioteca de aprendizado profundo de alto desempenho. Os problemas
do `bloqueio do interpretador
global `__ do Python
são bem conhecidos. No contexto de *Deep Learning*, podemos nos
preocupar que nossas GPU(s) extremamente rápidas pode ter que esperar
até uma CPU insignificante executa o código Python antes de obter outro
trabalho para ser executado. A melhor maneira de acelerar o Python é
evitá-lo completamente.
.. raw:: html
.. raw:: html
Sumário
-------
- Camadas são blocos.
- Muitas camadas podem incluir um bloco.
- Muitos blocos podem incluir um bloco.
- Um bloco pode conter código.
- Os blocos cuidam de muitas tarefas domésticas, incluindo
inicialização de parâmetros e *backpropagation*.
- As concatenações sequenciais de camadas e blocos são tratadas pelo
bloco ``Sequencial``.
Exercícios
----------
1. Que tipos de problemas ocorrerão se você alterar ``MySequential``
para armazenar blocos em uma lista Python?
2. Implemente um bloco que tenha dois blocos como argumento, digamos
``net1`` e ``net2`` e retorne a saída concatenada de ambas as redes
na propagação direta. Isso também é chamado de bloco paralelo.
3. Suponha que você deseja concatenar várias instâncias da mesma rede.
Implemente uma função de fábrica que gere várias instâncias do mesmo
bloco e construa uma rede maior a partir dele.
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html
.. raw:: html