[Pytorch] Recorte de gradiente: el principio y el proceso de cálculo de torch.nn.utils.clip_grad_norm_


Como todos sabemos, el recorte de degradado sirve para evitar la explosión del degradado. Al entrenar el algoritmo FCOS, la pérdida es NaN durante el proceso de entrenamiento. Hay muchos problemas en el problema de Github donde la pérdida es NaN durante el proceso de entrenamiento. El autor también propuso ajustar los hiperparámetros del recorte de gradiente, por lo que se encargó de recorte de gradiente El proceso de cálculo de la función torch.nn.utils.clip_grad_norm_facilita el ajuste de parámetros.


一、torch.nn.utils.clip_grad_norm_

torch.nn.utils.clip_grad_norm_(parameters, max_norm, norm_type) , esta función de recorte de degradado generalmente solo necesita ajustar max_normestos norm_typedos parámetros.
parametersParámetros es una lista de parámetros que deben recortarse en gradiente. Generalmente es una lista de parámetros del modelo, es decir, model.parameters()
max_normlos parámetros pueden entenderse como gradientes (el valor predeterminado es la norma L2). El
norm_typeparámetro de umbral máximo de la norma puede entenderse como el tipo de la norma especificada. Por ejemplo norm_type=1, significa usar la norma L1, norm_type=2lo que indica usar la norma L2.
Al mismo tiempo, la diferencia entre (esta función ha quedado obsoleta) y (esta función ha quedado obsoleta) es que la primera modifica directamente el tensor original, mientras que el segundo no (hay muchos pares de funciones de este tipo en Pytorch, y una subrayado al final de la función generalmente significa operación torch.nn.utils.clip_grad_norm_directatorch.nn.utils.clip_grad_norm

import torch

# 构造两个Tensor
x = torch.tensor([99.0, 108.0], requires_grad=True)
y = torch.tensor([45.0, 75.0], requires_grad=True)

# 模拟网络计算过程
z = x ** 2 + y ** 3
z = z.sum()

# 反向传播
z.backward()

# 得到梯度
print(f"gradient of x is:{
      
      x.grad}") 
print(f"gradient of y is:{
      
      y.grad}") 

# 梯度裁剪
torch.nn.utils.clip_grad_norm_([x, y], max_norm=100, norm_type=2)


# 再次打印裁剪后的梯度
# 直接修改了原x.grad的值
print("---clip_grad---")
print(f"clip_grad of x is:{
      
      x.grad}") 
print(f"clip_grad of y is:{
      
      y.grad}") 


# 输出如下
"""
gradient of x is:tensor([198., 216.])
gradient of y is:tensor([ 6075., 16875.])
---clip_grad---
clip_grad of x is:tensor([1.1038, 1.2042])
clip_grad of y is:tensor([33.8674, 94.0762])
"""

Como se puede ver en el ejemplo anterior, el degradado recortado es mucho más pequeño que el degradado original. Al principio, el gradiente de la variable x es tensor([198., 216.]), esto es fácil de calcular, es encontrar la derivada parcial del par, es zdecir . Lo mismo ocurre con la variable y. El gradiente recortado es mucho más pequeño que el gradiente original, por lo que se puede aliviar el problema de la explosión del gradiente.x2*x

2. Proceso de cálculo

No es difícil consultar el código fuente para el proceso de cálculo del recorte de gradiente. El
CÓDIGO FUENTE DE TORCH.NN.UTILS.CLIP_GRAD
combina el código para convertirlo en una fórmula matemática. El proceso de cálculo es el siguiente:
Paso 1: Todavía Tome el código anterior como ejemplo, construya Tensor backpropagation y obtenga el gradiente del parámetro x, y que es el parámetro torch.nn.utils.clip_grad_norm_(parameters, max_norm, norm_type) en parameters.
Insertar descripción de la imagen aquí

import torch

# 构造两个Tensor
x = torch.tensor([99.0, 108.0], requires_grad=True)
y = torch.tensor([45.0, 75.0], requires_grad=True)

# 模拟网络计算过程
z = x ** 2 + y ** 3
z = z.sum()

# 反向传播
z.backward()

# 得到梯度
print(f"gradient of x is:{
      
      x.grad}") 
print(f"gradient of y is:{
      
      y.grad}") 

# 输出
"""
gradient of x is:tensor([198., 216.])
gradient of y is:tensor([ 6075., 16875.])
"""

Paso 2: Calcule la norma L2 del gradiente de cada variable (tome la norma L2 como ejemplo)
Insertar descripción de la imagen aquí

El significado de este código es definir una lista vacía normspara almacenar la norma L2 de cada gradiente de parámetro. Luego torch.stackcombine normstodos los tensores (la norma L2 calculada) en un tensor y finalmente encuentre la norma L2 del tensor fusionado para obtener la total_norm(norma total).

Cuando norm_typees inf, es decir, norma infinita, total_normse tomará directamente el que tenga el mayor gradiente de parámetros.

# 相当于把x_L2norm、y_L2norm放入代码中的norms空列表

# x的梯度的L2 范数
x_L2norm = torch.sum(x.grad ** 2) ** 0.5
# y的梯度的L2 范数
y_L2norm = torch.sum(y.grad ** 2) ** 0.5

# 相当于遍历norms列表合并成一个Tensor
total_norm = torch.sum(torch.stack([x_L2norm, y_L2norm]) ** 2) ** 0.5

"""
等价过程
x_L2norm = sum([198 ** 2, 216 ** 2]) ** 0.5
y_L2norm = sum([6075 ** 2, 16875 ** 2]) ** 0.5
total_norm = sum([x_L2norm ** 2, y_L2norm ** 2]) ** 0.5
"""

Paso 3: Calcule el coeficiente de recorte del gradiente
Insertar descripción de la imagen aquí

# 1e-6防止分母为0
# clip_coef = max_norm / (total_norm + 1e-6)
max_norm = 100
clip_coef = max_norm / total_norm 

Paso 4: Multiplique el gradiente original por el coeficiente de recorte de gradiente para obtener el gradiente recortado, que es consistente con el resultado del cálculo de la función.
Insertar descripción de la imagen aquí

print(f"clip_grad of x is: is {
      
      x.grad * clip_coef }")
print(f"clip_grad of x is: is {
      
      y.grad * clip_coef }")

# 输出
"""
clip_grad of x is: is tensor([1.1038, 1.2042])
clip_grad of x is: is tensor([33.8674, 94.0762])
"""

Integra el código:

import torch

# 构造两个Tensor
x = torch.tensor([99.0, 108.0], requires_grad=True)
y = torch.tensor([45.0, 75.0], requires_grad=True)

# 模拟网络计算过程
z = x ** 2 + y ** 3
z = z.sum()

# 反向传播
z.backward()

# 得到梯度
print(f"gradient of x is:{
      
      x.grad}") 
print(f"gradient of y is:{
      
      y.grad}") 

x_L2norm = torch.sum(x.grad ** 2) ** 0.5
y_L2norm = torch.sum(y.grad ** 2) ** 0.5
total_norm = torch.sum(torch.stack([x_L2norm, y_L2norm]) ** 2) ** 0.5

max_norm = 100
clip_coef = max_norm / total_norm 

print(f"clip_grad of x is: is {
      
      x.grad * clip_coef }")
print(f"clip_grad of x is: is {
      
      y.grad * clip_coef }")

# 输出如下
"""
gradient of x is:tensor([198., 216.])
gradient of y is:tensor([ 6075., 16875.])
clip_grad of x is:tensor([1.1038, 1.2042])
clip_grad of y is:tensor([33.8674, 94.0762])
"""

3. Determinar max_norm

De acuerdo con el proceso de cálculo anterior, lo más importante para el recorte de gradiente es calcular el coeficiente de recorte para obtener el gradiente recortado. clip_coef = max_norm / total_normEn la fórmula, cuanto más pequeño es clip_coef, mayor es el gradiente de recorte. Es decir
max_norm, cuanto más pequeño sea, mayor será el gradiente recortado, más pequeño será el gradiente y más obvio será el efecto de prevenir la explosión del gradiente.

Al entrenar el modelo, podemos total_normdeterminar aproximadamente un rango de valores de max_norm en función del valor de. torch.nn.utils.clip_grad_norm_([x, y], max_norm=100, norm_type=2)El valor que devuelve la función cuando se llama a la función total_norm. Por ejemplo, el parámetro inicial , en x、yeste momento , es muy grande, por lo que para evitar la explosión del gradiente, podemos establecerlo un poco más pequeño.total_norm17937.5879max_norm

# 以最开始的x、y为例
total_norm = torch.nn.utils.clip_grad_norm_([x, y], max_norm=100, norm_type=2)
print(total_norm)

#输出
# tensor(17937.5879) 

Supongo que te gusta

Origin blog.csdn.net/m0_46412065/article/details/131396098
Recomendado
Clasificación