入门神经网络:梯度下降

上一篇传送门

文章和代码都在这儿, 欢迎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 函数作为激活函数。

Sigmoid 函数
Sigmoid 函数
Sigmoid 函数的导数
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也可以达到全局最优(也可能我记错了)。

参考资料

获取授权

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容