新手必备 | 史上最全的PyTorch学习资源汇总
快速上手笔记,PyTorch模型训练实用教程(附代码)
PyTorch学习笔记
《深度学习框架PyTorch:入门与实践》的对应代码
PyTorch 中文文档
for i in range(nb_epochs):
params_grad = evaluate_gradient(loss_function, data, params)
params = params - learning_rate * params_grad
我们会事先定义一个迭代次数 epoch,首先计算梯度向量 params_grad,然后沿着梯度的方向更新参数 params,learning rate 决定了我们每一步迈多大。
Batch gradient descent 对于凸函数可以收敛到全局极小值,对于非凸函数可以收敛到局部极小值。
梯度更新规则: 和 BGD 的一次用所有数据计算梯度相比,SGD 每次更新时对每个样本进行梯度更新,
对于很大的数据集来说,可能会有相似的样本,这样 BGD 在计算梯度时会出现冗余,
而 SGD 一次只进行一次更新,就没有冗余,而且比较快,并且可以新增样本。
θ = θ − η ⋅ ∇ θ J ( θ ; x ( i ) ; y ( i ) ) \theta=\theta-\eta \cdot \nabla_{\theta} J\left(\theta ; x^{(i)} ; y^{(i)}\right) θ=θ−η⋅∇θJ(θ;x(i);y(i))
for i in range(nb_epochs):
np.random.shuffle(data)
for example in data:
params_grad = evaluate_gradient(loss_function, example, params)
params = params - learning_rate * params_grad
看代码,可以看到区别,就是整体数据集是个循环,其中对每个样本进行一次参数更新。
缺点: 但是 SGD 因为更新比较频繁,会造成 cost function 有严重的震荡。
BGD 可以收敛到局部极小值,当然 SGD 的震荡可能会跳到更好的局部极小值处。
当我们稍微减小 learning rate,SGD 和 BGD 的收敛性是一样的。
梯度更新规则: MBGD 每一次利用一小批样本,即 n 个样本进行计算,
这样它可以降低参数更新时的方差,收敛更稳定,
另一方面可以充分地利用深度学习库中高度优化的矩阵操作来进行更有效的梯度计算
θ = θ − η ⋅ ∇ θ J ( θ ; x ( i : i + n ) ; y ( i : i + n ) ) \theta=\theta-\eta \cdot \nabla_{\theta} J\left(\theta ; x^{(i : i+n)} ; y^{(i : i+n)}\right) θ=θ−η⋅∇θJ(θ;x(i:i+n);y(i:i+n))
和 SGD 的区别是每一次循环不是作用于每个样本,而是具有 n 个样本的批次
for i in range(nb_epochs):
np.random.shuffle(data)
for batch in get_batches(data, batch_size=50):
params_grad = evaluate_gradient(loss_function, batch, params)
params = params - learning_rate * params_grad
超参数设定值: n 一般取值在 50~256
缺点:
不过 Mini-batch gradient descent 不能保证很好的收敛性:
(有一种措施是先设定大一点的学习率,当两次迭代之间的变化低于某个阈值后,就减小 learning rate,不过这个阈值的设定需要提前写好,这样的话就不能够适应数据集的特点)
此外,这种方法是对所有参数更新时应用同样的 learning rate,如果我们的数据是稀疏的,我们更希望对出现频率低的特征进行大一点的更新。
另外,对于非凸函数,还要避免陷于局部极小值处,或者鞍点处,因为鞍点周围的error 是一样的,所有维度的梯度都接近于0,SGD 很容易被困在这里。
鞍点就是:一个光滑函数的鞍点邻域的曲线,曲面,或超曲面,都位于这点的切线的不同边。
例如下图这个二维图形,像个马鞍:在x-轴方向往上曲,在y-轴方向往下曲,鞍点就是(0,0)
SGD 在 ravines 的情况下容易被困住, ravines 就是曲面的一个方向比另一个方向更陡,这时 SGD 会发生震荡而迟迟不能接近极小值:
梯度更新规则: Momentum 通过加入 γv_t−1 ,可以加速 SGD, 并且抑制震荡
v t = γ v t − 1 + η ∇ θ J ( θ ) θ = θ − v t \begin{array}{l}{v_{t}=\gamma v_{t-1}+\eta \nabla_{\theta} J(\theta)} \\ {\theta=\theta-v_{t}}\end{array} vt=γvt−1+η∇θJ(θ)θ=θ−vt
当我们将一个小球从山上滚下来时,没有阻力的话,它的动量会越来越大,但是如果遇到了阻力,速度就会变小。
加入的这一项,可以使得梯度方向不变的维度上速度变快,梯度方向有所改变的维度上的更新速度变慢,这样就可以加快收敛并减小震荡。
超参数设定值:
一般 γ 取值 0.9 左右。
缺点:
这种情况相当于小球从山上滚下来时是在盲目地沿着坡滚,如果它能具备一些先知,例如快要上坡时,就知道需要减速了的话,适应性会更好。
梯度更新规则:
用 θ−γv_t−1 来近似当做参数下一步会变成的值,则在计算梯度时,不是在当前位置,而是未来的位置上
v t = γ v t − 1 + η ∇ θ J ( θ − γ v t − 1 ) θ = θ − v t \begin{array}{l}{v_{t}=\gamma v_{t-1}+\eta \nabla_{\theta} J\left(\theta-\gamma v_{t-1}\right)} \\ {\theta=\theta-v_{t}}\end{array} vt=γvt−1+η∇θJ(θ−γvt−1)θ=θ−vt
超参数设定值:
γ 仍然取值 0.9 左右。
效果比较:
蓝色是 Momentum 的过程,会先计算当前的梯度,然后在更新后的累积梯度后会有一个大的跳跃。
而 NAG 会先在前一步的累积梯度上(brown vector)有一个大的跳跃,然后衡量一下梯度做一下修正(red vector),这种预期的更新可以避免我们走的太快。
NAG 可以使 RNN 在很多任务上有更好的表现。
目前为止,我们可以做到,在更新梯度时顺应 loss function 的梯度来调整速度,并且对 SGD 进行加速。
我们还希望可以根据参数的重要性而对不同的参数进行不同程度的更新。
这个算法就可以对低频的参数做较大的更新,对高频的做较小的更新,也因此,对于稀疏的数据它的表现很好,很好地提高了 SGD 的鲁棒性,例如识别 Youtube 视频里面的猫,训练 GloVe word embeddings,因为它们都是需要在低频的特征上有更大的更新。
梯度更新规则: θ t + 1 , i = θ t , i − η G t , i i + ϵ ⋅ g t , i \theta_{t+1, i}=\theta_{t, i}-\frac{\eta}{\sqrt{G_{t, i i}+\epsilon}} \cdot g_{t, i} θt+1,i=θt,i−Gt,ii+ϵη⋅gt,i
其中 g 为:t 时刻参数 θ_i 的梯度 g t , i = ∇ θ J ( θ i ) g_{t, i}=\nabla_{\theta} J\left(\theta_{i}\right) gt,i=∇θJ(θi)
如果是普通的 SGD, 那么 θ_i 在每一时刻的梯度更新公式为 θ t + 1 , i = θ t , i − η ⋅ g t , i \theta_{t+1, i}=\theta_{t, i}-\eta \cdot g_{t, i} θt+1,i=θt,i−η⋅gt,i
但这里的 learning rate η 也随 t 和 i 而变:
θ t + 1 , i = θ t , i − η G t , i i + ϵ ⋅ g t , i \theta_{t+1, i}=\theta_{t, i}-\frac{\eta}{\sqrt{G_{t, i i}+\epsilon}} \cdot g_{t, i} θt+1,i=θt,i−Gt,ii+ϵη⋅gt,i
其中 G_t 是个对角矩阵, (i,i) 元素就是 t 时刻参数 θ_i 的梯度平方和。Adagrad 的优点是减少了学习率的手动调节
超参数设定值: 一般 η 就取 0.01。
缺点: 它的缺点是分母会不断积累,这样学习率就会收缩并最终会变得非常小。
这个算法是对 Adagrad 的改进,和 Adagrad 相比,就是分母的 G 换成了过去的梯度平方的衰减平均值:
Δ θ t = − η E [ g 2 ] t + ϵ g t \Delta \theta_{t}=-\frac{\eta}{\sqrt{E\left[g^{2}\right]_{t}+\epsilon}} g_{t} Δθt=−E[g2]t+ϵηgt
这个分母相当于梯度的均方根 root mean squared (RMS) ,所以可以用 RMS 简写: Δ θ t = − η R M S [ g ] t g t \Delta \theta_{t}=-\frac{\eta}{R M S[g]_{t}} g_{t} Δθt=−RMS[g]tηgt
其中 E 的计算公式如下,t 时刻的依赖于前一时刻的平均和当前的梯度:
E [ g 2 ] t = γ E [ g 2 ] t − 1 + ( 1 − γ ) g t 2 E\left[g^{2}\right]_{t}=\gamma E\left[g^{2}\right]_{t-1}+(1-\gamma) g_{t}^{2} E[g2]t=γE[g2]t−1+(1−γ)gt2
梯度更新规则:
此外,还将学习率 η 换成了 RMS[Δθ],这样的话,我们甚至都不需要提前设定学习率了:
Δ θ t = − R M S [ Δ θ ] t − 1 R M S [ g ] t g t θ t + 1 = θ t + Δ θ t \begin{aligned} \Delta \theta_{t} &=-\frac{R M S[\Delta \theta]_{t-1}}{R M S[g]_{t}} g_{t} \\ \theta_{t+1} &=\theta_{t}+\Delta \theta_{t} \end{aligned} Δθtθt+1=−RMS[g]tRMS[Δθ]t−1gt=θt+Δθt
超参数设定值:
γ 一般设定为 0.9。
RMSprop 是 Geoff Hinton 提出的一种自适应学习率方法。
RMSprop 和 Adadelta 都是为了解决 Adagrad 学习率急剧下降问题的,
梯度更新规则:
RMSprop 与 Adadelta 的第一种形式相同:
E [ g 2 ] t = 0.9 E [ g 2 ] t − 1 + 0.1 g t 2 θ t + 1 = θ t − η E [ g 2 ] t + ϵ g t \begin{array}{l}{E\left[g^{2}\right]_{t}=0.9 E\left[g^{2}\right]_{t-1}+0.1 g_{t}^{2}} \\ {\theta_{t+1}=\theta_{t}-\frac{\eta}{\sqrt{E\left[g^{2}\right]_{t}+\epsilon}} g_{t}}\end{array} E[g2]t=0.9E[g2]t−1+0.1gt2θt+1=θt−E[g2]t+ϵηgt
超参数设定值:
Hinton 建议设定 γ 为 0.9, 学习率 η 为 0.001。
这个算法是另一种计算每个参数的自适应学习率的方法。
除了像 Adadelta 和 RMSprop 一样存储了过去梯度的平方 vt 的指数衰减平均值 ,也像 momentum 一样保持了过去梯度 mt 的指数衰减平均值:
m t = β 1 m t − 1 + ( 1 − β 1 ) g t v t = β 2 v t − 1 + ( 1 − β 2 ) g t 2 \begin{array}{l}{m_{t}=\beta_{1} m_{t-1}+\left(1-\beta_{1}\right) g_{t}} \\ {v_{t}=\beta_{2} v_{t-1}+\left(1-\beta_{2}\right) g_{t}^{2}}\end{array} mt=β1mt−1+(1−β1)gtvt=β2vt−1+(1−β2)gt2
如果 mt 和 vt 被初始化为 0 向量,那它们就会向 0 偏置,所以做了偏差校正,
通过计算偏差校正后的 mt 和 vt 来抵消这些偏差:
m ^ t = m t 1 − β 1 t v ^ t = v t 1 − β 2 t \begin{aligned} \hat{m}_{t} &=\frac{m_{t}}{1-\beta_{1}^{t}} \\ \hat{v}_{t} &=\frac{v_{t}}{1-\beta_{2}^{t}} \end{aligned} m^tv^t=1−β1tmt=1−β2tvt
梯度更新规则:
$$
\theta_{t+1}=\theta_{t}-\frac{\eta}{\sqrt{\hat{v}{t}}+\epsilon} \hat{\hat{m}{t}}
$$
超参数设定值:
建议 β 1 = 0.9 , β 2 = 0.999 , ϵ = 10 e − 8 \beta 1=0.9, \quad \beta 2=0.999, \quad \epsilon=10 e-8 β1=0.9,β2=0.999,ϵ=10e−8
实践表明,Adam 比其他适应性学习方法效果要好。
上面两种情况都可以看出,Adagrad, Adadelta, RMSprop 几乎很快就找到了正确的方向并前进,收敛速度也相当快,而其它方法要么很慢,要么走了很多弯路才找到。
由图可知自适应学习率方法即 Adagrad, Adadelta, RMSprop, Adam 在这种情景下会更合适而且收敛性更好。
如果数据是稀疏的,就用自适用方法,即 Adagrad, Adadelta, RMSprop, Adam。
RMSprop, Adadelta, Adam 在很多情况下的效果是相似的。
Adam 就是在 RMSprop 的基础上加了 bias-correction 和 momentum,
随着梯度变的稀疏,Adam 比 RMSprop 效果会好。
整体来讲,Adam 是最好的选择。
很多论文里都会用 SGD,没有 momentum 等。SGD 虽然能达到极小值,但是比其它算法用的时间长,而且可能会被困在鞍点。
如果需要更快的收敛,或者是训练更深更复杂的神经网络,需要用一种自适应的算法。
import torch.nn.functional as F
import torch.nn.init as init
import torch
from torch.autograd import Variable
import matplotlib.pyplot as plt
import numpy as np
import math
%matplotlib inline
#梯度下降法
J=lambda w:15*w**4 - 15*w**3 + 3*w**2#创建一个较为复杂的函数
J_prime = lambda w:(6*w**3-45*w**2+6*w)*(1+10*np.random.random())
#在J函数导数后面加上了额一个噪声是为了模拟随机梯度下降
#随机梯度下降是每一次在数据里随机选取若干样本得到的梯度
w=np.linspace(-10,20,100)
plt.plot(w,J(w))#将结果画出来,w和dJ(w)/dw
#通过将梯度进行累加求平均可以解决噪声带来的影响,但是这样求平均并不是最优的方法
J=0
for i in range(100):
J += J_prime(1)
J/100
-199.57701450511132
#动量算法,我们希望每次累加的量但是更加重视当下的梯度,因此要做一个随着时间变化的加权平均
J=0
JJ=[]#用来记录梯度的值
for i in range(1000):
J=0.9*J+0.1*J_prime(1)
#我们对当下的梯度赋予一个权重,J_prime是当下梯度
JJ.append(J)
plt.plot(JJ)
[]
#动量梯度下降法,一定要初始化v
w= 2
epoch=100
lr=0.001
beta=0.5
y=[]
v=0
Loss=[]
W=[]
for i in range(epoch):
v=beta*v + (1-beta)*J_prime(w)
#第一次的梯度是没有的,所以初始化为0,beta控制两次梯度的权重来合成新的梯度
w=w-lr*v
#用v进行梯度更新
# Loss.append(J(w))
W.append(w)
plt.plot(Loss)
plt.figure()
plt.plot(W)
J(w)
#二维情况下的随机梯度下降
J = lambda w1,w2: w1**2 + 10*w2**2
#是一个椭圆函数,w1和w2的初值差异比较大
J_prime1 = lambda w1: 2*w1
J_prime2 = lambda w2:20*w2
#设初值
w1 = 1
w2 = -1
epoch = 200
lr = 0.01
y = []
v = 0
s = 0
Loss = []
W1 = []
W2 = []
for i in range(epoch):
w1 = w1 - lr*(J_prime1(w1))
w2 = w2 - lr*(J_prime2(w2))
W1.append(w1)
W2.append(w2)
Loss.append(J(w1,w2))
plt.plot(Loss)
plt.figure()
plt.plot(W1)
plt.plot(W2)
w1,w2
(0.017587946605721556, -4.149515568880996e-20)
#Ada自适应梯度调节法,为了解决两个参数优化是差异过大存在的问题,调节不同方向上梯度变化
J = lambda w1,w2: w1**2 + 10*w2**2
#是一个椭圆函数,w1和w2的初值差异比较大
J_prime1 = lambda w1: 2*w1
J_prime2 = lambda w2:20*w2
#设初值
w1 = 1
w2 = -1
epoch = 200
lr = 0.01
y = []
v = 0
s = 0
Loss = []
W1 = []
W2 = []
s1 = s2 = 0
for i in range(epoch):
s1 += J_prime1(w1)**2
w1 = w1 - lr*(J_prime1(w1)/np.sqrt(s1))
#大的梯度变化会除以更大的归一化因子
s2 += J_prime2(w2)**2
w2 = w2 - lr*(J_prime1(w2)/np.sqrt(s2))
W1.append(w1)
W2.append(w2)
Loss.append(J(w1,w2))
plt.plot(Loss)
plt.figure()
plt.plot(W1)
plt.plot(W2)
w1,w2
(0.7455204633536091, -0.9732717703657504)
#引入动量的思想
s = 0
S = []
beta = 0.8
for i in range(100):
s = 0.2*s + 0.8*J_prime1(w1)**2
S.append(np.sqrt(s))
w1 = w1 -lr*(J_prime1(w1)/s)
W1.append(w1)
plt.plot(S)
plt.figure()
plt.plot(W1)
[]
J = lambda w1,w2: w1**2 + 10*w2**2
#是一个椭圆函数,w1和w2的初值差异比较大
J_prime1 = lambda w1: 2*w1
J_prime2 = lambda w2: 20*w2
#设初值
w1 = 1
w2 = -1
epoch = 200
lr = 0.01
beta2 = 0.5
y = []
v = 0
s = 0
Loss = []
W1 = []
W2 = []
s1 = s2 = 0
for i in range(epoch):
s1 = beta2*s1 + (1-beta2)*(J_prime1(w1)**2)#引入动量的思想
s1_correct = s1/(1-beta2**(i+1))#s1_correct随时间变化的函数
w1 = w1 - lr*(J_prime1(w1)/np.sqrt(s1))
s2 = beta2*s2 + (1-beta2)*(J_prime1(w2)**2)
s2_correct = s2/(1-beta2**(i+1))
w2 = w2 - lr*(J_prime1(w2)/np.sqrt(s2))
W1.append(w1)
W2.append(w2)
Loss.append(J(w1,w2))
plt.plot(Loss)
plt.figure()
plt.plot(W1)
plt.plot(W2)
w1,w2
(-0.005000000000000253, 0.005000000000000253)
#一维情况
J=lambda w:1.5*w**4 - 15*w**3 + 3*w**2#创建一个较为复杂的函数
J_prime = lambda w:(6*w**3-45*w**2+6*w)*(1+10*np.random.random())
w = 1
epoch = 200
lr = 0.1
beta1 = 0.9
beta2 = 0.99
y = []
v = 0
s = 0
Loss = []
W = []
for i in range(epoch):
v = beta1*v + (1-beta1)*J_prime(w)
v_correct = v/(1-beta1**(i+1))
s = beta2*s + (1-beta2)*(J_prime(w)**2)
s_correct = s/(1-beta2**(i+1))
w = w - lr*(v/np.sqrt(s))
W.append(w)
Loss.append(J(w))
plt.plot(Loss)
plt.figure()
plt.plot(W)
w
7.364246471305756
#二维情况
J = lambda w1,w2: w1**2 + 10*w2**2
J_prime1 = lambda w1: 2*w1
J_prime2 = lambda w2: 20*w2
w1 = 1
w2 = -1
epoch = 200
lr = 0.01
beta1 = 0.9
beta2 = 0.99
y = []
v1 = v2 = 0
s1 = s1 = 0
Loss = []
W1 = []
W2 = []
for i in range(epoch):
v1 = beta1*v1 +(1-beta1)*J_prime1(w1)
v1_correct = v1/(1 - beta1**(i+1))
s1 = beta2*s1 +(1-beta2)*(J_prime1(w1)**2)
s1_correct = s1/(1 - beta2**(i+1))
w1 = w1 - lr*(v1/np.sqrt(s1))
v2 = beta1*v2 +(1-beta1)*J_prime1(w2)
v2_correct = v2/(1 - beta1**(i+1))
s2 = beta2*s2 +(1-beta2)*(J_prime1(w2)**2)
s2_correct = s2/(1 - beta2**(i+1))
w2 = w2 - lr*(v2/np.sqrt(s2))
W1.append(w1)
W2.append(w2)
Loss.append(J(w1,w2))
plt.plot(Loss)
plt.figure()
plt.plot(W1)
plt.plot(W2)
w1,w2
(4.6614365646922596e-05, -4.6504413581837425e-05)
import torch.nn.functional as F
import torch.nn.init as init
import torch
import pandas as pd
from torch.autograd import Variable
import matplotlib.pyplot as plt
import numpy as np
import math
%matplotlib inline
#%matplotlib inline 可以在Ipython编译器里直接使用
#功能是可以内嵌绘图,并且可以省略掉plt.show()这一步。
data = pd.read_csv("diabetes.csv",engine = "python").values
x_data = torch.from_numpy(data[:,0:-1])
y_data = torch.from_numpy(data[:,-1])
print(x_data.size(),y_data.size())
#print(x_data.shape,y_data.shape)
#建立网络模型
class Model(torch.nn.Module):
def __init__(self):
super(Model,self).__init__()
self.l1=torch.nn.Linear(8,6)
self.l2=torch.nn.Linear(6,4)
self.l3=torch.nn.Linear(4,1)
self.sigmoid = torch.nn.Sigmoid()
def forward(self,x):
out1=self.sigmoid(self.l1(x.float()))
out2=self.sigmoid(self.l2(out1))
y_pred=self.sigmoid(self.l3(out2))
return y_pred
#our model
model=Model()
criterion=torch.nn.BCELoss(size_average=True)
#optimizer=torch.optim.SGD(model.parameters(),lr=0.1),正常的梯度下降
#optimizer=torch.optim.SGD(model.parameters(),lr=0.1,momentum=0.9),带动量比列的梯度下降
#Adam
optimizer=torch.optim.Adam(model.parameters(),lr=0.05,betas=(0.9,0.999),weight_decay=0.001)
#training loop
Loss=[]
for epoch in range(200):
y_pred=model(x_data)
loss=criterion(y_pred.float(),y_data.float())
if epoch%20 == 0:
print("epoch = ",epoch," loss = ",loss.data.item())
Loss.append(loss.data.item())
optimizer.zero_grad()
loss.backward()
optimizer.step()
hour_var = Variable(torch.randn(1,8))
print("predict",model(hour_var).data[0]>0.5)
plt.plot(Loss)
torch.Size([768, 8]) torch.Size([768])
epoch = 0 loss = 0.7242081761360168
epoch = 20 loss = 0.6501374244689941
epoch = 40 loss = 0.64681476354599
epoch = 60 loss = 0.6456142067909241
epoch = 80 loss = 0.6380729079246521
epoch = 100 loss = 0.5820186734199524
epoch = 120 loss = 0.5744820833206177
epoch = 140 loss = 0.5743507146835327
epoch = 160 loss = 0.5673063397407532
epoch = 180 loss = 0.5591199994087219
predict tensor([1], dtype=torch.uint8)
[]