![]() | ![]() | ![]() | ![]() |
Configurar
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
Introdução
A API funcional Keras é uma maneira de criar modelos que são mais flexíveis do que o tf.keras.Sequential
API. A API funcional pode manipular modelos com topologia não linear, camadas compartilhadas e até mesmo várias entradas ou saídas.
A ideia principal é que um modelo de aprendizado profundo geralmente é um gráfico acíclico direcionado (DAG) de camadas. Portanto, a API funcional é uma maneira de construir gráficos 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 gráfico básico com três camadas. Para construir este modelo usando a API funcional, comece criando um nó de entrada:
inputs = keras.Input(shape=(784,))
A forma dos dados é definida como um vetor de 784 dimensões. O tamanho do lote é sempre omitido, pois apenas a forma de cada amostra é especificada.
Se, por exemplo, você tem uma entrada de imagem com uma forma de (32, 32, 3)
, você usaria:
# Just for demonstration purposes.
img_inputs = keras.Input(shape=(32, 32, 3))
As inputs
que é retornado contém informações sobre a forma e dtype
dos dados de entrada que você alimenta a seu modelo. Aqui está a forma:
inputs.shape
TensorShape([None, 784])
Aqui está o dtype:
inputs.dtype
tf.float32
Você cria um novo nó no gráfico de camadas chamando uma camada sobre este inputs
objeto:
dense = layers.Dense(64, activation="relu")
x = dense(inputs)
A ação de "chamada de camada" é como desenhar uma seta de "entradas" para essa camada que você criou. Você está "passando" as entradas para a dense
camada, e você terá x
como a saída.
Vamos adicionar mais algumas camadas ao gráfico 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 gráfico de camadas:
model = keras.Model(inputs=inputs, outputs=outputs, name="mnist_model")
Vamos verificar como é o resumo do modelo:
model.summary()
Model: "mnist_model" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_1 (InputLayer) [(None, 784)] 0 _________________________________________________________________ dense (Dense) (None, 64) 50240 _________________________________________________________________ dense_1 (Dense) (None, 64) 4160 _________________________________________________________________ dense_2 (Dense) (None, 10) 650 ================================================================= Total params: 55,050 Trainable params: 55,050 Non-trainable params: 0 _________________________________________________________________
Você também pode plotar o modelo como um gráfico:
keras.utils.plot_model(model, "my_first_model.png")
E, opcionalmente, exiba as formas de entrada e saída de cada camada no gráfico traçado:
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 do código, as setas de conexão são substituídas pela operação de chamada.
Um "gráfico 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 perto.
Treinamento, avaliação e inferência
Formação, avaliação e trabalho inferência exatamente da mesma forma para os modelos construídos utilizando a API funcional como para Sequential
modelos.
Os Model
ofertas de classe um built-in circuito de treinamento (o fit()
método) e um built-in circuito de avaliação (a evaluate()
método). Nota que você pode facilmente personalizar estes laços para implementar rotinas de treinamento além do aprendizado supervisionado (por exemplo GAN ).
Aqui, carregue os dados da imagem MNIST, reformule-os em vetores, ajuste o modelo nos dados (enquanto monitora o desempenho em uma divisão de validação) e avalie o modelo nos 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])
Epoch 1/2 750/750 [==============================] - 3s 3ms/step - loss: 0.3430 - accuracy: 0.9035 - val_loss: 0.1851 - val_accuracy: 0.9463 Epoch 2/2 750/750 [==============================] - 2s 3ms/step - loss: 0.1585 - accuracy: 0.9527 - val_loss: 0.1366 - val_accuracy: 0.9597 313/313 - 0s - loss: 0.1341 - accuracy: 0.9592 Test loss: 0.13414572179317474 Test accuracy: 0.9592000246047974
Para ler mais, veja a formação e avaliação guia.
Salvar e serializar
Salvar o modelo e serialização de trabalho da mesma forma para os modelos construídos utilizando a API funcional como eles fazem para Sequential
modelos. A forma padrão para 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 se o código que construiu o modelo não estiver mais disponível.
Este arquivo salvo inclui:
- arquitetura modelo
- valores de peso do modelo (que foram aprendidos durante o treinamento)
- modelo de configuração de treinamento, se for o caso (como passou a
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")
2021-08-25 17:50:55.989736: W tensorflow/python/util/util.cc:348] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them. INFO:tensorflow:Assets written to: path_to_my_model/assets
Para mais detalhes, leia o modelo de serialização e salvando guia.
Use o mesmo gráfico de camadas para definir vários modelos
Na API funcional, os modelos são criados especificando suas entradas e saídas em um gráfico de camadas. Isso significa que um único gráfico de camadas pode ser usado para gerar vários modelos.
No exemplo abaixo, você usar a mesma pilha de camadas para instanciar dois modelos: um encoder
modelo que entradas imagem se transforma em vetores 16-dimensionais, e um fim-de-final autoencoder
modelo 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()
Model: "encoder" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= img (InputLayer) [(None, 28, 28, 1)] 0 _________________________________________________________________ conv2d (Conv2D) (None, 26, 26, 16) 160 _________________________________________________________________ conv2d_1 (Conv2D) (None, 24, 24, 32) 4640 _________________________________________________________________ max_pooling2d (MaxPooling2D) (None, 8, 8, 32) 0 _________________________________________________________________ conv2d_2 (Conv2D) (None, 6, 6, 32) 9248 _________________________________________________________________ conv2d_3 (Conv2D) (None, 4, 4, 16) 4624 _________________________________________________________________ global_max_pooling2d (Global (None, 16) 0 ================================================================= Total params: 18,672 Trainable params: 18,672 Non-trainable params: 0 _________________________________________________________________ Model: "autoencoder" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= img (InputLayer) [(None, 28, 28, 1)] 0 _________________________________________________________________ conv2d (Conv2D) (None, 26, 26, 16) 160 _________________________________________________________________ conv2d_1 (Conv2D) (None, 24, 24, 32) 4640 _________________________________________________________________ max_pooling2d (MaxPooling2D) (None, 8, 8, 32) 0 _________________________________________________________________ conv2d_2 (Conv2D) (None, 6, 6, 32) 9248 _________________________________________________________________ conv2d_3 (Conv2D) (None, 4, 4, 16) 4624 _________________________________________________________________ global_max_pooling2d (Global (None, 16) 0 _________________________________________________________________ reshape (Reshape) (None, 4, 4, 1) 0 _________________________________________________________________ conv2d_transpose (Conv2DTran (None, 6, 6, 16) 160 _________________________________________________________________ conv2d_transpose_1 (Conv2DTr (None, 8, 8, 32) 4640 _________________________________________________________________ up_sampling2d (UpSampling2D) (None, 24, 24, 32) 0 _________________________________________________________________ conv2d_transpose_2 (Conv2DTr (None, 26, 26, 16) 4624 _________________________________________________________________ conv2d_transpose_3 (Conv2DTr (None, 28, 28, 1) 145 ================================================================= Total params: 28,241 Trainable params: 28,241 Non-trainable params: 0 _________________________________________________________________
Aqui, a arquitectura de descodificação é estritamente simétrica para a arquitectura de codificação, de modo que a forma de saída é a mesma que a forma de entrada (28, 28, 1)
.
O inverso de um Conv2D
camada é um Conv2DTranspose
camada, e o inverso de uma MaxPooling2D
camada é uma UpSampling2D
camada.
Todos os modelos podem ser chamados, assim como as camadas
Você pode tratar qualquer modelo como se fosse uma camada invocando-o em uma Input
ou na saída de uma outra camada. Ao chamar um modelo, você não está apenas reutilizando a arquitetura do modelo, você também está reutilizando seus pesos.
Para ver isso em ação, aqui está uma visão diferente do exemplo do autoencoder que cria um modelo de codificador, um modelo de decodificador e os encadeia em duas chamadas para obter o modelo de codificador automático:
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()
Model: "encoder" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= original_img (InputLayer) [(None, 28, 28, 1)] 0 _________________________________________________________________ conv2d_4 (Conv2D) (None, 26, 26, 16) 160 _________________________________________________________________ conv2d_5 (Conv2D) (None, 24, 24, 32) 4640 _________________________________________________________________ max_pooling2d_1 (MaxPooling2 (None, 8, 8, 32) 0 _________________________________________________________________ conv2d_6 (Conv2D) (None, 6, 6, 32) 9248 _________________________________________________________________ conv2d_7 (Conv2D) (None, 4, 4, 16) 4624 _________________________________________________________________ global_max_pooling2d_1 (Glob (None, 16) 0 ================================================================= Total params: 18,672 Trainable params: 18,672 Non-trainable params: 0 _________________________________________________________________ Model: "decoder" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= encoded_img (InputLayer) [(None, 16)] 0 _________________________________________________________________ reshape_1 (Reshape) (None, 4, 4, 1) 0 _________________________________________________________________ conv2d_transpose_4 (Conv2DTr (None, 6, 6, 16) 160 _________________________________________________________________ conv2d_transpose_5 (Conv2DTr (None, 8, 8, 32) 4640 _________________________________________________________________ up_sampling2d_1 (UpSampling2 (None, 24, 24, 32) 0 _________________________________________________________________ conv2d_transpose_6 (Conv2DTr (None, 26, 26, 16) 4624 _________________________________________________________________ conv2d_transpose_7 (Conv2DTr (None, 28, 28, 1) 145 ================================================================= Total params: 9,569 Trainable params: 9,569 Non-trainable params: 0 _________________________________________________________________ Model: "autoencoder" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= img (InputLayer) [(None, 28, 28, 1)] 0 _________________________________________________________________ encoder (Functional) (None, 16) 18672 _________________________________________________________________ decoder (Functional) (None, 28, 28, 1) 9569 ================================================================= Total params: 28,241 Trainable params: 28,241 Non-trainable params: 0 _________________________________________________________________
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 o modelo de assentamento é ensembling. Por exemplo, veja como agrupar um conjunto de modelos em um ú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)
Manipular topologias gráficas complexas
Modelos com várias entradas e saídas
A API funcional facilita a manipulação de várias entradas e saídas. Isso não pode ser tratado com o Sequential
API.
Por exemplo, se você estiver criando um sistema para classificar os tickets de problemas do cliente 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 de 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 deve 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 trace o modelo:
keras.utils.plot_model(model, "multi_input_and_output_model.png", show_shapes=True)
Ao compilar este modelo, você pode atribuir perdas diferentes 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],
)
Uma vez que as camadas de saída têm nomes diferentes, você também pode especificar as perdas e pesos de perda 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,
)
Epoch 1/2 40/40 [==============================] - 5s 9ms/step - loss: 1.2899 - priority_loss: 0.7186 - department_loss: 2.8564 Epoch 2/2 40/40 [==============================] - 0s 9ms/step - loss: 1.2668 - priority_loss: 0.6991 - department_loss: 2.8389 <keras.callbacks.History at 0x7fc1a66dc790>
Ao chamar ajuste com um Dataset
objeto, que deve produzir qualquer um tuplo de listas como ([title_data, body_data, tags_data], [priority_targets, dept_targets])
ou um tuplo 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 a formação e avaliação guia.
Um modelo de brinquedo ResNet
Além dos modelos com várias entradas e saídas, a API funcional faz com que seja fácil de manipular topologias de conectividade não lineares - estes são modelos com camadas que não são ligados sequencialmente, o que Sequential
API não pode manipular.
Um caso de uso comum para isso são as conexões residuais. Vamos construir um modelo de ResNet de brinquedo para CIFAR10 para demonstrar isso:
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()
Model: "toy_resnet" __________________________________________________________________________________________________ Layer (type) Output Shape Param # Connected to ================================================================================================== img (InputLayer) [(None, 32, 32, 3)] 0 __________________________________________________________________________________________________ conv2d_8 (Conv2D) (None, 30, 30, 32) 896 img[0][0] __________________________________________________________________________________________________ conv2d_9 (Conv2D) (None, 28, 28, 64) 18496 conv2d_8[0][0] __________________________________________________________________________________________________ max_pooling2d_2 (MaxPooling2D) (None, 9, 9, 64) 0 conv2d_9[0][0] __________________________________________________________________________________________________ conv2d_10 (Conv2D) (None, 9, 9, 64) 36928 max_pooling2d_2[0][0] __________________________________________________________________________________________________ conv2d_11 (Conv2D) (None, 9, 9, 64) 36928 conv2d_10[0][0] __________________________________________________________________________________________________ add (Add) (None, 9, 9, 64) 0 conv2d_11[0][0] max_pooling2d_2[0][0] __________________________________________________________________________________________________ conv2d_12 (Conv2D) (None, 9, 9, 64) 36928 add[0][0] __________________________________________________________________________________________________ conv2d_13 (Conv2D) (None, 9, 9, 64) 36928 conv2d_12[0][0] __________________________________________________________________________________________________ add_1 (Add) (None, 9, 9, 64) 0 conv2d_13[0][0] add[0][0] __________________________________________________________________________________________________ conv2d_14 (Conv2D) (None, 7, 7, 64) 36928 add_1[0][0] __________________________________________________________________________________________________ global_average_pooling2d (Globa (None, 64) 0 conv2d_14[0][0] __________________________________________________________________________________________________ dense_6 (Dense) (None, 256) 16640 global_average_pooling2d[0][0] __________________________________________________________________________________________________ dropout (Dropout) (None, 256) 0 dense_6[0][0] __________________________________________________________________________________________________ dense_7 (Dense) (None, 10) 2570 dropout[0][0] ================================================================================================== Total params: 223,242 Trainable params: 223,242 Non-trainable params: 0 __________________________________________________________________________________________________
Trace 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)
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz 170500096/170498071 [==============================] - 11s 0us/step 170508288/170498071 [==============================] - 11s 0us/step 13/13 [==============================] - 2s 29ms/step - loss: 2.3364 - acc: 0.1063 - val_loss: 2.2986 - val_acc: 0.0850 <keras.callbacks.History at 0x7fc19df22610>
Camadas compartilhadas
Outro bom uso para a API funcional são 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 vários caminhos no gráfico de camadas.
Camadas compartilhadas são frequentemente usadas para codificar entradas de espaços semelhantes (digamos, duas partes de texto diferentes que apresentam vocabulário semelhante). Eles permitem o compartilhamento de informações entre essas diferentes entradas e tornam possível treinar esse modelo com menos dados. Se uma determinada palavra for vista em uma das entradas, isso 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á um Embedding
camada compartilhada entre 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
Como o gráfico de camadas que você está manipulando é uma estrutura de dados estática, ele pode ser acessado e inspecionado. E é assim que você pode plotar modelos funcionais como imagens.
Isso também significa que você pode acessar as ativações de camadas intermediárias ("nós" no gráfico) e reutilizá-las em outro lugar - o que é muito útil para algo como extração de recursos.
Vejamos um exemplo. Este é um modelo VGG19 com pesos pré-treinados no ImageNet:
vgg19 = tf.keras.applications.VGG19()
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/vgg19/vgg19_weights_tf_dim_ordering_tf_kernels.h5 574717952/574710816 [==============================] - 15s 0us/step 574726144/574710816 [==============================] - 15s 0us/step
E essas são as ativações intermediárias do modelo, obtidas através da consulta à estrutura de dados do gráfico:
features_list = [layer.output for layer in vgg19.layers]
Use esses recursos para criar um novo modelo de extração de recursos que retorna 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 vem a calhar para tarefas como transferência de estilo neural , entre outras coisas.
Estenda a API usando camadas personalizadas
tf.keras
inclui uma vasta gama de incorporada em camadas, por exemplo:
- Camadas convolucionais:
Conv1D
,Conv2D
,Conv3D
,Conv2DTranspose
- Camadas agrupamento:
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 subclasse o Layer
classe e implementar:
-
call
método, que especifica o cálculo feito pela camada. -
build
método, que cria os pesos da camada (isto é apenas uma convenção estilo desde que você pode criar pesos em__init__
, também).
Para saber mais sobre a criação de camadas a partir do zero, leia camadas personalizados e modelos de guia.
O seguinte é 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 a serialização em sua camada de costume, definir um get_config
método que retorna os argumentos do construtor da instância 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, aplicar o método de classe from_config(cls, config)
que é usado quando recriando uma camada exemplo, dada a sua configuração dicionário. A implementação padrão de from_config
é:
def from_config(cls, config):
return cls(**config)
Quando usar a API funcional
Se você usar a API funcional Keras para criar um novo modelo, ou apenas subclasse o Model
classe diretamente? Em geral, a API funcional é de nível superior, mais fácil e segura, e possui vários recursos que os modelos de subclasse não suportam.
No entanto, a subclasse de modelo fornece maior flexibilidade ao construir modelos que não são facilmente expressáveis como gráficos acíclicos direcionados de camadas. Por exemplo, você não pode implementar uma árvore-RNN com a API funcional e teria a subclasse Model
diretamente.
Para um olhar em profundidade as diferenças entre a API funcional e modelo de subclasses, leia Quais são APIs simbólicos e imperativa em 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 de subclasse (que são bytecode Python, não estruturas de dados).
Menos verboso
Não há super(MyClass, self).__init__(...)
, não def call(self, ...):
, etc.
Comparar:
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 subclassificada:
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 gráfico de conectividade
Na API funcional, a especificação de entrada (forma e dtipo) é criado previamente (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, gerará uma mensagem de erro útil.
Isso garante que qualquer modelo que você possa construir com a API funcional será executado. Toda a depuração -- exceto a depuração relacionada à convergência -- acontece estaticamente durante a construção do modelo e não no tempo de execução. Isso é semelhante à verificação de tipo em um 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 nesse gráfico. Por exemplo, para extrair e reutilizar as ativações de camadas intermediárias (como visto em um 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 e não um pedaço de código, ele é serializável 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. Veja a serialização e salvando guia .
Para serializar um modelo subclasse, é necessário que o implementador para especificar um get_config()
e from_config()
método no nível do modelo.
Fraqueza da API funcional:
Não suporta arquiteturas dinâmicas
A API funcional trata os 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 na API funcional.
Misturar e combinar estilos de API
Escolher entre a API funcional ou a subclasse de modelo não é uma decisão binária que restringe você a uma categoria de modelos. Todos os modelos da tf.keras
API podem interagir uns com os outros, se eles são Sequential
modelos, modelos funcionais, ou modelos subclasse que são escritos a partir do zero.
Você sempre pode usar um modelo funcional ou Sequential
modelo como parte de um modelo subclassed ou camada:
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)))
(1, 10, 32)
Você pode usar qualquer camada subclasse ou modelo na API funcional, desde que implementa uma call
método que segue um dos seguintes padrões:
-
call(self, inputs, **kwargs)
- Ondeinputs
é um tensor ou uma estrutura aninhada dos 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 que indica se a camada deve comportar-se de modo de treinamento e modo de inferência. -
call(self, inputs, mask=None, **kwargs)
- Ondemask
é um tensor máscara booleano (útil para RNNs, por exemplo). -
call(self, inputs, training=None, mask=None, **kwargs)
- Claro, você pode ter os dois mascaramento e comportamento específico de formação, ao mesmo tempo.
Além disso, se você implementar o get_config
método em sua camada de costume ou modelo, os modelos funcionais que criar ainda será serializado e cloneable.
Aqui está 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)))