Path: blob/master/site/pt-br/guide/advanced_autodiff.ipynb
25115 views
Copyright 2020 The TensorFlow Authors.
Diferenciação automática avançada
O guia Introdução aos gradientes e diferenciação automática inclui tudo o que é necessário para calcular gradientes no TensorFlow. Este guia se concentra em recursos mais profundos e menos comuns da API tf.GradientTape
.
Configuração
Controlando a gravação de gradientes
No guia de diferenciação automática você viu como controlar quais variáveis e tensores são observados pela fita (tape) durante a construção do cálculo do gradiente.
A fita também possui métodos para manipular a gravação.
Pare de gravar
Se você quiser interromper a gravação de gradientes, pode usar tf.GradientTape.stop_recording
para suspender temporariamente a gravação.
Isto pode ser útil para reduzir a sobrecarga se você não quiser diferenciar uma operação complicada no meio do seu modelo. Isso pode incluir o cálculo de uma métrica ou de um resultado intermediário:
Reinicie/inicie a gravação do zero
Se você deseja recomeçar do zero, use tf.GradientTape.reset
. Simplesmente sair do bloco de fita de gradiente e reiniciar geralmente é mais fácil de ler, mas você pode usar o método reset
quando sair do bloco de fita for difícil ou impossível.
Pare o fluxo do gradiente com precisão
Em contraste com os controles globais de fita, mostrados acima, a função tf.stop_gradient
é muito mais precisa. Ela pode ser usada para impedir que gradientes fluam ao longo de um caminho específico, sem a necessidade de acesso à própria fita:
Gradientes personalizados
Em alguns casos, você talvez queira controlar exatamente como os gradientes são calculados, em vez de usar o padrão. Essas situações incluem:
Não há um gradiente definido para um novo op que você está escrevendo.
Os cálculos padrão são numericamente instáveis.
Você quer armazenar em cache uma computação cara do passo para frente.
Você quer modificar um valor (por exemplo, usando
tf.clip_by_value
outf.math.round
) sem modificar o gradiente.
No primeiro caso, para escrever um novo op, você pode usar tf.RegisterGradient
para configurar o seu (consulte a documentação da API para obter detalhes). (Observe que o registro de gradiente é global, portanto altere-o com cuidado.)
Para os últimos três casos, você pode usar tf.custom_gradient
.
Aqui está um exemplo que aplica tf.clip_by_norm
ao gradiente intermediário:
Consulte a documentação da API do decorador tf.custom_gradient
para mais detalhes.
Gradientes personalizados em SavedModel
Observação: esse recurso está disponível no TensorFlow 2.6.
Gradientes personalizados podem ser salvos em SavedModel usando a opção tf.saved_model.SaveOptions(experimental_custom_gradients=True)
.
Para ser salva no SavedModel, a função gradiente deve ser rastreável (para saber mais, confira o guia Melhor desempenho com tf.function).
Uma observação sobre o exemplo acima: se você tentar substituir o código acima por tf.saved_model.SaveOptions(experimental_custom_gradients=False)
, o gradiente ainda produzirá o mesmo resultado no carregamento. A razão é que o registro de gradiente ainda contém o gradiente personalizado usado na função call_custom_op
. No entanto, se você reiniciar o runtime depois de salvar sem gradientes personalizados, a execução do modelo carregado em tf.GradientTape
irá produzir o erro: LookupError: No gradient defined for operation 'IdentityN' (op type: IdentityN)
.
Múltiplas fitas
Múltiplas fitas interagem perfeitamente.
Por exemplo, aqui cada fita assiste a um conjunto diferente de tensores:
Gradientes de ordem superior
As operações dentro do gerenciador de contexto tf.GradientTape
são gravadas para diferenciação automática. Se forem computados gradientes nesse contexto, a computação do gradiente também será gravada. Como resultado, a mesma API funcionará também para gradientes de ordem superior.
Por exemplo:
Embora isto forneça a segunda derivada de uma função escalar, esse padrão não pode ser generalizado para produzir uma matriz Hessiana, já que tf.GradientTape.gradient
calcula apenas o gradiente de um escalar. Para construir uma matriz Hessiana, veja o exemplo Hessiano na seção Jacobiana.
"Chamadas aninhadas para tf.GradientTape.gradient
" é um bom padrão quando você está calculando um escalar a partir de um gradiente e, em seguida, o escalar resultante atua como origem para um segundo cálculo de gradiente, como no exemplo a seguir.
Exemplo: regularização de gradiente de entrada
Muitos modelos são suscetíveis a “exemplos adversários”. Esta coleção de técnicas modifica a entrada do modelo para confundir a sua saída. A implementação mais simples - como o Exemplo Adversário usando o ataque Fast Gradient Signed Method - dá um único passo ao longo do gradiente da saída em relação à entrada; o "gradiente de entrada".
Uma técnica usada para aumentar a robustez para exemplos adversários é a regularização do gradiente de entrada (Finlay & Oberman, 2019), que tenta minimizar a magnitude do gradiente de entrada. Se o gradiente de entrada for pequeno, a mudança na saída também deverá ser pequena.
Abaixo está uma implementação ingênua da regularização do gradiente de entrada. A implementação consiste em:
Calcular o gradiente da saída em relação à entrada usando uma fita interna.
Calcular a magnitude desse gradiente de entrada.
Calcular o gradiente dessa magnitude em relação ao modelo.
Jacobianos
Todos os exemplos anteriores obtiveram os gradientes de um destino escalar em relação a algum(s) tensor(es) de origem.
A matriz Jacobiana representa os gradientes de uma função com valor vetorial. Cada linha contém o gradiente de um dos elementos do vetor.
O método tf.GradientTape.jacobian
permite calcular com eficiência uma matriz Jacobiana.
Observe que:
Como
gradient
: o argumentosources
pode ser um tensor ou um container de tensores.Diferentemente de
gradient
: o tensortarget
deve ser um único tensor.
Origem escalar
Como primeiro exemplo, eis o Jacobiano de um destino vetorial em relação a uma origem escalar.
Quando você considera o Jacobiano em relação a um escalar, o resultado tem o formato do destino e fornece o gradiente de cada elemento em relação à origem:
Origem do tensor
Independente da entrada ser escalar ou tensor, tf.GradientTape.jacobian
calcula com eficiência o gradiente de cada elemento da origem em relação a cada elemento do(s) destino(s).
Por exemplo, a saída desta camada tem o formato (10, 7)
:
E o formato do kernel da camada é (5, 10)
:
O formato do Jacobiano da saída em relação ao kernel são esses dois formatos concatenados:
Se você somar as dimensões do destino, ficará com o gradiente da soma que teria sido calculado por tf.GradientTape.gradient
:
Embora tf.GradientTape
não forneça um método explícito para construir uma matriz Hessiana, é possível construir uma usando o método tf.GradientTape.jacobian
.
Observação: A matriz Hessiana contém N**2
parâmetros. Por esta e outras razões não é útil para a maioria dos modelos. Este exemplo foi incluído mais como uma demonstração de como usar o método tf.GradientTape.jacobian
e não é uma recomendação à otimização direta baseada no Hessiano. Um produto vetorial Hessiano pode ser calculado eficientemente com fitas aninhadas e é uma abordagem muito mais eficiente para otimização de segunda ordem.
Para usar este Hessiano para um passo do método de Newton, você primeiro deve achatar seus eixos para produzir uma matriz e achatar o gradiente para produzir um vetor:
A matriz Hessiana deve ser simétrica:
O passo de atualização do método de Newton é mostrado abaixo:
Observação: Não inverta a matriz.
Embora isso seja relativamente simples para uma única tf.Variable
, aplicar isto a um modelo não trivial exigiria concatenação e fatiamento cuidadosos para produzir um Hessiano completo em múltiplas variáveis.
Jacobiano em lote
Em alguns casos, você talvez queira obter o Jacobiano de cada pilha de destino em relação a uma pilha de origens, onde os Jacobianos de cada par destino-origem são independentes.
Por exemplo, aqui a entrada x
tem formato (batch, ins)
e a saída y
tem formato (batch, outs)
:
O Jacobiano completo de y
em relação a x
tem o formato (batch, ins, batch, outs)
, mesmo que você queira apenas (batch, ins, outs)
:
Se os gradientes de cada item na pilha forem independentes, então cada (batch, batch)
deste tensor é uma matriz diagonal:
Para obter o resultado desejado, você pode somar a dimensão batch
duplicada ou então selecionar as diagonais usando tf.einsum
:
Seria muito mais eficiente fazer o cálculo sem a dimensão extra. O método tf.GradientTape.batch_jacobian
faz exatamente isso:
Cuidado: tf.GradientTape.batch_jacobian
verifica apenas se a primeira dimensão da origem e do destino correspondem. Não verifica se os gradientes são realmente independentes. Você é quem decide usar batch_jacobian
apenas onde fizer sentido. Por exemplo, adicionar tf.keras.layers.BatchNormalization
destrói a independência, pois normaliza em toda a dimensão batch
:
Nesse caso, batch_jacobian
ainda roda e retorna algo com o formato esperado, mas seu conteúdo tem um significado pouco claro: