- 程序员的AI书:从代码开始
- 张力柯 潘晖
- 1740字
- 2025-02-17 23:17:07
2.2 线性回归、梯度下降及实现
2.2.1 分类的原理
在2.1节中,我们用代码实现了简单的感知器,学习了基本的神经网络原理实现。然而,在其中的代码实现里,我们提到感知器在修正权重w和偏移值bias时的修改规则如下:


这是整个训练过程中的核心,又是怎么得来的呢?本节将解释这个问题。
首先,我们要理解为什么要定义网络输入为wx +b。
实际上,我们做了一个假设:预测数据是线性可分的,因此我们希望用一个简单的斜线来判断所输入的数据落在哪个区间(类别)。
什么叫线性可分呢?请看图2-2。

图2-2 线性可分
由图2-2可以看到,实心圆和空心圆能够被一条直线分开。如果我们的数据能被一条直线分为两类(或多个类),我们便称其为线性可分。图2-2实际上是二维坐标的数据划分,我们将在2.4节通过实现一个神经网络来处理。对于在2.1节中所举的正负数分类问题,这时解决起来就更简单了,可以将其视为对x轴上的点进行划分,如图2-3所示。

图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节中假设:

这是在参考文献[2]中感知器的激活函数中设定的,但这在实际应用中几乎不可能这么简单判定。在感知器之后,人们提出了Adaptive Linear Neuron(简称Adaline)的概念,即其激活函数和输入一致:

这样,在Adaline中,激活函数的输出就是一个连续值,而不是如前面感知器那样的简单二分,这样的变化可以让我们定义两个重要的概念:损失函数和梯度下降。
损失函数即如何计算图2-1中的Error值。我们通常使用MSE来计算,即

其中,为真实值,
为预测值。
用代码实现如下:

在获得了损失函数之后,我们便要应用梯度下降来调整w和bias(记为b)。怎么调整?其实思路很简单,计算出损失函数针对w和bias的梯度变化和
,然后从w和bias中分别减去
w和
即可,如此反复,直到最后的损失函数值足够小。
根据上面的MSE公式,我们假设f为MSE,分别对w和b求导可以得到:


以上即梯度Δw和Δb,然后我们只需要更新w(wΔw)、b(b
Δb)即可。其代码实现如下:

在上面的代码中要注意的是,我们并没有直接从w和bias中减去梯度值,而是将梯度值乘以learning rate,以调整训练步长。
在我们完整实现一个基于线性函数的神经网络之前,还有一点要处理,即数据归一化。
在前面的感知器的实现中并没有这一步,因为感知器的“激活函数”实际上是一个单位阶跃函数(Unit Step Function),它能够把任何输入值都映射为0或1这两个值。然而,对于Adaline,因为激活函数就是网络输入本身,而x的值远超过[0,1]区间,因此我们需要处理输入值的范围,让最终的输出能够落在[0,1]区间(另一种做法是直接改变激活函数,使其能把任意输入都映射到[0,1]区间,这会在后面解释)。我们通常会把输入值映射到[0,1]区间,输入则变为

2.2.3 神经元的线性回归实现
现在我们就能来完整实现应用了梯度下降的单神经元网络了(不包括隐藏层):


通过前面的讨论,我们可以很容易理解上面这段代码了。
第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进行预测。
代码运行结果如下:

在上面的运行结果中,我们可以看到训练损失的确在收敛、下降,但在超过300之后已经没有太大变化了。
对于所有测试数据,我们可以看到阈值基本上在0.58左右,正数越大越趋近于1,负数越小越趋近于0,符合我们的预期。
关于线性回归的内容,会在第4章深入讨论。