Path: blob/master/site/pt-br/guide/keras/custom_layers_and_models.ipynb
25118 views
Copyright 2020 The TensorFlow Authors.
Criando novas camadas e modelos via subclasses
Configuração
A classe Layer
: a combinação de estado (pesos) e alguma computação
Uma das abstrações centrais do Keras é a classe Layer
(camada). Uma camada encapsula um estado (os "pesos" da camada) e uma transformação de entradas para saídas (uma "chamada" ou "call", o passo seguinte da camada).
Aqui está uma camada densamente conectada. Ela tem um estado: as variáveis w
e b
.
Você usa uma camada ao chamá-la em algumas entradas de tensor, de forma semelhante como se chama uma função Python.
Observe que os pesos w
e b
são rastreados automaticamente pela camada ao serem definidos como atributos da camada:
Observe que você também tem acesso a um atalho mais rápido para adicionar peso a uma camada: o método add_weight()
:
As camadas podem ter pesos não treináveis
Além dos pesos treináveis, você também pode adicionar pesos não treináveis a uma camada. Esses pesos não devem ser levados em consideração durante a retropropagação, quando você está treinando a camada.
Veja como adicionar e usar um peso não treinável:
Ele faz parte de layer.weights
, mas é categorizado como um peso não treinável:
Melhor prática: adiar a criação do peso até que o formato das entradas seja conhecido
Nossa camada Linear
acima recebeu um argumento input_dim
que foi usado para calcular o formato dos pesos w
e b
em __init__()
:
Em muitos casos, você pode não saber com antecedência o tamanho de suas entradas e pode querer criar pesos de forma postergada (lazy) quando esse valor se tornar conhecido, algum tempo depois de instanciar a camada.
Na API Keras, recomendamos criar pesos de camada no método build(self, inputs_shape)
de sua camada. Da forma mostrada a seguir:
O método __call__()
de sua camada será executado automaticamente na primeira vez que for chamado. Agora você tem uma camada lazy e, portanto, mais fácil de usar:
A implementação separada de build()
, como mostrado acima, separa bem a criação de pesos para uso único, do uso de pesos em cada chamada. No entanto, para algumas camadas personalizadas avançadas, pode se tornar impraticável separar a criação e a computação do estado. Os implementadores de camada podem adiar a criação de pesos para a primeira __call__()
, mas precisam tomar cuidado para que chamadas posteriores usem os mesmos pesos. Além disso, como __call__()
provavelmente será executada pela primeira vez dentro de uma tf.function
, qualquer criação de variável que ocorrer em __call__()
deve ser empacotada num tf.init_scope
.
As camadas podem ser compostas recursivamente
Se você atribuir uma instância de Layer como atributo de outra Layer, a camada externa começará a rastrear os pesos criados pela camada interna.
Recomendamos criar tais subcamadas no método __init__()
e deixar para que a primeira chamada __call__()
cuide da construção de seus pesos.
O método add_loss()
Ao escrever o método call()
de uma camada, você pode criar tensores de perda que deseja usar mais tarde, quando for escrever seu loop de treinamento. Isto é possível chamando self.add_loss(value)
:
Essas perdas (incluindo aquelas criadas por qualquer camada interna) podem ser recuperadas via layer.losses
. Esta propriedade é reiniciada no início de cada __call__()
para a camada de nível superior, de modo que layer.losses
sempre irá conter os valores de perda criados durante o último passo adiante.
Além disso, a propriedade loss
também contém perdas de regularização criadas para os pesos de qualquer camada interna:
Essas perdas devem ser levadas em consideração ao escrever loops de treinamento, como este:
Se quiser um guia detalhado sobre como escrever loops de treinamento, consulte o guia para escrever um loop de treinamento do zero .
Essas perdas também funcionam perfeitamente com fit()
(elas são automaticamente somadas e adicionadas à perda principal, se houver):
O método add_metric()
Assim como add_loss()
, as camadas também possuem um método add_metric()
para rastrear a média móvel de uma quantidade durante o treinamento.
Considere a seguinte camada: uma camada de "endpoint logístico". Ele recebe como entradas previsões e alvos, calcula uma perda que rastreia via add_loss()
, e calcula um escalar de exatidão, que ele rastreia via add_metric()
.
As métricas rastreadas dessa maneira podem ser acessadas via layer.metrics
:
Assim como para add_loss()
, essas métricas são rastreadas por fit()
:
Você pode, opcionalmente, ativar a serialização em suas camadas
Se você precisa que suas camadas personalizadas sejam serializáveis como parte de um modelo Functional, você pode, opcionalmente, implementar um método get_config()
:
Observe que o método __init__()
da classe Layer
base recebe alguns argumentos de palavra-chave, em particular um name
e um dtype
. É boa prática passar esses argumentos para a classe pai em __init__()
e incluí-los na configuração da camada:
Se você precisar de mais flexibilidade ao desserializar a camada de sua configuração, também poderá sobrepor o método de classe from_config()
. Esta é a implementação básica de from_config()
:
Para saber mais sobre serialização e salvamento, consulte o guia de salvamento e serialização de modelos.
Argumento training
privilegiado no método call()
Algumas camadas, em particular as camadas BatchNormalization
e Dropout
, têm comportamentos diferentes durante o treinamento e a inferência. Para tais camadas, é prática padrão expor um argumento training
(booleano) no método call()
.
Ao expor esse argumento em call()
, você permite que os loops integrados de treinamento e avaliação (por exemplo, fit()
) usem corretamente a camada em treinamento e inferência.
Argumento mask
privilegiado no método call()
O outro argumento privilegiado suportado por call()
é o argumento mask
.
Você vai encontrá-lo em todas as camadas Keras RNN. Uma máscara é um tensor booleano (um valor booleano por timestep na entrada) usado para ignorar determinados timestep de entrada ao processar dados de série temporal.
O Keras passará automaticamente o argumento mask
correto para __call__()
para as camadas que o suportarem, quando uma máscara for gerada por uma camada anterior. As camadas geradoras de máscara são a camada Embedding
configurada com mask_zero=True
e a camada Masking
.
Para saber mais sobre mascaramento e como escrever camadas habilitadas para mascaramento, consulte o guia "entendendo preenchimento e mascaramento".
A classe Model
Em geral, você usará a classe Layer
para definir blocos de computação internos e usará a classe Model
para definir o modelo externo: o objeto que você vai treinar.
Por exemplo, num modelo ResNet50, você teria vários blocos ResNet criando subclasses de Layer
, e um único Model
abrangendo toda a rede ResNet50.
A classe Model
tem a mesma API que Layer
, com as seguintes diferenças:
Ela expõe loops integrados de treinamento, avaliação e previsão (
model.fit()
,model.evaluate()
,model.predict()
).Ela expõe a lista de suas camadas internas, através da propriedade
model.layers
.Ela expõe APIs de salvamento e serialização (
save()
,save_weights()
...)
Efetivamente, a classe Layer
corresponde ao que nos referimos na literatura como uma "camada" (como em "camada de convolução" ou "camada recorrente") ou como um "bloco" (como em "bloco ResNet" ou "bloco Inception").
Já a classe Model
corresponde ao que é referido na literatura como "modelo" (como em "modelo de aprendizado profundo") ou como "rede" (como em "rede neural profunda").
Então, se você estiver se perguntando "devo usar a classe Layer
ou a classe Model
?", pergunte-se: precisarei chamar fit()
nela? Precisarei chamar save()
nela? Se assim for, vá com Model
. Caso contrário (porque sua classe é apenas um bloco em um sistema maior ou porque você mesmo está escrevendo treinamento e salvando código), use Layer
.
Por exemplo, poderíamos pegar nosso exemplo de mini-resnet acima e usá-lo para construir um Model
que poderíamos treinar com fit()
e que poderíamos salvar com save_weights()
:
Juntando tudo: um exemplo completo
Aqui está o que você aprendeu até agora:
Uma
Layer
encapsula um estado (criado em__init__()
oubuild()
) e alguma computação (definida emcall()
).As camadas podem ser aninhadas recursivamente para criar novos blocos de computação maiores.
As camadas podem criar e rastrear perdas (normalmente perdas de regularização), bem como métricas, via
add_loss()
eadd_metric()
O container externo, a coisa que você deseja treinar, é um
Model
. UmModel
é como umaLayer
, mas com utilitários adicionais de treinamento e serialização.
Vamos juntar todas essas coisas num exemplo completo: vamos implementar um Variational AutoEncoder (VAE). Vamos treiná-lo em dígitos MNIST.
Nosso VAE será uma subclasse de Model
, construída como uma composição aninhada de camadas que são subclasses de Layer
. Ela contará com uma perda de regularização (divergência KL).
Vamos escrever um loop de treinamento simples no MNIST:
Observe que, como o VAE é uma subclasse de Model
, ele apresenta loops de treinamento integrados. Então você também poderia tê-lo treinado assim:
Além do desenvolvimento orientado a objetos: a API Functional
Você achou que esse exemplo foi demasiado orientado a objetos para você? Você também pode criar modelos usando a API Functional. É importante ressaltar que escolher um estilo ou outro não impede que você aproveite os componentes escritos no outro estilo: você sempre pode misturar e combinar.
Por exemplo, o exemplo abaixo usando a API Functional reutiliza a mesma camada Sampling
que definimos no exemplo acima:
Para mais informações, não deixe de ler o guia da API Functional.