Início Tecnologia Construa modelos mais inteligentes com API funcional de Keras

Construa modelos mais inteligentes com API funcional de Keras

6
0

 

Visão geral do conteúdo

  • Camadas compartilhadas
  • Extrair e reutilizar nós no gráfico de camadas
  • Estender a API usando camadas personalizadas
  • Quando usar a API funcional
  • Forças funcionais da API
  • Fraquezas funcionais da API
  • Estilos de API de mistura e correspondência

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-eles 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 peças de texto diferentes que apresentam vocabulário semelhante). Eles permitem o compartilhamento de informações nessas diferentes entradas e possibilitam 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 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)

Extrair e reutilizar 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ê consegue 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 outros lugares – o que é muito útil para algo como extração de recursos.

Vejamos um exemplo. Este é um modelo VGG19 com pesos pré -criados no ImageNet:

vgg19 = keras.applications.VGG19()
Downloading data from 
574710816/574710816 [==============================] - 4s 0us/step

E essas são as ativações intermediárias do modelo, obtidas consultando a 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 retorne os valores das ativações intermediárias da camada:

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 é útil para tarefas como transferência de estilo neural, entre outras coisas.

Estender a API usando camadas personalizadas

keras Inclui uma ampla gama de camadas embutidas, por exemplo:

  • Camadas convolucionais: Conv1DAssim, Conv2DAssim, Conv3DAssim, Conv2DTranspose
  • Camadas de agrupamento: MaxPooling1DAssim, MaxPooling2DAssim, MaxPooling3DAssim, AveragePooling1D
  • Camadas RNN: GRUAssim, LSTMAssim, ConvLSTM2D
  • BatchNormalizationAssim, DropoutAssim, Embeddingetc.

Mas se você não encontrar o que precisa, é fácil estender a API criando suas próprias camadas. Todas as camadas subclassem o Layer classe e implemento:

  • call Método, que especifica o cálculo feito pela camada.
  • build método, que cria os pesos da camada (esta é apenas uma convenção de estilo, pois você pode criar pesos em __init__também).

Para saber mais sobre como criar camadas do zero, leia as camadas e os modelos personalizados.

A seguir é uma implementação básica de keras.layers.Dense:

class CustomDense(layers.Layer):
    def __init__(self, units=32):
        super().__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 de serialização em sua camada personalizada, defina um get_config Método que retorna os argumentos do construtor da instância da camada:

@keras.saving.register_keras_serializable()
class CustomDense(layers.Layer):
    def __init__(self, units=32):
        super().__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)

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

Se você usar a API funcional do 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 subclassem não suportam.

No entanto, a subclasse de modelo fornece maior flexibilidade ao criar modelos que não são facilmente expressíveis como gráficos acíclicos de camadas acíclicas. Por exemplo, você não poderia implementar uma árvore com a API funcional e teria que subclasse Model diretamente.

Para uma análise aprofundada das diferenças entre a API funcional e a subclasse de modelo, leia o que são APIs simbólicas e imperativas no tensorflow 2.0?.

Forças funcionais da API:

As propriedades a seguir também são verdadeiras para modelos seqüenciais (que também são estruturas de dados), mas não são verdadeiros para modelos subclassificados (que são bytecode Python, não estruturas de dados).

Menos detalhado

Não há super().__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)

Com a versão subclassem:

class MLP(keras.Model):

  def __init__(self, **kwargs):
    super().__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)))

Modelo Validação ao definir seu gráfico de conectividade

Na API funcional, a especificação de entrada (Shape e Dtype) é criada com antecedência (usando Input). Toda vez que você chama uma camada, a camada verifica se a especificação passada corresponde a suas suposições e levantará uma mensagem de erro útil, se não.

Isso garante que qualquer modelo que você possa criar 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 do tipo em um compilador.

Um modelo funcional é plotável e inspecável

Você pode plotar o modelo como um gráfico e pode acessar facilmente nós intermediários neste 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 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 o guia de serialização e salvamento.

Para serializar um modelo subclassificado, é necessário para o implementador especificar um get_config() e from_config() método no nível do modelo.

Fraqueza funcional da API:

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 todas – por exemplo, redes ou RNNs de árvores recursivas não seguem essa suposição e não podem ser implementadas na API funcional.

Estilos de API de mistura e correspondência

Escolher entre a API funcional ou a subclasse de modelo não é uma decisão binária que o restringe a uma categoria de modelos. Todos os modelos no keras API pode interagir entre si, seja eles Sequential Modelos, modelos funcionais ou modelos subclassificados que são escritos do zero.

Você sempre pode usar um modelo funcional ou Sequential Modelo como parte de um modelo ou camada subclassificada:

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)


@keras.saving.register_keras_serializable()
class CustomRNN(layers.Layer):
    def __init__(self):
        super().__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 ou modelo subclassificado na API funcional, desde que implemente um call Método que segue 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 sem tensor (não inputas).
  • call(self, inputs, training=None, **kwargs) — Onde training é um booleano indicando se a camada deve se comportar no modo de treinamento e no 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) -Obviamente, você pode ter comportamento específico de mascaramento e treinamento ao mesmo tempo.

Além disso, se você implementar o get_config Método em sua camada ou modelo personalizado, os modelos funcionais que você criar ainda serão serializáveis ​​e clonáveis.

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


@keras.saving.register_keras_serializable()
class CustomRNN(layers.Layer):
    def __init__(self):
        super().__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)))

Publicado originalmente no Tensorflow Site, este artigo aparece aqui sob uma nova manchete e é licenciado no CC por 4.0. Amostras de código compartilhadas sob a licença Apache 2.0.

fonte