本文主要对梯度下降算法的基本原理进行了讲解,然后使用手写梯度下降算法解决了线性回归问题。最后对 PyTorch 中的反向传播函数进行了讲解并利用该函数简明快速的完成了损失的求导与模型的训练。
梯度下降算法
- 一、人工梯度下降算法
- 1.线性回归问题
- 2.梯度下降算法
- 3.人工实现梯度下降算法
- 二、利用 PyTorch 实现梯度下降算法
一、人工梯度下降算法
我们使用梯度下降算法来对线性回归问题进行求解。
1.线性回归问题
)用以表示所有的数据点,如下:
如上图所示,线性回归问题其实就是求解上面的线性函数中最佳的值。合适函数可以表示标签和数据之间的关系,进而预测新的所对应的预测值,其中。那么我们应当用什么来衡量最佳的值呢?
与真实值的距离越小,那么该函数就越好的值就越趋近于最佳。在机器学习中,这种计算距离的函数有一个名字,叫做损失函数。定义如下:
import numpy as np
# 所有点的预测值和实际值的距离的平方和,再取平均值(这种距离叫做欧氏距离)。
def loss(y, y_pred):
return ((y_pred - y)**2).mean()
#测试代码
y_pred = np.array([1,2])
y = np.array([1,1])
loss(y, y_pred)
输出结果如下:
0.5
综上,线性回归问题其实就是求解损失函数最小的情况下的值。
2.梯度下降算法
梯度下降算法是一种用于求解函数极小值的方法。我们可以把梯度下降算法类比为一个下山的过程。假设一个人被困在山上,需要快速从山上下来,走到山底。 但是,由于山中浓雾很大,导致可视度很低。因此,我们无法确定下山的路径,只能看到周围的一些信息。也就是说我们需要走一步看一步再走一步。此时,我们就可以使用到梯度下降的算法。如下:
我们需要找的损失函数也可以看做一座山,我们的目标就是找到这座山的最小值,即山底。每走一步,我们都会重新找山脚的方向。因为沿着山脚方向走能够使我们最快到达山脚的位置。
由于梯度表示的是函数上升最快的方向,因此梯度的反方向也应该是函数下降最快的方向。我们每次到了一个新的位置,就会就计算该位置的梯度,找到下一步下山最快的方向。
根据梯度和当前位置更新下一次所在位置的数学表达式如下:
上面式子展示了损失函数的最小值的求解过程。
其中表示当前所在位置,表示下一步的位置,表示步长(即一次更新的距离), 表示损失函数的梯度的相反方向。我们可以将损失函数值定义为欧氏距离,如下:
损失函数关于的梯度为:
根据上面的梯度公式,让我们来定义损失函数的梯度计算公式:
#返回dJ/dw
def gradient(x, y, w):
return np.mean(2*w*x*x-2*x*y)
## 测试代码
x = np.array([1,2])
y = np.array([1,1])
gradient(x, y, 2)
输出结果如下:
7.0
3.人工实现梯度下降算法
假设 w 为损失函数需要求的变量,那么梯度下降算法的具体步骤如下:
1.随机初始化一个的值。
2.在该下进行正向传播,得到所有的预测值。
3.通过实际的值和预测值计算损失。
4.通过损失计算梯度。
5.更新,其中为步长,可自定义具体的值。
6.重复步骤 2−5,直到损失降到较小位置。
首先,让我们先来定义一下梯度下降算法所需要的数据集和变量值:
# 正向传播,计算预测值
def forward(x):
return w * x
# 定义数据集合和 w 的初始化
X = np.array([1, 2, 3, 4], dtype=np.float32)
Y = np.array([2, 4, 6, 8], dtype=np.float32)
w = 0.0
# 定义步长和迭代次数
learning_rate = 0.01
n_iters = 20
接下来,让我们根据上面步骤,利用梯度下降算法求解一元回归函数中的的值:
for epoch in range(n_iters):
y_pred = forward(X)
#计算损失
l = loss(Y, y_pred)
#计算梯度
dw = gradient(X, Y, w)
#更新权重 w
w -= learning_rate * dw
if epoch % 2 == 0:
print(f'epoch {epoch+1}: w = {w:.3f}, loss = {l:.8f}')
print(f'根据训练模型预测,当 x =5 时,y 的值为: {forward(5):.3f}')
输出结果如下:
epoch 1: w = 0.300, loss = 30.00000000
epoch 3: w = 0.772, loss = 15.66018677
epoch 5: w = 1.113, loss = 8.17471600
epoch 7: w = 1.359, loss = 4.26725292
epoch 9: w = 1.537, loss = 2.22753215
epoch 11: w = 1.665, loss = 1.16278565
epoch 13: w = 1.758, loss = 0.60698175
epoch 15: w = 1.825, loss = 0.31684822
epoch 17: w = 1.874, loss = 0.16539653
epoch 19: w = 1.909, loss = 0.08633806
根据训练模型预测,当 x =5 时,y 的值为: 9.612
从结果可以很清晰的函数,我们利用梯度下降算法,不断的降低损失的值,寻找最佳的权重。当损失不再发生变化时,证明我们已经找到了一个较小的值,此时的就是较佳权重(根据结果可以看到,的值无限接近于 2)。即线性函数 可以很好的表示上面的数据集合。
二、利用 PyTorch 实现梯度下降算法
由于线性函数的损失函数的梯度公式很容易被推导出来,因此我们能够手动的完成梯度下降算法。但是,在很多机器学习中,模型的函数表达式是非常复杂的,这个时候手动定义该函数的梯度函数需要很强的数学功底。因此,这里我们使用前文中所用的后向传播函数来实现梯度下降算法,求解最佳权重。
首先,让我们来定义数据集合以及的初始值,并将其设置为可以求偏导的张量。
import torch
X = torch.tensor([1, 2, 3, 4], dtype=torch.float32)
Y = torch.tensor([2, 4, 6, 8], dtype=torch.float32)
#初始化张量 w
w = torch.tensor(0.0, dtype=torch.float32, requires_grad=True)
# 定义步长和迭代次数
learning_rate = 0.01
n_iters = 20
接下来让我们使用.backward()
直接求解梯度:
for epoch in range(n_iters):
y_pred = forward(X)
l = loss(Y, y_pred)
# 无需定义梯度求解的函数,直接求解梯度
l.backward()
# 利用梯度下降更新参数
with torch.no_grad():
# w.grad :返回 w 的梯度
w.data -= learning_rate * w.grad
# 清空梯度
w.grad.zero_()
if epoch % 2 == 0:
print(f'epoch {epoch+1}: w = {w.item():.3f}, loss = {l.item():.8f}')
print(f'根据训练模型预测,当 x =5 时,y 的值为: {forward(5):.3f}')
输出结果如下:
epoch 1: w = 0.300, loss = 30.00000000
epoch 3: w = 0.772, loss = 15.66018772
epoch 5: w = 1.113, loss = 8.17471695
epoch 7: w = 1.359, loss = 4.26725292
epoch 9: w = 1.537, loss = 2.22753215
epoch 11: w = 1.665, loss = 1.16278565
epoch 13: w = 1.758, loss = 0.60698116
epoch 15: w = 1.825, loss = 0.31684780
epoch 17: w = 1.874, loss = 0.16539653
epoch 19: w = 1.909, loss = 0.08633806
根据训练模型预测,当 x =5 时,y 的值为: 9.612
可以看到,利用 PyTorch 进行的梯度下降的结果和人工梯度下降结果一致。我们可以通过 PyTorch 中的 .backward()
,简洁明了的求取任何复杂函数的梯度,大大的节约了我们公式推导的时间。