Path: blob/master/site/pt-br/guide/keras/functional.ipynb
25118 views
Copyright 2020 The TensorFlow Authors.
A API Functional
Configuração
Introdução
A API funcional do Keras (Functional) é uma maneira de criar modelos mais flexíveis do que a API tf.keras.Sequential
. A API funcional pode lidar com modelos que possuem topologia não linear, camadas compartilhadas e até mesmo múltiplas entradas ou saídas.
A ideia principal é que um modelo de aprendizado profundo geralmente é um grafo acíclico direcionado (Directed Acyclic Graph - DAG) de camadas. Portanto, a API funcional é uma forma de construir grafos de camadas.
Considere o seguinte modelo:
Este é um grafo básico com três camadas. Para construir esse modelo usando a API funcional, comece criando um nó de entrada:
O formato (shape) dos dados é definido como um vetor de 784 dimensões. O tamanho do lote é sempre omitido, pois apenas o formato de cada amostra é especificado.
Se, por exemplo, você tiver uma entrada de imagem com formato (32, 32, 3)
, você usaria:
O inputs
retornado contêm informações sobre o formato e o dtype
dos dados de entrada que você usa para alimentar seu modelo. Aqui está o formato:
Aqui está o dtype:
Você cria um novo nó no grafo de camadas chamando uma camada neste objeto inputs
:
A ação "chamada da camada" é como desenhar uma seta de "entradas" para esta camada que você criou. Você está "passando" as entradas para a camada dense
e obtenho x
como saída.
Vamos adicionar mais algumas camadas ao grafo de camadas:
Neste ponto, você pode criar um Model
especificando suas entradas e saídas no grafo de camadas:
Vamos verificar como fica o resumo do modelo (model summary):
Você também pode plotar o modelo como um gráfico:
E, opcionalmente, exibir os formatos de entrada e saída de cada camada no gráfico plotado:
Esta figura e o código são quase idênticos. Na versão código, as setas de conexão são substituídas pela operação de chamada.
Um "grafo de camadas" é uma imagem mental intuitiva para um modelo de aprendizado profundo, e a API funcional é uma maneira de criar modelos que espelham isso de forma próxima.
Treinamento, avaliação e inferência
Treinamento, avaliação e inferência funcionam exatamente da mesma forma para modelos construídos usando a API funcional e para modelos Sequential
.
A classe Model
oferece um loop de treinamento integrado (o método fit()
) e um loop de avaliação integrado (o método evaluate()
). Observe que você pode personalizar facilmente esses loops para implementar rotinas de treinamento além do aprendizado supervisionado (por exemplo, GANs).
Aqui, carregue os dados da imagem MNIST, remodele-os como vetores, ajuste o modelo aos dados (enquanto monitora o desempenho em uma divisão de validação). Depois avalie o modelo sobre os dados de teste:
Para saber mais, consulte o guia treinamento e avaliação.
Salvamento e serialização
O salvamento do modelo e a serialização funcionam da mesma maneira para modelos criados usando a API funcional e para modelos Sequential
. A maneira padrão de salvar um modelo funcional é chamar model.save()
para salvar o modelo inteiro como um único arquivo. Posteriormente, você pode recriar o mesmo modelo a partir desse arquivo, mesmo que o código que criou o modelo não esteja mais disponível.
Este arquivo salvo inclui:
arquitetura do modelo
valores de peso do modelo (que foram aprendidos durante o treinamento)
configuração de treinamento do modelo, se houver (conforme passado para
compile
)otimizador e seu estado, se houver (para reiniciar o treinamento de onde você parou)
Para mais detalhes, leia o guia serialização e salvamento do modelo.
Use o mesmo grafo de camadas para definir múltiplos modelos
Na API funcional, os modelos são criados especificando suas entradas e saídas em um grafo de camadas. Isto significa que um único grafo de camadas pode ser usado para gerar vários modelos.
No exemplo abaixo, você usa a mesma pilha de camadas para instanciar dois modelos: um modelo encoder
que transforma entradas de imagem em vetores de 16 dimensões, e um modelo autoencoder
, de ponta a ponta, para treinamento.
Aqui, a arquitetura de decodificação é estritamente simétrica à arquitetura de codificação, de modo que o formato de saída é o mesmo que o formato de entrada (28, 28, 1)
.
O reverso de uma camada Conv2D
é uma camada Conv2DTranspose
e o reverso de uma camada MaxPooling2D
é uma camada UpSampling2D
.
Todos os modelos podem ser chamados, assim como as camadas
Você pode tratar qualquer modelo como se fosse uma camada invocando-o num Input
ou na saída de outra camada. Ao chamar um modelo, você não está apenas reutilizando a arquitetura do modelo, mas também seus pesos.
Para ver isto em ação, eis aqui uma alternativa ao exemplo do autoencoder que cria um modelo de encoder, um modelo de decoder e os encadeia em duas chamadas para obter o modelo de autoencoder:
Como você pode ver, o modelo pode ser aninhado: um modelo pode conter submodelos (já que um modelo é como uma camada). Um caso de uso comum para aninhamento de modelos é ensemble. Por exemplo, veja como agrupar um conjunto de modelos num único modelo que calcula a média de suas previsões:
Manipulação de topologias de grafos complexos
Modelos com múltiplas entradas e saídas
A API funcional facilita a manipulação de múltiplas entradas e saídas. Isto não pode ser feito com a API Sequential
.
Por exemplo, se você estiver construindo um sistema para classificar tíquetes de problemas de clientes por prioridade e encaminhá-los para o departamento correto, o modelo terá três entradas:
o título do ticket (entrada de texto),
o corpo do texto do ticket (entrada de texto) e
quaisquer tags adicionadas pelo usuário (entrada categórica)
Este modelo terá duas saídas:
a pontuação de prioridade entre 0 e 1 (saída sigmóide escalar) e
o departamento que irá lidar com o ticket (saída softmax sobre o conjunto de departamentos).
Você pode construir este modelo em poucas linhas com a API funcional:
Agora desenhe o modelo:
Ao compilar este modelo, você pode atribuir diferentes perdas a cada saída. Você pode até atribuir pesos diferentes para cada perda, para modular sua contribuição para a perda total de treinamento.
Já que as camadas de saída têm nomes diferentes, você também pode especificar as perdas e os pesos das perdas com os nomes das camadas correspondentes:
Treine o modelo passando listas de matrizes NumPy de entradas e alvos:
Ao chamar fit com um objeto Dataset
, ele deve ou produzir uma tupla de listas como ([title_data, body_data, tags_data], [priority_targets, dept_targets])
ou uma tupla de dicionários como ({'title': title_data, 'body': body_data, 'tags': tags_data}, {'priority': priority_targets, 'department': dept_targets})
Para uma explicação mais detalhada, consulte o guia de treinamento e avaliação .
Um modelo ResNet de brinquedo
Além de modelos com múltiplas entradas e saídas, a API funcional facilita a manipulação de topologias de conectividade não lineares: são modelos com camadas que não são conectadas sequencialmente, e que a API Sequential
não consegue processar.
Um caso de uso comum são as conexões residuais. Para demonstrar isso, vamos construir um modelo ResNet de brinquedo para CIFAR10:
Plote o modelo:
Agora treine o modelo:
Camadas compartilhadas
Outro bom uso para a API funcional são os modelos que usam camadas compartilhadas. Camadas compartilhadas são instâncias de camada que são reutilizadas várias vezes no mesmo modelo: elas aprendem recursos que correspondem a múltiplos caminhos no grafo de camadas.
Camadas compartilhadas geralmente são usadas para codificar entradas de espaços semelhantes (digamos, duas partes diferentes de texto que apresentam vocabulário semelhante). Elas permitem o compartilhamento de informações entre essas diferentes entradas e possibilitam treinar esse modelo com menos dados. Se uma determinada palavra for vista em uma das entradas, isto beneficiará o processamento de todas as entradas que passam pela camada compartilhada.
Para compartilhar uma camada na API funcional, chame a mesma instância de camada várias vezes. Por exemplo, aqui está uma camada Embedding
compartilhada em duas entradas de texto diferentes:
Extraia e reutilize nós no gráfico de camadas
Já que o gráfico de camadas que você está manipulando é uma estrutura de dados estática, ela pode ser acessada e inspecionada. E é assim que você pode plotar modelos funcionais como imagens.
Isto também significa que você pode acessar as ativações de camadas intermediárias (os "nós" do gráfico) e reutilizá-las em outros lugares; Isto é muito útil para fazer coisas como extração de recursos.
Vejamos um exemplo. Este é um modelo VGG19 com pesos pré-treinados no ImageNet:
E essas são as ativações intermediárias do modelo, obtidas consultando a estrutura de dados do grafo:
Use esses recursos para criar um novo modelo de extração de recursos que retorne os valores das ativações da camada intermediária:
Isto é útil para tarefas como transferência de estilo neural, entre outras coisas.
Estenda a API usando camadas personalizadas
O tf.keras
inclui uma ampla gama de camadas internas, por exemplo:
Camadas convolucionais:
Conv1D
,Conv2D
,Conv3D
,Conv2DTranspose
Camadas de pooling:
MaxPooling1D
,MaxPooling2D
,MaxPooling3D
,AveragePooling1D
Camadas RNN:
GRU
,LSTM
,ConvLSTM2D
BatchNormalization
,Dropout
,Embedding
, etc.
Mas se você não encontrar o que precisa, é fácil estender a API criando suas próprias camadas. Todas as camadas são subclasses da classe Layer
e implementam:
um método
call
, que especifica a computação realizada pela camada.um método
build
, que cria os pesos da camada (esta é apenas uma convenção de estilo, pois você também pode criar pesos em__init__
).
Para saber mais sobre como criar camadas do zero, leia o guia camadas e modelos personalizados.
Veja a seguir uma implementação básica de tf.keras.layers.Dense
:
Para suporte à serialização em sua camada personalizada, defina um método get_config
que retorne os argumentos do construtor da instância da camada:
Opcionalmente, implemente o método de classe from_config(cls, config)
que é usado ao recriar uma instância de camada, dado seu dicionário de configuração. A implementação padrão de from_config
é:
Quando usar a API funcional
Você deve usar a API funcional Keras para criar um novo modelo ou apenas criar uma subclasse de Model
diretamente? Em geral, a API funcional é de nível superior, mais fácil e segura e possui vários recursos que os modelos de baseados em subclasse não suportam.
No entanto, criar uma subclasse do modelo garante maior flexibilidade ao construir modelos que não são facilmente expressos como grafos acíclicos direcionados de camadas. Por exemplo, você não poderia implementar uma RNN de Árvore com a API funcional e teria que usar uma subclasse de Model
diretamente.
Para uma análise aprofundada das diferenças entre a API funcional e o uso de subclasses de modelos, leia O que são as APIs simbólicas e imperativas no TensorFlow 2.0?.
Pontos fortes da API funcional:
As propriedades a seguir também são verdadeiras para modelos sequenciais (que também são estruturas de dados), mas não são verdadeiras para modelos implementados como subclasses (que são bytecode Python, não estruturas de dados).
Menos verbosidade
Não há super(MyClass, self).__init__(...)
, nenhuma def call(self, ...):
, etc.
Compare:
Com a versão usando uma subclasse:
Validação do modelo ao definir seu grafo de conectividade
Na API funcional, a especificação de entrada (shape e dtype) é criada antecipadamente (usando Input
). Toda vez que você chama uma camada, a camada verifica se a especificação passada a ela corresponde às suas suposições e, caso contrário, emitirá uma mensagem de erro útil.
Isso garante que qualquer modelo que você possa criar com a API funcional será executado. Toda a depuração, exceto depuração relacionada à convergência, ocorre estaticamente durante a construção do modelo e não no tempo de execução. Isto é semelhante ao processo de verificação de tipos num compilador.
Um modelo funcional é plotável e inspecionável
Você pode plotar o modelo como um gráfico e acessar facilmente os nós intermediários neste gráfico. Por exemplo, para extrair e reutilizar as ativações de camadas intermediárias (como visto no exemplo anterior):
Um modelo funcional pode ser serializado ou clonado
Como um modelo funcional é uma estrutura de dados em vez de um pedaço de código, ele pode ser serializado com segurança e pode ser salvo como um único arquivo que permite recriar exatamente o mesmo modelo sem ter acesso a nenhum código original. Consulte o guia de serialização e salvamento.
Para serializar um modelo implementado como subclasse, é necessário que o implementador especifique um método get_config()
e from_config()
ao nível do modelo.
Ponto fraco da API funcional:
Não suporta arquiteturas dinâmicas
A API funcional trata modelos como DAGs de camadas. Isso é verdade para a maioria das arquiteturas de aprendizado profundo, mas não para todas: por exemplo, redes recursivas ou RNNs de Árvore não seguem essa suposição e não podem ser implementadas com a API funcional.
Misture e combine estilos de API
Escolher entre a API funcional ou subclasse de Model não é uma decisão binária que vai limitá-lo a uma categoria de modelos. Todos os modelos da API tf.keras
podem interagir uns com os outros, sejam eles modelos Sequential
, modelos funcionais ou modelos implementados como subclasse e que são escritos do zero.
Você sempre pode usar um modelo funcional ou modelo Sequential
como parte de uma camada ou modelo subclasse:
Você pode usar qualquer camada ou modelo implementado como subclasse na API funcional, desde que implemente um método call
que siga um dos seguintes padrões:
call(self, inputs, **kwargs)
-- Ondeinputs
é um tensor ou uma estrutura aninhada de tensores (por exemplo, uma lista de tensores), e onde**kwargs
são argumentos não tensores (não entradas).call(self, inputs, training=None, **kwargs)
-- Ondetraining
é um booleano indicando se a camada deve se comportar em modo de treinamento e em modo de inferência.call(self, inputs, mask=None, **kwargs)
-- Ondemask
é um tensor de máscara booleana (útil para RNNs, por exemplo).call(self, inputs, training=None, mask=None, **kwargs)
-- Claro, você pode ter comportamento específico de treinamento e mascaramento ao mesmo tempo.
Além disso, se você implementar o método get_config
em sua camada ou modelo personalizado, os modelos funcionais criados ainda serão serializáveis e clonáveis.
Eis aqui um exemplo rápido de um RNN personalizado, escrito do zero, sendo usado em um modelo funcional: