一、写在前面

虽然现有的深度学习框架(如TensorFlow和PyTorch)已经大大降低了深度学习开发的门槛,但是在某种程度上会让初学者感到“玄学”

我写这篇文章的初心其实是想回归基础,因为我是先接触深度学习框架,再去学习理论基础的,有很多初学者可能跟我有类似的经历,我深知这样的经历是痛苦的

因此,这篇文章里所有的代码仅基于原生Python和Numpy,旨在让初学者朋友打好深度学习的理论基础

在上一篇文章(写给小白的神经网络前向传播原理详解)里,我对神经网络的预测过程做了详解。

在这篇文章里,我将重点分享神经网络的“学习”过程,希望能给各位初学者带来理论干货或是思想上的启发。

二、神经网络学习导论:梯度下降

对假设有效性的唯一检验是将其预测结果与经验进行比较。
                    ——米尔顿·弗里德曼

1.比较:你的神经网络是否做出了好的预测?

比较为预测的“误差”提供度量。

这里我们回到最简单的例子,输入一个数,输出它的相反数:

weight = -0.9    # 神经网络的权重
inputs = 1.0     # 输入神经网络的数据
goal_pred = -1.0 # 期望值即你想要的输出或正确的标签

pred = inputs * weight          # 得到预测结果
error = (pred - goal_pred) ** 2 # 计算均方误差

print("error:{}".format(error))

这里的误差指的是神经网络的输出与你期望的输出的衡量方法,上面用的方法是均方误差

  • goal_pred:与inputs变量类似,goal_pred也是从真实世界获取的。但通常情况下,它很难通过直接观察获取,比如在给定温度下,“穿短袖的人的百分比”。
  • error:误差的度量,值越小则表示预测结果越完美;相反地,值越大,则表示神经网络的输出与你的期望输出的差距越大。

看到这里,大家可能会有所疑惑。为什么需要测量误差?为什么只需要正的误差?而且平方运算不是会把大于1的误差放大、小于1的误差减小吗?

2.使用均方误差的可行性分析

“学习”告诉权重应该如何改变以降低误差。我们需要根据误差对权重做出相应的调节。

误差总是正的!

想象一位射手对准靶子进行射击。无论射得比目标低1cm还是高1cm,误差都只有1cm。而在刚刚的例子中,pred - goal_pred 在某些情况下有可能是负值,这显然与真实世界中对误差的定义不同。

另外,在做训练时,我们往往会把训练数据分一个批次,比如一个批次里有2个数据,如果第一个数据预测出来的误差是100,而第二个数据预测出来的误差是-100,那么平均误差为0!

那么问题来了,既然有可能出现负值,为什么不用绝对值误差呢?

不使用绝对值误差的原因

简单来说,绝对值不是处处可导的,在计算梯度的时候会有麻烦;另一方面,绝对值在收敛时会有不平滑的问题。

平方运算使较大的误差变大,而较小的误差变小

这样的做法正是我们想要的,就好像小时候做错事的时候,与撞坏家具比起来,折断笔芯显得微不足道,虽然两者都有犯错的意思在里面,但其中有严重程度

这样一来,在训练的过程中,较大的误差将被优先考虑,而较小的误差先暂时忽略或是直接忽略。

3.神经网络最简单的学习形式是什么?

冷热学习法!

神经网络的“学习”,其实只关心一件事:通过调整权重的方式来降低误差,当误差趋近于0时,“学习”的过程也就完成了!

那么,怎么知道要把权重调高或是调低呢?不妨两个方向都试一下!

冷热学习

冷热学习指的是通过扰动权重来确定向哪个方向调整可以使得误差的降低幅度最大,基于此将权重的值向那个方向移动,不断地重复这个过程,直到误差趋近于0.

了解了这个过程以后,来看看代码吧!还是前面的例子,输入一个数,输出它的相反数

weight = 0.5
inputs = 1.0
goal_prediction = -1.0

step_amount = 0.1 # 权重调整的幅度

for iteration in range(20):
    prediction = inputs * weight
    error = (prediction - goal_prediction) ** 2
    print("Iteration:{:2}, Error:{:25}, Prediction:{:.10}".format(iteration, error, prediction))

    up_prediction = inputs * (weight + step_amount) # 试一下提升权重的效果
    up_error = (goal_prediction - up_prediction) ** 2

    down_prediction = inputs * (weight - step_amount) # 试一下降低权重的效果
    down_error = (goal_prediction - down_prediction) ** 2

    if (down_error < up_error):
        weight = weight - step_amount

    if (down_error > up_error):
        weight = weight + step_amount

冷热学习带来的问题:

  • 效率低下,需要通过多次预测才能进行一次权重的更新,这似乎非常低效。
  • 有时准确预测出目标是不可能的,你无法准确地知道调整幅度大小为多少比较合适,就像上面这样,只会在正确结果附近徘徊。

4.基于误差调节权重——梯度下降

直接根据误差找到权重调节的方向和幅度。

还是前面的例子:输入一个数,输出它的相反数

weight = 0.5
inputs = 1.0
goal_prediction = -1.0

for iteration in range(5):
    prediction = inputs * weight
    error = (prediction - goal_prediction) ** 2
    direction_and_amount = (prediction - goal_prediction) * inputs # 纯误差 * 缩放、负值反转和停止调节
    weight = weight - direction_and_amount
    print("Iteration:{}, Error:{:5}, Prediction:{:5}".format(iteration, error, prediction))

这样做的效果已经很明显了,应该不用我多说了吧?但是这样做的可行性非常有趣!

direction_and_amount指的是我们希望如何更改权重,它由两部分组成,第一部分即纯误差,第二部分是与输入相乘的操作。比较有趣的是第二部分:

  • 停止调节:停止调节用于消除输入为0带来的误差,换句话说,输入为0时,因为预测结果总是0,所以模型不会进行“学习”。
  • 负值反转:输入为正时,向上提升权重的值能使预测结果也向上提升;但是如果输入为负,那么权重的调节就会改变方向!这样就能确保权重朝着正确方向移动。
  • 缩放:通过缩放,我们可以在输入很大时,让权重的更新也变得很大。这更像是一种副作用,因为他可能会失去控制。

另外,在更新权重前,可以引入一个小数值alpha,以此来控制神经网络的学习速度

5.神经网络“学习”的简单数学推导

“学习”就是通过修改权重的方式来减小误差。

再来回顾一下刚刚的代码:

for iteration in range(5):
    prediction = inputs * weight
    error = (prediction - goal_prediction) ** 2
    delta = prediction - goal_prediction
    weight_delta = delta * inputs
    weight = weight - weight_delta
    print("Iteration:{}, Error:{:5}, Prediction:{:5}".format(iteration, error, prediction))

由上面的代码,我们可以很简单地写出求error的数学公式:

error = ((inputs * weight) - goal_prediction) ** 2

这个公式其实是一个一元二次方程,因为inputs和goal_prediction都是已知的,我们可以带一个具体的数值进去:

error = ((0.5 * weight) - 0.8) ** 2

我们甚至可以画出它的图像:

写给小白的入门笔记,神经网络梯度下降原理详解_python

我们想让error趋近于0,其实就是求这个函数的最小值。我们的做法也很简单,就是调整横坐标的值(weight),以此来找到取到最小值时的横坐标。

换个思路,我们来个头脑风暴:是什么控制了输入和输出之间的关系呢?

1.更改goal_prediction(期望值)以减小误差

这样的做法虽有不妥,但完全可行。在生活中,你可以把这种行为(将目标设置成你已经做到的)叫做“放弃”。

2.改变输入直到误差为0

这就像是创造了一个你想看到的世界,而不是真实的世界。通过更改输入数据,知道预测出想要预测的内容为止,这样的做法是创始主义的工作原理,谷歌尝试过用这种方法生成图像。

3.改变2这个系数或者相关的加减乘除运算符

这只是改变了计算误差的方式而已,如果误差的计算方式实际上不能很好地度量模型犯了多少错,那么误差计算就没有意义。

4.剩下的唯一变量就是权重。

调整权重不会改变你对真实世界的感知,不会改变你的目标,也不会破坏你测量误差的方法。改变权重表示函数正在试图匹配数据中的模式!

6.从导数看梯度下降

误差增量就是导数!

想象一下,自行车的齿轮,大齿轮带动小齿轮。如果我让大齿轮转动一周,发现在大齿轮的带动下,小齿轮转动了两周,那么这时我就知道了两个齿轮之间的倍数关系:

  • 大齿轮的周长 = 小齿轮的周长 * 2

如果两个变量互相关联,则这两个变量之间总是存在导数。根据导数,我们可以知道改变一个变量时,另一个变量是如何运动的

如果导数是正的,那么当你改变一个变量时,另一个就会向同样的方向移动;如果导数为负,则改变一个变量时,另一个变量就会沿着相反的方向移动。

总结一下,给定一个函数,如果你改变其中一个变量,则它的导数代表了另一个变量发生变化的方向和幅度,这正是我们想要找的。

那么error和error对weight的导数有什么不同?

  • error用来衡量神经网络错了多少的量
  • error对weight的导数定义了权重的每一个取值和神经网络错了多少之间的关系

7.权重的过度修正

我们拿刚刚的代码测试一下,把输入和输出变得大一些:

weight = 0.5
inputs = 10.0
goal_prediction = -10.0

for iteration in range(5):
    prediction = inputs * weight
    error = (prediction - goal_prediction) ** 2
    direction_and_amount = (prediction - goal_prediction) * inputs # 纯误差 * 缩放、负值反转和停止调节
    weight = weight - direction_and_amount
    print("Iteration:{}, Error:{:5}, Prediction:{:5}".format(iteration, error, prediction))

写给小白的入门笔记,神经网络梯度下降原理详解_机器学习_02

这个结果可不是我们想要的!预测的结果爆炸了!每一步都离预期结果越来越远。

原因就在于输入变大了。如果输入足够大,即使误差很小,也会使权值的增量很大。当权重增量很大但误差很小时网络会矫正过度,这就导致了上面的现象,称之为发散

怎么解决这个问题呢?

8.引入α以防止过度修正权重

对于发散的情况,最简单的解决方案是将权重的增量乘以一个系数,让它变得更小。因此,这是一个介于0和1之间的实数,即学习率。

实现它也很简单,就像下面这样:

weight = 0.5
inputs = 2
goal_prediction = 0.8
alpha = 0.1

for iteration in range(70):
    prediction = inputs * weight
    error = (prediction - goal_prediction) ** 2
    direction_and_amount = (prediction - goal_prediction) * inputs # 纯误差 * 缩放、负值反转和停止调节
    weight = weight -alpha * direction_and_amount
    print("Iteration:{}, Error:{:25}, Prediction:{:20}".format(iteration, error, prediction))

写给小白的入门笔记,神经网络梯度下降原理详解_深度学习_03

看!现在的效果还可以吧!

三、总结与升华

到目前为止,你应该已经掌握了自己写一个最简单的神经网络的理论基础!

对于初学者来说,最好可以记住上面的这段代码,或者说,把上面这段代码理解性地掌握。

在下一篇文章里,我将重点讲解通用梯度下降即一次学习多个权重的方法。

个人介绍

  • 北京联合大学 机器人学院 自动化专业 2018级 本科生 郑博培
  • 百度飞桨开发者技术专家PPDE
  • 深圳柴火创客空间 认证会员
  • 百度大脑 智能对话训练师

目前正在使用PaddlePaddle学习深度学习以及强化学习方面的算法

我在AI Studio上获得钻石等级,点亮5个徽章,来互关呀~ https://aistudio.baidu.com/aistudio/personalcenter/thirdview/147378

欢迎大家fork喜欢评论三连,感兴趣的朋友也可互相关注一下啊~