来源:原始 Jupyter Notebook。内容已转换为网页阅读格式;下载原文件。
从一元线性回归和二元线性回归的解法中可以看到,线性回归的一般思路是这样的:
- 定义损失函数:loss=(y−y^)2
- 将线性回归函数 y^=a0+a1x1+a2x2+...+anxn 代入损失函数,并求解系数 a0,a1,…,an 使得损失函数最小
- 损失函数的最小值可以通过对损失函数求导来实现,分别对每个系数求偏导,并令其等于 0
- 得到一个关于各个系数的线性方程组,求解这个线性方程组得到各个系数的值
在求解二元线性回归时,线性方程组是这样的:
⎩⎨⎧∂a0∂F(a0,a1,a2)∂a1∂F(a0,a1,a2)∂a2∂F(a0,a1,a2)===2a0+2a1x1ˉ+2a2x2ˉ−2yˉ2a1x12ˉ+2a2x1x2ˉ+2a0x1ˉ−2x1yˉ2a2x22ˉ+2a1x1x2ˉ+2a0x2ˉ−2x2yˉ===000求得方程组的解是:
⎩⎨⎧a0a1a2===yˉ−a1x1ˉ−a2x2ˉ(x12ˉ−(x1ˉ)2)(x22ˉ−(x2ˉ)2)−(x1x2ˉ−x1ˉx2ˉ)2(x1yˉ−x1ˉyˉ)(x22ˉ−(x2ˉ)2)−(x2yˉ−x2ˉyˉ)(x1x2ˉ−x1ˉx2ˉ)(x1x2ˉ−x1ˉx2ˉ)2−(x22ˉ−(x2ˉ)2)(x12ˉ−(x1ˉ)2)(x1yˉ−x1ˉyˉ)(x1x2ˉ−x1ˉx2ˉ)−(x2yˉ−x2ˉyˉ)(x12ˉ−(x1ˉ)2)可以看到,使用这种方法来求解线性回归非常麻烦。当我们扩展到多元线性回归时,这个计算量是极大的。那么有没有一种通用的方法来求解线性回归问题呢?我们不妨把上面的方程组化简:
⎩⎨⎧a0+a1x1ˉ+a2x2ˉa1x12ˉ+a2x1x2ˉ+a0x1ˉa2x22ˉ+a1x1x2ˉ+a0x2ˉ===yˉx1yˉx2yˉ转换为矩阵形式:
1xˉ1xˉ2xˉ1x12x1x2xˉ2x1x2x22a0a1a2=yˉx1yx2y所以:
a0a1a2=1xˉ1xˉ2xˉ1x12x1x2xˉ2x1x2x22−1yˉx1yx2yimport numpy as np
X1 = np.array([6000, 6000, 8000, 8000, 10000])
X2 = np.array([60, 80, 70, 100, 90])
Y = np.array([340000, 350000, 400000, 450000, 500000])
x1 = np.sum(X1) / X1.size
x2 = np.sum(X2) / X2.size
y = np.sum(Y) / Y.size
print("bar_x1 = {0}".format(x1))
print("bar_x2 = {0}".format(x2))
print("bar_y = {0}".format(y))
x1y = np.sum(np.multiply(X1, Y)) / Y.size
x2y = np.sum(np.multiply(X2, Y)) / Y.size
x1x1 = np.sum(np.multiply(X1, X1)) / Y.size
x1x2 = np.sum(np.multiply(X1, X2)) / Y.size
x2x2 = np.sum(np.multiply(X2, X2)) / Y.size
print("bar_x1y = {0}".format(x1y))
print("bar_x2y = {0}".format(x2y))
print("bar_x1x1 = {0}".format(x1x1))
print("bar_x1x2 = {0}".format(x1x2))
print("bar_x2x2 = {0}".format(x2x2))
# 不使用科学计数法
np.set_printoptions(suppress=True)
mx = np.matrix([1, x1, x2, x1, x1x1, x1x2, x2, x1x2, x2x2]).reshape(3, 3)
my = np.matrix([y, x1y, x2y]).reshape(3, 1)
#a = np.linalg.inv(mx).dot(my)
a = mx.I * my
print("a = {0}".format(a))
运行结果
bar_x1 = 7600.0
bar_x2 = 80.0
bar_y = 408000.0
bar_x1y = 3188000000.0
bar_x2y = 33280000.0
bar_x1x1 = 60000000.0
bar_x1x2 = 620000.0
bar_x2x2 = 6600.0
a = [[62105.2631579 ]
[ 32.10526316]
[ 1273.68421053]]
这里使用了矩阵求逆和乘法运算,在计算量上比之前的方法并没有减少,但是从编程的角度来说,确实简化了很多人工计算,直接使用 a = mx.I * my 就完成了多元线性方程组的求解。
我们再回过头来看下线性回归函数:
y^=a0+a1x1+a2x2+...+anxn既然线性方程组可以转换为矩阵表示,这里的线性函数当然也可以转换为矩阵表示:
\[
\hat{y} =
\left[
\begin{array}{ccc}
1 & x_1 & x_2 & ... & x_n
\end{array}
\right ]
\left[
\begin{array}{ccc}
a_0 \\
a_1 \\
a_2 \\
... \\
a_n
\end{array}
\right ]
= \bf{x}^{T}\bf{a}
\]
其中,x 和 a 都是一个 n×1 的矩阵,也就是一个列向量,所以用小写加粗字母表示(一般情况,小写字母 x 表示标量,粗体小写字母 x 表示(列)向量,大写字母 X 表示矩阵)。
上面只是对某个样本数据的表示,假设有 k 个样本,那么可以写成矩阵形式:
y^1y^2⋮y^k=11⋮1x11x21⋮xk1⋯⋯⋱⋯x1nx2n⋮xkna0a1⋮an分别用 y^,a、X 代表上面的矩阵,上式可以简写成:
y^=Xa损失函数仍然和之前一样,使用平方损失:
\[
\begin{aligned}
loss &= (y - \hat{y})^2 \\
&= \sum_{i=0}^{k}(y_i - \hat{y_i})^2 \\
&=
\left[
\begin{array}{ccc}
y_1-\hat{y_1} & y_2-\hat{y_2} & ... & y_k-\hat{y_k}
\end{array}
\right ]
\left[
\begin{array}{ccc}
y_1-\hat{y_1} \\
y_2-\hat{y_2} \\
... \\
y_k-\hat{y_k}
\end{array}
\right ] \\
&=
\left[
\begin{array}{ccc}
y_1-\hat{y_1} \\
y_2-\hat{y_2} \\
... \\
y_k-\hat{y_k}
\end{array}
\right ]^T
\left[
\begin{array}{ccc}
y_1-\hat{y_1} \\
y_2-\hat{y_2} \\
... \\
y_k-\hat{y_k}
\end{array}
\right ] \\
&= (\bf{y}-\bf{\hat{y}})^T (\bf{y}-\bf{\hat{y}}) \\
&= (\bf{y}-\rm{X}\bf{a})^T (\bf{y}-\rm{X}\bf{a})
\end{aligned}
\]
根据矩阵的基本计算法则:(A+B)T=AT+BT 和 (AB)T=BTAT 可得:
loss=(y−Xa)T(y−Xa)=(yT−aTXT)(y−Xa)=yTy−yTXa−aTXTy+aTXTXa求解线性回归,也就是求解系数 a 使得 loss 函数取得最小值,为了求函数的最小值,这里也要用到之前说的求导数的方法,不过这里我们是对矩阵求导数。
矩阵求导(Matrix Derivative) 也叫 矩阵微分(Matrix Differential),在机器学习、图像处理、最优化等领域的公式推导中经常用到。关于矩阵求导的概念和原理这里不做过多的介绍,可以参考这里。我们要求损失函数关于向量 a 的导数:
$$
\begin{aligned}
\frac{\partial loss}{\partial \bf{a}}
&= \frac{\partial \bf{y}^T\bf{y}}{\partial \bf{a}}
- \frac{\partial \bf{y}^T\rm{X}\bf{a}}{\partial \bf{a}}
- \frac{\partial \bf{a}^T\rm{X}^T\bf{y}}{\partial \bf{a}}
- \frac{\partial \bf{a}^T\rm{X}^T\rm{X}\bf{a}}{\partial \bf{a}} \
&= 0 - \rm{X}^T\bf{y} - \rm{X}^T\bf{y} + 2\rm{X}^T\rm{X}\bf{a} \
&= 2\rm{X}^TX\bf{a} - 2\rm{X}^T\bf{y}
\end{aligned}
$$
令 ∂a∂loss=0 可得:
XTy=XTXa⇒a=(XTX)−1XTy其中,XTX 必须是可逆的,也就是说必须是满秩矩阵(又叫 非奇异矩阵)或正定矩阵。可以看出,通过一些简单的矩阵运算(转置、求逆、乘法)就可以求出损失函数的最小值,上面这个公式在统计学中被称为 正规方程(normal equation)。其中 (XTX)−1XT 称为矩阵 X 的 伪逆矩阵(pseudoinverse)或 广义逆(Generalized inverse),通常记为 X†。
a=X†y通过这种方法求解线性回归,代码实现非常简便:
import numpy as np
X = np.matrix([
[1, 1, 1, 1, 1],
[6000, 6000, 8000, 8000, 10000],
[60, 80, 70, 100, 90]]).T
Y = np.matrix([340000, 350000, 400000, 450000, 500000]).T
a = (X.T * X).I * X.T * Y
print("a = {0}".format(a))
运行结果
a = [[62105.26315789]
[ 32.10526316]
[ 1273.68421053]]
或者直接使用 numpy 自带的 pinv() 函数求伪逆矩阵:
import numpy as np
X = np.matrix([
[1, 1, 1, 1, 1],
[6000, 6000, 8000, 8000, 10000],
[60, 80, 70, 100, 90]]).T
Y = np.matrix([340000, 350000, 400000, 450000, 500000]).T
a = np.linalg.pinv(X).dot(Y)
print("a = {0}".format(a))
运行结果
a = [[62105.26315789]
[ 32.10526316]
[ 1273.68421053]]