学习笔记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)
下面我们要使用多项式回归过拟合一个样本,生成的曲线非常弯曲、陡峭。前面的参数会非常大,正则化要完成的,就是要限制这些系数的大小。
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()
在拟合完之后,就是非常明显的过拟合的效果,即目标函数为了尽可能地去拟合数据,减小模型和样本的误差,使得曲线变得很陡峭,在数学上就表示为线性方程前面的系数很大。
那么模型正则化如何解决上述问题呢?
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,表示只有少数特征对这个模型有贡献,绝大部分特征是没有贡献的,即使去掉对模型也没有什么影响,此时我们就可以只关注系数是非零值的特征。
这相当于对模型进行了一次特征选择,只留下一些比较重要的特征,提高模型的泛化能力,降低过拟合的可能。
假设在二维参数空间中,损失函数的等高线如下图所示
此时,L1正则化为,对应的等高线是一个菱形(我们可以画出多个这样的菱形):
首先来看一下不加L1正则的情况:我们使用梯度下降法去优化损失函数,随机选择一点,沿着梯度方向下降,得到一个近似的最优解M:
下面加上L1正则,情况则会有所不同。
两点在同一等高线上,即P与Q两个点的损失函数这一项上是相同的。但是的距离要大于距离:
可以得到经验损失函数(损失函数+正则项):
因为点的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)
可以看到,在加入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)
我们发现曲线更加的平滑了,相应的均方误差也变得更小了,说明结果更优了。那么如果继续放大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)
很明显,我们正则化得有些过了,变成一条直线了。因此,我们需要找到合适的alpha
系数,使正则化效果最好。
3 L2正则化
3.1 岭回归思路
除了如L1正则化一般,将参数累加()以外,很自然地联想到,我们也可以用平方和来做正则项。
即将为:
使尽可能小。
同样地,要使尽可能小,就要综合考虑两项,要考虑让的平方和尽可能小。
该模型正则化的方式被称为“岭回归”
3.2 L2正则防止过拟合
同样地,在二维参数空间中,L2正则项为:。即L2等高线是一个个的同心圆。
二维平面下L2正则化的函数图形是个圆,与方形相比,被磨去了棱角。因此等高线与损失函数相交于点时,都不为0,但是仍然比较靠近坐标轴。因此这也就是我们说的,L2范数能让解比较小(靠近0),但是比较平滑(不等于0)且不具有稀疏性。
此时和w2都不为0. 所以L2范式得到的不是稀疏解
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)
我们可以看到输出的均方误差在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)
如果我们继续增大系数的话,就会正则化得有些过了,其均方误差也会变小。
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)
可见LASSO回归和岭回归类似,取值过大反而会导致误差增加,拟合曲线为直线。但是LASSO更趋向于使得一部分的值为0,拟合曲线更趋向于直线,所以可以作为特征选择来使用,去除一些模型认为不需要的特征。 LASSO可能会去除掉正确的特征,从而降低准确度,但如果特征特别大,使用LASSO可以使模型变小。
总结
L1正则化就是在loss function后边所加正则项为L1范数,加上L1范数容易得到稀疏解(0比较多)。
L2正则化就是loss function后边所加正则项为L2范数的平方,加上L2正则相比于L1正则来说,得到的解比较平滑(不是稀疏),但是同样能够保证解中接近于0(但不是等于0,所以相对平滑)的维度比较多,降低模型的复杂度。