梯度裁剪是为了防止梯度爆炸。在训练FCOS算法时,因为训练过程出现了损失为NaN的情况,在github issue有很多都是这种训练过程出现loss为NaN,使用torch.nn.utils.clip_grad_norm_梯度裁剪函数,可以有效预防梯度爆炸的情况发生。
- def clip_grad_norm_(
- parameters: _tensor_or_tensors, max_norm: float, norm_type: float = 2.0,
- error_if_nonfinite: bool = False, foreach: Optional[bool] = None) -> torch.Tensor:
这个梯度裁剪函数一般来说只需要调整max_norm
和norm_type
这两个参数。clip_grad_norm_
最后就是对所有的梯度乘以一个clip_coef
,而且乘的前提是clip_coef一定是小于1的,所以,按照这个情况:clip_grad_norm
只解决梯度爆炸问题,不解决梯度消失问题
torch.nn.utils.clip_grad_norm_和torch.nn.utils.clip_grad_norm(已弃用)的区别就是前者是直接修改原Tensor,后者不会。在Pytorch中有很多这样的函数对均是如此,在函数最后多了下划线一般都是表示直接在原Tensor上进行操作。
clip_coef
的公式为:
max_norm的取值:
假定忽略clip_coef > 1的情况,则可以根据公式推断出:
max_norm可以取小数
total_norm受梯度大小和norm_type的影响:
这样就避免权重梯度爆炸导致模型训练困难,对于大梯度的缩小,小梯度的不变。
- import torch
-
- # 构造两个Tensor
- x = torch.tensor([102.0, 155.0], requires_grad=True)
- y = torch.tensor([201.0, 221.0], requires_grad=True)
-
- # 模拟网络计算过程
- z = x ** 3 + y ** 4
- 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=200, 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([31212., 72075.])
- gradient of y is:tensor([32482404., 43175444.])
- ---clip_grad---
- clip_grad of x is:tensor([0.1155, 0.2668])
- clip_grad of y is:tensor([120.2386, 159.8205])
上例中可以看出,裁剪后的梯度远小于原来的梯度。一开始变量x的梯度是tensor([31212., 72075.]),就是求z
对x
的偏导,变量y同理。裁剪后的梯度远小于原来的梯度,所以可以缓解梯度爆炸的问题。