此前已经介绍了线性模型LinearRegression的原理和具体实现。在代码验证阶段,使用wave数据集得到的结果是:训练集得分为0.67,测试集得分为0.66。此时,两者得分是十分接近的,但是距离满分1还有很大差距,这是欠拟合的表现,解决该问题的一种方案是增加更多有效的特征。
除了欠拟合,还有一种常见的情况:过拟合。先看如下一个例子:将LinearRegression应用到bostons数据集上
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
def lr_by_sklearn(X, y):
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
lr = LinearRegression().fit(X_train, y_train)
print('lr: training set score: {:.2f}'.format(lr.score(X_train, y_train)))
print('lr: test set score: {:.2f}'.format(lr.score(X_test, y_test)))
if __name__ == '__main__':
X_arr, y_arr = bostons()
lr_by_sklearn(X_arr, y_arr)
运行后,得到结果为
lr: training set score: 0.95
lr: test set score: 0.61
从结果上可以看出,模型在训练集上表现很好,但是在测试集上表现较差,这就是过拟合。但事实上,我们训练模型的目标就是希望模型能在测试集上表现优秀,从而保证模型的泛化能力,因此过拟合并不是我们希望看到的结果。
为了解决过拟合问题,我们先看一下出现过拟合时,线性模型得到的数据特征。经分析发现,线性模型的权重系数往往非常大。这是因为过拟合时,线性模型为了很好地拟合每一个数据点,拟合函数的波动往往会很大,即在某些很小的区间里,函数值的变化很剧烈。由于自变量本身可大可小,所以权重系数就必须足够大。
还是用上面的实例,我们输出权重系数的最大和最小值,作为对比,将X和y的最大和最小值也输出。可以发现,X和y的范围都比较小,但是权重系数却高出了2个数量级。
In[3]: [max(lr.coef_), min(lr.coef_)]
Out[3]: [2980.7814393954595, -2239.8694355606854]
In[4]: [X.min(), X.max()]
Out[4]: [0.0, 1.0]
In[5]: [y.min(), y.max()]
Out[5]: [5.0, 50.0]
正则化(regularization)是在模型的目标函数中增加显式约束,限制权重系数的大小,从而在一定程度上减少过拟合。最常见的有两种:岭回归和Lasso回归。
岭回归是一种在线性模型基础上添加L2范数的正则化方式
J = ∑ i = 1 n ( y i − ( w i x i + b i ) ) 2 + λ ∑ i = 1 n w i 2 J=\sum_{i=1}^n(y_i-(w_ix_i+b_i))^2+\lambda\sum_{i=1}^nw_i^2 J=i=1∑n(yi−(wixi+bi))2+λi=1∑nwi2
将上式做一下归并
J = ∑ i = 1 n [ ( ( y i − b i ) − w i x i ) 2 + λ w i 2 ] J=\sum_{i=1}^n[((y_i-b_i)-w_ix_i)^2+\lambda w_i^2] J=i=1∑n[((yi−bi)−wixi)2+λwi2]
求和公式中的"[]"部分定义为一个新的变量 f i f_i fi,并将 b i b_i bi合并到 y i y_i yi中
f = ( y − w x ) 2 + λ w 2 f=(y-wx)^2+\lambda w^2 f=(y−wx)2+λw2
上式中,为了方便书写,去掉了下标 i i i。为了得到最佳的 w w w,对 f f f求一阶倒数,并令其等于0
f ′ = − 2 ( y − w x ) x + 2 λ w = 0 f'=-2(y-wx)x+2\lambda w=0 f′=−2(y−wx)x+2λw=0
最终得到
w = x y x 2 + λ w=\frac{xy}{x^2+\lambda} w=x2+λxy
显然, λ \lambda λ的引入,可以降低 w w w的值。 λ \lambda λ越大, w w w越小,过拟合风险越低,但是也会因此带来模型偏差的增大。此处,模型偏差的含义为真值与预测值之间的差值绝对值
∣ y − w ∗ x ∣ = ∣ y − x 2 y x 2 + λ ∣ |y-w*x|=|y-\frac{x^2y}{x^2+\lambda}| ∣y−w∗x∣=∣y−x2+λx2y∣当 λ \lambda λ为0时,偏差也为0; λ \lambda λ越大,偏差越大。即, λ \lambda λ在其中起到了调和模型偏差和过拟合风险的作用。
Lasso回归则是在线性模型的基础上添加了L1范数
J = ∑ i = 1 n ( y i − ( w i x i + b i ) ) 2 + λ ∑ i = 1 n ∣ w i ∣ J=\sum_{i=1}^n(y_i-(w_ix_i+b_i))^2+\lambda\sum_{i=1}^n \mid{w_i}\mid J=i=1∑n(yi−(wixi+bi))2+λi=1∑n∣wi∣
关于上式最优解的推导,有些难,此处直接引用文章,并给出结果:
定义 m j = ∑ i = 1 n x i j ( y i − ∑ k ≠ j w k x i k ) m_j=\sum_{i=1}^nx_{ij}(y_i-\sum_{k \neq j}w_kx_{ik}) mj=∑i=1nxij(yi−∑k=jwkxik), n j = ∑ i = 1 x i j 2 n_j=\sum_{i=1}x_{ij}^2 nj=∑i=1xij2,那么 w j w_j wj的值为
w j = { ( m j − λ 2 ) / n j , if m j > λ 2 0 , if m j ∈ [ − λ 2 , λ 2 ] ( m j + λ 2 ) / n j , if m j < λ 2 w_j=\left\{ \begin{aligned} (m_j-\frac{\lambda}{2})/n_j,\ \ \text{if} \ \ m_j > \frac{\lambda}{2}\\ 0, \ \ \text{if} \ \ m_j \in [-\frac{\lambda}{2}, \frac{\lambda}{2}]\\ (m_j+\frac{\lambda}{2})/n_j,\ \ \text{if} \ \ m_j < \frac{\lambda}{2} \end{aligned} \right. wj=⎩ ⎨ ⎧(mj−2λ)/nj, if mj>2λ0, if mj∈[−2λ,2λ](mj+2λ)/nj, if mj<2λ
此处我们不必去细致关注每个变量的含义,更有意思的方面在于,有些 w w w直接变为0了,相当于模型筛选了一批不重要的特征,将其权重系数设置为0。这是岭回归和Lasso之间的很大差异之一:岭回归倾向于让 w w w值变小,而Lasso回归将部分 w w w设置为0。
以下代码评估了线性回归、岭回归和Lasso回归三种模型在bostons数据集上的表现,同时针对核心指标,进行了输出和对比:
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression, Ridge, Lasso, RidgeCV, LassoCV
import numpy as np
def lr_by_sklearn(X, y):
# 数据集拆分
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
# 线性模型
lr = LinearRegression().fit(X_train, y_train)
print('lr: training set score: {:.2f}'.format(lr.score(X_train, y_train)))
print('lr: test set score: {:.2f}'.format(lr.score(X_test, y_test)))
print('lr: min_coef: {:.2f}, max_coef: {:.2f}'.format(min(lr.coef_), max(lr.coef_)))
print('\n=================================\n')
# 构造不同的lambda值,RidgeCV交叉验证得到最佳lambda
Lambdas = np.logspace(-5, 2, 20)
rdCV = RidgeCV(alphas=Lambdas, cv=5)
rdCV.fit(X_train, y_train)
print('Ridge_best_lambda: {}'.format(rdCV.alpha_))
# 输出最佳Ridge模型指标
best_rd = Ridge(alpha=rdCV.alpha_).fit(X_train, y_train)
print('rd: training set score: {:.2f}'.format(best_rd.score(X_train, y_train)))
print('rd: test set score: {:.2f}'.format(best_rd.score(X_test, y_test)))
print('rd: number of features used: {}'.format(np.sum(best_rd.coef_ != 0)))
print('rd: min_coef: {:.2f}, max_coef: {:.2f}'.format(min(best_rd.coef_), max(best_rd.coef_)))
print('\n=================================\n')
# 构造不同的lambda值,LassoCV交叉验证得到最佳lambda
Lambdas = np.logspace(-5, 2, 20)
lsCV = LassoCV(alphas=Lambdas, max_iter=1000000, cv=5)
lsCV.fit(X_train, y_train)
print('Lasso_best_lambda: {}'.format(lsCV.alpha_))
# 输出最佳Ridge模型指标
best_ls = Lasso(alpha=lsCV.alpha_, max_iter=1000000).fit(X_train, y_train)
print('ls: training set score: {:.2f}'.format(best_ls.score(X_train, y_train)))
print('ls: test set score: {:.2f}'.format(best_ls.score(X_test, y_test)))
print('ls: number of features used: {}'.format(np.sum(best_ls.coef_ != 0)))
print('ls: min_coef: {:.2f}, max_coef: {:.2f}'.format(min(best_ls.coef_), max(best_ls.coef_)))
if __name__ == '__main__':
X_arr, y_arr = bostons()
lr_by_sklearn(X_arr, y_arr)
运行结果如下。从结果上可以看出,由于引入了正则化,岭回归模型(rd)和Lasso回归模型(ls)在训练集上的分数略低于线性模型(lr),从0.95降低至0.93,但是在测试集上的分数却有了明显的提升,从0.61提升至0.76+;权重系数方面,rd和ls也从千量级降低至十量级;模型使用的特征数量方面,rd使用了所有的特征,共计104个,而ls只使用了其中的60个。
lr: training set score: 0.95
lr: test set score: 0.61
lr: min_coef: -2239.87, max_coef: 2980.78
=================================
Ridge_best_lambda: 0.04832930238571752
rd: training set score: 0.93
rd: test set score: 0.76
rd: number of features used: 104
rd: min_coef: -21.64, max_coef: 27.12
=================================
Lasso_best_lambda: 0.001623776739188721
ls: training set score: 0.93
ls: test set score: 0.77
ls: number of features used: 60
ls: min_coef: -29.34, max_coef: 47.46