赞
踩
之前已经学习了简单线性回归,其输入特征值有一维,即
y
=
θ
0
+
θ
1
x
1
;
y=\theta_0+\theta_1x_1;
y=θ0+θ1x1;当推广到多维特征,即多元线性回归:
y
=
θ
0
+
θ
1
x
1
+
θ
2
x
2
+
…
+
θ
n
x
n
。
y=\theta_0+\theta_1x_1+\theta_2x_2+…+\theta_nx_n。
y=θ0+θ1x1+θ2x2+…+θnxn。
但在使用线性回归的时候必须要有一个很强的假设条件:数据存在线性关系。但在现实中更多的数据之前具有非线性关系。因此我们对线性回归法进行改进,使用多项式回归法可以对非线性数据进行处理。
研究一个因变量与一个或多个自变量间多项式的回归分析方法,称为多项式回归(Polynomial Regression)。多项式回归是线性回归模型的一种,其回归函数关于回归系数是线性的。其中自变量x和因变量y之间的关系被建模为n次多项式。
如果自变量只有一个时,称为一元多项式回归;如果自变量有多个时,称为多元多项式回归。在一元回归分析中,如果变量y与自变量x的关系为非线性的,但是又找不到适当的函数曲线来拟合,则可以采用一元多项式回归。
由于任一函数都可以用多项式逼近,因此多项式回归有着广泛应用。
举个例子:
对于下面这种非线性关系的数据,使用直线拟合会显得很牵强。
如果画一个曲线,拟合度更好一些。
如果只有一个特征,则可以写出表达式为:
y
=
a
x
2
+
b
x
+
c
y=ax^2+bx+c
y=ax2+bx+c
首先进行数据准备,创建一个一元二次方程,并增加一些噪音:
如果直接用线性回归进行数据拟合,则可以得到:
很明显,拟合效果并不好。那需要如何解决?
多项式回归的思路是:添加一个特征,即对于X中的每个数据进行平方。
第
一
个
系
数
是
x
前
的
系
数
,
第
二
个
系
数
是
x
2
前
的
系
数
。
并
输
出
其
截
距
。
第一个系数是x前的系数,第二个系数是x^2前的系数。并输出其截距。
第一个系数是x前的系数,第二个系数是x2前的系数。并输出其截距。
lin_reg2.coef_
lin_reg2.intercept_
--------
array([0.98825773, 1.05762488])
2.5290699028243284
对于上一小节生成的虚拟数据,使用sklearn中的多项式回归。多项式回归可以看作是对数据进行预处理,给数据添加新的特征,所以调用的库在preprocessing中:
from sklearn.preprocessing import PolynomialFeatures # 这个degree表示我们使用多少次幂的多项式 poly = PolynomialFeatures(degree=2) poly.fit(X) X2 = poly.transform(X) X2.shape # 输出:(100, 3) # 查看数据 X2[:5,:] -------- array([[ 1. , 2.99374892, 8.96253258], [ 1. , -1.02586236, 1.05239358], [ 1. , 2.03379074, 4.13630478], [ 1. , 2.24787245, 5.05293053], [ 1. , -2.43115954, 5.91053672]])
X2的结果第一列常数项,可以看作是加入了一列x的0次方;第二列一次项系数(原来的样本X特征),第三列二次项系数(X平方前的特征)。
特征准备好之后进行训练:
from sklearn.linear_model import LinearRegression
reg = LinearRegression()
reg.fit(X2, y)
y_predict = reg.predict(X2)
plt.scatter(x, y)
plt.plot(np.sort(x), y_predict2[np.argsort(x)], color='r')
plt.show()
得到的系数和截距与上一小节中的结果是相同的:
lin_reg2.coef_
lin_reg2.intercept_
--------
array([0. , 0.98825773, 1.05762488])
2.5290699028243284
之前使用的都是1维数据,如果使用2维3维甚至更高维呢?
import numpy as np X = np.arange(1, 11).reshape(5, 2) #arange()一个参数时,参数值为终点,起点取默认值0,步长取默认值1。 #两个参数时,第一个参数为起点,第二个参数为终点,步长取默认值1。 #三个参数时,第一个参数为起点,第二个参数为终点,第三个参数为步长。其中步长支持小数。 # 5行2列 [1-10]的10个元素的矩阵 X.shape -------- (5, 2) --------- from sklearn.preprocessing import PolynomialFeatures poly = PolynomialFeatures() #如果有a,b两个特征,那么它的2次多项式为(1,a,b,a^2,ab, b^2),这个多项式的形式是使用poly的效果。 poly.fit(X) # 将X转换成最多包含X二次幂的数据集 X2 = poly.transform(X) # 5行6列 X2.shape X2 ---------- array([[ 1., 1., 2., 1., 2., 4.], [ 1., 3., 4., 9., 12., 16.], [ 1., 5., 6., 25., 30., 36.], [ 1., 7., 8., 49., 56., 64.], [ 1., 9., 10., 81., 90., 100.]])
可以看出当数据维度是2维的,经过多项式预处理生成了6维数据。
第一列很显然是0次项系数;第二列和第三列就是原本的X矩阵;第四列是第二列(原X的第一列)平方的结果;第五列是第二、三两列相乘的结果;第六列是第三列(原X的第二列)平方的结果。
由此可以猜想一下如果数据是3维的时候是什么情况:
poly = PolynomialFeatures(degree=3) poly.fit(X) X3 = poly.transform(X) X3.shape X3 ------------- array([[ 1., 1., 2., 1., 2., 4., 1., 2., 4., 8.], [ 1., 3., 4., 9., 12., 16., 27., 36., 48., 64.], [ 1., 5., 6., 25., 30., 36., 125., 150., 180., 216.], [ 1., 7., 8., 49., 56., 64., 343., 392., 448., 512.], [ 1., 9., 10., 81., 90., 100., 729., 810., 900., 1000.]])
过PolynomiaFeatures,将所有的可能组合,升维的方式呈指数型增长。这也会带来一定的问题。
在具体编程实践时,可以使用sklearn中的pipeline对操作进行整合。
首先我们回顾多项式回归的过程:
Pipeline就是将这些步骤都放在一起。参数传入一个列表,列表中的每个元素是管道中的一个步骤。每个元素是一个元组,元组的第一个元素是名字(字符串),第二个元素是实例化。
from sklearn.pipeline import Pipeline from sklearn.preprocessing import PolynomialFeatures from sklearn.linear_model import LinearRegression from sklearn.preprocessing import StandardScaler poly_reg = Pipeline([ ('poly', PolynomialFeatures(degree=2)), ('std_scale', StandardScaler()), ('lin_reg', LinearRegression()) ]) poly_reg.fit(X, y) y_predict = poly_reg.predict(X) plt.scatter(x, y) plt.plot(np.sort(x), y_predict[np.argsort(x)], color='r') plt.show()
其实多项式回归在算法并没有什么新的地方,完全是使用线性回归的思路,关键在于为数据添加新的特征,而这些新的特征是原有的特征的多项式组合,采用这样的方式就能解决非线性问题。
这样的思路跟PCA这种降维思想刚好相反,而多项式回归则是升维,添加了新的特征之后,使得更好地拟合高维数据。
在机器学习中,过拟合和欠拟合都会使训练好的机器学习模型在真实的数据中出现错误。我们可以将错误分为**偏差(Bias)和方差(Variance)**两类。下面就来看看偏差和方差的定义、产生原因以及二者之间如何权衡。
偏差和方差的定义如下:
**偏差(bias):偏差衡量了模型的预测值与实际值之间的偏离关系。**例如某模型的准确度为96%,则说明是低偏差;反之,如果准确度只有70%,则说明是高偏差。
**方差(variance):**方差描述的是训练数据在不同迭代阶段的训练模型中,**预测值的变化波动情况(或称之为离散情况)。**从数学角度看,可以理解为每个预测值与预测均值差的平方和的再求平均数。通常在模型训练中,初始阶段模型复杂度不高,为低方差;随着训练量加大,模型逐步拟合训练数据,复杂度开始变高,此时方差会逐渐变高。
也可以通过下面的图片直观理解偏差和方差:
以上四种情况
低方差 | 高方差 | |
---|---|---|
低偏差 | 这是训练的理想模型,此时蓝色点集基本落在靶心范围内,且数据离散程度小,基本在靶心范围内; | 这是深度学习面临的最大问题,过拟合了。也就是模型太贴合训练数据了,导致其泛化(或通用)能力差,若遇到测试集,则准确度下降的厉害; |
高偏差 | 这往往是训练的初始阶段; | 这是训练最糟糕的情况,准确度差,数据的离散程度也差。 |
模型误差 = 偏差 + 方差 + 不可避免的误差(噪音)。一般来说,随着模型复杂度的增加,方差会逐渐增大,偏差会逐渐减小,见下图:
一个模型有偏差,主要的原因可能是对问题本身的假设是不正确的,或者欠拟合。如:针对非线性的问题使用线性回归;或者采用的特征和问题完全没有关系,如用学生姓名预测考试成绩,就会导致高偏差。
**方差表现为数据的一点点扰动就会较大地影响模型。**即模型没有完全学习到问题的本质,而学习到很多噪音。通常原因可能是使用的模型太复杂,如:使用高阶多项式回归,也就是过拟合。(学了不该学的)
有一些算法天生就是高方差的算法,如kNN算法。非参数学习算法通常都是高方差,因为不对数据进行任何假设。
有一些算法天生就是高偏差算法,如线性回归。参数学习算法通常都是高偏差算法,因为对数据有迹象。
偏差和方差通常是矛盾的。降低偏差,会提高方差;降低方差,会提高偏差。
这就需要在偏差和方差之间保持一个平衡。
以多项式回归模型为例,我们可以选择不同的多项式的次数,来观察多项式次数对模型偏差&方差的影响:
多项式次数 | 模型复杂度 | 方差 | 偏差 | 过/欠拟合 |
---|---|---|---|---|
低 | 低 | 低 | 高 | 欠拟合 |
中 | 中 | 中 | 中 | 适度 |
高 | 高 | 高 | 低 | 过拟合 |
下面是多项式次数对训练误差/测试误差的影响:
多项式次数 | 模型复杂度 | 训练误差 | 测试误差 |
---|---|---|---|
低 | 低 | 高 | 高 |
中 | 中 | 中 | 低 |
高 | 高 | 低 | 高 |
从上表可以看出,并不是多项式次数越多,测试误差由高变低,再变高。因此多项式的次数应该选择在一个合理的次数。
关于解决方差和偏差的问题中:
我们要知道偏差和方差是无法完全避免的,只能尽量减少其影响。
其实在机器学习领域,主要的挑战来自方差。处理高方差的手段有:
偏差衡量了模型的预测值与实际值之间的偏离关系,主要的原因可能是对问题本身的假设是不正确的,或者欠拟合。
方差描述的是模型预测值的变化波动情况(或称之为离散情况),模型没有完全学习到问题的本质,通常原因可能是使用的模型太复杂,过拟合。
参数或者线性的算法一般是高偏差低方差;非参数或者非线性的算法一般是低偏差高方差。所以我们需要调整参数来去衡量方差和偏差的关系。
模型误差 = 偏差 + 方差 + 不可避免的误差,且在机器学习领域中最重要就是解决过拟合的问题,也就是降低模型的方差,有如下方法:
其实还有一个降低方差的重要方法:模型正则化。本文从理论及代码两个方面对L1正则、L2正则进行了介绍,其背后的原理以及实际的使用方法。
模型正则化(Regularization),对学习算法的修改,限制参数的大小,减少泛化误差而不是训练误差。我们在构造机器学习模型时,最终目的是让模型在面对新数据的时候,可以有很好的表现。当你用比较复杂的模型比如神经网络,去拟合数据时,很容易出现过拟合现象(训练集表现很好,测试集表现较差),这会导致模型的泛化能力下降,这时候,我们就需要使用正则化,降低模型的复杂度。
正则化的策略包括:约束和惩罚被设计为编码特定类型的先验知识 偏好简单模型 其他形式的正则化,如:集成的方法,即结合多个假说解释训练数据。
在实践中,过于复杂的模型不一定包含数据的真实的生成过程,甚至也不包括近似过程,这意味着控制模型的复杂程度不是一个很好的方法,或者说不能很好的找到合适的模型的方法。实践中发现的最好的拟合模型通常是一个适当正则化的大型模型。
数据准备
import numpy as np
import matplotlib.pyplot as plt
x = np.random.uniform(-3, 3, size=100)
X = x.reshape(-1, 1)
y = 0.5 + x**2 + x + 2 + np.random.normal(0, 1, size=100)
plt.scatter(x, y)
下面我们要使用多项式回归过拟合一个样本,生成的曲线非常弯曲、陡峭。前面的参数会非常大,正则化要完成的,就是要限制这些系数的大小。
from sklearn.linear_model import LinearRegression from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.preprocessing import PolynomialFeatures from sklearn.metrics import mean_squared_error from sklearn.model_selection import train_test_split lin_reg = LinearRegression() def PolynomialRegression(degree): return Pipeline([ ('poly',PolynomialFeatures(degree)), ('std_scaler',StandardScaler()), ('lin_reg',lin_reg) ]) np.random.seed(666) X_train, X_test, y_train, y_test = train_test_split(X,y) poly30_reg = PolynomialRegression(degree=30) poly30_reg.fit(X_train,y_train) y30_predict = poly30_reg.predict(X_test) mean_squared_error(y_test,y30_predict) X_plot = np.linspace(-3,3,100).reshape(100,1) y_plot = poly30_reg.predict(X_plot) plt.scatter(X,y) plt.plot(X_plot[:,0],y_plot,color='r') plt.axis([-3,3,0,10]) plt.show()
在拟合完之后,就是非常明显的过拟合的效果,即目标函数为了尽可能地去拟合数据,减小模型和样本的误差,使得曲线变得很陡峭,在数学上就表示为线性方程前面的系数很大。
那么模型正则化如何解决上述问题呢?
所谓的L1正则化,就是在目标函数中加了L1范数这一项。使用L1正则化的模型叫做LASSO回归。
线性回归问题去怎样求最优解,其目标相当于:
使
∑
i
=
1
m
(
y
(
i
)
−
−
θ
0
−
θ
1
X
1
i
−
θ
2
X
2
i
…
−
θ
n
X
n
i
)
2
尽
可
能
小
。
使\sum_{i=1}^m(y^{(i)-}-θ_0-θ_1X_1^i-θ_2X_2^i…-θ_nX_n^i)^2尽可能小。
使i=1∑m(y(i)−−θ0−θ1X1i−θ2X2i…−θnXni)2尽可能小。
这
也
就
是
等
同
于
求
原
始
数
据
y
和
使
用
参
数
θ
预
测
的
y
^
的
均
方
误
差
尽
可
能
的
小
:
这也就是等同于求原始数据y和使用参数θ预测的\hat y的均方误差尽可能的小:
这也就是等同于求原始数据y和使用参数θ预测的y^的均方误差尽可能的小:
使
J
(
θ
)
=
M
S
E
(
y
,
y
^
;
θ
)
尽
可
能
小
。
使J(θ)=MSE(y,\hat y;θ)尽可能小。
使J(θ)=MSE(y,y^;θ)尽可能小。
如果模型过拟合的话,参数就会非常大。为了限制参数,我们改变损失函数,加入模型正则化,即将其改为:
J
(
θ
)
=
M
S
E
(
y
,
y
^
;
θ
)
+
α
∑
i
=
1
n
∣
θ
i
∣
尽
可
能
小
。
J(θ)=MSE(y,\hat y;θ)+α\sum_{i=1}^n|θ_i|尽可能小。
J(θ)=MSE(y,y^;θ)+αi=1∑n∣θi∣尽可能小。
这样的话,要使J(θ)尽可能小,就要综合考虑两项,对于第二项来说,是θ_i的绝对值,因此我们要考虑θ_i让尽可能小。这样参数θ_i就限制住了,曲线也就没有那么陡峭了,这就是一种模型正则化的基本原理。
且该模型正则化的方式被称为“LASSO回归”(Least Absolute Shrinkage and Selection Operator Regression)
在这里有几个细节需要注意:
我们说,LASSO回归的全称是:Least Absolute Shrinkage and Selection Operator Regression.
这里面有一个特征选择的部分,或者说L1正则化可以使得参数稀疏化,即得到的参数是一个稀疏矩阵。
所谓稀疏性,说白了就是模型的很多参数是0。通常机器学习中特征数量很多,例如文本处理时,如果将一个词组(term)作为一个特征,那么特征数量会达到上万个(bigram)。在预测或分类时,那么多特征显然难以选择,但是如果代入这些特征得到的模型是一个稀疏模型,很多参数是0,表示只有少数特征对这个模型有贡献,绝大部分特征是没有贡献的,即使去掉对模型也没有什么影响,此时我们就可以只关注系数是非零值的特征。
这相当于对模型进行了一次特征选择,只留下一些比较重要的特征,提高模型的泛化能力,降低过拟合的可能。
假设在二维参数空间中,损失函数的等高线如下图所示:
此时,L1正则化为
∣
θ
1
∣
+
∣
θ
2
∣
|\theta_1|+|\theta_2|
∣θ1∣+∣θ2∣,对应的等高线是一个菱形(我们可以画出多个这样的菱形):
首先来看一下不加L1正则的情况:我们使用梯度下降法去优化损失函数,随机选择一点,沿着梯度方向下降,得到一个近似的最优解M:
下面加上L1正则,情况则会有所不同。
P、Q两点在同一等高线上,即P与Q两个点的损失函数这一项上是相同的。但是OP的距离要大于OQ距离:
可以得到经验损失函数(损失函数+正则项):
f
P
(
θ
1
,
θ
2
)
+
a
>
f
Q
(
θ
1
,
θ
2
)
+
b
f_P(\theta_1,\theta_2)+a>f_Q(\theta_1,\theta_2)+b
fP(θ1,θ2)+a>fQ(θ1,θ2)+b
因为点Q的L1范数小于点P的L1范数,因此我们更倾向于选择点Q,而不是点P。
而如果选择点Q,在直角的顶点上,对应的参数θ_1=0,这就体现了稀疏性。因此L1正则化会产生系数模型,好处是应用的特征比较小,模型更简单,运算更快。
由此可见:加入L1正则项相当于倾向将参数向离原点近的方向去压缩。直观上来说,就是加上正则项,参数空间会被缩小,意味着模型的复杂度会变小。
我们利用第一节得到的数据来对比使用LASSO回归进行正则化的方式。
在sklearn中,包含了一个方法:Lasso。下面我们以Pipeline的方式去封装一个LASSO回归的过程:
from sklearn.linear_model import Lasso
def LassoRegression(degree,alpha):
return Pipeline([
('poly',PolynomialFeatures(degree=degree)),
('std_scaler',StandardScaler()),
('lasso_reg',Lasso(alpha=alpha))
])
在封装好了一个LASSO回归函数后,传入dregree参数和alpha参数,就可以验证LASSO回归的效果了:
lasso_reg1 = LassoRegression(30,0.0001)
lasso_reg1.fit(X_train,y_train)
y1_predict=lasso_reg1.predict(X_test)
mean_squared_error(y_test,y1_predict)
plot_model(lasso_reg1)
可以看到,在加入L1正则后,均方误差比原来小,且曲线光滑了很多,效果比较好。
我们保持degree参数不变,调整alpha参数,让其变大,也就是将L1正则项的比重放大,即让参数θ变小:
lasso_reg2 = LassoRegression(30,0.1)
lasso_reg2.fit(X_train,y_train)
y2_predict=lasso_reg2.predict(X_test)
mean_squared_error(y_test,y2_predict)
plot_model(lasso_reg2)
我们发现曲线更加的平滑了,相应的均方误差也变得更小了,说明结果更优了。那么如果继续放大alpha系数呢?
lasso_reg3 = LassoRegression(30,10)
lasso_reg3.fit(X_train,y_train)
y3_predict=lasso_reg3.predict(X_test)
mean_squared_error(y_test,y3_predict)
plot_model(lasso_reg3)
很明显,我们正则化得有些过了,变成一条直线了。因此,我们需要找到合适的alpha系数,使正则化效果最好。
除了如L1正则化一般,将参数累加()以外,很自然地联想到,我们也可以用平方和来做正则项。
即将为:
使
J
(
θ
)
=
M
S
E
(
y
,
y
^
;
θ
)
+
α
∑
(
i
=
1
)
n
θ
i
2
尽
可
能
小
。
使J(θ)=MSE(y,\hat y;θ)+α\sum_{(i=1)}^nθ_i^2尽可能小。
使J(θ)=MSE(y,y^;θ)+α(i=1)∑nθi2尽可能小。
同样地,要使J(θ)尽可能小,就要综合考虑两项,要考虑让θ_i的平方和尽可能小。
该模型正则化的方式被称为“岭回归”。
同样地,在二维参数空间中,L2正则项为:。即L2等高线是一个个的同心圆。
二维平面下L2正则化的函数图形是个圆,与方形相比,被磨去了棱角。因此等高线与损失函数相交于P,Q点时,θ_1,θ_2都不为0,但是仍然比较靠近坐标轴,但得到的不是稀疏解。因此这也就是我们说的,L2范数能让解比较小(靠近0),但是比较平滑(不等于0)且不具有稀疏性。
我们利用第一节得到的数据来对比使用岭回归进行正则化的方式。
在sklearn中,包含了一个岭回归的方法:Ridge。下面我们以Pipeline的方式去封装一个岭回归的过程:
from sklearn.linear_model import Ridge
from sklearn.pipeline import Pipeline
# 需要传入一个多项式项数的参数degree以及一个alpha值
def ridgeregression(degree,alpha):
return Pipeline([
("poly", PolynomialFeatures(degree=degree)),
("standard", StandardScaler()),
("ridge_reg", Ridge(alpha=alpha)) #alpha值就是正则化那一项的系数
])
在封装好了一个岭回归函数后,就可以验证岭回归的效果了:
ridge1_reg = ridgeregression(degree=30,alpha=0.0001)
ridge1_reg.fit(X_train,y_train)
y1_predict = ridge1_reg.predict(X_test)
mean_squared_error(y_test,y1_predict)
#1.7863216371703603
plot_model(ridge1_reg)
我们可以看到输出的均方误差在1左右,比以前的2点多变好了。且通过画图可以看到,曲线变平滑了。
如果我们调整系数,将其变大,意味着对参数的约束又变强了,曲线会更加光滑:
ridge2_reg = ridgeregression(degree=30,alpha=1)
ridge2_reg.fit(X_train,y_train)
y2_predict = ridge2_reg.predict(X_test)
mean_squared_error(y_test,y2_predict)
# 输出:1.2885362833816492
plot_model(ridge2_reg)
如果我们继续增大α系数的话,就会正则化得有些过了,其均方误差也会变小。
ridge3_reg = ridgeregression(degree=30,alpha=100)
ridge3_reg.fit(X_train,y_train)
y3_predict = ridge3_reg.predict(X_test)
mean_squared_error(y_test,y3_predict)
# 输出:3.117263856050919
plot_model(ridge3_reg)
可见LASSO回归和岭回归类似,α取值过大反而会导致误差增加,拟合曲线为直线。但是LASSO更趋向于使得一部分的θ值为0,拟合曲线更趋向于直线,所以可以作为特征选择来使用,去除一些模型认为不需要的特征。LASSO可能会去除掉正确的特征,从而降低准确度,但如果特征特别大,使用LASSO可以使模型变小。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。