本系列所有的代码和数据都可以从陈强老师的个人主页上下载:Python数据程序
参考书目:陈强.机器学习及Python应用. 北京:高等教育出版社, 2021.
本系列基本不讲数学原理,只从代码角度去让读者们利用最简洁的Python代码实现机器学习方法。
惩罚回归听着很奇特,其实就是普通的最小二乘回归的损失函数后面加了一个惩罚项,从而可以让模型的系数变小,即系数收缩,降低模型的复杂性,防止过拟合。
根据惩罚项的不同,如果惩罚项为一范数的话,就是lasso回归,损失函数如下:
如果惩罚项是二范数的话,就是计量经济学里面的岭回归。
为什么惩罚回归可以收缩系数,看图:
正方形为1范数罚项,圆圈为2范数罚项。其中估计量为模型普通的最小二乘估计的系数,可以看到,最小化
时会让模型系数朝着原点的方向收缩,即系数整体范数变小,甚至有些变量的系数会成为0。因此惩罚回归还具有一定的变量筛选功能。当模型系数变小,变量变少后,模型复杂度自然降低,也不容易过拟合。
导入包,读取数据,展示前五行
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.model_selection import KFold
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import Ridge
from sklearn.linear_model import RidgeCV
from sklearn.linear_model import Lasso
from sklearn.linear_model import lasso_path
from sklearn.linear_model import LassoCV
from sklearn.linear_model import ElasticNet
from sklearn.linear_model import ElasticNetCV
from sklearn.linear_model import enet_path
prostate = pd.read_csv('prostate.csv')
prostate.head()
数据长这个样子
其中,响应变量y为lpsa,其他都是特征变量x
取出X和y,进行标准化
X_raw = prostate.iloc[:, :-1]
y = prostate.iloc[:, -1]
scaler = StandardScaler()
X = scaler.fit_transform(X_raw)
首先进行岭回归
model = Ridge()
#拟合模型
model.fit(X, y)
#计算测试集上的拟合优度
model.score(X, y)
#模型截距
model.intercept_
#模型系数
model.coef_
#数据框展示系数
pd.DataFrame(model.coef_, index=X_raw.columns, columns=['Coefficient'])
当惩罚系数大小,即惩罚力度不一样时,模型得到的系数是不一样的,下面画出变量系数随着惩罚力度变化图:
# Plot ridge coefficient path , fit_intercept=False
alphas = np.logspace(-3, 6, 100)
coefs = []
for alpha in alphas:
model = Ridge(alpha=alpha)
model.fit(X, y)
coefs.append(model.coef_)
ax = plt.gca()
ax.plot(alphas, coefs)
ax.set_xscale('log')
plt.xlabel('alpha (log scale)')
plt.ylabel('Coefficients')
plt.title('Ridge Cofficient Path')
plt.axhline(0, linestyle='--', linewidth=1, color='k')
plt.legend(X_raw.columns)
搜索最优惩罚系数
# Refine the search grid
alphas=np.linspace(1, 10, 1000)
model = RidgeCV(alphas=alphas, store_cv_values=True)
model.fit(X, y)
model.alpha_
model.cv_values_.shape
mse = np.mean(model.cv_values_, axis=0)
np.min(mse)
index_min = np.argmin(mse)
print(index_min)
alphas[index_min], mse[index_min]
plt.plot(alphas, mse)
plt.axvline(alphas[index_min], linestyle='--', linewidth=1, color='k')
plt.xlabel('alpha')
plt.ylabel('Mean Squared Error')
plt.title('CV Error for Ridge Regression')
plt.tight_layout()
model.coef_
pd.DataFrame(model.coef_, index=X_raw.columns, columns=['Coefficient'])
可以看到最优的惩罚系数在6点多左右,此时模型系数为
### Lasso regression
model = Lasso(alpha=0.1)
model.fit(X, y)
model.score(X, y)
results = pd.DataFrame(model.coef_, index=X_raw.columns, columns=['Coefficient'])
results
可以看出lasso回归的系数很多都成为了0 ,说明这些变量不重要。得到的是稀疏的解。因为一范数对小系数惩罚性较大,这保证了模型的稀疏性。
下面考察不同的惩罚力度和系数的变化
alphas, coefs, _ = lasso_path(X, y, eps=1e-4)
ax = plt.gca()
ax.plot(alphas, coefs.T)
ax.set_xscale('log')
plt.xlabel('alpha (log scale)')
plt.ylabel('Coefficients')
plt.title('Lasso Cofficient Path')
plt.axhline(0, linestyle='--', linewidth=1, color='k')
plt.legend(X_raw.columns)
可以看到系数依次变为0,下面使用K折交叉验证收缩最优惩罚系数:
# Choose best Lasso regularization by 10-fold CV
kfold = KFold(n_splits=10, shuffle=True, random_state=1)
alphas=np.logspace(-4, -2, 100)
model = LassoCV(alphas=alphas, cv=kfold)
model.fit(X, y)
model.alpha_
pd.DataFrame(model.coef_, index=X_raw.columns, columns=['Coefficient'])
最优惩罚系数为 0.00722,此时模型回归的系数为:
使用手工交叉验证,计算最优惩罚系数:
alphas = np.logspace(-4, -2, 100)
scores = []
for alpha in alphas:
model = Lasso(alpha=alpha)
kfold = KFold(n_splits=10, shuffle=True, random_state=1)
scores_val = -cross_val_score(model, X, y, cv=kfold, scoring='neg_mean_squared_error')
score = np.mean(scores_val)
scores.append(score)
mse = np.array(scores)
index_min = np.argmin(mse)
alphas[index_min]
最优系数为0.00722,和上面一样,模型的mse画图如下:
plt.plot(alphas, mse)
plt.axvline(alphas[index_min], linestyle='--', linewidth=1, color='k')
plt.xlabel('alpha')
plt.ylabel('Mean Squared Error')
plt.title('CV Error for Lasso')
plt.tight_layout()
弹性网回归
所谓弹性网回归,就是一范数和二范数都要,损失函数里面都加一点,代码如下
model = ElasticNet(alpha=0.1, l1_ratio=0.5)
model.fit(X, y)
model.score(X, y)
pd.DataFrame(model.coef_, index=X_raw.columns, columns=['Coefficient'])
模型拟合优度为0.6419,系数为如下
画惩罚系数和变量系数变化图:
# Plot Elastic Net coefficient path
alphas, coefs, _ = enet_path(X, y, eps=1e-4, l1_ratio = 0.5)
ax = plt.gca()
ax.plot(alphas, coefs.T)
ax.set_xscale('log')
plt.xlabel('alpha (log scale)')
plt.ylabel('Coefficients')
plt.title('Elastic Net Cofficient Path (l1_ratio = 0.5)')
plt.axhline(0, linestyle='--', linewidth=1, color='k')
plt.legend(X_raw.columns)
搜索最优超参数——惩罚系数
# Choose best ElasticNet hyperparameters by 10-fold CV
alphas = np.logspace(-4, 0, 100)
kfold = KFold(n_splits=10, shuffle=True, random_state=1)
model = ElasticNetCV(cv=kfold, alphas = alphas, l1_ratio=[0.0001, 0.001, 0.01, 0.1, 0.5, 1])
model.fit(X,y)
#最优惩罚系数
model.alpha_
#一范数的比例
model.l1_ratio_
#拟合优度
model.score(X,y)
#变量系数
pd.DataFrame(model.coef_, index=X_raw.columns, columns=['Coefficient'])
划分训练集和测试集 验证模型是否过拟合,和泛化能力
## Use optimal ridge regression for prediction
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1)
model = RidgeCV(alphas=np.linspace(1, 20, 1000))
model.fit(X_train, y_train)
model.alpha_
#训练集拟合优度
model.score(X_train, y_train)
#测试集拟合优度
model.score(X_test, y_test)