Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/pt-br/guide/migrate/model_mapping.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.

Use modelos TF1.x em workflows TF2

Este guia fornece uma visão geral e exemplos de um shim para código de modelagem que você pode empregar para usar seus modelos TF1.x existentes em workflows TF2, como estratégias de execução antecipada (eager), tf.function e de distribuição com alterações mínimas no seu código de modelagem.

Escopo de uso

O shim descrito neste guia foi projetado para modelos TF1.x que dependem de:

  1. tf.compat.v1.get_variable e tf.compat.v1.variable_scope para controlar a criação e reutilização de variáveis ​​e

  2. APIs baseadas em coleções de grafos, como tf.compat.v1.global_variables(), tf.compat.v1.trainable_variables, tf.compat.v1.losses.get_regularization_losses() e tf.compat.v1.get_collection() para acompanhar de pesos e perdas de regularização

Isso inclui a maioria dos modelos criados com base nas APIs tf.compat.v1.layer, tf.contrib.layers e TensorFlow-Slim.

O shim NÃO é necessário para os seguintes modelos TF1.x:

  1. Modelos Keras standalone que já rastreiam todos os seus pesos treináveis ​​e perdas de regularização via model.trainable_weights e model.losses, respectivamente.

  2. tf.Modules que já rastreiam todos os seus pesos treináveis ​​via module.trainable_variables e apenas criam pesos se ainda não tiverem sido criados.

Esses modelos provavelmente já funcionarão no TF2 com execução eager e tf.functions.

Configuração

Importe o TensorFlow e outras dependências.

!pip uninstall -y -q tensorflow
# Install tf-nightly as the DeterministicRandomTestTool is available only in # Tensorflow 2.8 !pip install -q tf-nightly
import tensorflow as tf import tensorflow.compat.v1 as v1 import sys import numpy as np from contextlib import contextmanager

O decorador track_tf1_style_variables

O principal shim descrito neste guia é tf.compat.v1.keras.utils.track_tf1_style_variables, um decorador que você pode usar nos métodos pertencentes a tf.keras.layers.Layer e tf.Module para rastrear pesos de estilo TF1.x e capturar perdas de regularização.

Decorar métodos de chamada de objetos tf.keras.layers.Layer ou tf.Module com tf.compat.v1.keras.utils.track_tf1_style_variables permite a criação e reutilização de variáveis ​​via tf.compat.v1.get_variable (e por extensão tf.compat.v1.layers) para funcionar corretamente dentro do método decorado em vez de sempre criar uma nova variável a cada chamada. Isso também fará com que a camada ou módulo rastreie implicitamente quaisquer pesos criados ou acessados ​​via get_variable dentro do método decorado.

Além de rastrear os próprios pesos sob as propriedades padrão layer.variable/module.variable/etc., se o método pertencer a um tf.keras.layers.Layer, quaisquer perdas de regularização especificadas através dos argumentos do regularizador get_variable ou tf.compat.v1.layers serão rastreadas pela camada sob a propriedade padrão layer.losses.

Esse mecanismo de rastreamento permite o uso de grandes classes de código model-forward-pass no estilo TF1.x dentro das camadas Keras ou objetos tf.Module no TF2, mesmo com os comportamentos TF2 ativados.

Exemplos de uso

Os exemplos de uso abaixo demonstram os shims de modelagem usados ​​para decorar os métodos tf.keras.layers.Layer, mas, exceto onde eles interagem especificamente com os recursos do Keras, eles também são aplicáveis ​​ao decorar métodos tf.Module.

Camada construída com tf.compat.v1.get_variable

Imagine que você tenha uma camada implementada diretamente sobre tf.compat.v1.get_variable da seguinte forma:

def dense(self, inputs, units): out = inputs with tf.compat.v1.variable_scope("dense"): # The weights are created with a `regularizer`, kernel = tf.compat.v1.get_variable( shape=[out.shape[-1], units], regularizer=tf.keras.regularizers.L2(), initializer=tf.compat.v1.initializers.glorot_normal, name="kernel") bias = tf.compat.v1.get_variable( shape=[units,], initializer=tf.compat.v1.initializers.zeros, name="bias") out = tf.linalg.matmul(out, kernel) out = tf.compat.v1.nn.bias_add(out, bias) return out

Use o shim para transformá-la numa camada e chame-a nas entradas.

class DenseLayer(tf.keras.layers.Layer): def __init__(self, units, *args, **kwargs): super().__init__(*args, **kwargs) self.units = units @tf.compat.v1.keras.utils.track_tf1_style_variables def call(self, inputs): out = inputs with tf.compat.v1.variable_scope("dense"): # The weights are created with a `regularizer`, # so the layer should track their regularization losses kernel = tf.compat.v1.get_variable( shape=[out.shape[-1], self.units], regularizer=tf.keras.regularizers.L2(), initializer=tf.compat.v1.initializers.glorot_normal, name="kernel") bias = tf.compat.v1.get_variable( shape=[self.units,], initializer=tf.compat.v1.initializers.zeros, name="bias") out = tf.linalg.matmul(out, kernel) out = tf.compat.v1.nn.bias_add(out, bias) return out layer = DenseLayer(10) x = tf.random.normal(shape=(8, 20)) layer(x)

Acesse as variáveis ​​rastreadas e as perdas de regularização capturadas como uma camada Keras padrão.

layer.trainable_variables layer.losses

Para ver se os pesos são reutilizados sempre que você chama a camada, defina todos os pesos como zero e chame a camada novamente.

print("Resetting variables to zero:", [var.name for var in layer.trainable_variables]) for var in layer.trainable_variables: var.assign(var * 0.0) # Note: layer.losses is not a live view and # will get reset only at each layer call print("layer.losses:", layer.losses) print("calling layer again.") out = layer(x) print("layer.losses: ", layer.losses) out

Você também pode usar a camada convertida diretamente na construção do modelo funcional Keras.

inputs = tf.keras.Input(shape=(20)) outputs = DenseLayer(10)(inputs) model = tf.keras.Model(inputs=inputs, outputs=outputs) x = tf.random.normal(shape=(8, 20)) model(x) # Access the model variables and regularization losses model.weights model.losses

Modelo criado com tf.compat.v1.layers

Imagine que você tenha uma camada ou modelo implementado diretamente sobre tf.compat.v1.layers da seguinte forma:

def model(self, inputs, units): with tf.compat.v1.variable_scope('model'): out = tf.compat.v1.layers.conv2d( inputs, 3, 3, kernel_regularizer="l2") out = tf.compat.v1.layers.flatten(out) out = tf.compat.v1.layers.dense( out, units, kernel_regularizer="l2") return out

Use o shim para transformá-lo numa camada e chame-a nas entradas.

class CompatV1LayerModel(tf.keras.layers.Layer): def __init__(self, units, *args, **kwargs): super().__init__(*args, **kwargs) self.units = units @tf.compat.v1.keras.utils.track_tf1_style_variables def call(self, inputs): with tf.compat.v1.variable_scope('model'): out = tf.compat.v1.layers.conv2d( inputs, 3, 3, kernel_regularizer="l2") out = tf.compat.v1.layers.flatten(out) out = tf.compat.v1.layers.dense( out, self.units, kernel_regularizer="l2") return out layer = CompatV1LayerModel(10) x = tf.random.normal(shape=(8, 5, 5, 5)) layer(x)

Aviso: por questões de segurança, certifique-se de colocar todos os tf.compat.v1.layers dentro de uma string não vazia variable_scope. O motivo é que tf.compat.v1.layers com nomes gerados automaticamente sempre incrementará automaticamente o nome quando estiver fora de qualquer escopo de variável. Isso significa que os nomes das variáveis ​​solicitadas serão incompatíveis sempre que você chamar a camada/módulo. Assim, em vez de reutilizar os pesos já criados, ele criará um novo conjunto de variáveis ​​a cada chamada.

Acesse as variáveis ​​rastreadas e as perdas de regularização capturadas como uma camada Keras padrão.

layer.trainable_variables layer.losses

Para ver se os pesos são reutilizados sempre que você chama a camada, defina todos os pesos como zero e chame a camada novamente.

print("Resetting variables to zero:", [var.name for var in layer.trainable_variables]) for var in layer.trainable_variables: var.assign(var * 0.0) out = layer(x) print("layer.losses: ", layer.losses) out

Você também pode usar a camada convertida diretamente na construção do modelo funcional Keras.

inputs = tf.keras.Input(shape=(5, 5, 5)) outputs = CompatV1LayerModel(10)(inputs) model = tf.keras.Model(inputs=inputs, outputs=outputs) x = tf.random.normal(shape=(8, 5, 5, 5)) model(x)
# Access the model variables and regularization losses model.weights model.losses

Capture atualizações de normalização em lote e argumentos de modelo training

No TF1.x, você executa a normalização em lote da seguinte forma:

x_norm = tf.compat.v1.layers.batch_normalization(x, training=training) # ... update_ops = tf.compat.v1.get_collection(tf.GraphKeys.UPDATE_OPS) train_op = optimizer.minimize(loss) train_op = tf.group([train_op, update_ops])

Observe que:

  1. As atualizações de média móvel da normalização em lote são rastreadas por get_collection, que foi chamado separadamente da camada

  2. tf.compat.v1.layers.batch_normalization requer um argumento training (geralmente chamado de is_training ao usar camadas de normalização em lote TF-Slim)

No TF2, devido à execução antecipada (eager) e às dependências de controle automático, as atualizações da média móvel da normalização do lote serão executadas imediatamente. Não há necessidade de coletá-las separadamente da coleção de atualizações e adicioná-las como dependências de controle explícitas.

Além disso, se você fornecer um argumento training ao método de passo para frente da sua tf.keras.layers.Layer, o Keras poderá passar a fase de treinamento atual e quaisquer camadas aninhadas para ele, assim como faria com qualquer outra camada. Consulte a documentação da API para tf.keras.Model para obter mais informações sobre como o Keras lida com o argumento training.

Se você estiver decorando métodos tf.Module, precisará certificar-se de passar manualmente todos os argumentos training conforme necessário. No entanto, as atualizações de média móvel de normalização de lote ainda serão aplicadas automaticamente sem a necessidade de dependências de controle explícitas.

As amostras de código a seguir demonstram como incorporar camadas de normalização de lote no shim e como funciona seu uso num modelo Keras (aplicável a tf.keras.layers.Layer).

class CompatV1BatchNorm(tf.keras.layers.Layer): @tf.compat.v1.keras.utils.track_tf1_style_variables def call(self, inputs, training=None): print("Forward pass called with `training` =", training) with v1.variable_scope('batch_norm_layer'): return v1.layers.batch_normalization(x, training=training)
print("Constructing model") inputs = tf.keras.Input(shape=(5, 5, 5)) outputs = CompatV1BatchNorm()(inputs) model = tf.keras.Model(inputs=inputs, outputs=outputs) print("Calling model in inference mode") x = tf.random.normal(shape=(8, 5, 5, 5)) model(x, training=False) print("Moving average variables before training: ", {var.name: var.read_value() for var in model.non_trainable_variables}) # Notice that when running TF2 and eager execution, the batchnorm layer directly # updates the moving averages while training without needing any extra control # dependencies print("calling model in training mode") model(x, training=True) print("Moving average variables after training: ", {var.name: var.read_value() for var in model.non_trainable_variables})

Reuso variável baseado em escopo de variáveis

Quaisquer criações de variáveis ​​no passo para frente baseado em get_variable manterão a mesma nomenclatura de variável e semântica de reutilização que os escopos de variáveis ​​têm no TF1.x. Isto se aplica desde que você tenha pelo menos um escopo externo não vazio para quaisquer tf.compat.v1.layers com nomes gerados automaticamente, conforme mencionado acima.

Observação: A nomenclatura e a reutilização terão como escopo uma única instância de camada/módulo. As chamadas para get_variable dentro de uma camada ou módulo decorado com shim não poderão se referir a variáveis ​​criadas dentro de camadas ou módulos. Você pode contornar isso usando referências Python a outras variáveis ​​diretamente, se necessário, em vez de acessar variáveis ​​via get_variable.

Execução avançada (eager) e tf.function

Como visto acima, os métodos decorados para tf.keras.layers.Layer e tf.Module são executados dentro da execução antecipada (eager) e também são compatíveis com tf.function. Isto significa que você pode usar pdb e outras ferramentas interativas para percorrer seu passo para frente durante a execução.

Importante: Embora seja perfeitamente seguro chamar seus métodos de camada/módulo decorados com shim de dentro de um tf.function, não é seguro colocar tf.functions dentro de seus métodos decorados com shim se esses tf.functions contiverem chamadas get_variable. Inserir um tf.function redefine os variable_scope, o que significa que o reuso de uma variável (baseada em escopo de variável no estilo TF1.x que o shim simula) será quebrada nessa configuração.

Estratégias de distribuição

Chamadas para get_variable dentro de @track_tf1_style_variables - camada decorada ou métodos de módulo usam criações padrão da variável tf.Variable nos bastidores. Isto significa que você pode usá-las com as várias estratégias de distribuição disponíveis com tf.distribute, como MirroredStrategy e TPUStrategy.

Aninhando objetos tf.Variable, tf.Module, tf.keras.layers e tf.keras.models em chamadas decoradas

Decorar sua chamada de camada em tf.compat.v1.keras.utils.track_tf1_style_variables vai apenas adicionar rastreamento implícito automático das variáveis ​​criadas (e reutilizadas) via tf.compat.v1.get_variable. Isto não vai capturar pesos criados diretamente por chamadas tf.Variable, como aquelas usadas por camadas típicas do Keras e a maioria dos objetos tf.Module. Esta seção descreve como lidar com esses casos aninhados.

(Usos pré-existentes) tf.keras.layers e tf.keras.models

Para usos pré-existentes de camadas e modelos Keras aninhados, use tf.compat.v1.keras.utils.get_or_create_layer. Isto é recomendado apenas para facilitar a migração dos usos de Keras aninhados em TF1.x existentes; o novo código deve usar configuração de atributo explícito conforme descrito abaixo para tf.Variables e tf.Modules.

Para usar tf.compat.v1.keras.utils.get_or_create_layer, envolva o código que constrói seu modelo aninhado dentro de um método e passe-o para o método. Por exemplo:

class NestedModel(tf.keras.Model): def __init__(self, units, *args, **kwargs): super().__init__(*args, **kwargs) self.units = units def build_model(self): inp = tf.keras.Input(shape=(5, 5)) dense_layer = tf.keras.layers.Dense( 10, name="dense", kernel_regularizer="l2", kernel_initializer=tf.compat.v1.ones_initializer()) model = tf.keras.Model(inputs=inp, outputs=dense_layer(inp)) return model @tf.compat.v1.keras.utils.track_tf1_style_variables def call(self, inputs): # Get or create a nested model without assigning it as an explicit property model = tf.compat.v1.keras.utils.get_or_create_layer( "dense_model", self.build_model) return model(inputs) layer = NestedModel(10) layer(tf.ones(shape=(5,5)))

Este método garante que essas camadas aninhadas sejam reutilizadas e rastreadas corretamente pelo Tensorflow. Observe que o decorador @track_tf1_style_variables ainda é necessário no método apropriado. O método para construção do modelo passado para get_or_create_layer (neste caso, self.build_model), não deve receber argumentos.

Os pesos são rastreados:

assert len(layer.weights) == 2 weights = {x.name: x for x in layer.variables} assert set(weights.keys()) == {"dense/bias:0", "dense/kernel:0"} layer.weights

E a perda de regularização também:

tf.add_n(layer.losses)

Migração incremental: tf.Variables e tf.Modules

Se você precisar incorporar chamadas tf.Variable ou tf.Module em seus métodos decorados (por exemplo, se estiver seguindo a migração incremental para APIs TF2 não legadas descritas mais adiante neste guia), você ainda precisará rastreá-las explicitamente, com os seguintes requisitos:

  • Garanta, de forma explicita, que a variável/módulo/camada seja criada apenas uma vez

  • Anexe-as, de forma explícita, como atributos de instância, assim como você faria ao definir um módulo ou camada típico

  • Reutilize, de forma explícita, o objeto já criado em chamadas subsequentes

Isso garante que os pesos não sejam criados a cada nova chamada e sejam reutilizados corretamente. Além disso, isso também garante que os pesos existentes e as perdas de regularização sejam rastreados.

Eis um exemplo de como isso pode ser feito:

class NestedLayer(tf.keras.layers.Layer): def __init__(self, units, *args, **kwargs): super().__init__(*args, **kwargs) self.units = units @tf.compat.v1.keras.utils.track_tf1_style_variables def __call__(self, inputs): out = inputs with tf.compat.v1.variable_scope("inner_dense"): # The weights are created with a `regularizer`, # so the layer should track their regularization losses kernel = tf.compat.v1.get_variable( shape=[out.shape[-1], self.units], regularizer=tf.keras.regularizers.L2(), initializer=tf.compat.v1.initializers.glorot_normal, name="kernel") bias = tf.compat.v1.get_variable( shape=[self.units,], initializer=tf.compat.v1.initializers.zeros, name="bias") out = tf.linalg.matmul(out, kernel) out = tf.compat.v1.nn.bias_add(out, bias) return out class WrappedDenseLayer(tf.keras.layers.Layer): def __init__(self, units, **kwargs): super().__init__(**kwargs) self.units = units # Only create the nested tf.variable/module/layer/model # once, and then reuse it each time! self._dense_layer = NestedLayer(self.units) @tf.compat.v1.keras.utils.track_tf1_style_variables def call(self, inputs): with tf.compat.v1.variable_scope('outer'): outputs = tf.compat.v1.layers.dense(inputs, 3) outputs = tf.compat.v1.layers.dense(inputs, 4) return self._dense_layer(outputs) layer = WrappedDenseLayer(10) layer(tf.ones(shape=(5, 5)))

Observe que o rastreamento explícito do módulo aninhado é necessário, mesmo que seja decorado com o decorador track_tf1_style_variables. Isso ocorre porque cada módulo/camada com métodos decorados possui seu próprio armazenamento de variáveis ​​associado a ele.

Os pesos são rastreados corretamente:

assert len(layer.weights) == 6 weights = {x.name: x for x in layer.variables} assert set(weights.keys()) == {"outer/inner_dense/bias:0", "outer/inner_dense/kernel:0", "outer/dense/bias:0", "outer/dense/kernel:0", "outer/dense_1/bias:0", "outer/dense_1/kernel:0"} layer.trainable_weights

Assim como a perda de regularização:

layer.losses

Observe que, se NestedLayer fosse um tf.Module não Keras, as variáveis ​​ainda seriam rastreadas, mas as perdas de regularização não seriam rastreadas automaticamente, então você teria que rastreá-las explicitamente de forma separada.

Orientação sobre nomes de variáveis

Chamadas explícitas tf.Variable e camadas Keras usam um mecanismo de geração automática de nome de variável / nome de camada diferente do que você pode estar acostumado a partir da combinação de get_variable e variable_scopes. Embora o shim faça com que os nomes de variáveis ​​correspondam às variáveis ​​criadas por get_variable, mesmo ao migrar de grafos TF1.x para TF2 com execução antecipada (eager) e tf.function, ele não poderá garantir o mesmo para os nomes de variáveis ​​gerados para chamadas tf.Variable e camadas Keras que você incorpora dentro de seus decoradores de método. É até possível ter múltiplas variáveis ​​compartilhando o mesmo nome na execução antecipada (eager) TF2 e tf.function.

Você deve dedicar uma atenção especial a essa questão quando for seguir as seções sobre validação da exatidão e mapeamento de checkpoints TF1.x mais adiante neste guia.

Usando tf.compat.v1.make_template no método decorado

É altamente recomendável que você use tf.compat.v1.keras.utils.track_tf1_style_variables diretamente, em vez de usar tf.compat.v1.make_template, pois é uma camada mais fina sobre TF2.

Siga as orientações desta seção para código TF1.x existente que já dependia de tf.compat.v1.make_template.

Como tf.compat.v1.make_template agrupa o código que usa get_variable, o decorador track_tf1_style_variables permite que você use esses modelos em chamadas de camada e rastreie com sucesso os pesos e as perdas de regularização.

No entanto, não deixe de chamar make_template apenas uma vez e depois reutilizar o mesmo modelo em cada chamada de camada. Caso contrário, um novo modelo será criado cada vez que você chamar a camada junto com um novo conjunto de variáveis.

Por exemplo,

class CompatV1TemplateScaleByY(tf.keras.layers.Layer): def __init__(self, **kwargs): super().__init__(**kwargs) def my_op(x, scalar_name): var1 = tf.compat.v1.get_variable(scalar_name, shape=[], regularizer=tf.compat.v1.keras.regularizers.L2(), initializer=tf.compat.v1.constant_initializer(1.5)) return x * var1 self.scale_by_y = tf.compat.v1.make_template('scale_by_y', my_op, scalar_name='y') @tf.compat.v1.keras.utils.track_tf1_style_variables def call(self, inputs): with tf.compat.v1.variable_scope('layer'): # Using a scope ensures the `scale_by_y` name will not be incremented # for each instantiation of the layer. return self.scale_by_y(inputs) layer = CompatV1TemplateScaleByY() out = layer(tf.ones(shape=(2, 3))) print("weights:", layer.weights) print("regularization loss:", layer.losses) print("output:", out)

Importante: Evite compartilhar o mesmo modelo criado por make_template em múltiplas instâncias de camada, pois isto poderá quebrar os mecanismos de rastreamento de perda de regularização e variável do decorador shim. Além disso, se você planeja usar o mesmo nome make_template dentro de múltiplas instâncias de camada, precisará aninhar o uso do modelo criado dentro de um variable_scope. Caso contrário, o nome gerado para a variable_scope do modelo será incrementado a cada nova instância da camada. Isto poderá alterar os nomes dos pesos de maneiras inesperadas.

Migração incremental para TF2 nativo

Conforme mencionado anteriormente, track_tf1_style_variables permite que você misture tf.Variable/tf.keras.layers.Layer/tf.Module orientado a objetos no estilo TF2 com o uso legado de tf.compat.v1.get_variable/tf.compat.v1.layers dentro do mesmo módulo/camada decorada.

Isto significa que depois de tornar seu modelo TF1.x totalmente compatível com TF2, você pode escrever todos os novos componentes de modelo com APIs TF2 nativas (não- tf.compat.v1) e fazer com que interoperem com seu código antigo.

No entanto, se você continuar a modificar seus componentes de modelo mais antigos, você talvez também queira gradualmente substituir o uso do tf.compat.v1 legado para as APIs orientadas a objeto puramente nativas recomendadas para código TF2 novo.

O uso de tf.compat.v1.get_variable pode ser substituído por chamadas self.add_weight se você estiver decorando uma camada/modelo Keras ou com chamadas tf.Variable se estiver decorando objetos Keras ou tf.Module.

Tanto as tf.compat.v1.layers de estilo funcional como as orientadas a objeto geralmente podem ser substituídas por uma camada tf.keras.layers equivalente sem a necessidade de alterações nos argumentos.

Você talvez também queira considerar partes de seu modelo ou padrões comuns em camadas/módulos individuais durante sua mudança incremental para APIs puramente nativas, que podem usar track_tf1_style_variables.

Uma observação sobre Slim e contrib.layers

Bastante código TF 1.x antigo usa a biblioteca Slim, que foi empacotada com o TF 1.x em tf.contrib.layers. Converter código que usa Slim para TF 2 nativo é mais complexo do que converter v1.layers. Na verdade, pode ser melhor converter seu código Slim para v1.layers primeiro e depois converter para Keras. Abaixo estão algumas orientações gerais para converter o código Slim.

  • Garanta que todos os argumentos sejam explícitos. Remova arg_scopes se possível. Se você ainda precisar usá-los, separe normalizer_fn e activation_fn em camadas próprias.

  • Camadas convolucionais separáveis ​​são mapeadas para uma ou mais camadas Keras diferentes (camadas Keras separáveis, ponto a ponto, profundidade a profundidade).

  • Slim e v1.layers têm nomes de argumentos e valores padrão diferentes.

  • Observe que alguns argumentos usam escalas diferentes.

Migração para TF2 nativo ignorando a compatibilidade de checkpoints

O exemplo de código a seguir demonstra a migração incremental de um modelo para APIs puramente nativas sem considerar a compatibilidade dos checkpoints.

class CompatModel(tf.keras.layers.Layer): def __init__(self, units, *args, **kwargs): super().__init__(*args, **kwargs) self.units = units @tf.compat.v1.keras.utils.track_tf1_style_variables def call(self, inputs, training=None): with tf.compat.v1.variable_scope('model'): out = tf.compat.v1.layers.conv2d( inputs, 3, 3, kernel_regularizer="l2") out = tf.compat.v1.layers.flatten(out) out = tf.compat.v1.layers.dropout(out, training=training) out = tf.compat.v1.layers.dense( out, self.units, kernel_regularizer="l2") return out

Em seguida, substitua as APIs compat.v1 por suas equivalentes nativas orientadas a objetos, de forma segmentada. Comece trocando a camada de convolução por um objeto Keras criado no construtor da camada.

class PartiallyMigratedModel(tf.keras.layers.Layer): def __init__(self, units, *args, **kwargs): super().__init__(*args, **kwargs) self.units = units self.conv_layer = tf.keras.layers.Conv2D( 3, 3, kernel_regularizer="l2") @tf.compat.v1.keras.utils.track_tf1_style_variables def call(self, inputs, training=None): with tf.compat.v1.variable_scope('model'): out = self.conv_layer(inputs) out = tf.compat.v1.layers.flatten(out) out = tf.compat.v1.layers.dropout(out, training=training) out = tf.compat.v1.layers.dense( out, self.units, kernel_regularizer="l2") return out

Use a classe v1.keras.utils.DeterministicRandomTestTool para verificar se essa alteração incremental deixa o modelo com o mesmo comportamento de antes.

random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops') with random_tool.scope(): tf.keras.utils.set_random_seed(42) layer = CompatModel(10) inputs = tf.random.normal(shape=(10, 5, 5, 5)) original_output = layer(inputs) # Grab the regularization loss as well original_regularization_loss = tf.math.add_n(layer.losses) print(original_regularization_loss)
random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops') with random_tool.scope(): tf.keras.utils.set_random_seed(42) layer = PartiallyMigratedModel(10) inputs = tf.random.normal(shape=(10, 5, 5, 5)) migrated_output = layer(inputs) # Grab the regularization loss as well migrated_regularization_loss = tf.math.add_n(layer.losses) print(migrated_regularization_loss)
# Verify that the regularization loss and output both match np.testing.assert_allclose(original_regularization_loss.numpy(), migrated_regularization_loss.numpy()) np.testing.assert_allclose(original_output.numpy(), migrated_output.numpy())

Agora você substituiu todos os compat.v1.layers individuais por camadas Keras nativas.

class NearlyFullyNativeModel(tf.keras.layers.Layer): def __init__(self, units, *args, **kwargs): super().__init__(*args, **kwargs) self.units = units self.conv_layer = tf.keras.layers.Conv2D( 3, 3, kernel_regularizer="l2") self.flatten_layer = tf.keras.layers.Flatten() self.dense_layer = tf.keras.layers.Dense( self.units, kernel_regularizer="l2") @tf.compat.v1.keras.utils.track_tf1_style_variables def call(self, inputs): with tf.compat.v1.variable_scope('model'): out = self.conv_layer(inputs) out = self.flatten_layer(out) out = self.dense_layer(out) return out
random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops') with random_tool.scope(): tf.keras.utils.set_random_seed(42) layer = NearlyFullyNativeModel(10) inputs = tf.random.normal(shape=(10, 5, 5, 5)) migrated_output = layer(inputs) # Grab the regularization loss as well migrated_regularization_loss = tf.math.add_n(layer.losses) print(migrated_regularization_loss)
# Verify that the regularization loss and output both match np.testing.assert_allclose(original_regularization_loss.numpy(), migrated_regularization_loss.numpy()) np.testing.assert_allclose(original_output.numpy(), migrated_output.numpy())

Por fim, remova qualquer uso restante de variable_scope (não é mais necessário) e o decorador track_tf1_style_variables.

Agora você tem uma versão do modelo que usa APIs totalmente nativas.

class FullyNativeModel(tf.keras.layers.Layer): def __init__(self, units, *args, **kwargs): super().__init__(*args, **kwargs) self.units = units self.conv_layer = tf.keras.layers.Conv2D( 3, 3, kernel_regularizer="l2") self.flatten_layer = tf.keras.layers.Flatten() self.dense_layer = tf.keras.layers.Dense( self.units, kernel_regularizer="l2") def call(self, inputs): out = self.conv_layer(inputs) out = self.flatten_layer(out) out = self.dense_layer(out) return out
random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops') with random_tool.scope(): tf.keras.utils.set_random_seed(42) layer = FullyNativeModel(10) inputs = tf.random.normal(shape=(10, 5, 5, 5)) migrated_output = layer(inputs) # Grab the regularization loss as well migrated_regularization_loss = tf.math.add_n(layer.losses) print(migrated_regularization_loss)
# Verify that the regularization loss and output both match np.testing.assert_allclose(original_regularization_loss.numpy(), migrated_regularization_loss.numpy()) np.testing.assert_allclose(original_output.numpy(), migrated_output.numpy())

Mantendo a compatibilidade de checkpoints durante a migração para o Native TF2

O processo de migração acima para APIs TF2 nativas alterou os nomes das variáveis ​​(já que as APIs Keras produzem nomes de pesos muito diferentes) e os caminhos orientados a objetos que apontam para pesos diferentes no modelo. O impacto dessas alterações é que elas quebrarão qualquer checkpoint baseado em nome no estilo TF1 existente ou checkpoint orientado a objeto no estilo TF2.

No entanto, em alguns casos, você pode pegar seu checkpoint original, baseado em nome, e encontrar um mapeamento das variáveis ​​para seus novos nomes com abordagens como a detalhada no Guia Reuso de checkpoints TF1.x.

Algumas dicas para tornar isso viável são as seguintes:

  • As variáveis ​​ainda têm um argumento name que você pode definir.

  • Os modelos Keras também recebem um argumento name que eles definem como prefixo para suas variáveis.

  • A função v1.name_scope pode ser usada para definir prefixos de nomes de variáveis. Isto é muito diferente de tf.variable_scope. Afeta apenas nomes e não rastreia variáveis ​​e reuso.

Levando em conta os ponteiros acima, os exemplos de código a seguir demonstram um fluxo de trabalho que você pode adaptar ao seu código para atualizar de forma incremental parte de um modelo enquanto atualiza simultaneamente os checkpoints.

Observação: devido à complexidade da nomenclatura de variáveis ​​com camadas Keras, não há garantia de que isso funcione em todos os casos de uso.

  1. Comece trocando as tf.compat.v1.layers de estilo funcional por suas versões orientadas a objetos.

class FunctionalStyleCompatModel(tf.keras.layers.Layer): @tf.compat.v1.keras.utils.track_tf1_style_variables def call(self, inputs, training=None): with tf.compat.v1.variable_scope('model'): out = tf.compat.v1.layers.conv2d( inputs, 3, 3, kernel_regularizer="l2") out = tf.compat.v1.layers.conv2d( out, 4, 4, kernel_regularizer="l2") out = tf.compat.v1.layers.conv2d( out, 5, 5, kernel_regularizer="l2") return out layer = FunctionalStyleCompatModel() layer(tf.ones(shape=(10, 10, 10, 10))) [v.name for v in layer.weights]
  1. Em seguida, atribua os objetos compat.v1.layer e quaisquer variáveis ​​criadas por compat.v1.get_variable como propriedades do objeto tf.keras.layers.Layer/tf.Module cujo método é decorado com track_tf1_style_variables (observe que agora, quaisquer checkpoints orientados a objeto em estilo TF2 irão salvar dois caminhos: um por nome de variável e o novo caminho orientado a objetos).

class OOStyleCompatModel(tf.keras.layers.Layer): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.conv_1 = tf.compat.v1.layers.Conv2D( 3, 3, kernel_regularizer="l2") self.conv_2 = tf.compat.v1.layers.Conv2D( 4, 4, kernel_regularizer="l2") @tf.compat.v1.keras.utils.track_tf1_style_variables def call(self, inputs, training=None): with tf.compat.v1.variable_scope('model'): out = self.conv_1(inputs) out = self.conv_2(out) out = tf.compat.v1.layers.conv2d( out, 5, 5, kernel_regularizer="l2") return out layer = OOStyleCompatModel() layer(tf.ones(shape=(10, 10, 10, 10))) [v.name for v in layer.weights]
  1. Salve novamente um checkpoint carregado neste ponto para salvar os caminhos pelo nome da variável (para compat.v1.layers) ou pelo grafo orientado a objetos.

weights = {v.name: v for v in layer.weights} assert weights['model/conv2d/kernel:0'] is layer.conv_1.kernel assert weights['model/conv2d_1/bias:0'] is layer.conv_2.bias
  1. Agora você pode trocar os compat.v1.layers orientados a objetos por camadas Keras nativas enquanto ainda poderá carregar o checkpoint salvo recentemente. Certifique-se de preservar os nomes das variáveis ​​para as compat.v1.layers restantes, registrando os variable_scopes gerados automaticamente das camadas substituídas. Agora, essas camadas/variáveis ​​trocadas usarão apenas o caminho do atributo do objeto para as variáveis ​​no checkpoint, em vez do caminho do nome da variável.

Em geral, você pode substituir o uso de compat.v1.get_variable em variáveis ​​anexadas a propriedades através de:

  • Trocando-as pelo uso de tf.Variable, OU

  • Atualizando-as com tf.keras.layers.Layer.add_weight. Observe que, se você não estiver trocando todas as camadas de uma só vez, poderá haver troca nos nomes das camadas/variáveis geradas automaticamente para as compat.v1.layers restantes que não tiverem um argumento name. Se for esse o caso, você deve manter os nomes das variáveis ​​para as compat.v1.layers restantes abrindo e fechando manualmente um variable_scope correspondente ao nome do escopo gerado pela compat.v1.layer removida. Caso contrário, os caminhos dos checkpoints existentes podem entrar em conflito e o carregamento do checkpoint ocorrerá de forma incorreta.

def record_scope(scope_name): """Record a variable_scope to make sure future ones get incremented.""" with tf.compat.v1.variable_scope(scope_name): pass class PartiallyNativeKerasLayersModel(tf.keras.layers.Layer): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.conv_1 = tf.keras.layers.Conv2D( 3, 3, kernel_regularizer="l2") self.conv_2 = tf.keras.layers.Conv2D( 4, 4, kernel_regularizer="l2") @tf.compat.v1.keras.utils.track_tf1_style_variables def call(self, inputs, training=None): with tf.compat.v1.variable_scope('model'): out = self.conv_1(inputs) record_scope('conv2d') # Only needed if follow-on compat.v1.layers do not pass a `name` arg out = self.conv_2(out) record_scope('conv2d_1') # Only needed if follow-on compat.v1.layers do not pass a `name` arg out = tf.compat.v1.layers.conv2d( out, 5, 5, kernel_regularizer="l2") return out layer = PartiallyNativeKerasLayersModel() layer(tf.ones(shape=(10, 10, 10, 10))) [v.name for v in layer.weights]

Salvar um checkpoint nesta etapa depois da construção das variáveis ​​fará com que ele contenha apenas os caminhos de objeto atualmente disponíveis.

Não deixe de registrar os escopos das compat.v1.layers removidas para preservar os nomes de peso gerados automaticamente para as compat.v1.layers restantes.

weights = set(v.name for v in layer.weights) assert 'model/conv2d_2/kernel:0' in weights assert 'model/conv2d_2/bias:0' in weights
  1. Repita os passos acima até ter substituído todos os compat.v1.layers e compat.v1.get_variable do seu modelo por equivalentes totalmente nativos.

class FullyNativeKerasLayersModel(tf.keras.layers.Layer): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.conv_1 = tf.keras.layers.Conv2D( 3, 3, kernel_regularizer="l2") self.conv_2 = tf.keras.layers.Conv2D( 4, 4, kernel_regularizer="l2") self.conv_3 = tf.keras.layers.Conv2D( 5, 5, kernel_regularizer="l2") def call(self, inputs, training=None): with tf.compat.v1.variable_scope('model'): out = self.conv_1(inputs) out = self.conv_2(out) out = self.conv_3(out) return out layer = FullyNativeKerasLayersModel() layer(tf.ones(shape=(10, 10, 10, 10))) [v.name for v in layer.weights]

Lembre-se de testar para garantir que o checkpoint recém-atualizado ainda se comporte conforme o esperado. Aplique as técnicas descritas no guia de validação da exatidão numérica em cada passo incremental deste processo para garantir que seu código migrado seja executado corretamente.

Lidando com mudanças de comportamento na migração de TF1.x para TF2 não cobertas pelos shims de modelagem

Os shims de modelagem descritos neste guia podem garantir que variáveis, camadas e perdas de regularização criadas com get_variable, tf.compat.v1.layers e semânticas variable_scope continuem a funcionar como antes ao usar execução antecipada (eager) e tf.function, sem precisar depender de coleções.

Isto não abrange todas as semânticas específicas do TF1.x das quais podem depender os passos para frente do seu modelo. Em alguns casos, os shims podem ser insuficientes para garantir que o passo para frente do seu modelo rode no TF2 por conta própria. Leia o Guia de comportamentos TF1.x vs TF2 para saber mais sobre as diferenças comportamentais entre TF1.x e TF2.