A diferenciação automática é útil para implementar algoritmos de aprendizado de máquina, como retropropagação para o treinamento de redes neurais.
Neste guia, você explorará maneiras de calcular gradientes com o TensorFlow, especialmente na execução ansiosa.
Configurar
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
2024-08-15 01:30:07.003169: E exterior/local_xla/xla/stream_executor/cuda/cuda_fft.cc:485] Unable to register cuFFT manufacturing facility: Making an attempt to register manufacturing facility for plugin cuFFT when one has already been registered
2024-08-15 01:30:07.023862: E exterior/local_xla/xla/stream_executor/cuda/cuda_dnn.cc:8454] Unable to register cuDNN manufacturing facility: Making an attempt to register manufacturing facility for plugin cuDNN when one has already been registered
2024-08-15 01:30:07.029954: E exterior/local_xla/xla/stream_executor/cuda/cuda_blas.cc:1452] Unable to register cuBLAS manufacturing facility: Making an attempt to register manufacturing facility for plugin cuBLAS when one has already been registered
Gradientes de computação
Para diferenciar automaticamente, o TensorFlow precisa lembrar quais operações acontecem em que ordem durante o avançar passar. Então, durante o passe para trásTensorflow atravessa esta lista de operações em ordem inversa para calcular gradientes.
Fitas de gradiente
O TensorFlow fornece o tf.GradientTape
API para diferenciação automática; isto é, calcular o gradiente de um cálculo em relação a algumas entradas, geralmente tf.Variable
s. Operações relevantes do Tensorflow “Registros” executadas dentro do contexto de um tf.GradientTape
em uma “fita”. O TensorFlow usa então essa fita para calcular os gradientes de um cálculo “gravado” usando a diferenciação do modo reverso.
Aqui está um exemplo simples:
x = tf.Variable(3.0)
with tf.GradientTape() as tape:
y = x**2
WARNING: All log messages earlier than absl::InitializeLog() is named are written to STDERR
I0000 00:00:1723685409.408818 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685409.412555 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685409.416343 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685409.420087 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685409.431667 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685409.435229 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685409.438777 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685409.442350 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685409.445712 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685409.449141 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685409.452491 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685409.456034 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685410.685265 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685410.687389 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685410.689411 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685410.691490 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685410.693542 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685410.695541 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685410.697441 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685410.699432 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685410.701351 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685410.703333 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685410.705229 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685410.707222 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685410.744994 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685410.747037 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685410.749507 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685410.751538 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685410.753500 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685410.755501 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685410.757421 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685410.759404 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685410.761363 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685410.763858 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685410.766199 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
I0000 00:00:1723685410.768560 20970 cuda_executor.cc:1015] profitable NUMA node learn from SysFS had unfavourable worth (-1), however there should be at the very least one NUMA node, so returning NUMA node zero. See extra at
Depois de gravar algumas operações, use GradientTape.gradient(goal, sources)
Para calcular o gradiente de algum alvo (geralmente uma perda) em relação a alguma fonte (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 tão facilmente em qualquer tensor:
w = tf.Variable(tf.random.regular((3, 2)), title='w')
b = tf.Variable(tf.zeros(2, dtype=tf.float32), title='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
Com relação às duas variáveis, você pode passar como fontes para o gradient
método. A fita é flexível sobre como as fontes são passadas e aceitarão qualquer combinação aninhada de listas ou dicionários e devolverá 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 fonte tem a forma da fonte:
print(w.form)
print(dl_dw.form)
(3, 2)
(3, 2)
Aqui está o cálculo do gradiente novamente, desta vez passando um dicionário de variáveis:
my_vars = {
'w': w,
'b': b
}
grad = tape.gradient(loss, my_vars)
grad['b']
Gradients with respect to a model
It’s common to collect tf.Variables
into a tf.Module
or one of its subclasses (layers.Layer
, keras.Model
) for checkpointing and exporting.
In most cases, you will want to calculate gradients with respect to a model’s trainable variables. Since all subclasses of tf.Module
aggregate their variables in the Module.trainable_variables
property, you can calculate these gradients in a few lines of code:
layer = tf.keras.layers.Dense(2, activation='relu')
x = tf.constant([[1., 2., 3.]]) com tf.gradientetape () como fita: # ahead passa y = camada (x) perda = tf.reduce_mean (y ** 2) # calcule gradientes em relação a cada variável treinável = tapra.gradiente (perda, layer.trainable_variables)
for var, g in zip(layer.trainable_variables, grad):
print(f'{var.title}, form: {g.form}')
kernel, form: (3, 2)
bias, form: (2,)
Controlando o que a fita observa
O comportamento padrão é gravar todas as operações depois de acessar um treinável tf.Variable
. As razões para isso são:
- A fita precisa saber quais operações registrarem no passe para a frente para calcular os gradientes no passe para trás.
- A fita contém referências a saídas intermediárias, para que você não registre 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 seguinte falha em calcular um gradiente porque o tf.Tensor
não é “assistido” por padrão, e o tf.Variable
não é treinável:
# A trainable variable
x0 = tf.Variable(3.0, title='x0')
# Not trainable
x1 = tf.Variable(3.0, title='x1', trainable=False)
# Not a Variable: A variable + tensor returns a tensor.
x2 = tf.Variable(2.0, title='x2') + 1.0
# Not a variable
x3 = tf.fixed(3.0, title='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)
tf.Tensor(6.0, form=(), dtype=float32)
None
None
None
Você pode listar as variáveis que estão sendo observadas pela fita usando o GradientTape.watched_variables
método:
[var.name for var in tape.watched_variables()]
['x0:0']
tf.GradientTape
Fornece ganchos que dão ao controle do usuário sobre o que é ou não assistido.
Para registrar gradientes em relação a um tf.Tensor
você precisa ligar GradientTape.watch(x)
:
x = tf.fixed(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())
6.0
Por outro lado, para desativar o comportamento padrão de assistir a todos tf.Variables
definir watch_accessed_variables=False
Ao criar a fita gradiente. Este cálculo usa duas variáveis, mas conecta apenas o gradiente para uma das variáveis:
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)
Desde GradientTape.watch
não foi chamado x0
nenhum gradiente é 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())
dy/dx0: None
dy/dx1: 0.9999546
Você também pode solicitar gradientes da saída em relação aos valores intermediários calculados dentro do tf.GradientTape
contexto.
x = tf.fixed(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 worth y.
# dz_dy = 2 * y and y = x ** 2 = 9
print(tape.gradient(z, y).numpy())
18.0
Por padrão, os recursos mantidos por um GradientTape
são lançados assim que o GradientTape.gradient
O método é chamado. Para calcular vários gradientes no mesmo cálculo, crie uma fita gradiente com persistent=True
. Isso permite várias chamadas para o gradient
O método como os recursos são liberados quando o objeto de fita é coletado de lixo. Por exemplo:
x = tf.fixed([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])
[ 4. 108.]
[2. 6.]
del tape # Drop the reference to the tape
Notas sobre desempenho
-
Há uma pequena sobrecarga associada à execução de operações dentro de um contexto de fita gradiente. Para a execução mais ansiosa, isso não será um custo notável, mas você ainda deve usar o contexto de fita em torno das áreas somente onde for necessário.
-
As fitas gradientes usam memória para armazenar resultados intermediários, incluindo entradas e saídas, para uso durante o passe para trás.
Para eficiência, alguns OPs (como
ReLU
) não precisam manter seus resultados intermediários e eles são podados durante o passe para a frente. No entanto, se você usarpersistent=True
Na sua fita, Nada é descartado E o uso de memória de pico de memória será maior.
Gradientes de alvos não escalares
Um gradiente é fundamentalmente uma operação em 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())
4.0
-0.25
Assim, se você solicitar o gradiente de vários alvos, o resultado de cada fonte é:
- O gradiente da soma dos alvos, ou equivalente
- A soma dos gradientes de cada alvo.
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())
3.75
Da mesma forma, se o (s) alvo (s) não for escalar, o gradiente da soma é calculado:
x = tf.Variable(2.)
with tf.GradientTape() as tape:
y = x * [3., 4.]
print(tape.gradient(y, x).numpy())
7.0
Isso simplifica o gradiente da soma de uma coleção de perdas ou o gradiente da soma de um cálculo de perda no elemento.
Se você precisar de um gradiente separado para cada merchandise, consulte os jacobianos.
Em alguns casos, você pode pular o jacobiano. Para um cálculo em termos de elemento, o gradiente da soma fornece a derivada de cada elemento em relação ao seu elemento de entrada, pois 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')
Fluxo de controle
Como um gradiente registra as operações de registro quando são executadas, o fluxo de controle do Python é tratado naturalmente (por exemplo, if
e whereas
declarações).
Aqui, uma variável diferente é usada em cada ramo de um if
. O gradiente se conecta apenas à variável que foi usada:
x = tf.fixed(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:
consequence = v0
else:
consequence = v1**2
dv0, dv1 = tape.gradient(consequence, [v0, v1])
print(dv0)
print(dv1)
tf.Tensor(1.0, form=(), dtype=float32)
None
Lembre-se de que as declarações de controle em si não são diferenciáveis, por isso são invisíveis para otimizadores baseados em gradientes.
Dependendo do valor de x
No exemplo acima, a fita registra consequence = v0
ou consequence = v1**2
. O gradiente em relação a x
é sempre None
.
dx = tape.gradient(consequence, x)
print(dx)
None
Casos onde gradient
retorna None
Quando um alvo não está conectado a uma fonte, gradient
voltará None
.
x = tf.Variable(2.)
y = tf.Variable(3.)
with tf.GradientTape() as tape:
z = y * y
print(tape.gradient(z, x))
None
Aqui z
obviamente não está conectado a x
mas existem várias maneiras menos óbvias que um gradiente pode ser desconectado.
1. Substituiu uma variável por um tensor
Na seção sobre “Controlando o que a fita observa” que você viu que a fita vai assistir automaticamente um tf.Variable
mas não um tf.Tensor
.
Um erro comum é substituir inadvertidamente um tf.Variable
com um tf.Tensor
em vez de usar Variable.assign
Para atualizar o tf.Variable
. Aqui está um exemplo:
x = tf.Variable(2.0)
for epoch in vary(2):
with tf.GradientTape() as tape:
y = x+1
print(sort(x).__name__, ":", tape.gradient(y, x))
x = x + 1 # This must be `x.assign_add(1)`
2. Os cálculos fora do tensorflow
A fita não pode 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.imply(x2, axis=0)
# Like most ops, reduce_mean will forged the NumPy array to a relentless tensor
# utilizing `tf.convert_to_tensor`.
y = tf.reduce_mean(y, axis=0)
print(tape.gradient(y, x))
None
3. Tomou gradientes através de um número inteiro ou corda
Inteiros e cordas 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 cordas sejam diferenciáveis, mas é fácil criar acidentalmente um int
constante ou variável se você não especificar o dtype
.
x = tf.fixed(10)
with tf.GradientTape() as g:
g.watch(x)
y = x * x
print(g.gradient(y, x))
WARNING:tensorflow:The dtype of the watched tensor should be floating (e.g. tf.float32), acquired tf.int32
None
O TensorFlow não é lançado automaticamente entre os tipos; portanto, na prática, você geralmente recebe um erro de tipo em vez de um gradiente ausente.
4. Tomou gradientes através de um objeto com estado de Estado
O estado interrompe os gradientes. Quando você lê de um objeto com estado, a fita só pode observar o estado atual, não a história que o leva.
UM tf.Tensor
é imutável. Você não pode alterar um tensor assim que for criado. Tem um valormas não estado. Todas as operações discutidas até agora também estão apátridas: a saída de um tf.matmul
depende apenas de suas entradas.
UM tf.Variable
tem estado interno – seu valor. Quando você usa a variável, o estado é lido. É regular calcular um gradiente em relação a uma variável, mas o estado da variável bloqueia os cálculos de gradiente de voltar mais longe. Por exemplo:
x0 = tf.Variable(3.0)
x1 = tf.Variable(0.0)
with tf.GradientTape() as tape:
# Replace x1 = x1 + x0.
x1.assign_add(x0)
# The tape begins recording from x1.
y = x1**2 # y = (x1 + x0)**2
# This does not work.
print(tape.gradient(y, x0)) #dy/dx0 = 2*(x1 + x0)
None
De forma related, tf.information.Dataset
iteradores e tf.queue
S são estabelecidos e impedirão todos os gradientes em tensores que passam por eles.
Nenhum gradiente registrado
Alguns tf.Operation
S são registrado como não sendo diferenciável e voltará None
. Outros têm Nenhum gradiente registrado.
O tf.raw_ops
A página mostra quais operações de baixo nível têm gradientes registrados.
Se você tentar assumir um gradiente através de um OP de flutuação que não possui gradiente registrado, a fita fará um erro em vez de retornar silenciosamente None
. Dessa forma, você sabe que algo deu errado.
Por exemplo, o tf.picture.adjust_contrast
Funções de função raw_ops.AdjustContrastv2
que pode ter um gradiente, mas o gradiente não é implementado:
picture = tf.Variable([[[0.5, 0.0, 0.0]]])
delta = tf.Variable(0.1)
with tf.GradientTape() as tape:
new_image = tf.picture.adjust_contrast(picture, delta)
strive:
print(tape.gradient(new_image, [image, delta]))
assert False # This could not occur.
besides LookupError as e:
print(f'{sort(e).__name__}: {e}')
LookupError: gradient registry has no entry for: AdjustContrastv2
Se você precisar diferenciar este OP, você precisará implementar o gradiente e registrá -lo (usando tf.RegisterGradient
) ou reimplementar a função usando outras operações.
Zeros em vez de nenhum
Em alguns casos, seria conveniente obter 0 em vez de None
para gradientes desconectados. Você pode decidir o que retornar quando tiver gradientes desconectados usando o unconnected_gradients
argumento:
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))
tf.Tensor([0. 0.], form=(2,), dtype=float32)
Publicado originalmente no