梯度:
- 官方解释:梯度的本意是一个向量(矢量),表示某一函数在该点处的方向导数沿着该方向取得最大值,即函数在该点处沿着该方向(此梯度的方向)变化最快,变化率最大(为该梯度的模)。
- 通俗理解:我们对一个多元函数求偏导,会得到多个偏导函数.这些导函数组成的向量,就是梯度。一元函数的梯度就是导数
梯度中元素(导函数)的个数同未知数的个数是对应,每一个未知数都可以像求解一元一次函数一样,通过它所对应的梯度求得最优解(包括参数).其实求解多元函数和一元函数的道理是一样的,只不过函数是一元的时候,梯度中只有一个导函数,函数时多元的时候,梯度中有多个导函数.
如图,当所有维度都下降到梯度为0的点,这个点就是多元函数最优解的那个点呢?
梯度下降:
梯度下降就是让梯度中所有偏导函数都下降到最低点的过程.(划重点:下降)
这里需要注意一点,梯度是对所有未知数求偏导,所得偏导函数组成的向量.在多元线性回归中,谁才是未知数呢?我们使用梯度下降法的目的是求解多元线性回归中的最小二乘函数的,在最小二乘函数中,已拥有的条件是一些样本点和样本点的结果,就是矩阵X和每一条X样本的lable值y.X是矩阵,y是向量.
所以我们要知道,梯度下降中求偏导数的未知数不是x和y,而是x的参数W
下降:
梯度下降中的下降,意思是让函数的未知数随着梯度的方向运动.什么是梯度的方向呢?把这一点带入到梯度函数中,结果为正,那我们就把这一点的值变小一些,同时就是让梯度变小些;当这一点带入梯度函数中的结果为负的时候,就给这一点的值增大一些.
所以梯度下降可以理解为,计算未知数的梯度——让其往梯度为负的方向运动
下面是一个二维随机梯度下降示意:x12+2x22
def train_2d(trainer, steps=20):
x1, x2 = -5, -2
results = [(x1, x2)]
for i in range(steps):
x1, x2 = trainer(x1, x2)
results.append((x1, x2))
print('epoch %d, x1 %f, x2 %f' % (i + 1, x1, x2))
return results
def show_trace_2d(f, results):
d2l.plt.plot(*zip(*results), '-o', color='#ff7f0e')
x1, x2 = np.meshgrid(np.arange(-5.5, 1.0, 0.1), np.arange(-3.0, 1.0, 0.1))
d2l.plt.contour(x1, x2, f(x1, x2), colors='#1f77b4')
d2l.plt.xlabel('x1')
d2l.plt.ylabel('x2')
eta = 0.1
def f_2d(x1, x2): # 目标函数
return x1 ** 2 + 2 * x2 ** 2
def gd_2d(x1, x2):
return (x1 - eta * 2 * x1, x2 - eta * 4 * x2)
show_trace_2d(f_2d, train_2d(gd_2d))
对于梯度下降来说,每次进行参数更新使用的是整个数据集上的平均梯度来对参数进行更新,在数据有一定共性的情况下,对数据的利用率是比较低的。
随机梯度下降
而随机梯度下降进行更新的时候用的是单个样本点的梯度,即batch_size = 1
算力的应用率比较低,没有用上现在比如GPU,多线程的计算能力
随机梯度下降的时间复杂度是O(1)
代码:
def f(x1, x2):
return x1 ** 2 + 2 * x2 ** 2 # Objective
def gradf(x1, x2):
return (2 * x1, 4 * x2) # Gradient
def sgd(x1, x2): # Simulate noisy gradient
global lr # Learning rate scheduler
(g1, g2) = gradf(x1, x2) # Compute gradient
(g1, g2) = (g1 + np.random.normal(0.1), g2 + np.random.normal(0.1))
eta_t = eta * lr() # Learning rate at time t
return (x1 - eta_t * g1, x2 - eta_t * g2) # Update variables
eta = 0.1
lr = (lambda: 1) # Constant learning rate
show_trace_2d(f, train_2d(sgd, steps=50))
小批量随机梯度下降
小批量梯度下降是以上两种方法折中的方案,随机选取一部分样本,然后使用梯度的均值进行更新
从零实现三种下降的对比:
def sgd(params, states, hyperparams):
for p in params:
p.data -= hyperparams['lr'] * p.grad.data
# 本函数已保存在d2lzh_pytorch包中方便以后使用
def train_ch7(optimizer_fn, states, hyperparams, features, labels,
batch_size=10, num_epochs=2):
# 初始化模型
net, loss = d2l.linreg, d2l.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))
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)
对比:
普通梯度下降:batch_size为1500,即全部
随机梯度下降,batch_size为1
小批量随机梯度下降:batch_size为10
以上代码的简洁实现
# 本函数与原书不同的是这里第一个参数优化器函数而不是优化器的名字
# 例如: optimizer_fn=torch.optim.SGD, optimizer_hyperparams={"lr": 0.05}
def train_pytorch_ch7(optimizer_fn, optimizer_hyperparams, features, labels,
batch_size=10, num_epochs=2):
# 初始化模型
net = nn.Sequential(
nn.Linear(features.shape[-1], 1)
)
loss = nn.MSELoss()
optimizer = optimizer_fn(net.parameters(), **optimizer_hyperparams)
def eval_loss():
return loss(net(features).view(-1), labels).item() / 2
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):
# 除以2是为了和train_ch7保持一致, 因为squared_loss中除了2
l = loss(net(X).view(-1), y) / 2
optimizer.zero_grad()
l.backward()
optimizer.step()
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')
动态学习率
在训练的过程中学习率并不是一成不变的,在最开始时学习率设计比较大,加速收敛,后来学习率慢慢减小
例如以下三种动态学习率设计方式,第一种是设置每个时段的学习率,第二种是指数衰减,第三种是多项式衰减方式