本文的方法
在这一节中,我将介绍我用来准备数据和拟合正则化回归的方法。
在得到X和y之前,我不会详细说明数据。我使用来自美国县级国家健康排名数据收集的一个版本来生成下面的结果,但对于这个示例来说,这真的无关紧要。
因此,假设您有一个漂亮而干净的X和y,下一步是使用方便的train_test_split留出一个测试数据集。如果想让结果重现,可以为my_random_state选择任何数字。
X_train , X_test, y_train, y_test=train_test_split( X, y, test_size=1000, random_state=my_random_state)
下一步是包含多项式特性。我们将结果保存在多边形对象中,这很重要,我们将在以后使用它。
poly=PolynomialFeatures( degree=2, include_bias=False, interaction_only=False)
这将产生变量的所有二阶多项式组合。需要注意的是,我们将include_bias设置为False。这是因为我们不需要截距列,回归模型本身将包含一个截距列。
这是我们转换和重命名X的方法。它假设您将X保存在一个pandas DataFrame中,并且需要进行一些调整以保持列名可用。如果你不想要名字,你只需要第一行。
X_train_poly=poly.fit_transform(X_train) polynomial_column_names=\poly.get_feature_names(input_features=X_train.columns) X_train_poly=\pd.DataFrame(data=X_train_poly, columns=polynomial_column_names ) X_train_poly.columns=X_train_poly.columns.str.replace(' ', '_') X_train_poly.columns=X_train_poly.columns.str.replace('^', '_')
完成这一步后,下一步是扩展。在引入多项式之后,这就更加重要了,没有缩放,大小就会到处都是。
sc=StandardScaler() X_train_poly_scaled=sc.fit_transform(X_train_poly) X_train_poly_scaled=pd.DataFrame( \data=X_train_poly_scaled, columns=X_train_poly.columns)
棘手的部分来了。如果我们想要使用测试数据集,我们需要应用相同的步骤。
但是,我们不需要再次适合这些对象。好吧,对于poly无所谓,但是对于sc,我们想要保留用于X_train_poly的方法。是的,这意味着测试数据不会完全标准化,这很好。我们用transform代替fit_transform。
X_test_poly=poly.transform(X_test) X_test_poly_scaled=sc.transform(X_test_poly)
您可能想知道如何生成上面使用的图。我使用两个函数,构建在上面列出的库之上。第一个函数绘制一个图:
defregmodel_param_plot( validation_score, train_score, alphas_to_try, chosen_alpha, scoring, model_name, test_score=None, filename=None): plt.figure(figsize= (8,8)) sns.lineplot(y=validation_score, x=alphas_to_try, label='validation_data') sns.lineplot(y=train_score, x=alphas_to_try, label='training_data') plt.axvline(x=chosen_alpha, linestyle='--') iftest_scoreisnotNone: sns.lineplot(y=test_score, x=alphas_to_try, label='test_data') plt.xlabel('alpha_parameter') plt.ylabel(scoring) plt.title(model_name+' Regularisation') plt.legend() iffilenameisnotNone: plt.savefig(str(filename) +".png") plt.show()
第二个本质上是一个网格搜索,带有一些额外的东西:它也运行测试分数,当然还保存。
defregmodel_param_test( alphas_to_try, X, y, cv, scoring='r2', model_name='LASSO', X_test=None, y_test=None, draw_plot=False, filename=None): validation_scores= [] train_scores= [] results_list= [] ifX_testisnotNone: test_scores= [] scorer=get_scorer(scoring) else: test_scores=Noneforcurr_alphainalphas_to_try: ifmodel_name=='LASSO': regmodel=Lasso(alpha=curr_alpha) elifmodel_name=='Ridge': regmodel=Ridge(alpha=curr_alpha) else: returnNoneresults=cross_validate( regmodel, X, y, scoring=scoring, cv=cv, return_train_score=True) validation_scores.append(np.mean(results['test_score'])) train_scores.append(np.mean(results['train_score'])) results_list.append(results) ifX_testisnotNone: regmodel.fit(X,y) y_pred=regmodel.predict(X_test) test_scores.append(scorer(regmodel, X_test, y_test)) chosen_alpha_id=np.argmax(validation_scores) chosen_alpha=alphas_to_try[chosen_alpha_id] max_validation_score=np.max(validation_scores) ifX_testisnotNone: test_score_at_chosen_alpha=test_scores[chosen_alpha_id] else: test_score_at_chosen_alpha=Noneifdraw_plot: regmodel_param_plot( validation_scores, train_scores, alphas_to_try, chosen_alpha, scoring, model_name, test_scores, filename) returnchosen_alpha, max_validation_score, test_score_at_chosen_alpha
我不想在这里讲得太详细,我认为这是不言自明的,稍后我们会看到如何调用它的例子。
有一件事,我认为非常酷:sklearn有一个get_scorer函数,它根据sklearn字符串代码返回一个scorer对象。例如:
scorer=get_scorer('r2') scorer(model, X_test, y_test)
现在我们有另外一种方法来计算相同的东西。
一旦建立了这样的进程,我们所需要做的就是为不同的alpha数组运行函数。
这个过程的一个有趣之处在于,我们也在绘制测试分数:
- 取训练数据集和alpha值;
- 进行交叉验证,保存培训和验证分数;
- 假设这是我们选择并拟合模型的alpha值,而不需要对整个训练数据进行交叉验证;
- 计算该模型将对测试数据实现的分数,并保存测试分数。
这不是您在“现实生活”中会做的事情(除非您参加Kaggle竞赛),因为现在有了优化测试数据集的可能性。我们在这里仅仅是为了说明模型的性能。红线表示的是不同alpha的测试分数。
我们还需要一个交叉验证对象,这里没有一个好的答案,这是一个选项:
cv=KFold(n_splits=5, shuffle=True, random_state=my_random_state)
为了说明我关于多步参数搜索的重要性的观点,让我们假设我们想要检查这些alpha:
lasso_alphas=np.linspace(0, 0.02, 11)
运行函数后:
chosen_alpha, max_validation_score, test_score_at_chosen_alpha=\regmodel_param_test( lasso_alphas, X_train_poly_scaled, y_train, cv, scoring='r2', model_name='LASSO', X_test=X_test_poly_scaled, y_test=y_test, draw_plot=True, filename='lasso_wide_search') print("Chosen alpha: %.5f"%\chosen_alpha) print("Validation score: %.5f"%\max_validation_score) print("Test score at chosen alpha: %.5f"%\test_score_at_chosen_alpha)
结果
Chosenalpha: 0.00200Validationscore: 0.82310Testscoreatchosenalpha: 0.80673
这是否意味着我们找到了最优?你可以看一下图,看到一个漂亮的尖刺,但是它是否够高了。不完全是。如果我们在更细粒度的层面上运行它:
lasso_alphas=np.linspace(0, 0.002, 11)
这是结果,请注意0.02,最右边的点是我们在上一个图表中出现峰值的地方:
Chosenalpha: 0.00060Validationscore: 0.83483Testscoreatchosenalpha: 0.82326
如果我们不进行详细的测试,我们就会选择一个能使整体检验R²降低2%的,我认为这很重要。
这篇文章的标题包括Ridge,除了理论介绍之外,我们还没有讨论过它。
原因很简单:它的工作方式与Lasso完全一样,您可能只是想选择不同的alpha参数,并在model_name参数中传递' Ridge '。Ridge也存在同样的问题(我不包括搜索alpha范围的部分):
你会注意到,我们根据蓝线选择的点似乎不再是红线的最佳点。没错,但我觉得这对Lasso 模型来说是个巧合。
总结
这就是我为Lasso和Ridge做超参数调整的方法。希望对大家有所帮助,再次介绍一下要点:
- 记住缩放变量;
- alpha = 0是线性回归;
- 多步搜索最佳参数;
- 使用基于分数的平方差异来衡量表现。