线性模型描述了一个或多个自变量对另一个因变量的影响所呈现的线性比例和关系。线性模型在二维空间内为一条直线,在三维空间中为一个平面,更高维度下的线性模型称为超平面。

二维空间下线性模型表现为一条直线
三维空间下线性模型表现为一个平面

线性回归是要根据一组输入和输出数据(样本),寻找一个线性模型,能最佳程度拟合给定的数值分布,从而对新给定的输入数据进行输出预测。如:

输入(x) 输出(y)
0.5 5.0
0.6 5.5
0.8 6.0
1.1 6.8
1.4 6.8

根据样本拟合的线性模型:

线性模型拟合

线性模型定义

设给定一组属性$x, x=(x_1;x_2;…;x_n)$,线性方程的一般表达形式为:
$$
y = w_1x_1 + w_2x_2 + w_3x_3 + … + w_nx_n + b
$$
写成向量形式为:
$$
y = w^Tx + b
$$
其中,$w=(w_1;w_2;…;w_n), x=(x_1;x_2;…;x_n)$,w和b经过学习后,模型就可以确定. 当自变量数量为1时,上述线性模型即为平面下的直线方程:
$$
y = wx + b
$$
线性模型形式简单、易于建模,却蕴含着机器学习中一些重要的基本思想。许多功能强大的非线性模型可以在线性模型基础上引入层级结构或高维映射而得。此外,由于$w$直观表达了各属性在预测中的重要性,因此线性模型具有很好的可解释性。

例如,判断一个西瓜是否为好瓜,可以用如下表达式来判断:(参考周志华机器学习及西瓜书案例)
$$
f_{好瓜}(x) = 0.2x_{色泽} + 0.5x_{根蒂} + 0.3x_{敲声} + 1
$$
上述公式可以解释为,一个西瓜是否为好瓜,可以通过色泽、根蒂、敲声等因素共同判断,其中根蒂最重要(权重最高),其次是敲声和色泽.

模型训练

在二维平面中,给定两点可以确定一条直线。但在实际工程中,很多个样本点,无法找到一条直线精确穿过所有样本点,只能找到一条与样本“足够接近”或“距离足够小”的直线,近似拟合给定的样本,如:

线性模型拟合

可使用损失函数度量所有样本到直线的距离。

损失函数

损失函数用来度量真实值(由样本中给出)和预测值(由模型算出)之间的差异。

  • 损失函数值越小,表明模型预测值和真实值之间差异越小,模型性能越好;
  • 损失函数值越大,模型预测值和真实值之间差异越大,模型性能越差。

在回归问题中,均方差是常用的损失函数,其表达式如下所示:
$$
E = \frac{1}{2}\sum_{i=1}^{n}{(y - y’)^2}
$$

其中,y为模型预测值,y’为真实值。线性回归的任务是寻找最优线性模型,损失函数值最小,即:
$$
(w^*, b^*) = arg min \frac{1}{2}\sum_{i=1}^{n}{(y - y’)^2} \
= arg min \frac{1}{2}\sum_{i=1}^{n}{(y’ - wx_i - b)^2}
$$

基于均方误差最小化来进行模型求解的方法称为“最小二乘法”。线性回归中,最小二乘法试图找到一条直线,使所有样本到直线的欧式距离之和最小。可将损失函数对w和b分别求导,得到损失函数的导函数,并令导函数为0即可得到w和b的最优解。

梯度下降

为什么使用梯度下降

在实际计算中,通过最小二乘法求解最优参数有一定的问题:

  1. 最小二乘法需要计算逆矩阵,有可能逆矩阵不存在;
  2. 当样本特征数量较多时,计算逆矩阵非常耗时甚至不可行.

所以,在实际计算中,通常采用梯度下降法来求解损失函数的极小值,从而找到模型的最优参数.

什么是梯度下降

已在DeepLearning学习笔记-4-数值计算/#基于梯度的优化方法中对梯度下降做了详细介绍。

线性回归实现

编程实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 线性回归示例
import numpy as np
import matplotlib.pyplot as mp
from mpl_toolkits.mplot3d import axes3d
import sklearn.preprocessing as sp

# 训练数据集
train_x = np.array([0.5, 0.6, 0.8, 1.1, 1.4]) # 输入集
train_y = np.array([5.0, 5.5, 6.0, 6.8, 7.0]) # 输出集

n_epochs = 1000 # 迭代次数
lrate = 0.01 # 学习率
epochs = [] # 记录迭代次数
losses = [] # 记录损失值

w0, w1 = [1], [1] # 模型初始值

for i in range(1, n_epochs + 1):
epochs.append(i) # 记录第几次迭代

y = w0[-1] + w1[-1] * train_x # 取出最新的w0,w1计算线性方程输出
# 损失函数(均方差)
loss = (((train_y - y) ** 2).sum()) / 2
losses.append(loss) # 记录每次迭代的损失值

print("%d: w0=%f, w1=%f, loss=%f" % (i, w0[-1], w1[-1], loss))

# 计算w0,w1的偏导数
d0 = -(train_y - y).sum()
d1 = -(train_x * (train_y - y)).sum()

# 更新w0,w1
w0.append(w0[-1] - (d0 * lrate))
w1.append(w1[-1] - (d1 * lrate))

程序执行结果:

1
2
3
4
5
6
7
8
9
10
11
1 w0=1.00000000 w1=1.00000000 loss=44.17500000
2 w0=1.20900000 w1=1.19060000 loss=36.53882794
3 w0=1.39916360 w1=1.36357948 loss=30.23168666
4 w0=1.57220792 w1=1.52054607 loss=25.02222743
5 w0=1.72969350 w1=1.66296078 loss=20.71937337
......
996 w0=4.06506160 w1=2.26409126 loss=0.08743506
997 w0=4.06518850 w1=2.26395572 loss=0.08743162
998 w0=4.06531502 w1=2.26382058 loss=0.08742820
999 w0=4.06544117 w1=2.26368585 loss=0.08742480
1000 w0=4.06556693 w1=2.26355153 loss=0.08742142

数据可视化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
###################### 训练过程可视化 ######################
# 训练过程可视化
## 损失函数收敛过程
w0 = np.array(w0[:-1])
w1 = np.array(w1[:-1])

mp.figure("Losses", facecolor="lightgray") # 创建一个窗体
mp.title("epoch", fontsize=20)
mp.ylabel("loss", fontsize=14)
mp.grid(linestyle=":") # 网格线:虚线
mp.plot(epochs, losses, c="blue", label="loss")
mp.legend() # 图例
mp.tight_layout() # 紧凑格式

## 显示模型直线
pred_y = w0[-1] + w1[-1] * train_x # 根据x预测y
mp.figure("Linear Regression", facecolor="lightgray")
mp.title("Linear Regression", fontsize=20)
mp.xlabel("x", fontsize=14)
mp.ylabel("y", fontsize=14)
mp.grid(linestyle=":")
mp.scatter(train_x, train_y, c="blue", label="Traing") # 绘制样本散点图
mp.plot(train_x, pred_y, c="red", label="Regression")
mp.legend()

# 显示梯度下降过程(复制粘贴即可,不需要编写)
# 计算损失函数曲面上的点 loss = f(w0, w1)
arr1 = np.linspace(0, 10, 500) # 0~9间产生500个元素的均匀列表
arr2 = np.linspace(0, 3.5, 500) # 0~3.5间产生500个元素的均匀列表

grid_w0, grid_w1 = np.meshgrid(arr1, arr2) # 产生二维矩阵

flat_w0, flat_w1 = grid_w0.ravel(), grid_w1.ravel() # 二维矩阵扁平化
loss_metrix = train_y.reshape(-1, 1) # 生成误差矩阵(-1,1)表示自动计算维度
outer = np.outer(train_x, flat_w1) # 求外积(train_x和flat_w1元素两两相乘的新矩阵)
# 计算损失:((w0 + w1*x - y)**2)/2
flat_loss = (((flat_w0 + outer - loss_metrix) ** 2).sum(axis=0)) / 2
grid_loss = flat_loss.reshape(grid_w0.shape)

mp.figure('Loss Function')
ax = mp.gca(projection='3d')
mp.title('Loss Function', fontsize=14)
ax.set_xlabel('w0', fontsize=14)
ax.set_ylabel('w1', fontsize=14)
ax.set_zlabel('loss', fontsize=14)
ax.plot_surface(grid_w0, grid_w1, grid_loss, rstride=10, cstride=10, cmap='jet')
ax.plot(w0, w1, losses, 'o-', c='orangered', label='BGD', zorder=5)
mp.legend(loc='lower left')

mp.show()

线性回归模型

损失函数收敛过程

梯度下降过程

通过sklearn API实现

同样,可以使用sklearn库提供的API实现线性回归。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 利用LinearRegression实现线性回归
import numpy as np
import sklearn.linear_model as lm # 线性模型# 线性模型
import sklearn.metrics as sm # 模型性能评价模块
import matplotlib.pyplot as mp

train_x = np.array([[0.5], [0.6], [0.8], [1.1], [1.4]]) # 输入集
train_y = np.array([5.0, 5.5, 6.0, 6.8, 7.0]) # 输出集

# 创建线性回归器
model = lm.LinearRegression()
# 用已知输入、输出数据集训练回归器
model.fit(train_x, train_y)
# 根据训练模型预测输出
pred_y = model.predict(train_x)

print("coef_:", model.coef_) # 系数
print("intercept_:", model.intercept_) # 截距

# 可视化回归曲线
mp.figure('Linear Regression', facecolor='lightgray')
mp.title('Linear Regression', fontsize=20)
mp.xlabel('x', fontsize=14)
mp.ylabel('y', fontsize=14)
mp.tick_params(labelsize=10)
mp.grid(linestyle=':')

# 绘制样本点
mp.scatter(train_x, train_y, c='blue', alpha=0.8, s=60, label='Sample')

# 绘制拟合直线
mp.plot(train_x, # x坐标数据
pred_y, # y坐标数据
c='orangered', label='Regression')

mp.legend()
mp.show()

执行结果:

线性回归模型

模型评价指标

  • 平均绝对误差(Mean Absolute Deviation):单个观测值与算术平均值的偏差的绝对值的平均;

  • 均方误差:单个样本到平均值差值的平方平均值;

  • MAD(中位数绝对偏差):与数据中值绝对偏差的中值;

  • R2决定系数:趋向于1,模型越好;趋向于0,模型越差.