文章和代码都在这儿,骗 欢迎Star GitHub repo
简介
梯度下降是机器学习中较为基本也比较常见的一类优化算法的总称。在这里,我假设你已经知道了什么是 sigmoid 函数,掌握求导时的链式法则,和一些基础的矩阵乘法。
如果你对以上三点有疑问的话,可以参考老朋友 Wikipedia 和 Google 给出的意见 。
度量错误
梯度下降是一种从“错误”中学习的算法。你也许意识到我们需要找到一种度量预测的错误程度的方法(metric)。通常情况下我们会选择均方误差(MSE),但也有一些其他的选择,比如误差平方和(SSR)或是平均绝对误差(MAE)。本文中将统一使用均方误差。
图中的 y 以及 y_hat 的上角标 μ 指代的是训练数据集中第 μ 个数据,而非表示幂乘。
Google 和 Wikipedia 永远是最好的帮手,如果你想权衡使用 MSE 和 MAE 的利弊,我推荐你自己去探索一下。之前在 StackExchange 上看到过一个相关的问题,留在这里供你参考。一言以蔽之,MSE 对偏离实际值越多的预测值“惩罚”的越多(你想想,都给误差值平方了),有利于我们的模型更好的趋近最优情况。关于 MSE 和 MAE 的介绍,也可以参考我之前的一篇文章。
批量梯度下降
这里需要插一句,梯度下降有很多不同的变种(稍后讨论),我们上面中给出的均方误差公式和接下来的算法,是对应批量梯度下降法(Batch Gradient Descent,简称BGD)的,这是梯度下降法最原始的形式,它的具体思路是在更新每一参数时都使用所有的样本来进行更新。我们将继续使用 sigmoid 函数作为激活函数。
我们先给出从 Udacity 深度学习课程中截取的算法描述,我们进行逐步分析这个算法的实现。
预设:
这一部分没有写在上图的算法中,但是是不可缺少的。我们需要初始化一些参数。分别为训练次数 e,学习率(learning rate)η 和预设权重(weight)w。关于预设权重的设定,请参考下面这段话。
First, you'll need to initialize the weights. We want these to be small such that the input to the sigmoid is in the linear region near 0 and not squashed at the high and low ends. It's also important to initialize them randomly so that they all have different starting values and diverge, breaking symmetry. So, we'll initialize the weights from a normal distribution centered at 0. A good value for the scale is 1/√
n where n is the number of input units. This keeps the input to the sigmoid low for increasing numbers of input units.
第一步将初始化变化的权重 Δw,这一步实际上是便于我们稍后将算法转为代码。接下来第二步,针对训练数据集中的每一组数据我们执行以下三个小步骤 (公式已略去,参见上图):
- 将数据集在神经网络中进行一次正向传递(可参考我的上篇博客或 Google),得到预测结果 y_hat
- 计算输出层神经元的误差梯度(error gradient)δ
- 更新权重变化 Δw_i
在完成了一次对整个数据集的遍历之后,我们将进行第三步,将Δw_i (权重变化值)和 w_i (预设的权重)相加,得到新的 w_i。 这样,我们便完成了一次对权重的更新。之后我们只需要重复 e 次第二、三步。
梯度下降的整个过程可以用下图来进行理解。最开始我们预设的权重在最外侧深红色圆环上,经过一次一次的迭代逐渐靠近中心的最优点(optima)。
接下来我们来一起构建一个 Python 完成的梯度下降算法。完整的数据和代码可以在我的 GitHub Repo 找到,这里就不贴出数据和准备数据的代码了。
哦对了,你可以试着更改预设部分提到的学习率和训练次数,看看它们会如何影响我们的训练结果。
import numpy as np
from data_prep import features, targets, features_test, targets_test
def sigmoid(x):
"""Calculate sigmoid"""
return 1 / (1 + np.exp(-x))
np.random.seed(42)
n_records, n_features = features.shape
last_loss = None
# 预设权重
weights = np.random.normal(scale=1 / n_features ** .5, size=n_features)
# 设定循环次数和学习率
epochs = 1000
learnrate = 0.5
for e in range(epochs):
# 第一步,设定预设变化的权重为0
del_w = np.zeros(weights.shape)
# 遍历全部数据集
for x, y in zip(features.values, targets):
# 正向传递计算y_hat
output = sigmoid(np.dot(weights, x))
# 计算误差梯度
error = (y - output) * output * (1 - output)
# 更新权重变化
del_w += error * x
# 对预设权重的更新
weights += learnrate * del_w / n_records
# 打印出运算过程的一些数据
if e % (epochs / 10) == 0:
out = sigmoid(np.dot(features, weights))
loss = np.mean((out - targets) ** 2)
if last_loss and last_loss < loss:
print("Train loss: ", loss, " WARNING - Loss Increasing")
else:
print("Train loss: ", loss)
last_loss = loss
# 验证我们的算法,在测试数据上进行测试
tes_out = sigmoid(np.dot(features_test, weights))
predictions = tes_out > 0.5
accuracy = np.mean(predictions == targets_test)
print("Prediction accuracy: {:.3f}".format(accuracy))
如果你对第三步中更新预设权重的部分有疑惑,这里有一行代码需要你额外注意,
# 对预设权重的更新
weights += learnrate * del_w / n_records
由于我们是将所有训练数据都遍历了一遍之后得到的变化权重 del_w
, 所以需要将它除以训练数据集的数量。这也是和我们即将提到的另一种算法有差异的部分。
其他梯度下降方法
可以从参考资料的第一篇文章中得知,除了批量梯度下降法(Batch Gradient Descent,简称BGD)外,还有随机梯度下降法(Stochastic Gradient Descent,简称SGD)以及更进一步的小批量梯度下降法(Mini-batch Gradient Descent,简称MBGD)。这几种不同方法的优劣对比可以单开一篇文章来探讨(或者参见参考资料第一篇),这里只通过介绍性的知识进行简单总结。
随机梯度下降法和批量梯度下降法不同的是,在后者的训练过程中,每一次的训练都需要遍历全部的训练数据集,这种算法的确可以保证达到全局最优解(global optimal)。然而,如果我们的数据量较大,或者训练数据的维度较高(特征数量多)的时候,巨大的计算量会极大的拖慢我们模型的训练速度。所以这里提出一种改进的算法——随机梯度下降法。唯一一点和批量梯度下降法不同的是,我们每次选取一个训练数据,计算误差梯度后,直接在预设权重上进行更新。这样就避免了遍历全部数据后再求平均变化权重的计算过程。极大的减少了计算量,对训练速度有着明显的提高。美中不足的是,这种算法只能达到一个和全局最优解极为接近的数值,而且不利于并行实现。
下面给出随机梯度下降法的实现
import numpy as np
from data_prep import features, targets, features_test, targets_test
def sigmoid(x):
"""Calculate sigmoid"""
return 1 / (1 + np.exp(-x))
np.random.seed(42)
n_records, n_features = features.shape
last_loss = None
# 预设权重
weights = np.random.normal(scale=1 / n_features ** .5, size=n_features)
# 设定学习率
learnrate = 0.5
# 遍历全部数据集
for x, y in zip(features.values, targets):
# 正向传递计算y_hat
output = sigmoid(np.dot(weights, x))
# 计算误差梯度
error = (y - output) * output * (1 - output)
# 更新预设权重
weights += error * x
# 验证我们的算法,在测试数据上进行测试
tes_out = sigmoid(np.dot(features_test, weights))
predictions = tes_out > 0.5
accuracy = np.mean(predictions == targets_test)
print("Prediction accuracy: {:.3f}".format(accuracy))
可以看到,除了少去了整体循环的过程和更新权重的部分有变化,其余的地方并没有太多改动过。
小批量梯度下降法(MBGD),是一种结合了以上两种梯度下降法的新想法。其思路非常简单,在 BGD 方法中,每次循环都将遍历整个数据集,而在 SGD 方法中,没有额外循环,只遍历每个数据一次即可。MBGD 则保留了 BGD 中循环的思路,但每次循环中并不会遍历全部数据,而是有选择的随机选取少量数据。具体的思路可以参考 Google。
BGD | SGD | MBGD | |
---|---|---|---|
全局最优 | 是 | 近似 | 比SGD更接近最优 |
训练速度 | 很慢 | 很快 | 比SGD稍慢 |
这块有一点我记得不是非常清楚了,希望有明白的朋友指点一下。忘记在哪本书里看到过SGD也可以达到全局最优(也可能我记错了)。
参考资料
- All formulas are generated by HostMath
- Some figures are taken from the Udacity deep learning course
- 梯度下降法的三种形式BGD、SGD以及MBGD
- The Gradient Descent - Wikipedia
- Neural Networks Tutorial Slides by Andrew Moore