Lasso模型选择:交叉验证 / AIC / BIC

翻译者:@Loopy
校验者:@barrycg

本示例利用Akaike信息判据(AIC)、Bayes信息判据(BIC)和交叉验证,来筛选Lasso回归的正则化项参数alpha的最优值。

通过LassoLarsIC得到的结果,是基于AIC/BIC判据的。

这种基于信息判据(AIC/BIC)的模型选择非常快,但它依赖于对自由度的正确估计。该方式的假设模型必需是正确, 而且是对大样本(渐近结果)进行推导,即,数据实际上是由该模型生成的。当问题的背景条件很差时(特征数大于样本数),该模型选择方式会崩溃。

对于交叉验证,我们使用20-fold的2种算法来计算Lasso路径:LassoCV类实现的坐标下降和LassoLarsCV类实现的最小角度回归(Lars)。这两种算法给出的结果大致相同,但它们在执行速度和数值误差来源方面有所不同。

Lars仅为路径中的每个拐点计算路径解决方案。因此,当只有很少的弯折时,也就是很少的特征或样本时,它是非常有效的。此外,它能够计算完整的路径,而不需要设置任何元参数。与之相反,坐标下降算法计算预先指定的网格上的路径点(本示例中我们使用缺省值)。因此,如果网格点的数量小于路径中的拐点的数量,则效率更高。如果特征数量非常大,并且有足够的样本来选择大量特征,那么这种策略就非常有趣。在数值误差方面,Lars会因变量间的高相关度而积累更多的误差,而坐标下降算法只会采样网格上路径。

注意观察alpha的最优值是如何随着每个fold而变化。这是为什么当估交叉验证选择参数的方法的性能时,需要使用嵌套交叉验证的原因:这种参数的选择对于不可见数据可能不是最优的。

  1. import time
  2. import numpy as np
  3. import matplotlib.pyplot as plt
  4. from sklearn.linear_model import LassoCV, LassoLarsCV, LassoLarsIC
  5. from sklearn import datasets
  1. # 这样做是为了避免在np.log10时除零
  2. EPSILON = 1e-4
  3. diabetes = datasets.load_diabetes()
  4. X = diabetes.data
  5. y = diabetes.target
  6. rng = np.random.RandomState(42)
  7. X = np.c_[X, rng.randn(X.shape[0], 14)] # 添加一些不好的特征
  1. # 将最小角度回归得到的数据标准化,以便进行比较
  2. X /= np.sqrt(np.sum(X ** 2, axis=0))
  1. # LassoLarsIC: 用BIC/AIC判据进行最小角度回归
  2. model_bic = LassoLarsIC(criterion='bic')
  3. t1 = time.time()
  4. model_bic.fit(X, y)
  5. t_bic = time.time() - t1
  6. alpha_bic_ = model_bic.alpha_
  7. model_aic = LassoLarsIC(criterion='aic')
  8. model_aic.fit(X, y)
  9. alpha_aic_ = model_aic.alpha_
  1. def plot_ic_criterion(model, name, color):
  2. alpha_ = model.alpha_ + EPSILON
  3. alphas_ = model.alphas_ + EPSILON
  4. criterion_ = model.criterion_
  5. plt.plot(-np.log10(alphas_), criterion_, '--', color=color,
  6. linewidth=3, label='%s 判据' % name)
  7. plt.axvline(-np.log10(alpha_), color=color, linewidth=3,
  8. label='alpha: %s 估计' % name)
  9. plt.xlabel('-log(alpha)')
  10. plt.ylabel('判据')
  11. plt.figure()
  12. plot_ic_criterion(model_aic, 'AIC', 'b')
  13. plot_ic_criterion(model_bic, 'BIC', 'r')
  14. plt.legend()
  15. plt.title('模型选择的信息判据 (训练时间:%.3fs)'
  16. % t_bic)
  1. Text(0.5, 1.0, '模型选择的信息判据 (训练时间:0.024s)')

png

  1. # LassoCV: 坐标下降
  2. # 计算路径
  3. t1 = time.time()
  4. model = LassoCV(cv=20).fit(X, y)
  5. t_lasso_cv = time.time() - t1
  6. # 显示结果
  7. m_log_alphas = -np.log10(model.alphas_ + EPSILON)
  8. plt.figure()
  9. ymin, ymax = 2300, 3800
  10. plt.plot(m_log_alphas, model.mse_path_, ':')
  11. plt.plot(m_log_alphas, model.mse_path_.mean(axis=-1), 'k',
  12. label='平均', linewidth=2)
  13. plt.axvline(-np.log10(model.alpha_ + EPSILON), linestyle='--', color='k',
  14. label='alpha:CV估计')
  15. plt.legend()
  16. plt.xlabel('-log(alpha)')
  17. plt.ylabel('均方惨差')
  18. plt.title('每折上的均方残差: 坐标下降法'
  19. '(训练时间: %.2fs)' % t_lasso_cv)
  20. plt.axis('tight')
  21. plt.ylim(ymin, ymax)
  1. (2300, 3800)

png

  1. # LassoLarsCV: 最小角度回归法
  2. # Compute paths
  3. print("Computing regularization path using the Lars lasso...")
  4. t1 = time.time()
  5. model = LassoLarsCV(cv=20).fit(X, y)
  6. t_lasso_lars_cv = time.time() - t1
  7. # Display results
  8. m_log_alphas = -np.log10(model.cv_alphas_ + EPSILON)
  9. plt.figure()
  10. plt.plot(m_log_alphas, model.mse_path_, ':')
  11. plt.plot(m_log_alphas, model.mse_path_.mean(axis=-1), 'k',
  12. label='平均', linewidth=2)
  13. plt.axvline(-np.log10(model.alpha_), linestyle='--', color='k',
  14. label='alpha CV')
  15. plt.legend()
  16. plt.xlabel('-log(alpha)')
  17. plt.ylabel('均方惨差')
  18. plt.title('每折上的均方残差: 最小角度回归法'
  19. '(训练时间: %.2fs)' % t_lasso_lars_cv)
  20. plt.axis('tight')
  21. plt.ylim(ymin, ymax)
  22. plt.show()
  1. Computing regularization path using the Lars lasso...

png