Path: blob/master/site/pt-br/guide/autodiff.ipynb
25115 views
Copyright 2020 The TensorFlow Authors.
Introdução aos gradientes e diferenciação automática
Diferenciação Automática e Gradientes
A diferenciação automática é útil para implementar algoritmos de aprendizado de máquina, como retropropagação, para treinar redes neurais.
Neste guia, você explorará maneiras de computar gradientes com o TensorFlow, especialmente usando eager execution.
Configuração
Computação de gradientes
Para diferenciar automaticamente, o TensorFlow precisa lembrar quais operações acontecem e em que ordem durante o passo para frente. Então, durante o passo para trás, o TensorFlow percorre essa lista de operações na ordem inversa para computar os gradientes.
Fitas de gradiente (gradient tapes)
O TensorFlow fornece a API tf.GradientTape
para diferenciação automática; isto é, calcular o gradiente de uma computação em relação a algumas entradas, geralmente de objetos tf.Variable
. O TensorFlow "grava" operações relevantes executadas no contexto de um tf.GradientTape
em uma "fita" (tape). O TensorFlow então usa essa fita para calcular os gradientes de um cálculo "gravado" usando diferenciação de modo reverso.
Aqui está um exemplo simples:
Depois que você gravar algumas operações, use GradientTape.gradient(target, sources)
para calcular o gradiente de algum destino (geralmente uma perda) em relação a alguma origem (geralmente as variáveis do modelo):
O exemplo acima usa escalares, mas tf.GradientTape
funciona facilmente em qualquer tensor:
Para obter o gradiente de loss
em relação a ambas as variáveis, você pode passar ambas como origens para o método gradient
. A fita aceita bastante flexibilidade sobre como as origens são passadas e aceitará qualquer combinação aninhada de listas ou dicionários e retornará o gradiente estruturado da mesma maneira (veja tf.nest
).
O gradiente em relação a cada origem tem o formato da origem:
Aqui está o cálculo do gradiente mais uma vez, desta vez passando um dicionário de variáveis:
Gradientes em relação a um modelo
É comum agrupar tf.Variables
num tf.Module
ou numa de suas subclasses (layers.Layer
, keras.Model
) para fazer checkpoints e para exportação.
Na maioria dos casos, você vai querer calcular gradientes em relação às variáveis treináveis de um modelo. Como todas as subclasses de tf.Module
agregam suas variáveis na propriedade Module.trainable_variables
, você pode calcular esses gradientes em poucas linhas de código:
O comportamento padrão é gravar todas as operações depois de acessar um tf.Variable
treinável. As razões para isso são:
A fita precisa saber quais operações gravar no passo para frente para calcular os gradientes no passo para trás.
A fita contém referências a saídas intermediárias, portanto você não vai querer gravar operações desnecessárias.
O caso de uso mais comum envolve o cálculo do gradiente de uma perda em relação a todas as variáveis treináveis de um modelo.
Por exemplo, o código a seguir falha ao calcular um gradiente porque o tf.Tensor
não é "monitorado" (watched) por padrão e o tf.Variable
não é treinável:
Você pode listar as variáveis que estão sendo monitoradas pela fita usando o método GradientTape.watched_variables
:
O tf.GradientTape
fornece hooks que dão ao usuário controle sobre o que é ou não monitorado.
Para gravar gradientes em relação a um tf.Tensor
, você precisa chamar GradientTape.watch(x)
:
Por outro lado, para desativar o comportamento padrão de monitorar todas as tf.Variables
, defina watch_accessed_variables=False
ao criar a fita de gradiente. Este cálculo usa duas variáveis, mas conecta apenas o gradiente de uma delas:
Já que GradientTape.watch
não foi chamado x0
, nenhum gradiente será calculado em relação a ele:
Resultados intermediários
Você também pode solicitar gradientes da saída em relação aos valores intermediários computados dentro do contexto de tf.GradientTape
.
Por padrão, os recursos mantidos por um GradientTape
são liberados assim que o método GradientTape.gradient
é chamado. Para computar vários gradientes na mesma computação, crie uma fita de gradiente com persistent=True
. Isto permite múltiplas chamadas para o método gradient
à medida que recursos são liberados quando o objeto fita é recolhido pelo coletor de lixo. Por exemplo:
Notas sobre o desempenho
Há uma pequena sobrecarga associada à execução de operações dentro de um contexto de fita de gradiente. Para a maior parte das execuções eager, isto não será um custo perceptível, mas você ainda deve usar o contexto de fita em áreas em que for obrigatório.
As fitas de gradiente usam a memória para armazenar resultados intermediários, incluindo entradas e saídas, para uso durante o passo para trás.
Para maior eficiência, alguns ops (como
ReLU
) não precisam manter seus resultados intermediários e são aparadas (pruned) durante o passo para frente. No entanto, se você usarpersistent=True
em sua fita, nada será descartado e seu pico de uso de memória será maior.
Gradientes de destinos não escalares
Um gradiente é fundamentalmente uma operação sobre um escalar.
Portanto, se você solicitar o gradiente de múltiplos destinos, o resultado para cada origem será:
O gradiente da soma dos destinos, ou equivalentemente
A soma dos gradientes de cada destino.
Da mesma forma, se o(s) destinos(s) não forem escalares, o gradiente da soma é calculado:
Isso facilita a obtenção do gradiente da soma de uma coleção de perdas ou o gradiente da soma de um cálculo de perda elemento a elemento.
Se você precisar de um gradiente separado para cada item, veja a seção Jacobianos.
Em alguns casos você pode ignorar o Jacobiano. Para um cálculo elemento a elemento, o gradiente da soma fornece a derivada de cada elemento em relação ao seu elemento de entrada, uma vez que cada elemento é independente:
Controle de fluxo
Como uma fita de gradiente grava as operações à medida que são executadas, o fluxo de controle do Python ocorre de forma natural (por exemplo, as instruções if
e while
).
Aqui, uma variável diferente é usada em cada ramo de um if
. O gradiente se conecta apenas à variável que foi usada:
Mas lembre-se de que as instruções de controle em si não são diferenciáveis, portanto são invisíveis para otimizadores baseados em gradiente.
Dependendo do valor de x
no exemplo acima, a fita registra result = v0
ou result = v1**2
. O gradiente em relação a x
é sempre None
.
Situações em que gradient
retorna None
Quando um destino não está conectado a uma origem, gradient
retornará None
.
Aqui z
obviamente não está conectado a x
, mas existem várias maneiras menos óbvias de desconectar um gradiente.
1. Substituição de uma variável por um tensor
Na seção sobre "controlando o que a fita monitora", você viu que a fita monitora automaticamente uma tf.Variable
mas não um tf.Tensor
.
Um erro comum é substituir inadvertidamente uma tf.Variable
por um tf.Tensor
, em vez de usar Variable.assign
para atualizar o tf.Variable
. Eis um exemplo:
2. Cálculos realizados fora do TensorFlow
A fita não poderá gravar o caminho do gradiente se o cálculo sair do TensorFlow. Por exemplo:
3. Gradientes obtidos por meio de um número inteiro ou string
Inteiros e strings não são diferenciáveis. Se um caminho de cálculo usar esses tipos de dados, não haverá gradiente.
Ninguém espera que as strings sejam diferenciáveis, mas é fácil criar acidentalmente uma constante ou variável int
se você não especificar o dtype
.
O TensorFlow não faz coerção automática entre tipos. Portanto, na prática, você geralmente receberá um erro de tipo em vez de um gradiente ausente.
4. Gradientes obtidos através de um objeto stateful
O estado interrompe os gradientes. Quando você lê um objeto stateful, a fita só poderá observar o estado atual, não o histórico que leva até ele.
Um tf.Tensor
é imutável. Você não pode alterar um tensor depois de criado. Ele tem um valor, mas não tem estado. Todas as operações discutidas até o momento também são stateless, ou seja, não têm estado: a saída de um tf.matmul
depende apenas de suas entradas.
Uma tf.Variable
possui um estado interno – seu valor. Quando você usa a variável, esse estado é lido. É normal calcular um gradiente em relação a uma variável, mas o estado da variável impede que os cálculos de gradiente tenham acesso a valores anteriores. Por exemplo:
Da mesma forma, os iteradores tf.data.Dataset
e tf.queue
são stateful e interromperão todos os gradientes nos tensores que passarem por eles.
Nenhum gradiente registrado
Alguns tf.Operation
são registrados como indiferenciáveis e retornarão None
. Outros não possuem gradiente registrado.
A página tf.raw_ops
mostra quais operações de baixo nível possuem gradientes registrados.
Se você tentar obter um gradiente via uma operação flutuante que não possui gradiente registrado, a fita lançará um erro em vez de retornar None
silenciosamente. Assim, você saberá que algo deu errado.
Por exemplo, a função tf.image.adjust_contrast
é um wrapper para raw_ops.AdjustContrastv2
, que poderia conter um gradiente, mas o gradiente não foi implementado:
Se vocÊ precisar diferenciar através desse op, você precisará ou implementar o gradiente e registrá-lo (usando tf.RegisterGradient
), ou reimplementar a função usando outros ops.
Zeros em vez de None
Em alguns casos pode ser mais conveniente receber 0 em vez de None
para gradientes não conectados. Você pode decidir o que retornar quando tiver gradientes desconectados usando o argumento unconnected_gradients
: