批量梯度下降(BGD)、随机梯度下降(SGD)、小批量随机梯度下降(MSGD)实现过程详解

接触过神经网络的人都知道,网络的训练是其核心,本人在读书时接触的是BP神经网络,那时写的代码训练样本量不大,没有注意到题目所列出的这些训练方式,偶尔也曾看到了 “批量梯度下降”的名词,却并没有深入研究它的实现过程。

样本是深度学习的主要学习来源,其样本量动则百十万个,再加上其结构和BP网络的不同,虽然在大理论上没变化,但是如果再用老一套方式去训练,其效率和结果都不会理想,这时,一些trick就显现出它的关键了。

研究过深度学习的人士可能对BGD、SGD、MSGD这几种训练方式并不陌生,在Caffe、Touch、TensorFlow等框架工具上,这些就是你用来训练网络所选的方式,你只需要用特定的参数设置就可以实现其对应的训练过程,可是对于其实现的详细方式却未必真正了解,笔者也曾对这几种训练方式有过困惑。其实关于这几个概念的博文有很多,可是总感觉有些地方存在问题:要么公式和代码对不上,要么各种复制粘贴整理得没有头绪(这里不是贬低他人的意思,也可能自己水平不够,还没能达到看了别人的博文就能通七窍的高度,毕竟最后这篇博文的结果还是在别人的博文基础上理解后总结归纳出来的),直到最近看了多篇博文,再结合自己所写的Lenet-5 C语言代码中的训练方式,然后自认为有所参悟了,将理论和代码进行整理后放上来。

为什么随机梯度要小批量

现在我们的数据可能包含海量特征,一个批量可能相当巨大,如果是超大批量,则单次迭代就可能要花费很长的时间计算。

包含随机抽象样本的大量数据集可能包含冗余数据。实际上,批量越大,出现冗余的可能性越高。一些冗余可能有助于消除杂乱的梯度,但更大批量的预测价值往往并不能提高。

如果我们可以通过更少的计算量得出正确的平均梯度,会怎么样?通过从我们的数据集中随机选择样本,我们可以通过小得多的数据集估算(尽管过程非常杂乱)出较大的平均值。 随机梯度下降法 (SGD) 将这种想法运用到极致,它每次迭代只使用一个样本(批量大小为 1)。如果进行足够的迭代,SGD 也可以发挥作用,但过程会非常杂乱。“随机”这一术语表示构成各个批量的一个样本都是随机选择的。

小批量随机梯度下降法小批量 SGD)是介于全批量迭代与 SGD 之间的折衷方案。小批量通常包含 10-1000 个随机选择的样本。小批量 SGD 可以减少 SGD 中的杂乱样本数量,但仍然比全批量更高效。

 

问题的提出

假设我们提供了这样的数据样本(样本值取自于y=3*x1+4*x2):

x1x2y
1419
2526
5119
4220

x1和x2是样本值,y是预测目标,我们需要以一条直线来拟合上面的数据,待拟合的函数如下:

  \——1式

我们的目的就是要求出θ1和θ2的值,让h(θ)尽量逼近目标值y。

这是一个线性回归问题,若对线性回归有所了解的同学就知道:利用最小二乘法则和梯度下降法可以求出两个参数,而深度学习也同样可以利用这两种方法求得所有的网络参数,因此,在这里用这个数学模型来解释BGD、SGD、MSGD这几个概念。

先熟悉一下梯度下降法:

我们首先确定损失函数:

 

——2式

其中,J(θ)是损失函数,m代表每次取多少样本进行训练,如果采用SGD进行训练,那每次随机取一组样本,m=1;如果是批处理,则m等于每次抽取作为训练样本的数量θ是参数,对应(1式)的θ1和θ2。求出了θ1和θ2,h(x)的表达式就出来了:

——3式

我们的目标是让损失函数J(θ)的值最小,根据梯度下降法,首先要用J(θ)θ求偏导:

——4式

由于是要最小化损失函数(2式),所以参数θ按其负梯度方向来更新:

——5式

 

现在结合代码来说明(python)

BGD(Batch gradient descent)批量梯度下降法:每次迭代使用所有的样本

每次迭代都需要把所有样本都送入,这样的好处是每次迭代都顾及了全部的样本,做的是全局最优化。

# -*- coding: utf-8 -*-
__date__ = '2019/1/9 14:05'
import random
def BSGD():
    """
    Batch Gradient Descent  -- 批量梯度下降
    每次利用梯度来更新参数值(权重)时,用的是每个数据点梯度值的平均值(梯度值之和再算平均)
    即 -- 每次更新参数需要迭代所有的数据点. m = 4 (数据个数)
    """
    # 用y = Θ1*x1 + Θ2*x2来拟合下面的输入和输出
    # input1  1   2   5   4
    # input2  4   5   1   2
    # output  19  26  19  20
    input_x = [[1, 4], [2, 5], [5, 1], [4, 2]]  # 输入
    y = [19, 26, 19, 20]  # 输出
    theta = [1, 1]  # θ参数初始化
    loss = 666  # loss先定义一个数,为了进入循环迭代
    step_size = 0.01  # 步长, 学习率
    eps = 0.0001  # 精度要求
    max_iters = 10000  # 最大迭代次数
    error = 0  # 损失值
    iter_count = 0  # 当前迭代次数

    err1 = [0, 0, 0, 0]  # 求Θ1梯度的中间变量1
    err2 = [0, 0, 0, 0]  # 求Θ2梯度的中间变量2

    while loss > eps and iter_count < max_iters:  # 迭代条件
        loss = 0  # 初始化误差为0
        err1sum = 0
        err2sum = 0
        for i in range(4):  # 每次迭代所有的样本都进行训练
            pred_y = theta[0] * input_x[i][0] + theta[1] * input_x[i][1]  # 预测值
            err1[i] = (pred_y - y[i]) * input_x[i][0]
            err1sum = err1sum + err1[i]
            err2[i] = (pred_y - y[i]) * input_x[i][1]
            err2sum = err2sum + err2[i]
        theta[0] = theta[0] - step_size * err1sum / 4  # 对应5式
        theta[1] = theta[1] - step_size * err2sum / 4  # 对应5式
        for i in range(4):
            pred_y = theta[0] * input_x[i][0] + theta[1] * input_x[i][1]  # 预测值
            error = (1 / (2 * 4)) * (pred_y - y[i]) ** 2  # 损失值, m=4
            loss = loss + error  # 总损失值
        iter_count += 1
        print("iters_count: %s, loss: %s" % (iter_count, loss))
    print('theta: ', theta)
    print('final loss: ', loss)
    print('iters: ', iter_count)

执行结果:

这里我们只有4个样本,所以训练的时间不长。但是,如果面对数量巨大的样本量(如40万个),采取这种训练方式,所耗费的时间会非常长。

SGD(Stochastic gradientdescent)随机梯度下降法:每次迭代使用一组样本

针对BGD算法训练速度过慢的缺点,提出了SGD算法,普通的BGD算法是每次迭代把所有样本都过一遍,每训练一组样本就把梯度更新一次。而SGD算法是从样本中随机抽出一组,训练后按梯度更新一次,然后再抽取一组,再更新一次,在样本量及其大的情况下,可能不用训练完所有的样本就可以获得一个损失值在可接受范围之内的模型了。

# -*- coding: utf-8 -*-
__date__ = '2019/1/9 14:05'
import random
def SGD():
    """
    Stochastic gradient descent -- 真随机梯度下降:每次迭代使用一组样本(一个数据点)
    这里Stochastic的意思是random, 指每批数据都是随机抽取的。 m=1
    """
    input_x = [[1, 4], [2, 5], [5, 1], [4, 2]]  # 输入
    y = [19, 26, 19, 20]  # 输出
    theta = [1, 1]  # θ参数初始化
    loss = 10  # loss先定义一个数,为了进入循环迭代
    step_size = 0.01  # 步长, 学习率
    eps = 0.0001  # 精度要求
    max_iters = 10000  # 最大迭代次数
    error = 0  # 损失值
    iter_count = 0  # 当前迭代次数

    while loss > eps and iter_count < max_iters:  # 迭代条件
        loss = 0
        i = random.randint(0, 3)  # 每次迭代在input_x中随机选取一组样本(一个数据)进行参数(权重)的更新
        pred_y = theta[0] * input_x[i][0] + theta[1] * input_x[i][1]  # 预测值
        theta[0] = theta[0] - step_size * (pred_y - y[i]) * input_x[i][0]  # (pred_y-y[i])*input_x[i][0]为当梯度
        theta[1] = theta[1] - step_size * (pred_y - y[i]) * input_x[i][1]
        for i in range(4):
            pred_y = theta[0] * input_x[i][0] + theta[1] * input_x[i][1]  # 预测值
            error = 0.5 * (pred_y - y[i]) ** 2  # m = 1
            loss = loss + error
        iter_count += 1
        print('iters_count', iter_count)
    print('theta: ', theta)
    print('final loss: ', loss)
    print('iters: ', iter_count)

执行结果:

 

MBGD(Mini-batch gradient descent)小批量梯度下降:每次迭代使用b组样本

SGD相对来说要快很多,但是也有存在问题,由于单个样本的训练可能会带来很多噪声,使得SGD并不是每次迭代都向着整体最优化方向,因此在刚开始训练时可能收敛得很快,但是训练一段时间后就会变得很慢。在此基础上又提出了小批量梯度下降法,它是每次从样本中随机抽取一小批进行训练,而不是一组。

# -*- coding: utf-8 -*-
__date__ = '2019/1/9 14:05'
import random
def mini_batch_SGD():
    """
    mini-batch Stochastic Gradient Descent -- 小批量随机梯度下降
    m = batch_size
    """
    # 用y = Θ1*x1 + Θ2*x2来拟合下面的输入和输出
    # input1  1   2   5   4
    # input2  4   5   1   2
    # output  19  26  19  20
    input_x = [[1, 4], [2, 5], [5, 1], [4, 2]]  # 输入
    y = [19, 26, 19, 20]  # 输出
    theta = [1, 1]  # θ参数初始化
    loss = 10  # loss先定义一个数,为了进入循环迭代
    step_size = 0.01  # 步长, 学习率
    eps = 0.0001  # 精度要求
    max_iters = 10000  # 最大迭代次数
    error = 0  # 损失值
    iter_count = 0  # 当前迭代次数

    while loss > eps and iter_count < max_iters:  # 迭代条件
        loss = 0
        # 这里每次批量选取的是2组样本进行更新,另一个点是随机点+1的相邻点
        i = random.randint(0, 3)  # 随机抽取一组样本
        j = (i + 1) % 4  # 抽取另一组样本,j=i+1。 即这里的batch_size=2
        pred_y0 = theta[0] * input_x[i][0] + theta[1] * input_x[i][1]  # 预测值1
        pred_y1 = theta[0] * input_x[j][0] + theta[1] * input_x[j][1]  # 预测值2
        avg_err_1 = (pred_y0 - y[i]) * input_x[i][0] + (pred_y1 - y[j]) * input_x[j][0]
        avg_err_2 = (pred_y0 - y[i]) * input_x[i][1] + (pred_y1 - y[j]) * input_x[j][1]
        theta[0] = theta[0] - step_size * (1 / 2) * avg_err_1  # 对应5式
        theta[1] = theta[1] - step_size * (1 / 2) * avg_err_2  # 对应5式
        for i in range(4):
            pred_y = theta[0] * input_x[i][0] + theta[1] * input_x[i][1]  # 总预测值
            error = (1 / (2 * 2)) * (pred_y - y[i]) ** 2  # 损失值, m=2
            loss = loss + error  # 总损失值
        iter_count += 1
        print('iters_count', iter_count)

    print('theta: ', theta)
    print('final loss: ', loss)
    print('iters: ', iter_count)

执行结果:

 

 

Reference:

1. https://blog.csdn.net/u014696921/article/details/54410193

2. https://www.cnblogs.com/maybe2030/p/5089753.html

  • 3
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值