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

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

import numpy as np import matplotlib.pyplot as plt import tensorflow as tf

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:

x = tf.Variable(3.0) with tf.GradientTape() as tape: y = x**2

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):

# dy = 2x * dx dy_dx = tape.gradient(y, x) dy_dx.numpy()

O exemplo acima usa escalares, mas tf.GradientTape funciona facilmente em qualquer tensor:

w = tf.Variable(tf.random.normal((3, 2)), name='w') b = tf.Variable(tf.zeros(2, dtype=tf.float32), name='b') x = [[1., 2., 3.]] with tf.GradientTape(persistent=True) as tape: y = x @ w + b loss = tf.reduce_mean(y**2)

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).

[dl_dw, dl_db] = tape.gradient(loss, [w, b])

O gradiente em relação a cada origem tem o formato da origem:

print(w.shape) print(dl_dw.shape)

Aqui está o cálculo do gradiente mais uma vez, desta vez passando um dicionário de variáveis:

my_vars = { 'w': w, 'b': b } grad = tape.gradient(loss, my_vars) grad['b']

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:

layer = tf.keras.layers.Dense(2, activation='relu') x = tf.constant([[1., 2., 3.]]) with tf.GradientTape() as tape: # Forward pass y = layer(x) loss = tf.reduce_mean(y**2) # Calculate gradients with respect to every trainable variable grad = tape.gradient(loss, layer.trainable_variables)
for var, g in zip(layer.trainable_variables, grad): print(f'{var.name}, shape: {g.shape}')

Controlando o que a fita monitora

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:

# A trainable variable x0 = tf.Variable(3.0, name='x0') # Not trainable x1 = tf.Variable(3.0, name='x1', trainable=False) # Not a Variable: A variable + tensor returns a tensor. x2 = tf.Variable(2.0, name='x2') + 1.0 # Not a variable x3 = tf.constant(3.0, name='x3') with tf.GradientTape() as tape: y = (x0**2) + (x1**2) + (x2**2) grad = tape.gradient(y, [x0, x1, x2, x3]) for g in grad: print(g)

Você pode listar as variáveis ​​que estão sendo monitoradas pela fita usando o método GradientTape.watched_variables:

[var.name for var in tape.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):

x = tf.constant(3.0) with tf.GradientTape() as tape: tape.watch(x) y = x**2 # dy = 2x * dx dy_dx = tape.gradient(y, x) print(dy_dx.numpy())

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:

x0 = tf.Variable(0.0) x1 = tf.Variable(10.0) with tf.GradientTape(watch_accessed_variables=False) as tape: tape.watch(x1) y0 = tf.math.sin(x0) y1 = tf.nn.softplus(x1) y = y0 + y1 ys = tf.reduce_sum(y)

Já que GradientTape.watch não foi chamado x0, nenhum gradiente será calculado em relação a ele:

# dys/dx1 = exp(x1) / (1 + exp(x1)) = sigmoid(x1) grad = tape.gradient(ys, {'x0': x0, 'x1': x1}) print('dy/dx0:', grad['x0']) print('dy/dx1:', grad['x1'].numpy())

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.

x = tf.constant(3.0) with tf.GradientTape() as tape: tape.watch(x) y = x * x z = y * y # Use the tape to compute the gradient of z with respect to the # intermediate value y. # dz_dy = 2 * y and y = x ** 2 = 9 print(tape.gradient(z, y).numpy())

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:

x = tf.constant([1, 3.0]) with tf.GradientTape(persistent=True) as tape: tape.watch(x) y = x * x z = y * y print(tape.gradient(z, x).numpy()) # [4.0, 108.0] (4 * x**3 at x = [1.0, 3.0]) print(tape.gradient(y, x).numpy()) # [2.0, 6.0] (2 * x at x = [1.0, 3.0])
del tape # Drop the reference to the tape

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ê usar persistent=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.

x = tf.Variable(2.0) with tf.GradientTape(persistent=True) as tape: y0 = x**2 y1 = 1 / x print(tape.gradient(y0, x).numpy()) print(tape.gradient(y1, x).numpy())

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.

x = tf.Variable(2.0) with tf.GradientTape() as tape: y0 = x**2 y1 = 1 / x print(tape.gradient({'y0': y0, 'y1': y1}, x).numpy())

Da mesma forma, se o(s) destinos(s) não forem escalares, o gradiente da soma é calculado:

x = tf.Variable(2.) with tf.GradientTape() as tape: y = x * [3., 4.] print(tape.gradient(y, x).numpy())

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:

x = tf.linspace(-10.0, 10.0, 200+1) with tf.GradientTape() as tape: tape.watch(x) y = tf.nn.sigmoid(x) dy_dx = tape.gradient(y, x)
plt.plot(x, y, label='y') plt.plot(x, dy_dx, label='dy/dx') plt.legend() _ = plt.xlabel('x')

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:

x = tf.constant(1.0) v0 = tf.Variable(2.0) v1 = tf.Variable(2.0) with tf.GradientTape(persistent=True) as tape: tape.watch(x) if x > 0.0: result = v0 else: result = v1**2 dv0, dv1 = tape.gradient(result, [v0, v1]) print(dv0) print(dv1)

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.

dx = tape.gradient(result, x) print(dx)

Situações em que gradient retorna None

Quando um destino não está conectado a uma origem, gradient retornará None.

x = tf.Variable(2.) y = tf.Variable(3.) with tf.GradientTape() as tape: z = y * y print(tape.gradient(z, x))

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:

x = tf.Variable(2.0) for epoch in range(2): with tf.GradientTape() as tape: y = x+1 print(type(x).__name__, ":", tape.gradient(y, x)) x = x + 1 # This should be `x.assign_add(1)`

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:

x = tf.Variable([[1.0, 2.0], [3.0, 4.0]], dtype=tf.float32) with tf.GradientTape() as tape: x2 = x**2 # This step is calculated with NumPy y = np.mean(x2, axis=0) # Like most ops, reduce_mean will cast the NumPy array to a constant tensor # using `tf.convert_to_tensor`. y = tf.reduce_mean(y, axis=0) print(tape.gradient(y, x))

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.

x = tf.constant(10) with tf.GradientTape() as g: g.watch(x) y = x * x print(g.gradient(y, x))

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:

x0 = tf.Variable(3.0) x1 = tf.Variable(0.0) with tf.GradientTape() as tape: # Update x1 = x1 + x0. x1.assign_add(x0) # The tape starts recording from x1. y = x1**2 # y = (x1 + x0)**2 # This doesn't work. print(tape.gradient(y, x0)) #dy/dx0 = 2*(x1 + x0)

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:

image = tf.Variable([[[0.5, 0.0, 0.0]]]) delta = tf.Variable(0.1) with tf.GradientTape() as tape: new_image = tf.image.adjust_contrast(image, delta) try: print(tape.gradient(new_image, [image, delta])) assert False # This should not happen. except LookupError as e: print(f'{type(e).__name__}: {e}')

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:

x = tf.Variable([2., 2.]) y = tf.Variable(3.) with tf.GradientTape() as tape: z = y**2 print(tape.gradient(z, x, unconnected_gradients=tf.UnconnectedGradients.ZERO))