在每一次迭代中,梯度下降使用整个训练数据集来计算梯度,因此它有时也被称为批量梯度下降(batch gradient descent)。
- 随机梯度下降在每次迭代中只随机采样一个样本来计算梯度。
- 可以在每轮迭代中随机均匀采样多个样本来组成一个小批量,然后使用这个小批量来计算梯度。
下面就来描述小批量随机梯度下降
- 设目标函数 f ( x ) : R d → R f(\boldsymbol{x}): \mathbb{R}^d \rightarrow \mathbb{R} f(x):Rd→R。
- 在迭代开始前的时间步设为0。
- 该时间步的自变量记为 x 0 ∈ R d \boldsymbol{x}_0\in \mathbb{R}^d x0∈Rd,通常由随机初始化得到
在接下来的每一个时间步 t > 0 t>0 t>0中,小批量随机梯度下降随机均匀采样一个由训练数据样本索引组成的小批量 B t \mathcal{B}_t Bt。我们可以通过重复采样(sampling with replacement)或者不重复采样(sampling without replacement)得到一个小批量中的各个样本。前者允许同一个小批量中出现重复的样本,后者则不允许如此,且更常见。对于这两者间的任一种方式,都可以使用
g t ← ∇ f B t ( x t − 1 ) = 1 ∣ B ∣ ∑ i ∈ B t ∇ f i ( x t − 1 ) \boldsymbol{g}_t \leftarrow \nabla f_{\mathcal{B}_t}(\boldsymbol{x}_{t-1}) = \frac{1}{|\mathcal{B}|} \sum_{i \in \mathcal{B}_t}\nabla f_i(\boldsymbol{x}_{t-1}) gt←∇fBt(xt−1)=∣B∣1i∈Bt∑∇fi(xt−1)
来计算时间步 t t t的小批量 B t \mathcal{B}_t Bt上目标函数位于 x t − 1 \boldsymbol{x}_{t-1} xt−1处的梯度 g t \boldsymbol{g}_t gt。这里 ∣ B ∣ |\mathcal{B}| ∣B∣代表批量大小,即小批量中样本的个数,是一个超参数。同随机梯度一样,重复采样所得的小批量随机梯度 g t \boldsymbol{g}_t gt也是对梯度 ∇ f ( x t − 1 ) \nabla f(\boldsymbol{x}_{t-1}) ∇f(xt−1)的无偏估计。给定学习率 η t \eta_t ηt(取正数),小批量随机梯度下降对自变量的迭代如下:
x t ← x t − 1 − η t g t . \boldsymbol{x}_t \leftarrow \boldsymbol{x}_{t-1} - \eta_t \boldsymbol{g}_t. xt←xt−1−ηtgt.
基于随机采样得到的梯度的方差在迭代过程中无法减小,因此在实际中,(小批量)随机梯度下降的学习率可以在迭代过程中自我衰减,例如 η t = η t α \eta_t=\eta t^\alpha ηt=ηtα(通常 α = − 1 \alpha=-1 α=−1或者 − 0.5 -0.5 −0.5)、 η t = η α t \eta_t = \eta \alpha^t ηt=ηαt(如 α = 0.95 \alpha=0.95 α=0.95)或者每迭代若干次后将学习率衰减一次。如此一来,
- 学习率和(小批量)随机梯度乘积的方差会减小
- 而梯度下降在迭代过程中一直使用目标函数的真实梯度,无须自我衰减学习率
小批量随机梯度下降中每次迭代的计算开销为 O ( ∣ B ∣ ) \mathcal{O}(|\mathcal{B}|) O(∣B∣)。
- 当批量大小为1时,该算法即为随机梯度下降;当批量大小等于训练数据样本数时,该算法即为梯度下降。
- 当批量较小时,每次迭代中使用的样本少,这会导致并行处理和内存使用效率变低。这使得在计算同样数目样本的情况下比使用更大批量时所花时间更多。
- 当批量较大时,每个小批量梯度里可能含有更多的冗余信息。
- 为了得到较好的解,批量较大时比批量较小时需要计算的样本数目可能更多,例如增大迭代周期数。
实现小批量随机梯度下降
- 使用一个来自NASA的测试不同飞机机翼噪音的数据集来比较各个优化算法
- 使用该数据集的前1,500个样本和5个特征
- 使用标准化对数据进行预处理。
%matplotlib inline
import numpy as np
import time
import torch
from torch import nn, optim
import sys
from matplotlib import pyplot as plt
sys.path.append("..")
def get_data_ch7():
data = np.genfromtxt('data/airfoil_self_noise.dat', delimiter='\t')
data = (data - data.mean(axis=0)) / data.std(axis=0)#标准化处理
# data.mean(axis=0) 输出矩阵为一行,求每列的平均值,同理data.mean(axis=1) 输出矩阵为一列,求每行的平均值
# data.std(axis=0) 输出矩阵为一列,求每列的标准差,同理data.std(axis=1) 输出矩阵为一列,求每行的标准差
return torch.tensor(data[:1500, :-1], dtype=torch.float32), \
torch.tensor(data[:1500, -1], dtype=torch.float32) # 前1500个样本(每个样本5个特征)
#[:-1]=[0:-1],表示除了最后一列的所有列
features, labels = get_data_ch7()
features.shape # torch.Size([1500, 5])
def sgd(params, states, hyperparams):#梯度下降(惩罚)
for p in params:
p.data -= hyperparams['lr'] * p.grad.data
def linreg(X, w, b):#线性函数
return torch.mm(X, w) + b
def squared_loss(y_hat, y):
# 注意这里返回的是向量, 另外, pytorch里的MSELoss并没有除以 2
return ((y_hat - y.view(y_hat.size())) ** 2) / 2
def set_figsize(figsize=(3.5, 2.5)):
use_svg_display()
# 设置图的尺寸
plt.rcParams['figure.figsize'] = figsize
#初始化一个线性回归模型,然后可以使用小批量随机梯度下降以及其他算法来训练模型
def train_ch7(optimizer_fn, states, hyperparams, features, labels,
batch_size=10, num_epochs=2):
# 初始化模型
net, loss = linreg, squared_loss
w = torch.nn.Parameter(torch.tensor(np.random.normal(0, 0.01, size=(features.shape[1], 1)), dtype=torch.float32),
requires_grad=True)
b = torch.nn.Parameter(torch.zeros(1, dtype=torch.float32), requires_grad=True)
def eval_loss():
return loss(net(features, w, b), labels).mean().item()
ls = [eval_loss()]
data_iter = torch.utils.data.DataLoader(
torch.utils.data.TensorDataset(features, labels), batch_size, shuffle=True)
for _ in range(num_epochs):
start = time.time()
for batch_i, (X, y) in enumerate(data_iter):
l = loss(net(X, w, b), y).mean() # 使用平均损失
# 梯度清零
if w.grad is not None:
w.grad.data.zero_()
b.grad.data.zero_()
l.backward()
optimizer_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))
set_figsize()
plt.plot(np.linspace(0, num_epochs, len(ls)), ls)
#np.linspace分割横坐标
plt.xlabel('epoch')
plt.ylabel('loss')
当批量大小为样本总数1,500时,优化使用的是梯度下降。梯度下降的1个迭代周期对模型参数只迭代1次。可以看到6次迭代后目标函数值(训练损失)的下降趋向了平稳。
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)
当批量大小为1时,优化使用的是随机梯度下降。为了简化实现,有关(小批量)随机梯度下降的实验中,我们未对学习率进行自我衰减,而是直接采用较小的常数学习率。随机梯度下降中,每处理一个样本会更新一次自变量(模型参数),一个迭代周期里会对自变量进行1,500次更新。可以看到,目标函数值的下降在1个迭代周期后就变得较为平缓。
train_sgd(0.005, 1)
虽然随机梯度下降和梯度下降在一个迭代周期里都处理了1,500个样本,但实验中随机梯度下降的一个迭代周期耗时更多。这是因为随机梯度下降在一个迭代周期里做了更多次的自变量迭代,而且单样本的梯度计算难以有效利用矢量计算。
当批量大小为10时,优化使用的是小批量随机梯度下降。它在每个迭代周期的耗时介于梯度下降和随机梯度下降的耗时之间。
train_sgd(0.05, 10)
- 小批量随机梯度每次随机均匀采样一个小批量的训练样本来计算梯度。
- 在实际中,(小批量)随机梯度下降的学习率可以在迭代过程中自我衰减。
- 通常,小批量随机梯度在每个迭代周期的耗时介于梯度下降和随机梯度下降的耗时之间。