task2.1
自适应学习率
临界点其实不一定是在训练一个网络的时候会遇到的最大的障碍。
一般在训练一个网络的时候,损失原来很大,随着参数不断的更新,损失会越来越小,最后就卡住了,损失不再下降。当我们走到临界点的时候,意味着梯度非常小,但损失不再下降的时候,梯度并没有真的变得很小。看下图就可以看出来
范数(norm),即梯度这个向量的长度
随着迭代次数增多,虽然损失不再下降,但是梯度的范数并没有真的变得很小。
我们现在训练一个网络,训练到现在参数在临界点附近,再根据特征值的正负号判断该临界点是鞍点还是局部最小值。实际上在训练的时候,要走到鞍点或局部最小值,是一件困难的事情。一般的梯度下降,其实是做不到的。用一般的梯度下降训练,往往会在梯度还很大的时候,损失就已经降了下去,这个是需要特别方法训练的。要走到一个临界点其实是比较困难的,多数时候训练在还没有走到临界点的时候就已经停止了。
比如说我们所看到的下面的误差表面,目标误差表面的值应该是在❌在的点,
不断调整学习率,但是仍然到不了,因为学习率已经太小了。AB 段的坡度很陡,梯度的值很大,还能够前进一点。左拐以后,BC 段的坡度已经非常平坦了,这种小的学习率无法再让训练前进。事实上在 BC 段有 10 万个点(10 万次更新),但都无法靠近局部最小值,所以显然就算是一个凸的误差表面,梯度下降也很难训练。
最原始的梯度下降连简单的误差表面都做不好,因此需要更好的梯度下降的版本。在梯度下降里面,所有的参数都是设同样的学习率,这显然是不够的,应该要为每一个参数定制化学习率,即引入自适应学习率(adaptive learning rate)的方法,给每一个参数不同的学习率。
如果在某一个方向上,梯度的值很小,非常平坦,我们会希望学习率调大一点;如果在某一个方向上非常陡峭,坡度很大,我们会希望学习率可以设得小一点。
AdaGrad
能够根据梯度大小自动调整学习率。AdaGrad 可以做到梯度比较大的时候,学习率就减小,梯度比较小的时候,学习率就放大。
参数的更新:
因为刚开始比较小,所以他更新的步幅较大,斜率就较小
算法实现:
%matplotlib inline
import torch
import math
from d2l import torch as d2l
def adagrad_2d(x1,x2,s1,s2):
eps=1e-6
g1,g2=0.2*x1,4*x2
s1+=g1**2
s2+=g2**2
x1-=eta/math.sqrt(s1+eps)*g1
x2-=eta/math.sqrt(s2+eps)*g2
return x1,x2,s1,s2
def f_2d(x1,x2):
return 0.1*x1**2+2*x2**2
eta=0.4
d2l.show_trace_2d(f_2d,d2l.train_2d(adagrad_2d))
将学习率提高到2,可以看到更好的表现
eta=2
d2l.show_trace_2d(f_2d,d2l.train_2d(adagrad_2d))
从零开始实现:
注意:使用d2l.train_ch11调用时,要保证前面已经实现过train_ch11这个函数,由于我已经在以前
的代码中实现过,所以这里就可以进行调用
def init_adagrad_states(feature_dim):
s_w = torch.zeros((feature_dim, 1))
s_b = torch.zeros(1)
return (s_w, s_b)
def adagrad(params, states, hyperparams):
eps = 1e-6
for p, s in zip(params, states):
with torch.no_grad():
s[:] += torch.square(p.grad)
p[:] -= hyperparams['lr'] * p.grad / torch.sqrt(s + eps)
p.grad.data.zero_()
data_iter, feature_dim = d2l.get_data_ch11(batch_size=10)
d2l.train_ch11(adagrad, init_adagrad_states(feature_dim),
{'lr': 0.1}, data_iter, feature_dim);
简洁实现:
trainer=torch.optim.Adagrad
d2l.train_concise_ch11(trainer,{'lr':0.1},data_iter)
RMSprop
在Adagrad中每个g都占有相同的权重,使得对参数的更新限制很大,所以在这里我们使用权重不同的方法更新,其中0<<1
前两个不好用语言描述,所以我在纸上写了一下(字不好,大家多担待)
代码实现:
import torch
import math
from d2l import torch as d2l
d2l.set_figsize()
gammas=[0.95,0.9,0.8,0.7]
for gamma in gammas:
x=torch.arange(40).detach().numpy()
d2l.plt.plot(x,(1-gamma)*gamma**x,label=f'gamma={gamma:.2f}')
d2l.plt.xlabel('time')
从零开始实现:
def rmsprop_2d(x1,x2,s1,s2):
g1,g2,eps=0.2*x1,4*x2,1e-6
s1=gamma*s1+(1-gamma)*g1**2
s2=gamma*s2+(1-gamma)*g2**2
x1-=eta/math.sqrt(s1+eps)*g1
x2-=eta/math.sqrt(s2+eps)*g2
return x1,x2,s1,s2
def f_2d(x1,x2):
return 0.1*x1**2+2*x2**2
eta,gamma=0.4,0.9
d2l.show_trace_2d(f_2d,d2l.train_2d(rmsprop_2d))
def init_rmsprop_states(feature_dim):
s_w=torch.zeros((feature_dim,1))
s_b=torch.zeros(1)
return (s_w,s_b)
def rmsprop(params,states,hyperparams):
gamma,eps=hyperparams['gamma'],1e-6
for p,s in zip(params,states):
with torch.no_grad():
s[:]=gamma*s+(1-gamma)*torch.square(p.grad)
p[:]-=hyperparams['lr']*p.grad/torch.sqrt(s+eps)
p.grad.data.zero_()
data_iter,feature_dim=d2l.get_data_ch11(batch_size=10)
d2l.train_ch11(rmsprop,init_rmsprop_states(feature_dim),{'lr':0.01,'gamma':0.9},data_iter,feature_dim)
简洁实现:
直接调用pytorch中集成的RMSProp函数
trainer=torch.optim.RMSprop
d2l.train_concise_ch11(trainer,{'lr':0.01,'alpha':0.9},data_iter)
Adam
可以看作 RMSprop 加上动量,其使用动量作为参数更新方向,并且能够自适应调整学习率。
Adam算法的关键组成部分之一是:它使用指数加权移动平均值来估算梯度的动量和二次矩,即它使用状态 变量
这里β1和β2是非负加权参数。常将它们设置为β1 = 0.9和β2 = 0.999
也就是说,方差估计的移动远远慢于 动量估计的移动。注意,如果我们初始化v0 = s0 = 0,就会获得一个相当大的初始偏差。我们可以通过使 用来解决这个问题。相应地,标准化状态变量由下式获得
最后简单更新:
xt ← xt−1 − g ′ t .
代码实现:
%matplotlib inline
import torch
from d2l import torch as d2l
def init_adam_states(feature_dim):
v_w,v_b=torch.zeros((feature_dim,1)),torch.zeros(1)
s_w,s_b=torch.zeros((feature_dim,1)),torch.zeros(1)
return ((v_w,s_w),(v_b,s_b))
def adam(params,states,hyperparams):
beta1,beta2,eps=0.9,0.999,1e-6
for p,(v,s) in zip(params,states):
with torch.no_grad():
v[:]=beta1*v+(1-beta1)*p.grad
s[:]=beta2*s+(1-beta2)*torch.square(p.grad)
v_bias_corr=v/(1-beta1**hyperparams['t'])
s_bias_corr=s/(1-beta2**hyperparams['t'])
p[:]-=hyperparams['lr']*v_bias_corr/(torch.sqrt(s_bias_corr)+eps)
p.grad.data.zero_()
hyperparams['t']+=1
data_iter,feature_dim=d2l.get_data_ch11(batch_size=10)
d2l.train_ch11(adam,init_adam_states(feature_dim),{'lr':0.01,'t':1},data_iter,feature_dim)
简洁实现:
trainer=torch.optim.Adam
d2l.train_concise_ch11(trainer,{'lr':0.01},data_iter)
简单的误差表面,我们都训练不起来,加上自适应学习率以后,使用AdaGrad 方法优化的结果如下图所示。一开始优化的时候很顺利,在左转的时候,有 AdaGrad 以后,可以再继续走下去,走到非常接近终点的位置。走到 BC 段时,因为横轴方向的梯度很小,所以学习率会自动变大,步伐就可以变大,从而不断前进。接下来的问题走到图中红圈的地方,快走到终点的时候突然“爆炸”了。是把过去所有的梯度拿来作平均。在 AB段梯度很大,但在 BC 段,纵轴的方向梯度很小,因此纵轴方向累积了很小的,累积到一定程度以后,步伐就变很大,但有办法修正回来。因为步伐很大,其会走到梯度比较大的地方。走到梯度比较大的地方后,会慢慢变大,更新的步伐大小会慢慢变小,从而回到原来的路线。
学习率调度
通过学习率调度(learning rate scheduling)可以解决这个问题。之前的学习率调整方法中 η 是一个固定的值,而在学习率调度中 η 跟时间有关。学习率调度中最常见的策略是学习率衰减(learning rate decay),也称为学习率退火(learning rateannealing)。随着参数的不断更新,让 η 越来越小。在图 上红圈的地方,虽然步伐很大,但 η 变得非常小,步伐乘上 η 就变小了,就可以慢慢地走到终点。
优化的总结
task2.2分类
分类与回归
分类问题简单来说就是给一个东西,然后机器输出是哪一类,不过不是直接给出类别的名字,而是给出所有类别的概率,概率最大的那个值对应的类别就是机器识别出的类。
前面我们所看到的神经网络都只有一个输出,这里需要多个输出。输入是类别的种类
在输出前,会乘上不同的参数最终得出结果
在分类里面,有一种one-hot的算法,独热编码,也就是对应类别值为1,其他均为0
分类实际过程是:输入 x,乘上 W,加上 b,通过激活函数 σ,乘上W′,再加上 b′ 得到向量 。但实际做分类的时候,往往会把 yˆ 通过 softmax 函数得到 y′,才去计算 y′ 跟 yˆ 之间的距离。
这里比较一下前面学过的regression和classification
regression 只需要输出对应标签就可以,而classification需要使用softmax将最后的数值限制在0到1之间
y'=
使用softmax的好处是,在最终输出的时候最好的类别和最差的类别之间数值差距很大,错误的预测对最终预测的影响很小
当只有两类时,sigmoid函数和softmax是等价的
分类的损失
当我们把 x 输入到一个网络里面产生 yˆ 后,通过 softmax 得到 y′,再去计算 y′ 跟 y 之间的距离 e
计算这个距离有两种方法
1.MSE
2.Cross-entropy
对比图:
MSE容易在损失比较大的时候卡住,均方误差在这种损失很大的地方,它是非常平坦的,其梯度是非常小趋近于 0 的。如果初始时在圆圈的位置,离目标非常远,其梯度又很小,无法用梯度下降顺利地“走”到右下角。而在交叉熵的图上左上角圆圈所在的点有斜率的,所以可以通过梯度,一路往右下的地方“走”;所以一般使用Cross-entropy比较好
task2.3实战学习-HW3卷积神经网络(图像分类)
1.准备算力:
-
链接:https://university.aliyun.com/mobile?userCode=1h9ofup
我这里使用支付宝扫码登录,登录后可以在右上角看到自己的账号
学生验证后领取->扫码
再次点学生验证后领取
-
链接:https://www.aliyun.com/activity/bigdata/pai/ds
在终端中,输入git clone https://www.modelscope.cn/datasets/Datawhale/LeeDL-HW3-CNN.git
下载需要的数据集
选择HW3-ImageClassification运行
运行的整个过程用时大概10分钟左右,最后出现2个可视化的图,运行完成记得停止实例,要不然会一直消耗代金卷