小批量随机梯度下降:在更新每一参数时都使用一部分样本来进行更新。为了克服上面两种方法的缺点,又同时兼顾两种方法的优点。
小批量随机梯度下降随机均匀采样一个由训练数据样本索引组成的小批量βt。我们可以通过重复采样(sampling with replacement)或者不重复采样(sampling without replacement)得到一个小批量中的各个样本。可以使用下列公式来计算时间步t的小批量βt上目标函数位于xt-1的梯度gt:
给定学习率 ηt (取正数),小批量随机梯度下降对自变量的迭代如下:
基于随机采样得到的梯度的方差在迭代过程中无法减小。因此在实际中,(小批量)随机梯度下降的学习率可以在迭代过程中自我衰减。
(一)读取数据
本章里我们将使用一个来自NASA的测试不同飞机机翼噪音的数据集来比较各个优化算法 [1]。我们使用该数据集的前1,500个样本和5个特征,并使用标准化对数据进行预处理。
import d2lzh as d2l
from mxnet import autograd, gluon, init, nd
from mxnet.gluon import nn, data as gdata, loss as gloss
import numpy as np
import time
def get_data_ch7():
data = np.genfromtxt('../data/airfoil_self_noise.dat', delimiter='\t') # 从文本文件加载数据
data = (data - data.mean(axis=0)) / data.std(axis=0)
# numpy.mean(axis=0)计算一列的均值,numpy.std(axis=0)计算一列的标准差
return nd.array(data[:1500, :-1]), nd.array(data[:1500, -1]) # [:-1]读取倒数第一个元素之外的所有元素,[-1]读取倒数第一个元素
features, labels = get_data_ch7()
(二)从零开始实现
定义小批量随机梯度下降算法sgd
def sgd(params,states,hyperparams): # 在字典hyperparams里添加了一个状态输入states
for p in params:
p[:] -= hyperparams['lr']*p.grad # 由于训练函数里对各个小批量样本的损失求了平均,所以优化算法的梯度不需要除以批量大小
实现通用的训练函数train_ch7()
def train_ch7(trainer_fn,states,hyperparams,features,labels,batch_size=10,num_epochs=2):
net,loss=d2l.linreg,d2l.squared_loss # 线性回归,平方损失
w=nd.random.normal(scale=0.01,shape=(features.shape[1],1)) # 初始化权重
b=nd.zeros(1) # 初始化偏差
w.attach_grad()
b.attach_grad()
def eval_loss(): # 对损失求平均
return loss(net(features,w,b),labels).mean().asscalar()
ls = [eval_loss()]
data_iter=gdata.DataLoader(gdata.ArrayDataset(features,labels),batch_size,shuffle=True)
for _ in range(num_epochs):
start=time.time()
for batch_i,(X,y) in enumerate(data_iter):
with autograd.record():
l = loss(net(X,w,b),y).mean() # 使用平均误差
l.backward()
trainer_fn([w,b],states,hyperparams) # 迭代模型参数
if (batch_i+1) * batch_size % 100 == 0 :
ls.append(eval_loss()) # 每100个样本记录当前训练误差
print('loss: %f,%f sec per epoch' % (ls[-1],time.time()-start))
d2l.set_figsize()
d2l.plt.plot(np.linspace(0,num_epochs,len(ls)),ls)
d2l.plt.xlabel('epoch')
d2l.plt.ylabel('loss')
def train_sgd(lr,batch_size,num_epochs=2):
train_ch7(sgd,None,{'lr':lr},features,labels,batch_size,num_epochs)
train_sgd(1,1500,6) # 每次迭代数目是总量,代表使用的是梯度下降,训练周期是6
train_sgd(0.005,1) # 当批量大小为1时,优化使用的时随机梯度下降。每次迭代只采用一个样本,所以每个周期需要1500次迭代
train_sgd(0.05,10) # 每次迭代的批量大小为10,小批量随机梯度下降
(三)简洁实现
def train_gluon_ch7(trainer_name,trainer_hyperparams,features,labels,batch_size=10,num_epochs=2):
net = nn.Sequential()
net.add(nn.Dense(1))
net.initialize(init.Normal(sigma=0.01))
loss=gloss.L2Loss()
def eval_loss():
return loss(net(features),labels).mean().asscalar()
ls=[eval_loss()]
data_iter=gdata.DataLoader(gdata.ArrayDataset(features,labels),batch_size,shuffle=True)
trainer=gluon.Trainer(net.collect_params(),trainer_name,trainer_hyperparams)
# 在Gluon里可以通过创建Trainer实例来调用优化算法。
for _ in range (num_epochs):
start = time.time()
for batch_i,(X,y) in enumerate(data_iter):
with autograd.record():
l=loss(net(X),y)
l.backward()
trainer.step(batch_size)
if (batch+i +1) * batch_size % 100 == 0:
ls.append(eval_loss())
print('loss: %f,%f sec per epoch' % (ls[-1],time.time() - start))
d2l.set_figsize()
d2l.plt.plot(np.linspace(0,num_epochs,len(ls)),ls)
d2l.plt.xlabel('epoch')
d2l.plt.ylabel('loss')