![程序员的AI书:从代码开始](https://wfqqreader-1252317822.image.myqcloud.com/cover/120/30638120/b_30638120.jpg)
2.2 线性回归、梯度下降及实现
2.2.1 分类的原理
在2.1节中,我们用代码实现了简单的感知器,学习了基本的神经网络原理实现。然而,在其中的代码实现里,我们提到感知器在修正权重w和偏移值bias时的修改规则如下:
![](https://epubservercos.yuewen.com/95D877/16699149904933906/epubprivate/OEBPS/Images/txt003_13.jpg?sign=1739346405-iZnpapxOaPB78aeGHLQX9O3Lkyk0M8SZ-0-f15fb034b6752816120f73cb2d09ac3e)
![](https://epubservercos.yuewen.com/95D877/16699149904933906/epubprivate/OEBPS/Images/txt003_14.jpg?sign=1739346405-qWtT8VJcuyaAKLsilv30LEWqanOtWQ4x-0-537bd3c38c9bf1dc486ce9f957ff3104)
这是整个训练过程中的核心,又是怎么得来的呢?本节将解释这个问题。
首先,我们要理解为什么要定义网络输入为wx +b。
实际上,我们做了一个假设:预测数据是线性可分的,因此我们希望用一个简单的斜线来判断所输入的数据落在哪个区间(类别)。
什么叫线性可分呢?请看图2-2。
![](https://epubservercos.yuewen.com/95D877/16699149904933906/epubprivate/OEBPS/Images/txt003_15.jpg?sign=1739346405-SmDxaAjigrhuIfOYPkIR0lHMZ0Iv5cQB-0-ad958a3491a6f48a83a4a9db7a0b5a96)
图2-2 线性可分
由图2-2可以看到,实心圆和空心圆能够被一条直线分开。如果我们的数据能被一条直线分为两类(或多个类),我们便称其为线性可分。图2-2实际上是二维坐标的数据划分,我们将在2.4节通过实现一个神经网络来处理。对于在2.1节中所举的正负数分类问题,这时解决起来就更简单了,可以将其视为对x轴上的点进行划分,如图2-3所示。
![](https://epubservercos.yuewen.com/95D877/16699149904933906/epubprivate/OEBPS/Images/txt003_16.jpg?sign=1739346405-kuzqUyGClaOdKPJmc1sZdr355GzdSSrq-0-253cb36aac778cb327ce5da8b14b548e)
图2-3 正负数分类
所以,现在我们知道了为什么要定义y’(预测值)=(wx+b),那么我们来看真正的关键问题:怎么得到w和b?
这实际上是一个线性回归(Linear Regression)问题,线性回归可以处理多于一个输入的形式,例如y'=(w1·x1+w2·x2+w3·x3)。在这个例子中,我们实际上预设了x0=1、w0=bias,即y'=
(w0·x0+w1·x1)=
(w0+w1·x1)=
(w·x+b)。注意,这是一个常见的技巧,给输入参数增加一个固定为1的常量,可以让我们不用单独处理bias参数,而能将它作为权重值统一计算,2.4节会讲解如何计算。
2.2.2 损失函数与梯度下降
在2.1节中假设:
![](https://epubservercos.yuewen.com/95D877/16699149904933906/epubprivate/OEBPS/Images/txt003_22.jpg?sign=1739346405-1GxAxgxIcOtVj7ehFjgtCSmAwzV6LC6D-0-7a8e4c82a3812da34006aebd05a9ea40)
这是在参考文献[2]中感知器的激活函数中设定的,但这在实际应用中几乎不可能这么简单判定。在感知器之后,人们提出了Adaptive Linear Neuron(简称Adaline)的概念,即其激活函数和输入一致:
![](https://epubservercos.yuewen.com/95D877/16699149904933906/epubprivate/OEBPS/Images/txt003_23.jpg?sign=1739346405-GBl4PdNuJcK6ANmpkBodh52ZqFULxHUI-0-b657ad7eba0b35f07eef191a128d5f61)
这样,在Adaline中,激活函数的输出就是一个连续值,而不是如前面感知器那样的简单二分,这样的变化可以让我们定义两个重要的概念:损失函数和梯度下降。
损失函数即如何计算图2-1中的Error值。我们通常使用MSE来计算,即
![](https://epubservercos.yuewen.com/95D877/16699149904933906/epubprivate/OEBPS/Images/txt003_24.jpg?sign=1739346405-HLXkU3ZjCMM61meQWS3mRCHIFJjTljD5-0-dc00d8181834bdfa51fe6944d827acb9)
其中,为真实值,
为预测值。
用代码实现如下:
![](https://epubservercos.yuewen.com/95D877/16699149904933906/epubprivate/OEBPS/Images/txt003_27.jpg?sign=1739346405-aAIePwJ0uZSfbt2ZCZEJDORH5xam89Ob-0-715921e842f26e70c1e68669df664f75)
在获得了损失函数之后,我们便要应用梯度下降来调整w和bias(记为b)。怎么调整?其实思路很简单,计算出损失函数针对w和bias的梯度变化和
,然后从w和bias中分别减去
w和
即可,如此反复,直到最后的损失函数值足够小。
根据上面的MSE公式,我们假设f为MSE,分别对w和b求导可以得到:
![](https://epubservercos.yuewen.com/95D877/16699149904933906/epubprivate/OEBPS/Images/txt003_32.jpg?sign=1739346405-pbUgo6B5TMbQIMIjgEZfRqwei9XAQccE-0-7955ccc405d231b0627835cb7cfbe59b)
![](https://epubservercos.yuewen.com/95D877/16699149904933906/epubprivate/OEBPS/Images/txt003_33.jpg?sign=1739346405-yLqLxtdb1ca1oBw62mNPlVShR0Fs4XQi-0-618249fef1e95995e67b1739e6ec4615)
以上即梯度Δw和Δb,然后我们只需要更新w(wΔw)、b(b
Δb)即可。其代码实现如下:
![](https://epubservercos.yuewen.com/95D877/16699149904933906/epubprivate/OEBPS/Images/txt003_36.jpg?sign=1739346405-EPh43CBvC7gQWOJHUA97Tc4TIVRlTU9n-0-df5f1e23a0ee078e369f6891963210d8)
在上面的代码中要注意的是,我们并没有直接从w和bias中减去梯度值,而是将梯度值乘以learning rate,以调整训练步长。
在我们完整实现一个基于线性函数的神经网络之前,还有一点要处理,即数据归一化。
在前面的感知器的实现中并没有这一步,因为感知器的“激活函数”实际上是一个单位阶跃函数(Unit Step Function),它能够把任何输入值都映射为0或1这两个值。然而,对于Adaline,因为激活函数就是网络输入本身,而x的值远超过[0,1]区间,因此我们需要处理输入值的范围,让最终的输出能够落在[0,1]区间(另一种做法是直接改变激活函数,使其能把任意输入都映射到[0,1]区间,这会在后面解释)。我们通常会把输入值映射到[0,1]区间,输入则变为
![](https://epubservercos.yuewen.com/95D877/16699149904933906/epubprivate/OEBPS/Images/txt003_37.jpg?sign=1739346405-MjHXrOGFYWyOtBSai5mTvAHyDaiod3ZQ-0-9a12f1214f4ddb01fc24f0e36e4c5a62)
2.2.3 神经元的线性回归实现
现在我们就能来完整实现应用了梯度下降的单神经元网络了(不包括隐藏层):
![](https://epubservercos.yuewen.com/95D877/16699149904933906/epubprivate/OEBPS/Images/txt003_38.jpg?sign=1739346405-10BxTZTsbnRr2SYj4jkSxndNgpJ6yjPJ-0-d2d0e57575523c03e9ccc9432d58b53c)
![](https://epubservercos.yuewen.com/95D877/16699149904933906/epubprivate/OEBPS/Images/txt003_39.jpg?sign=1739346405-GiMwceKdyOwGfFRp0LzMvrrKxljemQSn-0-a32b8819f67931f7677be010665a2acb)
通过前面的讨论,我们可以很容易理解上面这段代码了。
第8~27行是损失函数和利用梯度调整w和bias的计算。
在第30~39行的fit实现中,我们实际上并没有用cost function所得到的cost来控制训练次数,而是简化为训练固定次数,即反复调用update_weight调整w和bias。
第47~49行负责使用w和bias进行预测分类。其中,第1步先将输入值x归一化,因为我们从输入数值可以看到最小值为-100,最大值为200。然后根据我们对激活函数的设定,将wx+bias直接作为输出返回。
第52~59行是训练过程。可以看到,我们使用了比之前的例子更多的数据以期获得可接受的结果,在第57行对所有训练数据都进行了数据归一化,使其落在[0,1]区间,然后调用fit函数训练500次(每次都使用所有数据)。
第65行调用predict方法,使用训练得到的w和bias对在第61行中所定义的输入x进行预测。
代码运行结果如下:
![](https://epubservercos.yuewen.com/95D877/16699149904933906/epubprivate/OEBPS/Images/txt003_40.jpg?sign=1739346405-xeVbVf8ukjR4xjmBfKJsJ49JXfbTwKrJ-0-4a6d9123f62207b9132c62409f41da5f)
在上面的运行结果中,我们可以看到训练损失的确在收敛、下降,但在超过300之后已经没有太大变化了。
对于所有测试数据,我们可以看到阈值基本上在0.58左右,正数越大越趋近于1,负数越小越趋近于0,符合我们的预期。
关于线性回归的内容,会在第4章深入讨论。