线性回归适用于数据呈线性分布的回归问题。如果数据样本呈明显非线性分布,线性回归模型就不再适用(下图左),而采用多项式回归可能更好(下图右)。例如:
与线性模型相比,多项式模型引入了高次项,自变量的指数大于 1,例如一元二次方程:
y = w 0 + w 1 x + w 2 x 2 y = w_0 + w_1x + w_2x^2 y=w0+w1x+w2x2
一元三次方程:
y = w 0 + w 1 x + w 2 x 2 + w 3 x 3 y = w_0 + w_1x + w_2x^2 + w_3x ^ 3 y=w0+w1x+w2x2+w3x3
推广到一元 n 次方程:
y = w 0 + w 1 x + w 2 x 2 + w 3 x 3 + . . . + w n x n y = w_0 + w_1x + w_2x^2 + w_3x ^ 3 + ... + w_nx^n y=w0+w1x+w2x2+w3x3+...+wnxn
上述表达式可以简化为:
y = ∑ i = 1 N w i x i y = \sum_{i=1}^N w_ix^i y=i=1∑Nwixi
多项式回归可以理解为线性回归的扩展,在线性回归模型中添加了新的特征值。例如,要预测一栋房屋的价格,有 x 1 , x 2 , x 3 x_1, x_2, x_3 x1,x2,x3三个特征值,分别表示房子长、宽、高,则房屋价格可表示为以下线性模型:
y = w 1 x 1 + w 2 x 2 + w 3 x 3 + b y = w_1 x_1 + w_2 x_2 + w_3 x_3 + b y=w1x1+w2x2+w3x3+b
对于房屋价格,也可以用房屋的体积,而不直接使用 x 1 , x 2 , x 3 x_1, x_2, x_3 x1,x2,x3三个特征:
y = w 0 + w 1 x + w 2 x 2 + w 3 x 3 y = w_0 + w_1x + w_2x^2 + w_3x ^ 3 y=w0+w1x+w2x2+w3x3
相当于创造了新的特征 x , x x, x x,x = 长 _ 宽 _ 高。
以上两个模型可以解释为:
房屋价格是关于长、宽、高三个特征的线性模型
房屋价格是关于体积的多项式模型
因此,可以将一元 n 次多项式变换成 n 元一次线性模型。
对于一元 n 次多项式,同样可以利用梯度下降对损失值最小化的方法,寻找最优的模型参数 w 0 , w 1 , w 2 , . . . , w n w_0, w_1, w_2, ..., w_n w0,w1,w2,...,wn。可以将一元 n 次多项式,变换成 n 元一次多项式,求线性回归。以下是一个多项式回归的实现。
# 多项式回归示例
import numpy as np
# 线性模型
import sklearn.linear_model as lm
# 模型性能评价模块
import sklearn.metrics as sm
import matplotlib.pyplot as mp
# 管线模块
import sklearn.pipeline as pl
import sklearn.preprocessing as sp
train_x, train_y = [], [] # 输入、输出样本
with open("poly_sample.txt", "rt") as f:
for line in f.readlines():
data = [float(substr) for substr in line.split(",")]
train_x.append(data[:-1])
train_y.append(data[-1])
train_x = np.array(train_x) # 二维数据形式的输入矩阵,一行一样本,一列一特征
train_y = np.array(train_y) # 一维数组形式的输出序列,每个元素对应一个输入样本
# print(train_x)
# print(train_y)
# 将多项式特征扩展预处理,和一个线性回归器串联为一个管线
# 多项式特征扩展:对现有数据进行的一种转换,通过将数据映射到更高维度的空间中
# 进行多项式扩展后,我们就可以认为,模型由以前的直线变成了曲线
# 从而可以更灵活的去拟合数据
# pipeline连接两个模型
model = pl.make_pipeline(sp.PolynomialFeatures(3), # 多项式特征扩展,扩展最高次项为3
lm.LinearRegression())
# 用已知输入、输出数据集训练回归器
model.fit(train_x, train_y)
# print(model[1].coef_)
# print(model[1].intercept_)
# 根据训练模型预测输出
pred_train_y = model.predict(train_x)
# 评估指标
err4 = sm.r2_score(train_y, pred_train_y) # R2得分, 范围[0, 1], 分值越大越好
print(err4)
# 在训练集之外构建测试集
test_x = np.linspace(train_x.min(), train_x.max(), 1000)
pre_test_y = model.predict(test_x.reshape(-1, 1)) # 对新样本进行预测
# 可视化回归曲线
mp.figure('Polynomial Regression', facecolor='lightgray')
mp.title('Polynomial 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='dodgerblue', alpha=0.8, s=60, label='Sample')
mp.plot(test_x, pre_test_y, c='orangered', label='Regression')
mp.legend()
mp.show()
打印输出:
0.9224401504764776
执行结果:
在上一小节多项式回归示例中,多项特征扩展器 PolynomialFeatures()进行多项式扩展时,指定了最高次数为 3,该参数为多项式扩展的重要参数,如果选取不当,则可能导致不同的拟合效果。下图显示了该参数分别设为 1、20 时模型的拟合图像:
这两种其实都不是好的模型。前者没有学习到数据分布规律,模型拟合程度不够,预测准确度过低,这种现象称为“欠拟合”;后者过于拟合更多样本,以致模型泛化能力(新样本的适应性)变差,这种现象称为“过拟合”。欠拟合模型一般表现为训练集、测试集下准确度都比较低;过拟合模型一般表现为训练集下准确度较高、测试集下准确度较低。 一个好的模型,不论是对于训练数据还是测试数据,都有接近的预测精度,而且精度不能太低。
【思考 1】以下哪种模型较好,哪种模型较差,较差的原因是什么?
训练集 R2 值 | 测试集 R2 值 |
---|---|
0.6 | 0.5 |
0.9 | 0.6 |
0.9 | 0.88 |
【答案】第一个模型欠拟合;第二个模型过拟合;第三个模型适中,为可接受的模型。
【思考 2】以下哪个曲线为欠拟合、过拟合,哪个模型拟合最好?
【答案】第一个模型欠拟合;第三个模型过拟合;第二个模型拟合较好。
欠拟合:提高模型复杂度,如增加特征、增加模型最高次幂等等;
过拟合:降低模型复杂度,如减少特征、降低模型最高次幂等等。
过拟合还有一个常见的原因,就是模型参数值太大,所以可以通过抑制参数的方式来解决过拟合问题。如下图所示,右图产生了一定程度过拟合,可以通过弱化高次项的系数(但不删除)来降低过拟合。
例如,可以通过在 θ 3 , θ 4 \theta_3, \theta_4 θ3,θ4的系数上添加一定的系数,来压制这两个高次项的系数,这种方法称为正则化。但在实际问题中,可能有更多的系数,我们并不知道应该压制哪些系数,所以,可以通过收缩所有系数来避免过拟合。
正则化是指,在目标函数后面添加一个范数,来防止过拟合的手段,这个范数定义为:
∣ ∣ x ∣ ∣ p = ( ∑ i = 1 N ∣ x ∣ p ) 1 p ||x||_p = (\sum_{i=1}^N |x|^p)^{\frac{1}{p}} ∣∣x∣∣p=(i=1∑N∣x∣p)p1
当 p=1 时,称为 L1 范数(即所有系数绝对值之和):
∣ ∣ x ∣ ∣ 1 = ( ∑ i = 1 N ∣ x ∣ ) ||x||_1 = (\sum_{i=1}^N |x|) ∣∣x∣∣1=(i=1∑N∣x∣)
当 p=2 是,称为 L2 范数(即所有系数平方之和再开方):
∣ ∣ x ∣ ∣ 2 = ( ∑ i = 1 N ∣ x ∣ 2 ) 1 2 ||x||_2 = (\sum_{i=1}^N |x|^2)^{\frac{1}{2}} ∣∣x∣∣2=(i=1∑N∣x∣2)21
通过对目标函数添加正则项,整体上压缩了参数的大小,从而防止过拟合。
Lasso 回归和岭回归(Ridge Regression)都是在标准线性回归的基础上修改了损失函数的回归算法。 Lasso 回归全称为 Least absolute shrinkage and selection operator,又译“最小绝对值收敛和选择算子”、”套索算法”,其损失函数如下所示:
E = 1 n ( ∑ i = 1 N y i − y i ′ ) 2 + λ ∣ ∣ w ∣ ∣ 1 E = \frac{1}{n}(\sum_{i=1}^N y_i - y_i')^2 + \lambda ||w||_1 E=n1(i=1∑Nyi−yi′)2+λ∣∣w∣∣1
岭回归损失函数为:
E = 1 n ( ∑ i = 1 N y i − y i ′ ) 2 + λ ∣ ∣ w ∣ ∣ 2 E = \frac{1}{n}(\sum_{i=1}^N y_i - y_i')^2 + \lambda ||w||_2 E=n1(i=1∑Nyi−yi′)2+λ∣∣w∣∣2
从逻辑上说,Lasso 回归和岭回归都可以理解为通过调整损失函数,减小函数的系数,从而避免过于拟合于样本,降低偏差较大的样本的权重和对模型的影响程度。
线性模型变种模型:在损失函数后面 + 正则项
以下关于 Lasso 回归于岭回归的 sklearn 实现:
# Lasso回归和岭回归示例
import numpy as np
# 线性模型
import sklearn.linear_model as lm
# 模型性能评价模块
import sklearn.metrics as sm
import matplotlib.pyplot as mp
x, y = [], [] # 输入、输出样本
with open("abnormal.txt", "rt") as f:
for line in f.readlines():
data = [float(substr) for substr in line.split(",")]
x.append(data[:-1])
y.append(data[-1])
x = np.array(x) # 二维数据形式的输入矩阵,一行一样本,一列一特征
y = np.array(y) # 一维数组形式的输出序列,每个元素对应一个输入样本
# print(x)
# print(y)
# 创建线性回归器
model = lm.LinearRegression()
# 用已知输入、输出数据集训练回归器
model.fit(x, y)
# 根据训练模型预测输出
pred_y = model.predict(x)
# 创建岭回归器并进行训练
# Ridge: 第一个参数为正则强度,该值越大,异常样本权重就越小
model_2 = lm.Ridge(alpha=200, max_iter=1000) # 创建对象, max_iter为最大迭代次数
model_2.fit(x, y) # 训练
pred_y2 = model_2.predict(x) # 预测
# lasso回归
model_3 = lm.Lasso(alpha=0.5, # L1范数相乘的系数
max_iter=1000) # 最大迭代次数
model_3.fit(x, y) # 训练
pred_y3 = model_3.predict(x) # 预测
# 可视化回归曲线
mp.figure('Linear & Ridge & Lasso', facecolor='lightgray')
mp.title('Linear & Ridge & Lasso', fontsize=20)
mp.xlabel('x', fontsize=14)
mp.ylabel('y', fontsize=14)
mp.tick_params(labelsize=10)
mp.grid(linestyle=':')
mp.scatter(x, y, c='dodgerblue', alpha=0.8, s=60, label='Sample')
sorted_idx = x.T[0].argsort()
mp.plot(x[sorted_idx], pred_y[sorted_idx], c='orangered', label='Linear') # 线性回归
mp.plot(x[sorted_idx], pred_y2[sorted_idx], c='limegreen', label='Ridge') # 岭回归
mp.plot(x[sorted_idx], pred_y3[sorted_idx], c='blue', label='Lasso') # Lasso回归
mp.legend()
mp.show()
以下是执行结果:
可以使用 Python 提供的功能对模型对象进行保存。使用方法如下:
import pickle
# 保存模型
pickle.dump(模型对象, 文件对象)
# 加载模型
model_obj = pickle.load(文件对象)
保存训练模型应该在训练完成或评估完成之后,完整代码如下:
# 模型保存示例
import numpy as np
import sklearn.linear_model as lm # 线性模型
import pickle
x = np.array([[0.5], [0.6], [0.8], [1.1], [1.4]]) # 输入集
y = np.array([5.0, 5.5, 6.0, 6.8, 7.0]) # 输出集
# 创建线性回归器
model = lm.LinearRegression()
# 用已知输入、输出数据集训练回归器
model.fit(x, y)
print("训练完成.")
# 保存训练后的模型
with open('linear_model.pkl', 'wb') as f:
pickle.dump(model, f)
print("保存模型完成.")
执行完成后,可以看到与源码相同目录下多了一个名称为 linear_model.pkl 的文件,这就是保存的训练模型。使用该模型代码:
# 模型加载示例
import numpy as np
import sklearn.linear_model as lm # 线性模型
import sklearn.metrics as sm # 模型性能评价模块
import matplotlib.pyplot as mp
import pickle
x = np.array([[0.5], [0.6], [0.8], [1.1], [1.4]]) # 输入集
y = np.array([5.0, 5.5, 6.0, 6.8, 7.0]) # 输出集
# 加载模型
with open('linear_model.pkl', 'rb') as f:
model = pickle.load(f)
print("加载模型完成.")
# 根据加载的模型预测输出
pred_y = model.predict(x)
# 可视化回归曲线
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(x, y, c='blue', alpha=0.8, s=60, label='Sample')
mp.plot(x, pred_y, c='orangered', label='Regression')
mp.legend()
mp.show()
执行结果和训练模型预测结果一样。
线性模型是自然界最简单的模型之一,反映自变量、因变量之间的等比例增长关系。
线性模型只能用于满足线性分布规律的数据中。
给定一组样本,给定初始的 w 和 b,通过梯度下降法求最优的 w 和 b。
R2 系数详细计算过程如下:
若用 y i y_i yi表示真实的观测值,用 y ˉ \bar{y} yˉ表示真实观测值的平均值,用 y i ^ \hat{y_i} yi^表示预测值,则有以下评估指标:
回归平方和(SSR)
S S R = ∑ i = 1 n ( y i ^ − y ˉ ) 2 SSR = \sum_{i=1}^{n}(\hat{y_i} - \bar{y})^2 SSR=i=1∑n(yi^−yˉ)2
残差平方和(SSE)
S S E = ∑ i = 1 n ( y i − y i ^ ) 2 SSE = \sum_{i=1}^{n}(y_i-\hat{y_i} )^2 SSE=i=1∑n(yi−yi^)2
总离差平方和(SST)
S S T = S S R + S S E = ∑ i = 1 n ( y i − y ˉ ) 2 SST =SSR + SSE= \sum_{i=1}^{n}(y_i - \bar{y})^2 SST=SSR+SSE=i=1∑n(yi−yˉ)2
R2_score,即决定系数,反映因变量的全部变异能通过回归关系被自变量解释的比例。计算公式:
R 2 = 1 − S S E S S T R^2=1-\frac{SSE}{SST} R2=1−SSTSSE
即:
R 2 = 1 − ∑ i = 1 n ( y i − y ^ i ) 2 ∑ i = 1 n ( y i − y ˉ ) 2 R^2 = 1 - \frac{\sum_{i=1}^{n} (y_i - \hat{y}_i)2}{\sum_{i=1}{n} (y_i - \bar{y})^2} R2=1−∑i=1n(yi−yˉ)2∑i=1n(yi−y^i)2
进一步化简为:
R 2 = 1 − ∑ i ( y i − y i ) 2 / n ∑ i ( y i − y ^ ) 2 / n = 1 − R M S E V a r R^2 = 1 - \frac{\sum\limits_i(y_i - y_i)^2 / n}{\sum\limits_i(y_i - \hat{y})^2 / n} = 1 - \frac{RMSE}{Var} R2=1−i∑(yi−y^)2/ni∑(yi−yi)2/n=1−VarRMSE
分子就变成了常用的评价指标均方误差 MSE,分母就变成了方差,对于 R 2 R^2 R2可以通俗地理解为使用均值作为误差基准,看预测误差是否大于或者小于均值基准误差。
R2_score = 1,样本中预测值和真实值完全相等,没有任何误差,表示回归分析中自变量对因变量的解释越好。
R2_score = 0,此时分子等于分母,样本的每项预测值都等于均值。
线性函数定义为:
y = w 0 + w 0 x 1 y = w_0 + w_0 x_1 y=w0+w0x1
采用均方差损失函数:
l o s s = 1 2 ( y − y ′ ) 2 loss = \frac{1}{2} (y - y')^2 loss=21(y−y′)2
其中,y 为真实值,来自样本;y’为预测值,即线性方程表达式,带入损失函数得:
l o s s = 1 2 ( y − ( w 0 + w 1 x 1 ) ) 2 loss = \frac{1}{2} (y - (w_0 + w_1 x_1))^2 loss=21(y−(w0+w1x1))2
将该式子展开:
l o s s = 1 2 ( y 2 − 2 y ( w 0 + w 1 x 1 ) + ( w 0 + w 1 x 1 ) 2 ) = 1 2 ( y 2 − 2 y ∗ w 0 − 2 y ∗ w 1 x 1 + w 0 2 + 2 w 0 ∗ w 1 x 1 + w 1 2 x 1 2 ) loss = \frac{1}{2} (y^2 - 2y(w_0 + w_1 x_1) + (w_0 + w_1 x_1)^2) =\\\frac{1}{2} (y^2 - 2y*w_0 - 2y*w_1x_1 + w_0^2 + 2w_0*w_1 x_1 + w_1^2x_1^2) \\ loss=21(y2−2y(w0+w1x1)+(w0+w1x1)2)=21(y2−2y∗w0−2y∗w1x1+w02+2w0∗w1x1+w12x12)
对 w 0 w_0 w0求导:
∂ l o s s ∂ w 0 = 1 2 ( 0 − 2 y − 0 + 2 w 0 + 2 w 1 x 1 + 0 ) = 1 2 ( − 2 y + 2 w 0 + 2 w 1 x 1 ) = 1 2 ∗ 2 ( − y + ( w 0 + w 1 x 1 ) ) = ( − y + y ′ ) = − ( y − y ′ ) \frac{\partial loss}{\partial w_0} = \frac{1}{2}(0-2y-0+2w_0 + 2w_1 x_1 +0) \\=\frac{1}{2}(-2y + 2 w_0 + 2w_1 x_1) \\= \frac{1}{2} * 2(-y + (w_0 + w_1 x_1)) \\=(-y + y') = -(y - y') ∂w0∂loss=21(0−2y−0+2w0+2w1x1+0)=21(−2y+2w0+2w1x1)=21∗2(−y+(w0+w1x1))=(−y+y′)=−(y−y′)
对 w 1 w_1 w1求导:
∂ l o s s ∂ w 1 = 1 2 ( 0 − 0 − 2 y ∗ x 1 + 0 + 2 w 0 x 1 + 2 w 1 x 1 2 ) = 1 2 ( − 2 y x 1 + 2 w 0 x 1 + 2 w 1 x 1 2 ) = 1 2 ∗ 2 x 1 ( − y + w 0 + w 1 x 1 ) = x 1 ( − y + y ′ ) = − x 1 ( y − y ′ ) \frac{\partial loss}{\partial w_1} = \frac{1}{2}(0-0-2y*x_1+0+2 w_0 x_1 + 2 w_1 x_1^2) \\= \frac{1}{2} (-2y x_1 + 2 w_0 x_1 + 2w_1 x_1^2) \\= \frac{1}{2} * 2 x_1(-y + w_0 + w_1 x_1) \\= x_1(-y + y') = - x_1(y - y') ∂w1∂loss=21(0−0−2y∗x1+0+2w0x1+2w1x12)=21(−2yx1+2w0x1+2w1x12)=21∗2x1(−y+w0+w1x1)=x1(−y+y′)=−x1(y−y′)
推导完毕。