Path: blob/master/site/pt-br/guide/migrate/tf1_vs_tf2.ipynb
25118 views
Copyright 2021 The TensorFlow Authors.
TensorFlow 1.x vs TensorFlow 2 – Comportamentos e APIs
Nos bastidores, o TensorFlow 2 segue um paradigma de programação fundamentalmente diferente do TF1.x.
Este guia descreve as diferenças fundamentais entre TF1.x e TF2 em termos de comportamentos e APIs, e como tudo isso se relaciona com sua jornada de migração.
Resumo de alto nível das principais mudanças
Fundamentalmente, TF1.x e TF2 apresentam um conjunto diferente de comportamentos em tempo de execução quanto ao modo de execução (eager no TF2), variáveis, fluxo de controle, formatos de tensor e comparações de igualdade entre tensores. Para ser compatível com TF2, seu código precisa ser compatível com o conjunto completo de comportamentos do TF2. Durante a migração, você poderá ativar ou desativar a maioria desses comportamentos individualmente através das APIs tf.compat.v1.enable_*
ou tf.compat.v1.disable_*
. A única exceção é a remoção de coleções, que é um efeito colateral da ativação/desativação da execução eager.
Em alto nível, o TensorFlow 2:
Remove APIs redundantes.
Deixa as APIs mais consistentes - por exemplo, RNNs unificadas e Otimizadores unificados.
Prefere funções em vez de sessões e integra-se melhor ao runtime do Python com execução eager ativada por padrão junto com
tf.function
que fornece dependências de controle automático para grafos e compilação.Descontinua as coleções de grafos globais.
Altera a semântica de concorrência de variáveis usando
ResourceVariables
em vez deReferenceVariables
.Suporta fluxo de controle diferenciável e baseado em funções (fluxo de controle v2).
Simplifica a API TensorShape para conter valores
int
em vez de objetostf.compat.v1.Dimension
.Atualiza a mecânica de igualdade de tensores. Em TF1.x, o operador
==
em tensores e variáveis verificava a igualdade entre referências do objeto. No TF2 ele verifica a igualdade de valores. Além disso, não é maios possível fazer hash de tensores/variáveis, mas você pode fazer hash em referências de objetos para eles viavar.ref()
se precisar usá-los em conjuntos ou como chaves em umdict
.
As seções abaixo fornecem mais contexto sobre as diferenças entre TF1.x e TF2. Para saber mais sobre o processo de design por trás do TF2, leia as RFCs e os documentos de design.
Limpeza de APIs
Muitas APIs ou desapareceram ou foram movidas no TF2. Algumas das principais mudanças incluem a remoção de tf.app
, tf.flags
e tf.logging
em favor do agora do código aberto absl-py, realojando projetos que viviam em tf.contrib
e limpando o namespace principal tf.*
ao mover funções menos usadas para subpacotes como tf.math
. Algumas APIs foram substituídas por equivalentes no TF2 - tf.summary
, tf.keras.metrics
e tf.keras.optimizers
.
tf.compat.v1
: endpoints de API legados e de compatibilidade
Os símbolos no contexto dos namespaces tf.compat
e tf.compat.v1
não são considerados APIs do TF2. Esses namespaces expõem uma mistura de símbolos de compatibilidade, bem como endpoints de API herdados do TF 1.x. O objetivo é ajudar na migração do TF1.x para o TF2. No entanto, como nenhuma dessas APIs compat.v1
são APIs TF2 idiomáticas, elas não devem ser usadas para escrever código TF2 novo.
Símbolos tf.compat.v1
individuais podem ser compatíveis com TF2 porque continuam a funcionar mesmo com comportamentos do TF2 habilitados (como tf.compat.v1.losses.mean_squared_error
), enquanto outros são incompatíveis com TF2 (como tf.compat.v1.metrics.accuracy
). Muitos símbolos compat.v1
(embora não todos) contêm informações de migração dedicadas em sua documentação que explicam seu grau de compatibilidade com comportamentos do TF2, bem como como migrá-los para APIs do TF2.
O script de upgrade do TF2 pode mapear muitos símbolos da API compat.v1
para APIs do TF2 equivalentes no caso em que eles sejam aliases ou tenham os mesmos argumentos, mas com uma ordem diferente. Você também pode usar o script de upgrade para renomear automaticamente APIs TF1.x.
Falsos cognatos
Há um conjunto de símbolos que são falsos cognatos encontrados no namespace tf
do TF2 (não em compat.v1
) e que na verdade ignoram os comportamentos do TF2 nos bastidores e/ou não são totalmente compatíveis com o conjunto completo de comportamentos do TF2. Como tal, é provável que essas APIs se comportem mal com o código TF2, potencialmente de forma silenciosa.
tf.estimator.*
: os estimadores criam e usam grafos e sessões nos bastidores. Como tal, estes não devem ser considerados compatíveis com TF2. Se o seu código executa estimadores, ele não está usando comportamentos do TF2.keras.Model.model_to_estimator(...)
: isto cria um estimador nos bastidores, que, como mencionado acima, não é compatível com TF2.tf.Graph().as_default()
: isto insere os comportamentos do grafo TF1.x e não segue os comportamentostf.function
padrão compatíveis com o TF2. O código que entra em grafos como este geralmente os executará via Sessions e não deve ser considerado compatível com TF2.tf.feature_column.*
As APIs da coluna de características geralmente dependem da criação de variáveistf.compat.v1.get_variable
no estilo TF1 e assumem que as variáveis criadas serão acessadas via coleções globais. Como o TF2 não oferece suporte a coleções, as APIs podem não funcionar corretamente ao executá-las com os comportamentos do TF2 habilitados.
Outras alterações na API
O TF2 traz melhorias significativas nos algoritmos de posicionamento de dispositivos, o que torna desnecessário o uso de
tf.colocate_with
. Se removê-lo causar degradação no desempenho, por favor, registre um bug.Substitua todo o uso de
tf.v1.ConfigProto
por funções equivalentes detf.config
.
Execução antecipada (eager)
TF1.x exigia que os usuários construíssem manualmente uma árvore de sintaxe abstrata (o grafo), fazendo chamadas de API tf.*
e, em seguida, compilassem manualmente a árvore de sintaxe abstrata, passando um conjunto de tensores de saída e tensores de entrada para uma chamada session.run
. O TF2 é executado em modo eager (como o Python normalmente é) e isto faz com que grafos e as sessões pareçam detalhes de implementação.
Um subproduto notável da execution eager é que tf.control_dependencies
não é mais necessário, já que todas as linhas de código são executadas sequencialmente (dentro de uma tf.function
, os códigos com efeitos colaterais são executados na ordem em que foram escritos).
O fim das variáveis globais
O TensorFlow 1.X dependia muito de namespaces implicitamente globais. Quando você chamava tf.Variable
, ela era colocada no grafo padrão e permanecia lá, mesmo se você perdesse a variável do Python que apontava para ela. Depois, você poderia recuperar essa tf.Variable
, mas somente se soubesse o nome com que foi criada originalmente. Isto era difícil de fazer se você não tivesse controle sobre a criação da variável. Como resultado, houve uma proliferação de mecanismos para tentar ajudar os usuários a encontrar suas variáveis novamente e para os frameworks encontrarem variáveis criadas por usuários: escopos de variáveis, coleções globais, métodos auxiliares, como tf.get_global_step
e tf.global_variables_initializer
, otimizadores computando gradientes implicitamente sobre todas as variáveis treináveis e assim por diante. O TensorFlow 2.0 elimina todos esses mecanismos (Variables 2.0 RFC), dando lugar ao mecanismo padrão: manter o controle de suas variáveis! Se você perder o controle de uma tf.Variable
, ela é recolhida pelo coletor de lixo.
O requisito de rastrear variáveis cria algum trabalho extra, mas com ferramentas como os shims de modelagem e comportamentos como coleções implícitas de variáveis orientadas a objetos em tf.Module
s e tf.keras.layers.Layer
, o trabalho é minimizado.
Funções, não sessões
Uma chamada session.run
é quase como uma chamada de função: você especifica as entradas e a função a ser chamada e recebe de volta um conjunto de saídas. No TF2, você pode decorar uma função Python usando tf.function
para marcá-la para compilação JIT para que o TensorFlow a execute como um único grafo (Functions 2.0 RFC). Este mecanismo permite que o TF2 obtenha todos os benefícios do modo grafo:
Desempenho: A função pode ser otimizada (remoção de nós, fusão de kernel, etc.)
Portabilidade: a função pode ser exportada/reimportada (SavedModel 2.0 RFC), permitindo reutilizar e compartilhar funções modulares do TensorFlow.
Com o poder de intercalar livremente o código Python e TensorFlow, você pode aproveitar a expressividade do Python. No entanto, o TensorFlow portátil é executado em contextos sem um interpretador Python, como dispositivos móveis, C++ e JavaScript. Para ajudar a evitar reescrever seu código ao adicionar tf.function
, use AutoGraph para converter um subconjunto de construtos do Python em seus equivalentes do TensorFlow:
for
/while
->tf.while_loop
(break
econtinue
são suportados)if
->tf.cond
for _ in dataset
->dataset.reduce
O AutoGraph oferece suporte a aninhamentos arbitrários de fluxo de controle, o que torna possível implementar de maneira concisa e com bom desempenho muitos programas complexos de aprendizado de máquina, como modelos de sequência, aprendizado por reforço, loops de treinamento personalizados e muito mais.
Adaptação mudanças de comportamento do TF 2.x
Sua migração para o TF2 só estará concluída depois que você migrar para o conjunto completo de comportamentos do TF2. O conjunto completo de comportamentos pode ser ativado ou desativado via tf.compat.v1.enable_v2_behaviors
e tf.compat.v1.disable_v2_behaviors
. As seções abaixo discutem detalhadamente cada mudança importante de comportamento.
Usando tf.function
As maiores mudanças em seus programas durante a migração provavelmente virão da mudança fundamental de paradigma do modelo de programação de grafos e sessões para execução eager e tf.function
. Consulte os guias de migração do TF2 para saber mais sobre como migrar de APIs incompatíveis com execução eager e tf.function
para APIs compatíveis.
Observação: durante a migração, você poderá optar por ativar e desativar diretamente a execução eager com tf.compat.v1.enable_eager_execution
e tf.compat.v1.disable_eager_execution
, mas isto só pode ser feito uma vez durante a vida útil do seu programa.
Abaixo estão alguns padrões de programação comuns não vinculados a nenhuma API que podem causar problemas ao trocar objetos tf.Graph
por tf.compat.v1.Session
para execução eager com objetos tf.function
.
Padrão 1: manipulação de objetos Python e variáveis destinadas a serem criadas apenas uma vez e executadas múltiplas vezes
Em programas TF1.x que dependem de grafos e sessões, a expectativa geralmente é que toda a lógica Python no seu programa seja executada apenas uma vez. No entanto, com execução eager e tf.function
é justo esperar que sua lógica Python seja executada pelo menos uma vez, mas possivelmente mais vezes (várias vezes em modo eager ou várias vezes em diferentes rastreamentos de tf.function
). Às vezes, tf.function
irá rastrear duas vezes na mesma entrada, causando comportamentos inesperados (veja os Exemplos 1 e 2). Consulte o guia tf.function
para obter mais detalhes.
Observação: Esse padrão geralmente faz com que seu código se comporte mal silenciosamente ao executar de forma eager sem objetos tf.function
, mas geralmente gera um InaccessibleTensorError
ou um ValueError
ao tentar empacotar o código problemático dentro de um tf.function
. Para descobrir e depurar esse problema, é recomendável empacotar seu código com tf.function
desde o início e usar pdb ou depuração interativa para identificar a origem do InaccessibleTensorError
.
Exemplo 1: criação de variáveis
Considere o exemplo abaixo, onde a função cria uma variável quando chamada:
Não é permitido, porém, empacotar ingenuamente a função acima que contém a criação de variáveis com tf.function
, pois tf.function
suporta apenas criações de variáveis singleton na primeira chamada. Para garantir isso, quando tf.function detectar a criação de variável na primeira chamada, ele tentará rastrear novamente e gerará um erro se acontecer a criação da variável no segundo rastreamento.
Uma solução alternativa é armazenar em cache e reutilizar a variável depois de ela ser criada na primeira chamada.
Exemplo 2: tensores fora do escopo devido ao rastreamento repetido de tf.function
Conforme demonstrado no Exemplo 1, tf.function
irá rastrear novamente quando detectar a criação de variável na primeira chamada. Isto pode causar uma confusão extra, pois os dois rastreamentos criarão dois grafos. Quando o grafo criado no segundo rastreamento tenta acessar um Tensor do grafo gerado durante o primeiro rastreamento, o Tensorflow lançará um erro reclamando que o Tensor está fora do escopo. Para demonstrar o cenário, o código abaixo cria um dataset na primeira chamada tf.function
. Isto iria executar como esperado.
No entanto, se também tentarmos criar uma variável na primeira chamada tf.function
, o código produzirá um erro reclamando que o dataset está fora do escopo. Isto ocorre porque o dataset está no primeiro grafo, enquanto que o segundo grafo também tenta acessá-lo.
A solução mais simples é garantir que a criação da variável e a criação do dataset estejam fora da chamada tf.function
. Por exemplo:
No entanto, às vezes não há como evitar a criação de variáveis em tf.function
(como variáveis de slot em alguns otimizadores TF keras). Ainda assim, podemos simplesmente mover a criação do dataset para fora da chamada tf.function
. A razão pela qual podemos confiar nisso é porque tf.function
receberá o dataset como uma entrada implícita e ambos os grafos poderão acessá-lo corretamente.
Exemplo 3: recriações inesperadas de objetos Tensorflow devido ao uso de dict
tf.function
tem suporte muito ruim para efeitos colaterais do python, como anexar a uma lista ou verificar/adicionar a um dicionário. Mais detalhes estão em "Melhor desempenho com tf.function". No exemplo abaixo, o código usa dicionários para armazenar datasets e iteradores em cache. Para a mesma chave, cada chamada ao modelo retornará o mesmo iterador do dataset.
No entanto, o padrão acima não funcionará como esperado em tf.function
. Durante o rastreamento, tf.function
irá ignorar o efeito colateral do Python da adição aos dicionários. Em vez disso, ele apenas lembra a criação de um novo dataset e iterador. Como resultado, cada chamada ao modelo sempre retornará um novo iterador. Este problema é difícil de perceber, a menos que os resultados numéricos ou o desempenho sejam suficientemente significativos. Portanto, recomendamos que os usuários pensem cuidadosamente no código antes de empacotar tf.function
ingenuamente em código python.
Podemos usar tf.init_scope
para elevar o dataset e a criação do iterador para fora do grafo, e assim alcançar o comportamento esperado:
A regra geral é evitar depender dos efeitos colaterais do Python em sua lógica e usá-los apenas para depurar seus rastreamentos.
Exemplo 4: manipulando uma lista Python global
O código TF1.x a seguir usa uma lista global de perdas que é usada apenas para manter a lista de perdas geradas pelo passo de treinamento atual. Observe que a lógica Python que anexa perdas à lista será chamada apenas uma vez, independentemente de quantos passos de treinamento forem executados para a sessão.
No entanto, se esta lógica Python for ingenuamente mapeada para TF2 com execução eager, a lista global de perdas terá novos valores anexados a ela em cada passo de treinamento. Isto significa que o código do passo de treinamento, que anteriormente esperava que a lista contivesse apenas as perdas do passo de treinamento atual, agora vê na verdade a lista de perdas de todos os passos de treinamento executados até o momento. Esta é uma mudança de comportamento não intencional, e a lista precisará ser apagada no início de cada passo ou tornada local no passo de treinamento.
Padrão 2: um tensor simbólico destinado a ser recomputado a cada passo no TF1.x é acidentalmente armazenado em cache com o valor inicial ao mudar para o modo eager.
Este padrão geralmente faz com que seu código se comporte mal silenciosamente ao executar de forma eager fora de objetos tf.function, mas gera um InaccessibleTensorError
se o cache do valor inicial ocorrer dentro de um tf.function
. No entanto, esteja ciente de que, para evitar o Padrão 1 acima, você muitas vezes estruturará inadvertidamente seu código de forma que esse cache de valor inicial aconteça fora de qualquer tf.function
que possa gerar um erro. Portanto, tome cuidado adicional se souber que seu programa pode ser suscetível a esse padrão.
A solução geral para esse padrão é reestruturar o código ou usar callables do Python, se necessário, para garantir que o valor seja recalculado a cada vez, em vez de ser armazenado em cache acidentalmente.
Exemplo 1: Taxa de aprendizagem/hiperparâmetro/etc. cronogramas que dependem de um passo global
No trecho de código a seguir, a expectativa é que toda vez que a sessão for executada, o valor global_step
mais recente seja lido e uma nova taxa de aprendizado seja computada.
No entanto, ao tentar mudar para eager, tenha cuidado para não acabar com a taxa de aprendizagem sendo computada apenas uma vez e depois reutilizada, em vez de seguir o cronograma pretendido:
Como este exemplo específico é um padrão comum e os otimizadores devem ser inicializados apenas uma vez, e não em cada passo de treinamento, os otimizadores TF2 suportam cronogramas tf.keras.optimizers.schedules.LearningRateSchedule
ou chamadas Python como argumentos para a taxa de aprendizagem e outros hiperparâmetros.
Exemplo 2: inicializações simbólicas de números aleatórios passados como atributos de objetos e reutilizados via ponteiros são acidentalmente armazenados em cache ao mudar para o modo eager
Considere o seguinte módulo NoiseAdder
:
Usá-lo da seguinte maneira em TF1.x calculará um novo tensor de ruído aleatório toda vez que a sessão for executada:
No entanto, no TF2, inicializar o noise_adder
no início fará com que o noise_distribution
seja computado apenas uma vez e fique congelado para todos os passos de treinamento:
Para corrigir isso, refatore NoiseAdder
para chamar tf.random.normal
toda vez que um novo tensor aleatório for necessário, em vez de se referir ao mesmo objeto tensor todas as vezes.
Padrão 3: o código TF1.x depende diretamente de tensores e os procura por nome
É comum que os testes de código TF1.x dependam da verificação de quais tensores ou operações estão presentes em um grafo. Em alguns casos raros, o código de modelagem também dependerá dessas pesquisas por nome.
Os nomes dos tensores não são gerados durante a execução eager fora de tf.function
, portanto, todos os usos de tf.Tensor.name
devem acontecer dentro de um tf.function
. Tenha em mente que os nomes reais gerados provavelmente serão diferentes entre TF1.x e TF2, mesmo dentro do mesmo tf.function
, e as garantias da API não garantem a estabilidade dos nomes gerados em diferentes versões do TF.
Nota: nomes de variáveis ainda são gerados mesmo fora de tf.function
, mas também não é garantido que seus nomes correspondam entre TF1.x e TF2, exceto ao seguir as seções relevantes do guia de mapeamento de modelos.
Padrão 4: a sessão TF1.x executa seletivamente apenas parte do grafo gerado
No TF1.x, você pode construir um grafo e, em seguida, optar por executar seletivamente apenas um subconjunto dele com uma sessão, escolhendo um conjunto de entradas e saídas que não exigem a execução de cada operação no grafo.
Por exemplo, você pode ter um gerador e um discriminador dentro de um único grafo e usar chamadas tf.compat.v1.Session.run
separadas para alternar entre treinar apenas o discriminador ou apenas treinar o gerador.
No TF2, devido às dependências de controle automático em tf.function
e à execução eager, não há pruning seletivo dos rastreamentos tf.function
. Um grafo completo contendo todas as atualizações de variáveis seria executado mesmo se, por exemplo, apenas a saída do discriminador ou do gerador fosse gerada como saída do tf.function
.
Portanto, você precisaria ou usar múltiplos tf.function
contendo diferentes partes do programa ou um argumento condicional para o tf.function
que você poderia usar para decidir executar apenas as coisas que você realmente quer que sejam executadas.
Remoção de coleções
Quando a execução eager está ativada, as APIs compat.v1
relacionadas à coleção de grafos (incluindo aquelas que leem ou gravam em coleções ocultas, como tf.compat.v1.trainable_variables
) não estarão mais disponíveis. Algumas podem gerar ValueError
, enquanto outras podem retornar silenciosamente listas vazias.
O uso mais comum de coleções no TF1.x é manter inicializadores, o passo global, pesos, perdas de regularização, perdas de saída do modelo e atualizações de variáveis que precisam ser executadas, como nas camadas BatchNormalization
.
Veja como lidar com cada um desses usos comuns:
Inicializadores - ignore. A inicialização manual de variáveis não é necessária com a execução eager ativada.
Passo global – consulte a documentação de
tf.compat.v1.train.get_or_create_global_step
para obter instruções de migração.Pesos - mapeie seus modelos para objetos
tf.Module
/tf.keras.layers.Layer
/tf.keras.Model
seguindo as orientações no guia de mapeamento de modelos e, em seguida, use seus respectivos mecanismos de rastreamento de pesos, comotf.module.trainable_variables
.Perdas de regularização – mapeie seus modelos para objetos
tf.Module
/tf.keras.layers.Layer
/tf.keras.Model
seguindo as orientações no guia de mapeamento de modelos e, em seguida, usetf.keras.losses
. Alternativamente, você também pode rastrear manualmente suas perdas de regularização.Perdas de saída do modelo - use mecanismos de gerenciamento de perdas
tf.keras.Model
ou rastreie separadamente suas perdas sem usar coleções.Atualizações de peso – ignore esta coleção. Execução eager e
tf.function
(com dependências de autógrafo e controle automático) significam que todas as atualizações de variáveis serão executadas automaticamente. Portanto, você não terá que executar explicitamente todas as atualizações de peso no final, mas observe que isto significa que as atualizações de peso podem acontecer num momento diferente do que aconteciam no seu código TF1.x, dependendo de como você estava usando as dependências de controle.Resumos – Veja o guia da API sobre migração de resumos.
O uso de coleções mais complexas (como o uso de coleções personalizadas) pode exigir que você refatore seu código para manter seus próprios armazenamentos globais ou para que ele não dependa de nenhum armazenamento global.
ResourceVariables
em vez de ReferenceVariables
ResourceVariables
têm garantias de consistência de leitura e gravação mais fortes do que ReferenceVariables
. Isso leva a uma semântica mais previsível e mais fácil de entender se você observará ou não o resultado de uma gravação anterior ao usar suas variáveis. É extremamente improvável que essa alteração faça com que o código existente gere erros ou falhe silenciosamente.
No entanto, é possível, embora improvável, que essas garantias de consistência mais fortes aumentem o uso de memória do seu programa específico. Registre um issue se você achar que esse é o caso. Além disso, se você tiver testes de unidade que dependam de comparações exatas de strings com os nomes dos operadores em um grafo correspondente às leituras de variáveis, esteja ciente de que a ativação de variáveis de recursos poderá alterar ligeiramente os nomes desses operadores.
Para isolar o impacto dessa mudança de comportamento em seu código, se a execução eager estiver ativada, você pode usar tf.compat.v1.disable_resource_variables()
e tf.compat.v1.enable_resource_variables()
para desativar ou ativar globalmente essa mudança de comportamento. As ResourceVariables
sempre serão usadas se a execução eager estiver ativada.
Controle de fluxo v2
No TF1.x, as ops de controle de fluxo são ops como tf.cond
e tf.while_loop
, operações de baixo nível inline, como Switch
, Merge
, etc. O TF2 fornece operações de controle de fluxo com funcionalidade aprimorada que são implementadas com rastreamentos tf.function
separados para cada ramo e que suportam diferenciação de ordem superior.
Para isolar o impacto dessa mudança de comportamento em seu código, se a execução eager estiver desativada, você pode usar tf.compat.v1.disable_control_flow_v2()
e tf.compat.v1.enable_control_flow_v2()
para desativar ou ativar globalmente essa mudança de comportamento. No entanto, você só poderá desativar o fluxo de controle v2 se a execução eager também estiver desativada. Se estiver ativada, o fluxo de controle v2 sempre será utilizado.
Esta mudança de comportamento pode alterar drasticamente a estrutura dos programas TF gerados que usam fluxo de controle, pois eles conterão vários rastreamento de função aninhados em vez de um grafo plano. Portanto, qualquer código que seja altamente dependente da semântica exata dos rastreamentos produzidos pode exigir algumas modificações, que incluem:
Código que depende de nomes de operadores e tensores
Código que possui referências a tensores criados dentro de um ramo de fluxo de controle do TensorFlow chamadas de fora desse ramo. É provável que isto produza um
InaccessibleTensorError
É esperado que essa mudança de comportamento tenha uma mudança de desempenho neutro ou positivo, mas se você encontrar um problema em que o fluxo de controle v2 tem um desempenho pior do que o fluxo de controle TF1.x, registre um issue descrevendo as etapas de reprodução do problema.
Mudanças de comportamento na API TensorShape
A classe TensorShape
foi simplificada para conter valores int
, em vez de objetos tf.compat.v1.Dimension
. Portanto, não há necessidade de chamar .value
para obter um int
.
Objetos tf.compat.v1.Dimension
individuais ainda podem ser acessados em tf.TensorShape.dims
.
Para isolar o impacto dessa mudança de comportamento em seu código, você pode usar tf.compat.v1.disable_v2_tensorshape()
e tf.compat.v1.enable_v2_tensorshape()
para desativar ou ativar globalmente essa mudança de comportamento.
A seguir demonstramos as diferenças entre TF1.x e TF2.
Se você tinha isto no TF1.x:
Então faça isto no TF2:
Se você tinha isto no TF1.x:
Então faça isto no TF2:
Se você tinha isto no TF1.x (ou usou qualquer outro método de dimensão):
Então faça isto no TF2:
O valor booleano de um tf.TensorShape
é True
se a classificação for conhecida, False
caso contrário.
Erros potenciais devido a alterações no TensorShape
É improvável que as mudanças de comportamento do TensorShape quebrem silenciosamente seu código. No entanto, você poderá perceber que códigos relacionados a formatos começam a gerar AttributeError
, pois valoresint
e None
não têm os mesmos atributos que objetos tf.compat.v1.Dimension
. Abaixo estão alguns exemplos de AttributeError
:
Igualdade de tensores por valor
Os operadores binários ==
e !=
em variáveis e tensores foram alterados para comparar por valor no TF2 em vez de comparar por referência de objeto como em TF1.x. Além disso, não é mais possível fazer hash em tensores e variáveis ou utilizá-los em conjuntos ou chaves de dicts, porque pode não ser possível fazer hash deles por valor. Em vez disso, eles expõem um método .ref()
que você pode usar para obter uma referência com hash ao tensor ou variável.
Para isolar o impacto dessa mudança de comportamento, você pode usar tf.compat.v1.disable_tensor_equality()
e tf.compat.v1.enable_tensor_equality()
para desativar ou ativar globalmente essa mudança de comportamento.
Por exemplo, no TF1.x, duas variáveis com o mesmo valor retornarão falso quando você usar o operador ==
:
Enquanto que no TF2 com verificações de igualdade de tensor ativadas, x == y
retornará True
.
Então, no TF2, se você precisar comparar por referência de objeto, certifique-se de usar is
e is not
Hash em tensores e variáveis
Com os comportamentos do TF1.x, você costumava adicionar variáveis e tensores diretamente a estruturas de dados que exigem hashing, como chaves para objetos set
e dict
.
No entanto, no TF2 com igualdade de tensores ativada, não é possível fazer hash de tensores e variáveis devido à mudança da semântica do operador ==
e !=
para verificações de igualdade de valor.
Portanto, no TF2, se você precisar usar tensores ou objetos variáveis como chaves ou conteúdo de um set
, poderá usar tensor.ref()
para obter uma referência com hash que poderá ser usada como chave:
Se necessário, você também pode obter o tensor ou variável da referência usando reference.deref()
:
Recursos e leitura adicional
Veja a seção Migrando para TF2 para ler mais sobre como migrar do TF1.x para TF2.
Leia o guia de mapeamento de modelos para saber mais sobre como mapear seus modelos TF1.x para trabalhar diretamente no TF2.