torch.nn.linear详细介绍(详解torch.nn.utils.clip_grad_norm_ 的使用与原理)
clip_grad_norm_的原理
本文是对梯度剪裁: torch.nn.utils.clip_grad_norm_()文章的补充 。所以可以先参考这篇文章
从上面文章可以看到 ,clip_grad_norm最后就是对所有的梯度乘以一个clip_coef ,而且乘的前提是clip_coef一定是小于1的 ,所以 ,按照这个情况:clip_grad_norm只解决梯度爆炸问题 ,不解决梯度消失问题
clip_grad_norm_参数的选择(调参)
从上面文章可以看到 ,clip_coef的公式为:
c
l
i
p
_
c
o
e
f
=
m
a
x
_
n
o
r
m
t
o
t
a
l
_
n
o
r
m
clip\_coef = \frac{max\_norm}{total\_norm}
clip_coef=total_normmax_normmax_norm的取值:
假定忽略clip_coef > 1的情况 ,则可以根据公式推断出:
clip_coef越小 ,则对梯度的裁剪越厉害 ,即,使梯度的值缩小的越多 max_norm越小 ,clip_coef越小 ,所以,max_norm越大 ,对于梯度爆炸的解决越柔和 ,max_norm越小,对梯度爆炸的解决越狠max_norm可以取小数
接下来看下total_norm和norm_type的取值:
从上面文章可以看到 ,total_norm受梯度大小和norm_type的影响 ,通过公式很难直观的感受到 ,这里我通过实验得出了以下结论(不严谨 ,欢迎勘误):
梯度越大 ,total_norm值越大 ,进而导致clip_coef的值越小 ,最终也会导致对梯度的裁剪越厉害 ,很合理 norm_type不管取多少 ,对于total_norm的影响不是太大(1和2的差距稍微大一点),所以可以直接取默认值2 norm_type越大 ,total_norm越小(实验观察到的结论 ,数学不好,不会证明 ,所以本条不一定对)实验过程如下:
首先我对源码进行了一些修改 ,将.grad去掉,增加了一些输出 ,方便进行实验:
import numpy as np import torch from torch import nn def clip_grad_norm_(parameters, max_norm, norm_type=2): if isinstance(parameters, torch.Tensor): parameters = [parameters] parameters = list(filter(lambda p: p is not None, parameters)) max_norm = float(max_norm) norm_type = float(norm_type) if norm_type == np.inf: total_norm = max(p.data.abs().max() for p in parameters) else: total_norm = 0 for p in parameters: param_norm = p.data.norm(norm_type) total_norm += param_norm.item() ** norm_type total_norm = total_norm ** (1. / norm_type) clip_coef = max_norm / (total_norm + 1e-6) if clip_coef < 1: for p in parameters: p.data.mul_(clip_coef) print("max_norm=%s, norm_type=%s, total_norm=%s, clip_coef=%s" % (max_norm, norm_type, total_norm, clip_coef))只改变norm_type的情况下 ,各变量值的变化:
for i in range(1, 5): clip_grad_norm_(torch.Tensor([125,75,45,15,5]), max_norm=1, norm_type=i) clip_grad_norm_(torch.Tensor([125,75,45,15,5]), max_norm=1, norm_type=np.inf) max_norm=1.0, norm_type=1.0, total_norm=265.0, clip_coef=0.0037735848914204344 max_norm=1.0, norm_type=2.0, total_norm=153.3786163330078, clip_coef=0.006519813631054263 max_norm=1.0, norm_type=3.0, total_norm=135.16899108886716, clip_coef=0.007398146457602848 max_norm=1.0, norm_type=4.0, total_norm=129.34915161132812, clip_coef=0.007731013151704421 max_norm=1.0, norm_type=inf, total_norm=tensor(125.), clip_coef=tensor(0.0080)只改变梯度 ,各变量值的变化:
for i in range(1, 5): clip_grad_norm_(torch.Tensor([125*i,75,45,15,5]), max_norm=1, norm_type=2) max_norm=1.0, norm_type=2.0, total_norm=153.3786163330078, clip_coef=0.006519813631054263 max_norm=1.0, norm_type=2.0, total_norm=265.3299865722656, clip_coef=0.003768891745519864 max_norm=1.0, norm_type=2.0, total_norm=385.389404296875, clip_coef=0.0025947781289671814 max_norm=1.0, norm_type=2.0, total_norm=507.83856201171875, clip_coef=0.001969129705451148只改变max_norm ,各变量值的变化:
for i in range(1, 5): clip_grad_norm_(torch.Tensor([125,75,45,15,5]), max_norm=i, norm_type=2) max_norm=1.0, norm_type=2.0, total_norm=153.3786163330078, clip_coef=0.006519813631054263 max_norm=2.0, norm_type=2.0, total_norm=153.3786163330078, clip_coef=0.013039627262108526 max_norm=3.0, norm_type=2.0, total_norm=153.3786163330078, clip_coef=0.01955944089316279 max_norm=4.0, norm_type=2.0, total_norm=153.3786163330078, clip_coef=0.02607925452421705clip_grad_norm_使用演示
在上面文章还提到一个重要的事情:clip_grad_norm_要放在backward和step之间 。接下来我会实际演示梯度在训练过程中的变化 ,并解释要这么做的原因:
首先定义一个测试模型:
class TestModel(nn.Module): def __init__(self): super().__init__() self.model = nn.Sequential( nn.Linear(1,1, bias=False), nn.Sigmoid(), nn.Linear(1,1, bias=False), nn.Sigmoid(), nn.Linear(1,1, bias=False), nn.Sigmoid(), nn.Linear(1,1, bias=False), nn.Sigmoid(), ) def forward(self, x): return self.model(x) model = TestModel()定义好模型后 ,固定一下模型参数:
for param in model.parameters(): param.data = torch.Tensor([[0.5]]) print("param=%s" % (param.data.item())) param=0.5 param=0.5 param=0.5 param=0.5可以看目前四个线性层的权重参数都为0.5 。之后对模型进行一轮训练 ,并进行反向传播:
criterion = nn.MSELoss() optimizer = torch.optim.SGD(model.parameters(), lr=1) predict_y = model(torch.Tensor([0.1])) loss = criterion(predict_y, torch.Tensor([1])) model.zero_grad() loss.backward()反向传播过后 ,再次打印模型参数 ,可以看到反向传播后计算好的各个参数的梯度:
for param in model.parameters(): print("param=%s, grad=%s" % (param.data.item(), param.grad.item())) param=0.5, grad=-3.959321111324243e-05 param=0.5, grad=-0.0016243279678747058 param=0.5, grad=-0.014529166743159294 param=0.5, grad=-0.11987950652837753重点来了,各个参数的梯度如上图所示(越靠近输入的位置 ,梯度越小 ,虽然没有出现梯度爆炸,反而出现了梯度消失 ,但不影响本次实验) ,现在对其进行梯度裁剪:
nn.utils.clip_grad_norm_(model.parameters(), max_norm=0.01208, norm_type=2) tensor(0.1208)在上面,我传入的max_norm=0.01208 ,而total_norm=0.1208 ,所以可得clip_coef=0.1 ,即所有的梯度都会缩小一倍 ,此时我们再打印一下梯度:
for param in model.parameters(): print("param=%s, grad=%s" % (param.data.item(), param.grad.item())) param=0.5, grad=-3.960347839893075e-06 param=0.5, grad=-0.00016247491294052452 param=0.5, grad=-0.001453293371014297 param=0.5, grad=-0.01199105940759182看到没 ,所有的梯度都减小了10倍 。之后我们执行step()操作 ,其就会将进行param=param-lr*grad操作来进行参数更新 。再次打印网络参数:
optimizer.step() for param in model.parameters(): print("param=%s, grad=%s" % (param.data.item(), param.grad.item())) param=0.5000039339065552, grad=-3.960347839893075e-06 param=0.5001624822616577, grad=-0.00016247491294052452 param=0.5014532804489136, grad=-0.001453293371014297 param=0.5119910836219788, grad=-0.01199105940759182可以看到 ,在执行step后 ,执行了param=param-grad操作(我设置的lr为1) 。同时 ,grad并没有清0,所以这也是为什么要显式的调用zero_grad的原因 。
参考资料
梯度剪裁: torch.nn.utils.clip_grad_norm_():https://blog.csdn.net/Mikeyboi/article/details/119522689
什么是范数(Norm) ,其具有哪些性质: https://blog.csdn.net/zhaohongfei_358/article/details/122818616
创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!