梯度迭代类算法已成为目前各种领域的主流算法。各种现实中的问题分解抽象成机器可以处理的形式之后,基本都可归类为图像、自然语言处理、决策、时序、强化学习这几种类型,而当今解决这些问题的顶尖算法中,梯度迭代(梯度上升或梯度下降)都占据主流地位,比如决策类问题的比赛中,梯度下降决策树GBDT类算法是完全的主流,使用深度学习网络处理图片自然语言问题更毋庸置疑。

那么,梯度迭代算法究竟是什么?简单地说,就是代入数据,预测结果,如果结果偏大就调小参数,结果偏小就调大参数。举一个简单的例子,分为三个小问题:

问题一

假设父亲的智商影响儿子的智商,设父亲的智商为x,儿子的智商为y,y=wx,训练一个参数w学习二者之间的关系。目前有多个父子智商数据对,其中第一个数据:父亲智商x=100,儿子智商w=110,将w初值设为w=1.0;学习率设为0.00001,计算平均误差。

如下程序用于学习w。

import torch  
import matplotlib.pyplot as plt  
%matplotlib inline  
  
x = torch.tensor([100.]) # 只做训练不求梯度  
y = torch.tensor([110.])  
w = torch.tensor([1.], requires_grad=True) # 模拟网络参数  
lr = 0.00001 # 学习率  
arr = []  
      
for i in range(10):  
    pred = x * w  
    loss = (pred-y)*(pred-y) # 平方误差  
    loss.backward() # 计算梯度  
    print("real", y.item(), "pred", pred.item(), "loss", loss.item())  
    print("w.data", w.data.item(), "w.grad", w.grad.item())  
    w.data = w.data - lr * w.grad # 按学习率调参  
    w.grad.zero_() # 梯度清 0,否则梯度会不断累加  
    arr.append(loss.item())  
  
plt.plot(arr)

程序中使用了torch的基本数据结构Tensor及其自动计算梯度的功能,模型、优化器、误差函数全部写代码实现。其误差等于实际的y值减预测值的平方,在第一次迭代中计算结果如下:

求误差函数对参数w的偏导数,用以调节w,具体使用链式法则:

然后使用梯度修改参数w,每次修改一个很小的步幅,即学习率。

每次w都变好一点,经过多次迭代,参数w逐渐逼近其真实值,误差也逐渐下降,如下图所示:

此时再代入值x=100,即可得到正确的预测y=110。上述是最简单的情况,为简化操作只训练了一个实例,如果有10000个实例代入训练,反复迭代20次,最终将通过微调的方法得到最为合理的参数w。

使用Pytorch提供的线性层、误差函数和优化器,功能与上面的程序一致,代码更加简单:

import torch  
  
x = torch.tensor([100.]) # 只做训练不求梯度  
y = torch.tensor([110.])  
lr = 0.00001 # 学习率  
  
model = torch.nn.Linear(1, 1, bias=False)  
model.weight.data.fill_(1.0) # 初始化参数  
optimizer = torch.optim.SGD(model.parameters(), lr=lr, momentum=0) # 优化器  
lossfunc = torch.nn.MSELoss() # 平方根损失  
  
for i in range(10):  
    pred = model(x)  
    loss = lossfunc(pred, y)  
    loss.backward() # 计算梯度  
    optimizer.step()  
    optimizer.zero_grad()

问题二

如果孩子的智商由父母双方决定,那么每一实例的x将提供父母双方的智商值x1,x2,学到的参数w也是两个:y=x1w1+x2w2,每次调节参数时误差函数分别对w1,w2求偏导。

(码字不易,转载请注明出处:谢彦的技术博客)

问题三

如果孩子的智商由父亲决定,父亲的智商由奶奶决定,此时网络的输入是奶奶的智商x,输出是孩子的智商y,父亲的智商成为中间变量。因此有:y=xw1w2,其中w1是奶奶对父亲的影响,w2是父亲对儿子的影响。(示例仅用于描述多层网络,实际上这样的双层网络与单层网络效果无异w3=w2*w1)。

import torch  
  
x = torch.tensor([100.]) # 只做训练不求梯度  
y = torch.tensor([110.])  
lr = 0.00001 # 学习率  
  
model1 = torch.nn.Linear(1, 1, bias=False)  
model2 = torch.nn.Linear(1, 1, bias=False)  
params = [{'params':model1.parameters()},   
          {'params':model2.parameters()}] # 同时优化两组参数  
optimizer = torch.optim.SGD(params, lr=lr, momentum=0) # 优化器  
lossfunc = torch.nn.MSELoss() # 平方根损失  
  
for i in range(10):  
    pred = model2(model1(x))  
    loss = lossfunc(pred, y)  
    loss.backward() # 计算梯度  
    optimizer.step()  
    print("real", y.item(), "pred", pred.item(), "loss", loss.item())  
    print("w1.data", model1.weight.data.item(), "w1.grad", model1.weight.grad.item())  
    print("w2.data", model2.weight.data.item(), "w2.grad", model2.weight.grad.item())  
    optimizer.zero_grad()

(码字不易,转载请注明出处:谢彦的技术博客)

梯度爆炸和梯度消失

梯度爆炸和梯度消失指的是梯度太陡或者梯度太平,引发的调参问题,该问题由连乘引发,假设一个n层网络,每一次都有参数w,b,试想比较简单的情况b=0,于是有如下的前向传播:

当所有的W都大于1,且网络非常深时,最终的Y将非常大甚至越界,反之,当所有W都小于1,且网络非常深时,最终的Y将趋于0。如果网络的后几层的输入和输出都是0,则梯度也必然是“平”的,导致无法正常调参。

一般用下列方法缓解梯度爆炸和消失问题:

  • 用归一化方法处理模型输入x。
  • 控制模型初值参数w。
  • 使用较小的学习率。
  • 对模型中的数据流做一些限制。
  • 使用残差网络,在前向传播过程中累加X值,使得W小于1时,Y也不再趋于0,从而解决梯度消失问题。

当模型参数无法如设想中的调整时,除上述方法还可以尝试:

  • 冻结一些层,调整另一些层。
  • 不能收敛时,或波动太大时,可考虑缩小学习率。
  • 跟踪backward之后参数梯度的均值和方差,查看有无异常。
  • 如果手动实现模型,需要验证梯度计算是否正确(计算双边误差)。
  • 调试时需去掉Dropout,以保持稳定。
  • 注意正则化项对代价函数的影响。
  • W和B一般在训练过程中逐渐变大,有些问题可能只在W,B较大时才出现。
  • 使用BatchNormal时,mini-batch size不能太小。
posted on 2020-06-07 10:53  xieyan0811  阅读(66)  评论(0编辑  收藏  举报