Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/pt-br/guide/keras/functional.ipynb
25118 views
Kernel: Python 3
#@title Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.

A API Functional

Configuração

import numpy as np import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers

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:

(input: 784-dimensional vectors) [Dense (64 units, relu activation)] [Dense (64 units, relu activation)] [Dense (10 units, softmax activation)] (output: logits of a probability distribution over 10 classes)

Este é um grafo básico com três camadas. Para construir esse modelo usando a API funcional, comece criando um nó de entrada:

inputs = keras.Input(shape=(784,))

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:

# Just for demonstration purposes. img_inputs = keras.Input(shape=(32, 32, 3))

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:

inputs.shape

Aqui está o dtype:

inputs.dtype

Você cria um novo nó no grafo de camadas chamando uma camada neste objeto inputs:

dense = layers.Dense(64, activation="relu") x = dense(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:

x = layers.Dense(64, activation="relu")(x) outputs = layers.Dense(10)(x)

Neste ponto, você pode criar um Model especificando suas entradas e saídas no grafo de camadas:

model = keras.Model(inputs=inputs, outputs=outputs, name="mnist_model")

Vamos verificar como fica o resumo do modelo (model summary):

model.summary()

Você também pode plotar o modelo como um gráfico:

keras.utils.plot_model(model, "my_first_model.png")

E, opcionalmente, exibir os formatos de entrada e saída de cada camada no gráfico plotado:

keras.utils.plot_model(model, "my_first_model_with_shape_info.png", show_shapes=True)

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:

(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data() x_train = x_train.reshape(60000, 784).astype("float32") / 255 x_test = x_test.reshape(10000, 784).astype("float32") / 255 model.compile( loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True), optimizer=keras.optimizers.RMSprop(), metrics=["accuracy"], ) history = model.fit(x_train, y_train, batch_size=64, epochs=2, validation_split=0.2) test_scores = model.evaluate(x_test, y_test, verbose=2) print("Test loss:", test_scores[0]) print("Test accuracy:", test_scores[1])

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)

model.save("path_to_my_model") del model # Recreate the exact same model purely from the file: model = keras.models.load_model("path_to_my_model")

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.

encoder_input = keras.Input(shape=(28, 28, 1), name="img") x = layers.Conv2D(16, 3, activation="relu")(encoder_input) x = layers.Conv2D(32, 3, activation="relu")(x) x = layers.MaxPooling2D(3)(x) x = layers.Conv2D(32, 3, activation="relu")(x) x = layers.Conv2D(16, 3, activation="relu")(x) encoder_output = layers.GlobalMaxPooling2D()(x) encoder = keras.Model(encoder_input, encoder_output, name="encoder") encoder.summary() x = layers.Reshape((4, 4, 1))(encoder_output) x = layers.Conv2DTranspose(16, 3, activation="relu")(x) x = layers.Conv2DTranspose(32, 3, activation="relu")(x) x = layers.UpSampling2D(3)(x) x = layers.Conv2DTranspose(16, 3, activation="relu")(x) decoder_output = layers.Conv2DTranspose(1, 3, activation="relu")(x) autoencoder = keras.Model(encoder_input, decoder_output, name="autoencoder") autoencoder.summary()

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:

encoder_input = keras.Input(shape=(28, 28, 1), name="original_img") x = layers.Conv2D(16, 3, activation="relu")(encoder_input) x = layers.Conv2D(32, 3, activation="relu")(x) x = layers.MaxPooling2D(3)(x) x = layers.Conv2D(32, 3, activation="relu")(x) x = layers.Conv2D(16, 3, activation="relu")(x) encoder_output = layers.GlobalMaxPooling2D()(x) encoder = keras.Model(encoder_input, encoder_output, name="encoder") encoder.summary() decoder_input = keras.Input(shape=(16,), name="encoded_img") x = layers.Reshape((4, 4, 1))(decoder_input) x = layers.Conv2DTranspose(16, 3, activation="relu")(x) x = layers.Conv2DTranspose(32, 3, activation="relu")(x) x = layers.UpSampling2D(3)(x) x = layers.Conv2DTranspose(16, 3, activation="relu")(x) decoder_output = layers.Conv2DTranspose(1, 3, activation="relu")(x) decoder = keras.Model(decoder_input, decoder_output, name="decoder") decoder.summary() autoencoder_input = keras.Input(shape=(28, 28, 1), name="img") encoded_img = encoder(autoencoder_input) decoded_img = decoder(encoded_img) autoencoder = keras.Model(autoencoder_input, decoded_img, name="autoencoder") autoencoder.summary()

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:

def get_model(): inputs = keras.Input(shape=(128,)) outputs = layers.Dense(1)(inputs) return keras.Model(inputs, outputs) model1 = get_model() model2 = get_model() model3 = get_model() inputs = keras.Input(shape=(128,)) y1 = model1(inputs) y2 = model2(inputs) y3 = model3(inputs) outputs = layers.average([y1, y2, y3]) ensemble_model = keras.Model(inputs=inputs, outputs=outputs)

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:

num_tags = 12 # Number of unique issue tags num_words = 10000 # Size of vocabulary obtained when preprocessing text data num_departments = 4 # Number of departments for predictions title_input = keras.Input( shape=(None,), name="title" ) # Variable-length sequence of ints body_input = keras.Input(shape=(None,), name="body") # Variable-length sequence of ints tags_input = keras.Input( shape=(num_tags,), name="tags" ) # Binary vectors of size `num_tags` # Embed each word in the title into a 64-dimensional vector title_features = layers.Embedding(num_words, 64)(title_input) # Embed each word in the text into a 64-dimensional vector body_features = layers.Embedding(num_words, 64)(body_input) # Reduce sequence of embedded words in the title into a single 128-dimensional vector title_features = layers.LSTM(128)(title_features) # Reduce sequence of embedded words in the body into a single 32-dimensional vector body_features = layers.LSTM(32)(body_features) # Merge all available features into a single large vector via concatenation x = layers.concatenate([title_features, body_features, tags_input]) # Stick a logistic regression for priority prediction on top of the features priority_pred = layers.Dense(1, name="priority")(x) # Stick a department classifier on top of the features department_pred = layers.Dense(num_departments, name="department")(x) # Instantiate an end-to-end model predicting both priority and department model = keras.Model( inputs=[title_input, body_input, tags_input], outputs=[priority_pred, department_pred], )

Agora desenhe o modelo:

keras.utils.plot_model(model, "multi_input_and_output_model.png", show_shapes=True)

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.

model.compile( optimizer=keras.optimizers.RMSprop(1e-3), loss=[ keras.losses.BinaryCrossentropy(from_logits=True), keras.losses.CategoricalCrossentropy(from_logits=True), ], loss_weights=[1.0, 0.2], )

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:

model.compile( optimizer=keras.optimizers.RMSprop(1e-3), loss={ "priority": keras.losses.BinaryCrossentropy(from_logits=True), "department": keras.losses.CategoricalCrossentropy(from_logits=True), }, loss_weights={"priority": 1.0, "department": 0.2}, )

Treine o modelo passando listas de matrizes NumPy de entradas e alvos:

# Dummy input data title_data = np.random.randint(num_words, size=(1280, 10)) body_data = np.random.randint(num_words, size=(1280, 100)) tags_data = np.random.randint(2, size=(1280, num_tags)).astype("float32") # Dummy target data priority_targets = np.random.random(size=(1280, 1)) dept_targets = np.random.randint(2, size=(1280, num_departments)) model.fit( {"title": title_data, "body": body_data, "tags": tags_data}, {"priority": priority_targets, "department": dept_targets}, epochs=2, batch_size=32, )

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:

inputs = keras.Input(shape=(32, 32, 3), name="img") x = layers.Conv2D(32, 3, activation="relu")(inputs) x = layers.Conv2D(64, 3, activation="relu")(x) block_1_output = layers.MaxPooling2D(3)(x) x = layers.Conv2D(64, 3, activation="relu", padding="same")(block_1_output) x = layers.Conv2D(64, 3, activation="relu", padding="same")(x) block_2_output = layers.add([x, block_1_output]) x = layers.Conv2D(64, 3, activation="relu", padding="same")(block_2_output) x = layers.Conv2D(64, 3, activation="relu", padding="same")(x) block_3_output = layers.add([x, block_2_output]) x = layers.Conv2D(64, 3, activation="relu")(block_3_output) x = layers.GlobalAveragePooling2D()(x) x = layers.Dense(256, activation="relu")(x) x = layers.Dropout(0.5)(x) outputs = layers.Dense(10)(x) model = keras.Model(inputs, outputs, name="toy_resnet") model.summary()

Plote o modelo:

keras.utils.plot_model(model, "mini_resnet.png", show_shapes=True)

Agora treine o modelo:

(x_train, y_train), (x_test, y_test) = keras.datasets.cifar10.load_data() x_train = x_train.astype("float32") / 255.0 x_test = x_test.astype("float32") / 255.0 y_train = keras.utils.to_categorical(y_train, 10) y_test = keras.utils.to_categorical(y_test, 10) model.compile( optimizer=keras.optimizers.RMSprop(1e-3), loss=keras.losses.CategoricalCrossentropy(from_logits=True), metrics=["acc"], ) # We restrict the data to the first 1000 samples so as to limit execution time # on Colab. Try to train on the entire dataset until convergence! model.fit(x_train[:1000], y_train[:1000], batch_size=64, epochs=1, validation_split=0.2)

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:

# Embedding for 1000 unique words mapped to 128-dimensional vectors shared_embedding = layers.Embedding(1000, 128) # Variable-length sequence of integers text_input_a = keras.Input(shape=(None,), dtype="int32") # Variable-length sequence of integers text_input_b = keras.Input(shape=(None,), dtype="int32") # Reuse the same layer to encode both inputs encoded_input_a = shared_embedding(text_input_a) encoded_input_b = shared_embedding(text_input_b)

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:

vgg19 = tf.keras.applications.VGG19()

E essas são as ativações intermediárias do modelo, obtidas consultando a estrutura de dados do grafo:

features_list = [layer.output for layer in vgg19.layers]

Use esses recursos para criar um novo modelo de extração de recursos que retorne os valores das ativações da camada intermediária:

feat_extraction_model = keras.Model(inputs=vgg19.input, outputs=features_list) img = np.random.random((1, 224, 224, 3)).astype("float32") extracted_features = feat_extraction_model(img)

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:

class CustomDense(layers.Layer): def __init__(self, units=32): super(CustomDense, self).__init__() self.units = units def build(self, input_shape): self.w = self.add_weight( shape=(input_shape[-1], self.units), initializer="random_normal", trainable=True, ) self.b = self.add_weight( shape=(self.units,), initializer="random_normal", trainable=True ) def call(self, inputs): return tf.matmul(inputs, self.w) + self.b inputs = keras.Input((4,)) outputs = CustomDense(10)(inputs) model = keras.Model(inputs, outputs)

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:

class CustomDense(layers.Layer): def __init__(self, units=32): super(CustomDense, self).__init__() self.units = units def build(self, input_shape): self.w = self.add_weight( shape=(input_shape[-1], self.units), initializer="random_normal", trainable=True, ) self.b = self.add_weight( shape=(self.units,), initializer="random_normal", trainable=True ) def call(self, inputs): return tf.matmul(inputs, self.w) + self.b def get_config(self): return {"units": self.units} inputs = keras.Input((4,)) outputs = CustomDense(10)(inputs) model = keras.Model(inputs, outputs) config = model.get_config() new_model = keras.Model.from_config(config, custom_objects={"CustomDense": CustomDense})

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 é:

def from_config(cls, config): return cls(**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:

inputs = keras.Input(shape=(32,)) x = layers.Dense(64, activation='relu')(inputs) outputs = layers.Dense(10)(x) mlp = keras.Model(inputs, outputs)

Com a versão usando uma subclasse:

class MLP(keras.Model): def __init__(self, **kwargs): super(MLP, self).__init__(**kwargs) self.dense_1 = layers.Dense(64, activation='relu') self.dense_2 = layers.Dense(10) def call(self, inputs): x = self.dense_1(inputs) return self.dense_2(x) # Instantiate the model. mlp = MLP() # Necessary to create the model's state. # The model doesn't have a state until it's called at least once. _ = mlp(tf.zeros((1, 32)))

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):

features_list = [layer.output for layer in vgg19.layers] feat_extraction_model = keras.Model(inputs=vgg19.input, outputs=features_list)

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:

units = 32 timesteps = 10 input_dim = 5 # Define a Functional model inputs = keras.Input((None, units)) x = layers.GlobalAveragePooling1D()(inputs) outputs = layers.Dense(1)(x) model = keras.Model(inputs, outputs) class CustomRNN(layers.Layer): def __init__(self): super(CustomRNN, self).__init__() self.units = units self.projection_1 = layers.Dense(units=units, activation="tanh") self.projection_2 = layers.Dense(units=units, activation="tanh") # Our previously-defined Functional model self.classifier = model def call(self, inputs): outputs = [] state = tf.zeros(shape=(inputs.shape[0], self.units)) for t in range(inputs.shape[1]): x = inputs[:, t, :] h = self.projection_1(x) y = h + self.projection_2(state) state = y outputs.append(y) features = tf.stack(outputs, axis=1) print(features.shape) return self.classifier(features) rnn_model = CustomRNN() _ = rnn_model(tf.zeros((1, timesteps, input_dim)))

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) -- Onde inputs é 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) -- Onde training é 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) -- Onde mask é 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:

units = 32 timesteps = 10 input_dim = 5 batch_size = 16 class CustomRNN(layers.Layer): def __init__(self): super(CustomRNN, self).__init__() self.units = units self.projection_1 = layers.Dense(units=units, activation="tanh") self.projection_2 = layers.Dense(units=units, activation="tanh") self.classifier = layers.Dense(1) def call(self, inputs): outputs = [] state = tf.zeros(shape=(inputs.shape[0], self.units)) for t in range(inputs.shape[1]): x = inputs[:, t, :] h = self.projection_1(x) y = h + self.projection_2(state) state = y outputs.append(y) features = tf.stack(outputs, axis=1) return self.classifier(features) # Note that you specify a static batch size for the inputs with the `batch_shape` # arg, because the inner computation of `CustomRNN` requires a static batch size # (when you create the `state` zeros tensor). inputs = keras.Input(batch_shape=(batch_size, timesteps, input_dim)) x = layers.Conv1D(32, 3)(inputs) outputs = CustomRNN()(x) model = keras.Model(inputs, outputs) rnn_model = CustomRNN() _ = rnn_model(tf.zeros((1, 10, 5)))