首发于 Notes
使用Python依靠梯度下降法实现线性模型(上)——多元线性回归

使用Python依靠梯度下降法实现线性模型(上)——多元线性回归

0.前言

为了使用Python实现多元线性回归的模型,我们首先要了解多元线性回归模型(multivariable linear regression model )的基本信息,解释它的构成、了解它的数学原理以及我们如何通过一系列数据得到我们需要的模型。

其次,我们需要了解评估一个线性模型好坏的标准,也就是我们的损失函数(loss function)或者称为代价函数(cost function)是如何存在、以及如何工作的。

紧接着,我们需要了解在机器学习与优化中广泛使用的梯度下降方法(gradient descent)的原理,形式以及工作方式,它能够通过迭代求出目标函数的最小值,或者收敛到最小值。

最后,我们需要将以上的数学知识转化为代码,并且将它们联系、驱动起来,最终实现多元线性模型的拟合,并且利用Matplotlib库将其可视化。

值得一提的是,因为我们需要数据来供给模型,这里依然选择经典的鸢尾花数据集,但是因为多元线性模型和最后我们进行可视化的一些要求,我们需要对原始的鸢尾花数据集进行一定的预处理,使他符合我们的要求。

总结下来,我们需要:

  • 了解多元线性回归模型
  • 介绍损失函数的构成以及评估方法
  • 介绍梯度下降法的原理
  • 对鸢尾花数据集预处理并实现拟合多元线性回归

使用正确的Python代码将其实现。

1.多元线性回归模型

  • 多元线性回归简介

在介绍多元线性回归模型之前,我们首先要简单介绍下线性回归(linear regression)的概念,线性回归是指利用数理统计中回归分析,来确定两种或两种以上变量间相互依赖的定量关系的一种统计分析方法。其常见的表达式为:

y=w'x+e

回归分析中,只包括一个自变量和一个因变量,二者的关系可以用一条直线近似表示。

而多元线性回归(multivariable linear regression)则是指包含多个自变量的线性回归。事实上,现实生活中的一种现象常常就是与多个因素有关,于是用多个自变量来进行预测或者估计因变量,要比只用一个自变量更有效。其常见的表达式为:

Z_{y}=\beta_{1}Z\cdot1+\beta_{2}Z\cdot2+...+\beta_{k}Z\cdot k

  • 多元线性回归模型

对于多元线性回归,它的计算模型我们常常表示为:

Y=b_{0}+b_{1}x_{1}+...+b_{k}x_{k}+e

其中的 b_{0} 为常数项, b_{1},b_{2}...b_{k} 为回归系数。

以一个二元线性回归模型为例,他的表达式应为:

Y=b_{0}+b_{1}x_{1}+b_{2}x_{2}

实际上,本文将以鸢尾花数据集的前两个特征,也就是花萼长度(SepalLength),花萼宽度(SepalWidth)和前两个类别,共一百个样本作为参数,拟合这样一个二元线性回归模型,之所以这样选择,是为了方便我们的可视化效果。而鸢尾花数据集的分布特点为前两个样本足够分散,后两个样本十分接近,也同样方便了我们的拟合效果。

有关鸢尾花数据集的内容,您可以通过其简单了解:


对于二元线性回归模型来说,我们就是要通过不断调整表达式中系数的值,使他逐渐接近真实的映射关系,那么如何调整呢?通过什么方式调整呢?这时候就要引入我们的损失函数(loss function)来进行介绍。

2.损失函数的构成以及评估原理

  • 损失函数简介
损失函数(loss function)或代价函数(cost function)是将随机事件或其有关随机变量的取值映射为非负实数以表示该随机事件的“风险”或“损失”的函数。在应用中,损失函数通常作为学习准则与优化问题相联系,即通过最小化损失函数求解和评估模型。——百度百科

简单来说,损失函数就是用来描述一个模型好坏的函数,而通过最小化这个损失函数,我们就可以求解模型的参数。

常见的损失函数有:

来源:百度百科

而在我们的多元线性回归模型中,我们使用均方误差(mean-square error, MSE)作为我们的损失函数,它的数学意义是求所有点到线的欧氏距离并求均值,以此来衡量一个线性模型的好坏。

  • 损失函数的评估原理

在这个模型中,均方误差的数学表达式一般为:

J(\Theta)=\frac{1}{2m}\sum_{i=1}^{m}{(h_{\Theta}(x^{(i)})-y^{(i)})}^{2}

其中:

  1. m是样本数(Samples)。
  2. ½是一个常量,这样是为了在求梯度的时候,二次方乘下来的2就和这里的½抵消了,消除了多余的常数系数,方便后续的计算,对结果不会有影响。
  3. y是数据集中每个点真实的y坐标值,也就是类标签(Classes)。
  4. h是我们要进行拟合的函数,每带入一个x,也就是我们的特征变量(Features)的值,根据 \Theta 计算得到预测的y值。

也就是说我们计算了每一个预估点到真实样本点的欧氏距离,将其平方后求和,并除以其样本数得到均值,值越小,也就是意味着我们的拟合越精确。

而这里的我们要拟合的函数,也就是我们的二元线性回归模型,表示为:

h_{\Theta}(x^{(i)})=\Theta_{0}+\Theta_{1}x_{1}^{(i)}+\Theta_{2}x_{2}^{(i)}

通过表达式可以观察到,这里我们有三个自变量。实际上对于这样一个损失函数,我们是可以通过数学方法求得闭式解,也就是可以用公式表示,然后依据最小二乘法求得最小值。

但是下一篇文章我们需要接着介绍 对数几率回归模型 ,对数几率模型使用的损失函数为对数损失(logloss),无法求得闭式解,所以这里我们选用梯度下降法对其求解,并讲解梯度下降法的工作原理。

而此时我们的目标为:

min\ J(\Theta)=\frac{1}{2m}\sum_{i=1}^{m}{(h_{\Theta}(x^{(i)})-y^{(i)})}^{2}

3.梯度下降法的工作原理

  • 梯度下降法简介

在介绍梯度下降法之前,我们先需要介绍 梯度(Gradient) 这个概念。

在微积分里面,对一个可微分的多元函数的参数求∂偏导数,把求得的各个参数的偏导数以向量的形式写出来,就是梯度。

比如函数f(x,y),分别对x,y求偏导数,求得的梯度向量就是(∂f/∂x, ∂f/∂y)T,简称grad f(x,y)或者▽f(x,y)。对于在点(x0,y0)的具体梯度向量就是(∂f/∂x0, ∂f/∂y0)T.或者▽f(x0,y0),如果是3个参数的向量梯度,就是(∂f/∂x, ∂f/∂y,∂f/∂z)T,以此类推。

我们可以把这个可微分的函数想象成一座山,我们站在山腰上的一个位置,我们的目标是找到最快下山的方法,这是我们就要求我们这一点的梯度,而梯度下降,则是指沿着梯度相反的方向“下山”,这样就能够以最快的速度下降。

相对的,也有着梯度上升法,是用来求最大值的。

所以通过反复求取梯度,利用梯度下降法,我们就可以到达函数的局部最小值。所以我们最终的迭代式中,梯度应该带上负号。

  • 梯度下降法的工作原理

为什么梯度下降的方向就是最陡峭的方向呢?这需要说到微分的性质了。

对于一个可微分的函数,微分的意义可以是:

  1. 某点的切线斜率
  2. 函数的变化率

我们都知道要求最小就要求导,而导函数的某一点值就是指函数在某一点的变化率,变化率最大的时候,也就代表着原函数的变化最大。

而梯度就是将微分进行了一般化,将函数微分后用非0项的系数,得到了一个常数或者一组常数,这就是这一点的梯度。即:

J(\Theta)=a_{1}\Theta_{1}+a_{2}\Theta_{2}+a_{3}\Theta_{3}

其中的a为常数,此时的梯度为:

▽J(\Theta)=(a_{1},a_{2},a_{3})

关于梯度下降的详细解释,可以参考:

此外,我们还需要介绍一个在梯度下降法中很重要的参数,它的名字叫做 学习率( \alpha /alpha) ,也称为步长。学习率代表了每次迭代之后进行 \Theta 更新的程度大小,举一个很简单的例子:

这里的梯度应该是越来越小,也就是线越来越短,为了展示方便就不做改变,下同

当黄线的学习率大于红线时,尽管他们都能到达“山谷”,但是红线到达了函数的最低谷,也就是(局部)最小,而黄线却错过了(局部)最小,所以学习率决定着我们迭代求解时 \Theta 的精确度。当我们下山的每一步都走的很小,那么就更容易走到最低谷,而不是错过。

那么这样看来,学习率越小越好吗?

答案是:并不是越小越好。

这时候就要介绍我们的另一个参数,我们的 迭代深度(epoch)

我们都知道在求解 \Theta 时,需要用迭代函数进行一定次数的迭代,才能求得理想的 \Theta 值,但是迭代不能是没有次数上限的,不可能一直迭代下去,这样是没有意义的,而随着学习率的缩小,迭代次数不变的情况下,很有可能无法到达(局部)最小。

可以看到,在迭代次数相同的情况下,我们把绿色的学习率减半,它甚至都没有到达“山谷”,也就是说我们的步子迈小了,但是走的步数没有变化,自然而然不容易到达“山谷”。

这么看来,学习率和迭代深度都是合适才最好。

了解了以上知识之后,我们只需要将他们组合,联系成完整的代码就好了。

4.对鸢尾花数据集进行预处理,并实现拟合多元线性回归模型

  • 鸢尾花数据集的预处理

前面提到了,我们使用鸢尾花数据集的前100个样本作为数据,所以我们需要对鸢尾花数据集进行一定的预处理。

同时,在进行计算时我们选用矩阵的形式进行计算,因为在Python中矩阵的计算十分方便,我们将代价函数和梯度均转换成矩阵向量相乘的形式:

J(\Theta)=\frac{1}{2m}(X\Theta-y^{\rightarrow})^{T}(X\Theta-y^{\rightarrow})

这里将矩阵转置后相乘,也就是求其内积,就等价于我们的求平方再求和操作。

同时梯度也可以转化为:

J(\Theta)=\frac{1}{m}X^{T}(X\Theta-y^{\rightarrow})

我们使用一种巧妙的方法优化我们的常数 \Theta_{0} ,在预处理时,给特征值每行加上一个常数1,这样就会将我们矩阵的计算统一,也就是不需要再额外迭代计算b的值。

接下来我们对鸢尾花数据集进行预处理。

#加载鸢尾花数据集的特征和类标签
iris = load_iris()
iris_feature = iris.data
iris_label = iris.target
#截取前100个样本的特征和标签
X=iris_feature[:100,:2]
#给每列加一个为1的常数,使其增加一维
X_marixadd=np.ones((m,1))
X=np.hstack((X,X_marixadd))
#将鸢尾花数据集类标签由行向量变为列向量,方便计算
Y=iris_label[:100].reshape(100,1)

之后我们分别封装我们的代价函数,梯度下降法和迭代函数。

#均方误差代价函数,用于评估模型好坏
def cost_function(theta,X,Y,m):
    #利用np.dot求矩阵内积,以下皆相同
    temp = np.dot(X,theta)-Y
    #利用np.transpose转置矩阵,以下皆相同
    return (1/(2*m))*np.dot(temp.transpose(),temp)
#梯度计算函数
def gradient_function(theta,X,Y,m):
    temp = np.dot(X,theta)-Y
    return (1/m)*np.dot(X.transpose(),temp)
#迭代函数
def gradient_descent(X,Y,alpha,m):
    #定义最早的theta为<1,1,1>矩阵,并转换为列向量
    theta = np.array([1,1,1]).reshape(3,1)
    #获取第第一个梯度
    gradient = gradient_function(theta,X,Y,m)
    #迭代更新,规定当梯度小于10**-5时停止迭代,并返回此时的theta
    while not all(abs(gradient) <= 1e-5):
        theta = theta - alpha * gradient
        gradient = gradient_function(theta,X,Y,m)
    return theta

接下来进行 \Theta 的求解

#开始进行迭代求解
optimal = gradient_descent(X,Y,alpha,m)
print(optimal)
#查看均方误差
print(gradient_function(optimal,X,Y,m))

最后我们将其可视化,因为是一个多元线性回归模型,我们将其在三维空间展示出来,并绘制出鸢尾花数据集的散点图。

fig=plt.figure(figsize=(14, 8))
plt.suptitle('多元线性回归模型', fontsize = 30,fontproperties=zhfont1)
ax = Axes3D(fig)
ax.scatter(X[:,0],X[:,1],Y)
x=np.linspace(0,10,10)
y=np.linspace(0,6,10)
X_area,Y_area=np.meshgrid(x,y)