Python 全栈体系【四阶】(七)

第四章 机器学习

六、多项式回归

1. 什么是多项式回归

线性回归适用于数据呈线性分布的回归问题。如果数据样本呈明显非线性分布,线性回归模型就不再适用(下图左),而采用多项式回归可能更好(下图右)。例如:

Python 全栈体系【四阶】(七)_第1张图片

2. 多项式模型定义

与线性模型相比,多项式模型引入了高次项,自变量的指数大于 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=1Nwixi

3. 与线性回归的关系

多项式回归可以理解为线性回归的扩展,在线性回归模型中添加了新的特征值。例如,要预测一栋房屋的价格,有 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 元一次线性模型。

4. 多项式回归实现

对于一元 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

执行结果:

Python 全栈体系【四阶】(七)_第2张图片

5. 过拟合与欠拟合

5.1 什么是欠拟合、过拟合

在上一小节多项式回归示例中,多项特征扩展器 PolynomialFeatures()进行多项式扩展时,指定了最高次数为 3,该参数为多项式扩展的重要参数,如果选取不当,则可能导致不同的拟合效果。下图显示了该参数分别设为 1、20 时模型的拟合图像:

Python 全栈体系【四阶】(七)_第3张图片

这两种其实都不是好的模型。前者没有学习到数据分布规律,模型拟合程度不够,预测准确度过低,这种现象称为“欠拟合”;后者过于拟合更多样本,以致模型泛化能力(新样本的适应性)变差,这种现象称为“过拟合”。欠拟合模型一般表现为训练集、测试集下准确度都比较低;过拟合模型一般表现为训练集下准确度较高、测试集下准确度较低。 一个好的模型,不论是对于训练数据还是测试数据,都有接近的预测精度,而且精度不能太低。

【思考 1】以下哪种模型较好,哪种模型较差,较差的原因是什么?

训练集 R2 值 测试集 R2 值
0.6 0.5
0.9 0.6
0.9 0.88

【答案】第一个模型欠拟合;第二个模型过拟合;第三个模型适中,为可接受的模型。

【思考 2】以下哪个曲线为欠拟合、过拟合,哪个模型拟合最好?

Python 全栈体系【四阶】(七)_第4张图片

【答案】第一个模型欠拟合;第三个模型过拟合;第二个模型拟合较好。

5.2 如何处理欠拟合、过拟合

欠拟合:提高模型复杂度,如增加特征、增加模型最高次幂等等;

过拟合:降低模型复杂度,如减少特征、降低模型最高次幂等等。

七、线性回归模型变种

1. 正则化

1.1 什么是正则化

过拟合还有一个常见的原因,就是模型参数值太大,所以可以通过抑制参数的方式来解决过拟合问题。如下图所示,右图产生了一定程度过拟合,可以通过弱化高次项的系数(但不删除)来降低过拟合。

Python 全栈体系【四阶】(七)_第5张图片

例如,可以通过在 θ 3 , θ 4 \theta_3, \theta_4 θ3,θ4的系数上添加一定的系数,来压制这两个高次项的系数,这种方法称为正则化。但在实际问题中,可能有更多的系数,我们并不知道应该压制哪些系数,所以,可以通过收缩所有系数来避免过拟合。

1.2 正则化的定义

正则化是指,在目标函数后面添加一个范数,来防止过拟合的手段,这个范数定义为:

∣ ∣ x ∣ ∣ p = ( ∑ i = 1 N ∣ x ∣ p ) 1 p ||x||_p = (\sum_{i=1}^N |x|^p)^{\frac{1}{p}} ∣∣xp=(i=1Nxp)p1

当 p=1 时,称为 L1 范数(即所有系数绝对值之和):

∣ ∣ x ∣ ∣ 1 = ( ∑ i = 1 N ∣ x ∣ ) ||x||_1 = (\sum_{i=1}^N |x|) ∣∣x1=(i=1Nx)

当 p=2 是,称为 L2 范数(即所有系数平方之和再开方):

∣ ∣ x ∣ ∣ 2 = ( ∑ i = 1 N ∣ x ∣ 2 ) 1 2 ||x||_2 = (\sum_{i=1}^N |x|^2)^{\frac{1}{2}} ∣∣x2=(i=1Nx2)21

通过对目标函数添加正则项,整体上压缩了参数的大小,从而防止过拟合。

2. Lasso 回归与岭回归

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=1Nyiyi)2+λ∣∣w1

岭回归损失函数为:

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=1Nyiyi)2+λ∣∣w2

从逻辑上说,Lasso 回归和岭回归都可以理解为通过调整损失函数,减小函数的系数,从而避免过于拟合于样本,降低偏差较大的样本的权重和对模型的影响程度。

线性模型变种模型:在损失函数后面 + 正则项

  • 损失函数 + L1 范数 -> Lasso 回归
  • 损失函数 + L2 范数 -> 岭回归

以下关于 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 全栈体系【四阶】(七)_第6张图片

八、模型保存与加载

可以使用 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()

执行结果和训练模型预测结果一样。

九、总结

1. 什么是线性模型

线性模型是自然界最简单的模型之一,反映自变量、因变量之间的等比例增长关系。

2. 什么时候使用线性回归

线性模型只能用于满足线性分布规律的数据中。

3. 如何实现线性回归

给定一组样本,给定初始的 w 和 b,通过梯度下降法求最优的 w 和 b。

十、补充知识

1. R2 系数详细计算

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=1n(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=1n(yiyi^)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=1n(yiyˉ)2

  • 即平均值与真实值的误差,反映与数学期望的偏离程度.
R2_score 计算公式

R2_score,即决定系数,反映因变量的全部变异能通过回归关系被自变量解释的比例。计算公式:

R 2 = 1 − S S E S S T R^2=1-\frac{SSE}{SST} R2=1SSTSSE

即:

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=1i=1n(yiyˉ)2i=1n(yiy^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=1i(yiy^)2/ni(yiyi)2/n=1VarRMSE

分子就变成了常用的评价指标均方误差 MSE,分母就变成了方差,对于 R 2 R^2 R2可以通俗地理解为使用均值作为误差基准,看预测误差是否大于或者小于均值基准误差。

R2_score = 1,样本中预测值和真实值完全相等,没有任何误差,表示回归分析中自变量对因变量的解释越好。

R2_score = 0,此时分子等于分母,样本的每项预测值都等于均值。

2. 线性回归损失函数求导过程

线性函数定义为:

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(yy)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(y22y(w0+w1x1)+(w0+w1x1)2)=21(y22yw02yw1x1+w02+2w0w1x1+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') w0loss=21(02y0+2w0+2w1x1+0)=21(2y+2w0+2w1x1)=212(y+(w0+w1x1))=(y+y)=(yy)

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') w1loss=21(002yx1+0+2w0x1+2w1x12)=21(2yx1+2w0x1+2w1x12)=212x1(y+w0+w1x1)=x1(y+y)=x1(yy)

推导完毕。

你可能感兴趣的:(Python全栈体系,python,开发语言)