学习笔记163—理解模型正则化:L1正则、L2正则(理论+代码)
理解模型正则化:L1正则、L2正则(理论+代码)
0 前言
我们已经知道了模型误差 = 偏差 + 方差 + 不可避免的误差
,且在机器学习领域中最重要就是解决过拟合的问题,也就是降低模型的方差。在上一篇文章《ML/DL重要基础概念:偏差和方差》已经列出了如下方法:
- 降低模型复杂度
- 减少数据维度;降噪
- 增加样本数
- 使用验证集
其实还有一个降低方差的重要方法:模型正则化。本文从理论及代码两个方面对L1正则、L2正则进行了介绍,帮助大家了解其背后的原理以及实际的使用方法。
1 学习模型正则化
1.1 什么是模型正则化
模型正则化(Regularization),对学习算法的修改,限制参数的大小,减少泛化误差而不是训练误差。我们在构造机器学习模型时,最终目的是让模型在面对新数据的时候,可以有很好的表现。当你用比较复杂的模型比如神经网络,去拟合数据时,很容易出现过拟合现象(训练集表现很好,测试集表现较差),这会导致模型的泛化能力下降,这时候,我们就需要使用正则化,降低模型的复杂度。
正则化的策略包括: 约束和惩罚被设计为编码特定类型的先验知识 偏好简单模型 其他形式的正则化,如:集成的方法,即结合多个假说解释训练数据
在实践中,过于复杂的模型不一定包含数据的真实的生成过程,甚至也不包括近似过程,这意味着控制模型的复杂程度不是一个很好的方法,或者说不能很好的找到合适的模型的方法。实践中发现的最好的拟合模型通常是一个适当正则化的大型模型。
1.2 模型的过拟合
准备一下数据
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)
![](https://pic2.zhimg.com/80/v2-a3bf806fc802d58cc1ff6fa2c67dd429_720w.jpg)
下面我们要使用多项式回归过拟合一个样本,生成的曲线非常弯曲、陡峭。前面的参数会非常大,正则化要完成的,就是要限制这些系数的大小。
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)
# 输出:2.8492121876294063
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()
![](https://pic1.zhimg.com/80/v2-6136ab1e31dc637a066c47c3d1bb4c18_720w.jpg)
在拟合完之后,就是非常明显的过拟合的效果,即目标函数为了尽可能地去拟合数据,减小模型和样本的误差,使得曲线变得很陡峭,在数学上就表示为线性方程前面的系数很大。
那么模型正则化如何解决上述问题呢?
2 L1正则化
所谓的L1正则化,就是在目标函数中加了L1范数这一项。使用L1正则化的模型建叫做LASSO回归。
2.1 LASSO回归思路
线性回归问题去怎样求最优解,其目标相当于:
使尽可能小。
这也就是等同于求原始数据和使用参数
预测的
的均方误差尽可能的小:
使尽可能小。
如果模型过拟合的话,参数就会非常大。为了限制参数
,我们改变损失函数,加入模型正则化,即将其改为:
使尽可能小。
这样的话,要使尽可能小,就要综合考虑两项,对于第二项来说,是
的绝对值,因此我们要考虑让
尽可能小。这样参数
就限制住了,曲线也就没有那么陡峭了,这就是一种模型正则化的基本原理。
且该模型正则化的方式被称为“LASSO回归”(Least Absolute Shrinkage and Selection Operator Regression)
在这里有几个细节需要注意:
,取值范围是1~n,即不包含
。这是因为,
不是任何一个参数的系数,是截距。反映到图形上就是
反映了曲线的高低,而不决定曲线每一部分的陡峭与缓和。所以模型正则化时不需要。
- 对于超参数
\alpha
系数,在模型正则化的新的损失函数中,要让每个都尽可能小的程度占整个优化损失函数程度的多少。即
\alpha
的大小表示优化的侧重。
2.2 L1正则化项与稀疏性
我们说,LASSO回归的全称是:Least Absolute Shrinkage and Selection Operator Regression.
这里面有一个特征选择的部分,或者说L1正则化可以使得参数稀疏化,即得到的参数是一个稀疏矩阵。
所谓稀疏性,说白了就是模型的很多参数是0。通常机器学习中特征数量很多,例如文本处理时,如果将一个词组(term)作为一个特征,那么特征数量会达到上万个(bigram)。在预测或分类时,那么多特征显然难以选择,但是如果代入这些特征得到的模型是一个稀疏模型,很多参数是0,表示只有少数特征对这个模型有贡献,绝大部分特征是没有贡献的,即使去掉对模型也没有什么影响,此时我们就可以只关注系数是非零值的特征。
这相当于对模型进行了一次特征选择,只留下一些比较重要的特征,提高模型的泛化能力,降低过拟合的可能。
假设在二维参数空间中,损失函数的等高线如下图所示
![](https://pic4.zhimg.com/80/v2-09eee18d9d506e1a2a235dfa7e759f17_720w.jpg)
此时,L1正则化为,对应的等高线是一个菱形(我们可以画出多个这样的菱形):
![](https://pic4.zhimg.com/80/v2-f429fab5fc951a8e0db129e1046209bf_720w.jpg)
首先来看一下不加L1正则的情况:我们使用梯度下降法去优化损失函数,随机选择一点,沿着梯度方向下降,得到一个近似的最优解M:
![](https://pic4.zhimg.com/80/v2-1fea4579e282360af3a6f98039ed2fa7_720w.jpg)
下面加上L1正则,情况则会有所不同。
两点在同一等高线上,即P与Q两个点的损失函数这一项上是相同的。但是
的距离要大于
距离:
![](https://pic1.zhimg.com/80/v2-ecb04202a4c4be914a448d218924e55c_720w.jpg)
可以得到经验损失函数(损失函数+正则项):
因为点的L1范数小于点
的L1范数,因此我们更倾向于选择点
,而不是点
。
而如果选择点,在直角的顶点上,对应的参数
,这就体现了稀疏性。因此L1正则化会产生系数模型,好处是应用的特征比较小,模型更简单,运算更快。
由此可见:加入L1正则项相当于倾向将参数向离原点近的方向去压缩。直观上来说,就是加上正则项,参数空间会被缩小,意味着模型的复杂度会变小。
2.3 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)
# 输出:0.8965099322966458
plot_model(lasso_reg1)
![](https://pic3.zhimg.com/80/v2-40b03d8eae0f616fc61249f3776c1476_720w.jpg)
可以看到,在加入L1正则后,均方误差比原来小,且曲线光滑了很多,效果比较好。
2.4 调参效果
我们保持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)
# 输出:0.7848672707093821
plot_model(lasso_reg2)
![](https://pic1.zhimg.com/80/v2-8b418afeb0d04128ba2d289abcdd9668_720w.jpg)
我们发现曲线更加的平滑了,相应的均方误差也变得更小了,说明结果更优了。那么如果继续放大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)
# 输出:12.007790775597146
plot_model(lasso_reg3)
![](https://pic3.zhimg.com/80/v2-08bf0cd41ca2ce397c30f9c87f03bb8e_720w.jpg)
很明显,我们正则化得有些过了,变成一条直线了。因此,我们需要找到合适的alpha
系数,使正则化效果最好。
3 L2正则化
3.1 岭回归思路
除了如L1正则化一般,将参数累加()以外,很自然地联想到,我们也可以用平方和来做正则项。
即将为:
使尽可能小。
同样地,要使尽可能小,就要综合考虑两项,要考虑让
的平方和尽可能小。
该模型正则化的方式被称为“岭回归”
3.2 L2正则防止过拟合
同样地,在二维参数空间中,L2正则项为:。即L2等高线是一个个的同心圆。
![](https://pic3.zhimg.com/80/v2-74140d755cd0bade0ec9014cfdb55cd6_720w.jpg)
二维平面下L2正则化的函数图形是个圆,与方形相比,被磨去了棱角。因此等高线与损失函数相交于点时,
都不为0,但是仍然比较靠近坐标轴。因此这也就是我们说的,L2范数能让解比较小(靠近0),但是比较平滑(不等于0)且不具有稀疏性。
此时和w2都不为0. 所以L2范式得到的不是稀疏解
![](https://pic4.zhimg.com/80/v2-ca5d7c52968e3fc0c55abdc27149a097_720w.jpg)
3.3 L2正则的使用及调参
我们利用第一节得到的数据来对比使用岭回归进行正则化的方式。
在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.00677921937119
plot_model(ridge1_reg)
![](https://pic4.zhimg.com/80/v2-fd48bd7fd4f03f42caf44b8e1851559b_720w.jpg)
我们可以看到输出的均方误差在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.0074844695164857
plot_model(ridge2_reg)
![](https://pic1.zhimg.com/80/v2-307ad211c64569ce7bdc43b29efd73f8_720w.jpg)
如果我们继续增大系数的话,就会正则化得有些过了,其均方误差也会变小。
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.4183903466684176
plot_model(ridge3_reg)
![](https://pic4.zhimg.com/80/v2-d892abfc459c751eda4de1d172d1634f_720w.jpg)
可见LASSO回归和岭回归类似,取值过大反而会导致误差增加,拟合曲线为直线。但是LASSO更趋向于使得一部分的
值为0,拟合曲线更趋向于直线,所以可以作为特征选择来使用,去除一些模型认为不需要的特征。 LASSO可能会去除掉正确的特征,从而降低准确度,但如果特征特别大,使用LASSO可以使模型变小。
总结
L1正则化就是在loss function后边所加正则项为L1范数,加上L1范数容易得到稀疏解(0比较多)。
L2正则化就是loss function后边所加正则项为L2范数的平方,加上L2正则相比于L1正则来说,得到的解比较平滑(不是稀疏),但是同样能够保证解中接近于0(但不是等于0,所以相对平滑)的维度比较多,降低模型的复杂度。