这段话来自 非洲经济女学家 Dambisa Moyo 的《Dead Aid》
❝种一棵树最好的时间是十年前,其次是现在。-- Dambisa Moyo
❞
❝只要你心里有信念,没有时间的差距,什么时候开始都可以。
❞
❝温馨提示:代码可能一行过长,可以左右滑动查看。
❞
这篇文章的主要内容会围绕着如何使用 「PyTorch」 的优化器来展开。同样地,在这一篇文章中优化的函数还是一个简单的一元二次函数。
在我的 「机器学习」 系列文章中,还有两篇文章是和本篇文章相关的。在这两篇文章中着重的是介绍了其中一种 「优化器」 :「梯度下降法」。(其实就是一种优化函数的方法,只不过被大家称为 「优化器」)
「梯度下降法」 这篇文章主要详细地阐述了它的数学原理,随后使用一个简单的代码模拟了这个过程。
【链接】Gradient-Descent (梯度下降,优化函数大法)
【链接】PyTorch 和 自动求导
「PyTorch 和 自动求导」 这篇文章主要详细地阐述了怎样使用「PyTorch」 的 API(Application Interface)完成自动求导这个动作,毕竟求导是一件非常累人的事情。但是这篇文章并没有告诉大家怎样完成各个 「权重」 的自动更新,而仅仅是求出了每个 「权重」 变量的导数而已。
那么,在这一篇文章就算是作为上边提到的第二篇文章的一个补充:使用 「PyTorch」 完成 「自动更新权重」 这个动作。
1 什么是优化器?
在上边已经提到过了,简单来说,优化损失函数的方法就是优化器。而在这篇文章主要介绍 「梯度下降法」 这种优化器。其实,其他还有一些 「优化器」,基本上也都是在梯度下降法的基础上改进的。而且,值得一提的是:在 PyTorch 中优化器的接口形式都是一样的,会使用梯度下降法的 API (Application Interface)后,其他的也就会了。
2 目标函数
我们就使用一个一元二次函数作为我们优化的 「目标函数」 好啦。我就随便地写一个吧,就用它了!
从这个式子就可以看出,当 且 的时候 达到最小值。
3 优化目标函数
需要注意的是:我的代码都是在 Jupyter-Notebook 中运行的,所以我直接运行一个 Cell 的时候能输出当前代码的运行结果。
「加载模块」
首先,我加载了需要使用的模块,并且把刚才我 「随便写的」 那个目标函数的图像画了出来。好!下边儿就是这个过程的代码了!当然,还有图像也放了出来。
import torch
import numpy as np
import matplotlib.pyplot as plt
plot_x = np.linspace(-5, 7, 100)
plot_y = (plot_x - 1)**2 + 2
plt.plot(plot_x, plot_y)
plt.show()
待会,我们初始化的位置大概就是红色箭头指向的这个地方了,这里是 这个点的位置。
「初始化 x 和 目标函数」
接下来,初始化一个 x,它是一个 torch.tensor 类型,待会我们每一次迭代的过程中更新的对象就是 x,并且最终 x 的值将会是最小值点的横坐标。x 是一个一维的向量,并且这个向量只有一个元素,初始值为 。因为要对 x 求导的,所以它应该是一个浮点类型的并且 「requires_grad=True」。
在这里我还会定义一个函数 f,这个函数输入的值是目标函数的自变量,返回值就是目标函数的值。
def f(x):
return torch.square(x-1)+2
x = torch.tensor([6],
dtype=torch.float32,
requires_grad=True)
好啦,让我们来看看,执行了上边的代码后,x 的具体状况。
「给定学习率和迭代次数」
在这儿,我初始化两个变量,其中 「lr」 代表学习率,其实就是 「learning_rate」 。另外的 「iter」 代表迭代次数。另外还有两个列表,「all_x」 用来存储每一次 x 更新后的值,「all_loss」 用来存储每一次迭代后的损失的值。通过记录这些值,最后我们就可以可视化出来算法运行过程中的情况了。
lr = 0.05
iter = 1000
all_x = []
all_loss = []
「初始化 优化器」
我们只需要这样的一行代码就完成了优化器的初始化,其中的 SGD 代表的就是 「梯度下降法」。可以注意到,SGD() 这个对象的第一个参数是一个列表,这个列表中需要放入我们需要在每一次迭代过程中更新的变量,另外第二个参数是 lr,这就是告诉它我们使用的学习率的大小。
optimizer = torch.optim.SGD([x], lr=lr)
「开始优化」
for i in range(iter):
loss = f(x)
all_x.append(x.item())
all_loss.append(loss.item())
# 第 1 行
optimizer.zero_grad()
# 第 2 行
loss.backward()
# 第 3 行
optimizer.step()
上边就是我们具体的优化过程的代码了。这将会迭代 「1000」 (iter 等于 1000) 次。用之前定义好的 f() 函数计算出损失的值 loss,这个过程就已经自动构成了 「计算图」。随后我们把当前的 x 的值放进 「all_x」 中,把当前的损失值放进 「all_loss」 中。使用 「item()」 方法会返回这个 「tensor」 中存储的值。
最重要的代码就是紧接着的下边的三行了,在代码中我用注释标注了这三行代码。
先来看,第 1 行:这个操作就和我们对需要求导的 「torch.tensor」 类型的变量的 「grad」 属性使用 「zero_()」 方法是一样的效果。这个操作会把优化器的第 1 个参数中所有的变量的梯度值归零,第 1 个参数就是刚才我们传入的列表(list),只不过我们这里的情况比较简单,在列表(list)中只有一个变量,当然,你可以传入很多个。
再来看,第 2 行:这个操作时作用在 「loss」 上的,因此会对 「loss」 相关的所有需要求导的变量进行求导操作。
最后,第 3 行:相信你已经知道了,我们只求完导数还不行,注意到,当前这个操作时作用在 「optimizer」 上的,所以,它会把那些我们放进列表(list)中的变量进行更新,这一步其实就是完成了下边儿这个非常眼熟的数学表达式所完成的工作。(数学表达式中的 代表学习率)
注意这个数学表达式,虽然我使用了偏微分符号,由于我们这里只有一个自变量,其实是和使用微分符号 表达的意思是一样的。
第 3 行就是完成了使用 「自己」 减去 「学习率」 乘以 「目标函数对自己的导数」 然后用得到的这个值去更新 「自己」 的这样一个过程。
「绘制 x 的轨迹」
通过下边的代码,就绘制出了所有 x 在更新过程中的路径,最终 x 在最小值点的附近停了下来。
plot_x = np.linspace(-5, 7, 100)
plot_y = (plot_x - 1)**2 + 2
plt.plot(plot_x, plot_y)
x_scatter = np.array(all_x)
y_scatter = np.array((x_scatter-1)**2 + 2)
plt.scatter(x_scatter, y_scatter, color='red')
plt.show()
「如何绘制 loss 损失变化图」
绘制 loss 的图:我会生成从 「0」 开始一直到 「999」 这 「1000」 个数字,这代表了每一次的迭代,这 「1000」 个数字用于图像的 「横坐标」,而它对应的 「纵坐标」 就是这每一次迭代过程中计算出的损失值的大小。
「绘制损失 loss 的变化」
loss_x = np.array(range(iter))
loss_y = np.array(all_loss)
plt.plot(loss_x, loss_y)
plt.show()
第 1 个 loss 轨迹图:
通过第 1 个 loss 轨迹图,我们会发现,损失值一下子就变得很小了,在后面很长的一段迭代过程中几乎都是没有什么变化的。因为横坐标的尺度太大了,我们几乎看不出来损失的变化过程是怎样的,所以我绘制第 2 个 loss 损失变化的图,在第 2 个图中,我将只绘制前 「50」 次迭代过程中 「loss」 的变化。
loss_x = np.array(range(iter))
loss_y = np.array(all_loss)
plt.plot(loss_x[:50], loss_y[:50])
plt.show()
第 2 个 loss 轨迹图:
「看看 x 最终的样子」
输出最后一次迭代后 x 的值,可以发现,它已经收敛到了最小值的位置。
推荐阅读:【链接】
PyTorch 和 自动求导
Gradient-Descent (梯度下降,优化函数大法)
Logistic-Regression(逻辑斯蒂回归)
Logistic-Regression(损失函数梯度推导)
机器学习基础(4)-主成分分析法PCA(下)
机器学习基础(4)-主成分分析法PCA(上)
如果喜欢我的文章,点个 「在看」 吧。谢谢你的支持。