API funcional Keras no TensorFlow



Keras possui duas APIs para criar rapidamente arquiteturas de rede neural Sequencial e Funcional. Se o primeiro permitir construir apenas arquiteturas seqüenciais de redes neurais, usando a API Funcional, você poderá definir uma rede neural na forma de um gráfico acíclico direcionado arbitrário, o que oferece muito mais oportunidades para a construção de modelos complexos. Este artigo é uma tradução do Functional API Feature Guide do site TensorFlow.

1. Introdução


A API Funcional permite criar modelos de forma mais flexível que a API Sequencial; ela pode processar modelos com topologia não linear, modelos com camadas comuns e modelos com várias entradas ou saídas.

É baseado no fato de que o modelo de aprendizado profundo é geralmente um gráfico acíclico direcionado (DAG) de camadas

API funcional é um conjunto de ferramentas para plotagem de camadas .

Considere o seguinte modelo:

(entrada: vetor 784-dimensional)

[Camada densa (64 elementos, ativação de relu)]

[Camada densa (64 elementos, ativação de relu)]

[Camada densa (10 elementos, ativação do softmax)]

(saída: distribuição de probabilidade acima de 10 classes)
Este é um gráfico simples de 3 camadas.

Para construir esse modelo usando a API Funcional, você precisa começar criando um nó de entrada:

from tensorflow import keras inputs = keras.Input(shape=(784,)) 

Aqui, simplesmente indicamos a dimensão dos nossos dados: vetores 784 dimensionais. Observe que a quantidade de dados é sempre omitida; indicamos apenas a dimensão de cada elemento. Para inserir o tamanho pretendido para as imagens `(32, 32, 3)`, usaríamos:

 img_inputs = keras.Input(shape=(32, 32, 3)) 

Quais inputs retornam contém informações sobre o tamanho e o tipo de dados que você planeja transferir para o seu modelo:

 inputs.shape 

 TensorShape([None, 784]) 

 inputs.dtype 

 tf.float32 

Você cria um novo nó no gráfico de camadas chamando a camada neste objeto de inputs :

 from tensorflow.keras import layers dense = layers.Dense(64, activation='relu') x = dense(inputs) 

"Chamar uma camada" é semelhante a desenhar uma seta da "entrada" para a camada que criamos. Passamos a entrada para a camada dense e obtemos x .

Vamos adicionar mais algumas camadas ao nosso gráfico de camadas:

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

Agora podemos criar um Model especificando suas entradas e saídas no gráfico de camadas:

 model = keras.Model(inputs=inputs, outputs=outputs) 

Vejamos novamente o processo completo de definição de modelo:

 inputs = keras.Input(shape=(784,), name='img') x = layers.Dense(64, activation='relu')(inputs) x = layers.Dense(64, activation='relu')(x) outputs = layers.Dense(10, activation='softmax')(x) model = keras.Model(inputs=inputs, outputs=outputs, name='mnist_model') 

Vamos ver como é o resumo do modelo:

 model.summary() 

 Model: "mnist_model" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= img (InputLayer) [(None, 784)] 0 _________________________________________________________________ dense_3 (Dense) (None, 64) 50240 _________________________________________________________________ dense_4 (Dense) (None, 64) 4160 _________________________________________________________________ dense_5 (Dense) (None, 10) 650 ================================================================= Total params: 55,050 Trainable params: 55,050 Non-trainable params: 0 _________________________________________________________________ 

Também podemos desenhar o modelo como um gráfico:

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

imagem

E, opcionalmente, derivar as dimensões da entrada e saída de cada camada no gráfico construído:

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

imagem

Esta imagem e o código que escrevemos são idênticos. Na versão do código, as setas de ligação são simplesmente substituídas por operações de chamada.

O "gráfico de camadas" é uma imagem mental muito intuitiva para o modelo de aprendizado profundo, e a API Funcional é uma maneira de criar modelos que refletem de perto essa imagem mental.

Treinamento, avaliação e conclusão


Aprender, avaliar e derivar trabalhos para modelos criados usando a API Funcional, como nos modelos Sequenciais.

Considere uma demonstração rápida.

Aqui, carregamos o conjunto de dados de imagem MNIST, convertemos em vetores, treinamos o modelo nos dados (enquanto monitoramos a qualidade do trabalho na amostra de teste) e, finalmente, avaliamos nosso 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='sparse_categorical_crossentropy', optimizer=keras.optimizers.RMSprop(), metrics=['accuracy']) history = model.fit(x_train, y_train, batch_size=64, epochs=5, 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]) 

Salvando e serializando


Salvar e serializar modelos criados com a API Funcional funciona exatamente da mesma forma que nos modelos Sequenciais.

A maneira padrão de salvar um modelo Funcional é chamar model.save( ), que permite salvar o modelo inteiro em um arquivo.

Posteriormente, você pode restaurar o mesmo modelo nesse arquivo, mesmo que não tenha mais acesso ao código que criou o modelo.

Este arquivo inclui:

  • Arquitetura de modelo
  • Pesos do modelo (que foram obtidos durante o treinamento)
  • Configuração de treinamento do modelo (o que você passou na compile )
  • O otimizador e sua condição, se fosse (isso permite que você retome o treinamento de onde parou)

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

Usando o mesmo gráfico de camada para definir vários modelos


Na API Funcional, os modelos são criados especificando dados de entrada e saída em um gráfico de camadas. Isso significa que um gráfico de camada única pode ser usado para gerar vários modelos.

No exemplo abaixo, usamos a mesma pilha de camadas para criar dois modelos:
um modelo de (encoder) que converte imagens de entrada em vetores de 16 dimensões e um modelo de (autoencoder) ponta a (autoencoder) 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() 

Observe que tornamos a arquitetura de decodificação estritamente simétrica à arquitetura de codificação, para que a dimensão dos dados de saída seja igual à dos dados de entrada (28, 28, 1) . A camada Conv2D é Conv2D para a camada Conv2D e a camada MaxPooling2D será a volta para a camada MaxPooling2D .

Os modelos podem ser chamados como camadas


Você pode usar qualquer modelo como se fosse uma camada, chamando-o de Input ou de saída de outra camada.

Observe que, ao invocar um modelo, você não apenas reutiliza sua arquitetura, mas também seus pesos. Vamos vê-lo em ação. Aqui está outra olhada em um exemplo de codificador automático quando um modelo de codificador, um modelo de decodificador é criado e eles são conectados em duas chamadas para obter um 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() 

Como você vê, um modelo pode ser aninhado: um modelo pode conter um submodelo (já que o modelo pode ser considerado como uma camada).

Um caso de uso comum para aninhar modelos é o conjunto .

Como exemplo, veja como combinar um conjunto de modelos em um modelo que calcula a média de suas previsões:

 def get_model(): inputs = keras.Input(shape=(128,)) outputs = layers.Dense(1, activation='sigmoid')(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) 


Manipulando topologias complexas de gráficos


Modelos com várias entradas e saídas


A API funcional simplifica a manipulação de várias entradas e saídas. Isso não pode ser feito com a API sequencial.

Aqui está um exemplo simples.

Suponha que você esteja criando um sistema para classificar os aplicativos dos clientes por prioridade e enviá-los para o departamento certo.

Seu modelo terá 3 entradas:

  • Cabeçalho do aplicativo (entrada de texto)
  • Conteúdo de texto do aplicativo (entrada de texto)
  • Quaisquer tags adicionadas pelo usuário (entrada categórica)

O modelo terá 2 saídas:

  • Escore de prioridade entre 0 e 1 (saída sigmóide escalar)
  • O departamento que deve processar o aplicativo (saída softmax referente a muitos departamentos)

Vamos construir um modelo em várias linhas usando a API Funcional.

 num_tags = 12 #     num_words = 10000 #         num_departments = 4 #     title_input = keras.Input(shape=(None,), name='title') #      body_input = keras.Input(shape=(None,), name='body') #      tags_input = keras.Input(shape=(num_tags,), name='tags') #    `num_tags` #      64-  title_features = layers.Embedding(num_words, 64)(title_input) #      64-  body_features = layers.Embedding(num_words, 64)(body_input) #        128-  title_features = layers.LSTM(128)(title_features) #        32-  body_features = layers.LSTM(32)(body_features) #          x = layers.concatenate([title_features, body_features, tags_input]) #         priority_pred = layers.Dense(1, activation='sigmoid', name='priority')(x) #       department_pred = layers.Dense(num_departments, activation='softmax', name='department')(x) #   ,     model = keras.Model(inputs=[title_input, body_input, tags_input], outputs=[priority_pred, department_pred]) 

Vamos desenhar um gráfico de modelo:

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



Ao compilar esse modelo, podemos atribuir diferentes funções de perda para cada saída.

Você pode até atribuir pesos diferentes a cada função de perda para variar sua contribuição para a função geral de perda de aprendizado.

 model.compile(optimizer=keras.optimizers.RMSprop(1e-3), loss=['binary_crossentropy', 'categorical_crossentropy'], loss_weights=[1., 0.2]) 

Como demos nomes às nossas camadas de saída, também podemos especificar funções de perda:

 model.compile(optimizer=keras.optimizers.RMSprop(1e-3), loss={'priority': 'binary_crossentropy', 'department': 'categorical_crossentropy'}, loss_weights=[1., 0.2]) 

Podemos treinar o modelo passando listas de matrizes Numpy de dados e rótulos de entrada:

 import numpy as np # 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 o ajuste com um objeto Dataset , uma tupla de listas como ([title_data, body_data, tags_data], [priority_targets, dept_targets]) ou uma tupla de dicionários ({'title': title_data, 'body': body_data, 'tags': tags_data}, {'priority': priority_targets, 'department': dept_targets}) devem ser retornadas ({'title': title_data, 'body': body_data, 'tags': tags_data}, {'priority': priority_targets, 'department': dept_targets}) .

Modelo de resnet de treinamento


Além de modelos com várias entradas e saídas, a API Funcional simplifica a manipulação de topologias com conectividade não linear, ou seja, modelos nos quais as camadas não estão conectadas em série. Esses modelos também não podem ser implementados usando a API sequencial (como o nome indica).

Um caso de uso comum para isso são as conexões residuais.

Vamos criar um modelo de treinamento ResNet para o 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, activation='softmax')(x) model = keras.Model(inputs, outputs, name='toy_resnet') model.summary() 

Vamos desenhar um gráfico de modelo:

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



E ensine a ela:

 (x_train, y_train), (x_test, y_test) = keras.datasets.cifar10.load_data() x_train = x_train.astype('float32') / 255. x_test = x_test.astype('float32') / 255. 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='categorical_crossentropy', metrics=['acc']) model.fit(x_train, y_train, batch_size=64, epochs=1, validation_split=0.2) 

Compartilhamento de camada


Outro bom uso da API funcional são os modelos que usam camadas comuns. Camadas comuns são instâncias de camadas que são reutilizadas no mesmo modelo: elas estudam recursos relacionados a vários caminhos em um gráfico de camadas.

Camadas comuns são frequentemente usadas para codificar dados de entrada provenientes dos mesmos espaços (digamos, de duas partes diferentes de texto que possuem o mesmo dicionário), pois fornecem a troca de informações entre esses dados diferentes, o que permite que esses modelos sejam treinados com menos dados. Se uma determinada palavra aparecer em uma das entradas, isso facilitará seu processamento em todas as entradas que passam pelo nível geral.

Para compartilhar uma camada na API Funcional, basta chamar a mesma instância da camada várias vezes. Por exemplo, aqui a camada Embedding é compartilhada em duas entradas de texto:

 #   1000    128-  shared_embedding = layers.Embedding(1000, 128) #     text_input_a = keras.Input(shape=(None,), dtype='int32') #     text_input_b = keras.Input(shape=(None,), dtype='int32') #           encoded_input_a = shared_embedding(text_input_a) encoded_input_b = shared_embedding(text_input_b) 

Recuperando e Reutilizando Nós em um Gráfico de Camadas


Como o gráfico de camada que você manipula na API Funcional é uma estrutura de dados estática, é possível acessá-lo e verificá-lo. É assim que construímos modelos funcionais, por exemplo, na forma de imagens.

Isso também significa que podemos acessar as ativações das camadas intermediárias (“nós” no gráfico) e usá-las em outros lugares. Isso é extremamente útil para extrair características, por exemplo!

Vamos ver um exemplo. Este é um modelo VGG19 com escalas pré-treinadas no ImageNet:

 from tensorflow.keras.applications import VGG19 vgg19 = VGG19() 

E estas são ativações de modelo intermediárias obtidas consultando a estrutura de dados do gráfico:

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

Podemos usar esses recursos para criar um novo modelo de extração de recursos que retorna valores de ativação de nível intermediário - e podemos fazer tudo isso em 3 linhas

 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) 

Isso é conveniente ao implementar a transferência de estilo neural, como em outros casos.

Estendendo a API escrevendo camadas personalizadas


tf.keras possui uma ampla variedade de camadas embutidas. Aqui estão alguns exemplos:

Camadas convolucionais: Conv1D , Conv2D , Conv3D , Conv2DTranspose , etc.
Camadas de MaxPooling1D : MaxPooling1D , MaxPooling2D , MaxPooling3D , MaxPooling3D , etc.
Camadas RNN: GRU , LSTM , ConvLSTM2D , etc.
BatchNormalization , BatchNormalization , Embedding , etc.

Se você não encontrou o que precisa, é fácil estender a API criando sua própria camada.

Todas as camadas subclassificam a classe Layer e implementam:

O método de call que define os cálculos executados pela camada.
O método de build que cria os pesos da camada (observe que esta é apenas uma convenção de estilo; você também pode criar pesos em __init__ ).

Aqui está uma implementação simples da camada 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) 

Se você deseja que sua camada customizada suporte a serialização, também deve definir o método get_config que retorna 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, você também pode implementar o método da classe from_config (cls, config) , responsável por recriar a instância da camada, conforme seu dicionário de configuração. A from_config padrão from_config é assim:

 def from_config(cls, config): return cls(**config) 

Quando usar a API Funcional


Como determinar quando é melhor usar a API Funcional para criar um novo modelo ou simplesmente subclassificar Model diretamente?

Em geral, a API Funcional é de alto nível e fácil de usar, possui várias funções que não são suportadas por modelos de subclasse.

No entanto, a subclassificação do modelo oferece grande flexibilidade ao criar modelos que não são facilmente descritos como um gráfico acíclico direcionado de camadas (por exemplo, você não pode implementar o Tree-RNN com a API funcional, é necessário subclassificar o Model diretamente).

Pontos fortes da API funcional:


As propriedades listadas abaixo são verdadeiras para modelos seqüenciais (que também são estruturas de dados), mas são verdadeiras para modelos de subclasse (que são código Python, não estruturas de dados).

A API funcional produz código mais curto.


Sem super(MyClass, self).__init__(...) , sem 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 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) #   . mlp = MLP() #    . #            . _ = mlp(tf.zeros((1, 32))) 

Seu modelo é validado conforme está escrito.


Na API Funcional, as especificações de entrada (shape e dtype) são criadas antecipadamente (via `Input`) e, cada vez que você chama a camada, a camada verifica se as especificações passadas correspondem às suas suposições; se esse não for o caso, você receberá uma mensagem de erro útil .

Isso garante que qualquer modelo que você construa com a API Funcional seja iniciado. Toda a depuração (não relacionada à depuração de convergência) ocorrerá estaticamente durante a construção do modelo, e não no tempo de execução. Isso é semelhante à verificação de tipo no compilador.

Seu modelo funcional pode ser representado graficamente e também é testável.


Você pode desenhar o modelo na forma de um gráfico e pode acessar facilmente os nós intermediários do gráfico, por exemplo, para extrair e reutilizar a ativação das camadas intermediárias, como vimos no exemplo anterior:

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

Como o modelo Funcional é mais uma estrutura de dados do que 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 acesso ao código-fonte.

Deficiências funcionais da API


Ele não suporta arquiteturas dinâmicas.


A API Funcional processa modelos como camadas DAG. Isso é verdade para a maioria das arquiteturas de aprendizado profundo, mas não para todos: por exemplo, redes recursivas ou RNNs da Árvore não atendem a essa suposição e não podem ser implementadas na API Funcional.

Às vezes, você só precisa escrever tudo do zero.


Ao escrever arquiteturas avançadas, convém fazer algo que vá além da “definição de camadas do DAG”: por exemplo, você pode usar vários métodos personalizados de treinamento e saída em uma instância do seu modelo. Isso requer subclassificação.

Combinando e combinando vários estilos de API


É importante observar que escolher entre a API Funcional ou subclassificar o Modelo não é uma solução binária que limita você a uma categoria de modelos.Todos os modelos na API tf.keras podem interagir entre si, sejam modelos sequenciais, modelos funcionais ou modelos / camadas subclassificados, escritos do zero.

Você sempre pode usar o modelo Funcional ou o modelo Sequencial como parte do modelo / camada subclassificado:

 units = 32 timesteps = 10 input_dim = 5 # Define a Functional model inputs = keras.Input((None, units)) x = layers.GlobalAveragePooling1D()(inputs) outputs = layers.Dense(1, activation='sigmoid')(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))) 

Por outro lado, você pode usar qualquer Camada ou Modelo subclassificado na API Funcional se implementar um método callque corresponda a um dos seguintes padrões:

call(self, inputs, **kwargs)onde inputsestá a estrutura do tensor ou do tensor aninhado (por exemplo, lista de tensores) e onde **kwargsestão os argumentos sem tensor (não entrada) .
call(self, inputs, training=None, **kwargs)onde trainingé um valor booleano indicando em qual modo a camada, aprendizado ou saída deve se comportar.
call(self, inputs, mask=None, **kwargs)onde maské o tensor da máscara booleana (útil para RNN, por exemplo).
call(self, inputs, training=None, mask=None, **kwargs)- é claro que você pode ter os dois parâmetros que definem o comportamento da camada ao mesmo tempo.

Além disso, se você implementar o método `get_config` em sua camada ou modelo personalizado, os modelos funcionais criados com ele serão serializados e clonados.

Abaixo está um pequeno exemplo em que usamos RNN personalizados escritos do zero Modelos funcionais:

 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, activation='sigmoid') 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) #           #  `batch_shape`,     `CustomRNN`  #    (     `state`). 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))) 

Isso conclui nosso Guia de API funcional!

Agora você tem em mãos um poderoso conjunto de ferramentas para a construção de modelos de aprendizado profundo.

Após a verificação, a tradução também aparecerá no Tensorflow.org. Se você deseja participar da tradução da documentação do site Tensorflow.org para o russo, entre em contato com um comentário ou pessoal. Quaisquer correções ou comentários são apreciados. Como ilustração, usamos a imagem do modelo GoogLeNet, que também é um gráfico acíclico direcionado.

Source: https://habr.com/ru/post/pt483664/


All Articles