文章目录
(4) 优化基础模型
特征提取的实例:向前逐步回归
案例来源:https://blog.csdn.net/weixin_44835596/article/details/89763300
根据AIC准则定义向前逐步回归进行变量筛选
#定义向前逐步回归函数
def forward_select(data,target):
variate=set(data.columns) #将字段名转换成字典类型
variate.remove(target) #去掉因变量的字段名
selected=[]
current_score,best_new_score=float('inf'),float('inf') #目前的分数和最好分数初始值都为无穷大(因为AIC越小越好)
#循环筛选变量
while variate:
aic_with_variate=[]
for candidate in variate: #逐个遍历自变量
formula="{}~{}".format(target,"+".join(selected+[candidate])) #将自变量名连接起来
aic=ols(formula=formula,data=data).fit().aic #利用ols训练模型得出aic值
aic_with_variate.append((aic,candidate)) #将第每一次的aic值放进空列表
aic_with_variate.sort(reverse=True) #降序排序aic值
best_new_score,best_candidate=aic_with_variate.pop() #最好的aic值等于删除列表的最后一个值,以及最好的自变量等于列表最后一个自变量
if current_score>best_new_score: #如果目前的aic值大于最好的aic值
variate.remove(best_candidate) #移除加进来的变量名,即第二次循环时,不考虑此自变量了
selected.append(best_candidate) #将此自变量作为加进模型中的自变量
current_score=best_new_score #最新的分数等于最好的分数
print("aic is {},continuing!".format(current_score)) #输出最小的aic值
else:
print("for selection over!")
break
formula="{}~{}".format(target,"+".join(selected)) #最终的模型式子
print("final formula is {}".format(formula))
model=ols(formula=formula,data=data).fit()
return(model)
import statsmodels.api as sm #最小二乘
from statsmodels.formula.api import ols #加载ols模型
forward_select(data=boston_data,target="Price")
> [1;32m<ipython-input-1-27dcfac17a63>[0m(12)[0;36mforward_select[1;34m()[0m
[1;32m 10 [1;33m [1;32mfor[0m [0mcandidate[0m [1;32min[0m [0mvariate[0m[1;33m:[0m [1;31m#逐个遍历自变量[0m[1;33m[0m[1;33m[0m[0m
[0m[1;32m 11 [1;33m [1;32mimport[0m [0mpdb[0m[1;33m;[0m [0mpdb[0m[1;33m.[0m[0mset_trace[0m[1;33m([0m[1;33m)[0m[1;33m[0m[1;33m[0m[0m
[0m[1;32m---> 12 [1;33m [0mformula[0m[1;33m=[0m[1;34m"{}~{}"[0m[1;33m.[0m[0mformat[0m[1;33m([0m[0mtarget[0m[1;33m,[0m[1;34m"+"[0m[1;33m.[0m[0mjoin[0m[1;33m([0m[0mselected[0m[1;33m+[0m[1;33m[[0m[0mcandidate[0m[1;33m][0m[1;33m)[0m[1;33m)[0m [1;31m#将自变量名连接起来[0m[1;33m[0m[1;33m[0m[0m
[0m[1;32m 13 [1;33m [0maic[0m[1;33m=[0m[0mols[0m[1;33m([0m[0mformula[0m[1;33m=[0m[0mformula[0m[1;33m,[0m[0mdata[0m[1;33m=[0m[0mdata[0m[1;33m)[0m[1;33m.[0m[0mfit[0m[1;33m([0m[1;33m)[0m[1;33m.[0m[0maic[0m [1;31m#利用ols训练模型得出aic值[0m[1;33m[0m[1;33m[0m[0m
[0m[1;32m 14 [1;33m [0maic_with_variate[0m[1;33m.[0m[0mappend[0m[1;33m([0m[1;33m([0m[0maic[0m[1;33m,[0m[0mcandidate[0m[1;33m)[0m[1;33m)[0m [1;31m#将第每一次的aic值放进空列表[0m[1;33m[0m[1;33m[0m[0m
[0m
ipdb> n
> [1;32m<ipython-input-1-27dcfac17a63>[0m(13)[0;36mforward_select[1;34m()[0m
[1;32m 11 [1;33m [1;32mimport[0m [0mpdb[0m[1;33m;[0m [0mpdb[0m[1;33m.[0m[0mset_trace[0m[1;33m([0m[1;33m)[0m[1;33m[0m[1;33m[0m[0m
[0m[1;32m 12 [1;33m [0mformula[0m[1;33m=[0m[1;34m"{}~{}"[0m[1;33m.[0m[0mformat[0m[1;33m([0m[0mtarget[0m[1;33m,[0m[1;34m"+"[0m[1;33m.[0m[0mjoin[0m[1;33m([0m[0mselected[0m[1;33m+[0m[1;33m[[0m[0mcandidate[0m[1;33m][0m[1;33m)[0m[1;33m)[0m [1;31m#将自变量名连接起来[0m[1;33m[0m[1;33m[0m[0m
[0m[1;32m---> 13 [1;33m [0maic[0m[1;33m=[0m[0mols[0m[1;33m([0m[0mformula[0m[1;33m=[0m[0mformula[0m[1;33m,[0m[0mdata[0m[1;33m=[0m[0mdata[0m[1;33m)[0m[1;33m.[0m[0mfit[0m[1;33m([0m[1;33m)[0m[1;33m.[0m[0maic[0m [1;31m#利用ols训练模型得出aic值[0m[1;33m[0m[1;33m[0m[0m
[0m[1;32m 14 [1;33m [0maic_with_variate[0m[1;33m.[0m[0mappend[0m[1;33m([0m[1;33m([0m[0maic[0m[1;33m,[0m[0mcandidate[0m[1;33m)[0m[1;33m)[0m [1;31m#将第每一次的aic值放进空列表[0m[1;33m[0m[1;33m[0m[0m
[0m[1;32m 15 [1;33m [0maic_with_variate[0m[1;33m.[0m[0msort[0m[1;33m([0m[0mreverse[0m[1;33m=[0m[1;32mTrue[0m[1;33m)[0m [1;31m#降序排序aic值[0m[1;33m[0m[1;33m[0m[0m
[0m
ipdb> n
> [1;32m<ipython-input-1-27dcfac17a63>[0m(14)[0;36mforward_select[1;34m()[0m
[1;32m 12 [1;33m [0mformula[0m[1;33m=[0m[1;34m"{}~{}"[0m[1;33m.[0m[0mformat[0m[1;33m([0m[0mtarget[0m[1;33m,[0m[1;34m"+"[0m[1;33m.[0m[0mjoin[0m[1;33m([0m[0mselected[0m[1;33m+[0m[1;33m[[0m[0mcandidate[0m[1;33m][0m[1;33m)[0m[1;33m)[0m [1;31m#将自变量名连接起来[0m[1;33m[0m[1;33m[0m[0m
[0m[1;32m 13 [1;33m [0maic[0m[1;33m=[0m[0mols[0m[1;33m([0m[0mformula[0m[1;33m=[0m[0mformula[0m[1;33m,[0m[0mdata[0m[1;33m=[0m[0mdata[0m[1;33m)[0m[1;33m.[0m[0mfit[0m[1;33m([0m[1;33m)[0m[1;33m.[0m[0maic[0m [1;31m#利用ols训练模型得出aic值[0m[1;33m[0m[1;33m[0m[0m
[0m[1;32m---> 14 [1;33m [0maic_with_variate[0m[1;33m.[0m[0mappend[0m[1;33m([0m[1;33m([0m[0maic[0m[1;33m,[0m[0mcandidate[0m[1;33m)[0m[1;33m)[0m [1;31m#将第每一次的aic值放进空列表[0m[1;33m[0m[1;33m[0m[0m
[0m[1;32m 15 [1;33m [0maic_with_variate[0m[1;33m.[0m[0msort[0m[1;33m([0m[0mreverse[0m[1;33m=[0m[1;32mTrue[0m[1;33m)[0m [1;31m#降序排序aic值[0m[1;33m[0m[1;33m[0m[0m
[0m[1;32m 16 [1;33m [0mbest_new_score[0m[1;33m,[0m[0mbest_candidate[0m[1;33m=[0m[0maic_with_variate[0m[1;33m.[0m[0mpop[0m[1;33m([0m[1;33m)[0m [1;31m#最好的aic值等于删除列表的最后一个值,以及最好的自变量等于列表最后一个自变量[0m[1;33m[0m[1;33m[0m[0m
[0m
ipdb> h
Documented commands (type help <topic>):
========================================
EOF cl disable interact next psource rv undisplay
a clear display j p q s unt
alias commands down jump pdef quit skip_hidden until
args condition enable l pdoc r source up
b cont exit list pfile restart step w
break continue h ll pinfo return tbreak whatis
bt d help longlist pinfo2 retval u where
c debug ignore n pp run unalias
Miscellaneous help topics:
==========================
exec pdb
ipdb> formula
'Price~NOX'
lm=ols("Price~LSTAT+RM+PTRATIO+DIS+NOX+CHAS+B+ZN+CRIM+RAD+TAX",data=boston_data).fit()
lm.summary()
Dep. Variable: | Price | R-squared: | 0.741 |
---|---|---|---|
Model: | OLS | Adj. R-squared: | 0.735 |
Method: | Least Squares | F-statistic: | 128.2 |
Date: | Sat, 12 Dec 2020 | Prob (F-statistic): | 5.54e-137 |
Time: | 17:57:30 | Log-Likelihood: | -1498.9 |
No. Observations: | 506 | AIC: | 3022. |
Df Residuals: | 494 | BIC: | 3072. |
Df Model: | 11 | ||
Covariance Type: | nonrobust |
coef | std err | t | P>|t| | [0.025 | 0.975] | |
---|---|---|---|---|---|---|
Intercept | 36.3411 | 5.067 | 7.171 | 0.000 | 26.385 | 46.298 |
LSTAT | -0.5226 | 0.047 | -11.019 | 0.000 | -0.616 | -0.429 |
RM | 3.8016 | 0.406 | 9.356 | 0.000 | 3.003 | 4.600 |
PTRATIO | -0.9465 | 0.129 | -7.334 | 0.000 | -1.200 | -0.693 |
DIS | -1.4927 | 0.186 | -8.037 | 0.000 | -1.858 | -1.128 |
NOX | -17.3760 | 3.535 | -4.915 | 0.000 | -24.322 | -10.430 |
CHAS | 2.7187 | 0.854 | 3.183 | 0.002 | 1.040 | 4.397 |
B | 0.0093 | 0.003 | 3.475 | 0.001 | 0.004 | 0.015 |
ZN | 0.0458 | 0.014 | 3.390 | 0.001 | 0.019 | 0.072 |
CRIM | -0.1084 | 0.033 | -3.307 | 0.001 | -0.173 | -0.044 |
RAD | 0.2996 | 0.063 | 4.726 | 0.000 | 0.175 | 0.424 |
TAX | -0.0118 | 0.003 | -3.493 | 0.001 | -0.018 | -0.005 |
Omnibus: | 178.430 | Durbin-Watson: | 1.078 |
---|---|---|---|
Prob(Omnibus): | 0.000 | Jarque-Bera (JB): | 787.785 |
Skew: | 1.523 | Prob(JB): | 8.60e-172 |
Kurtosis: | 8.300 | Cond. No. | 1.47e+04 |
Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 1.47e+04. This might indicate that there are
strong multicollinearity or other numerical problems.
岭回归实例分享:
sklearn.linear_model.ridge_regression(X, y, alpha, *, sample_weight=None, solver=‘auto’, max_iter=None, tol=0.001, verbose=0, random_state=None, return_n_iter=False, return_intercept=False, check_input=True)
https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.ridge_regression.html?highlight=rid#sklearn.linear_model.ridge_regression
- 参数:
alpha:较大的值表示更强的正则化。浮点数
sample_weight:样本权重,默认无。
solver:求解方法,{‘auto’, ‘svd’, ‘cholesky’, ‘lsqr’, ‘sparse_cg’, ‘sag’, ‘saga’}, 默认=’auto’。“ svd”使用X的奇异值分解来计算Ridge系数。'cholesky’使用标准的scipy.linalg.solve函数通过dot(XT,X)的Cholesky分解获得封闭形式的解。'sparse_cg’使用scipy.sparse.linalg.cg中的共轭梯度求解器。作为一种迭代算法,对于大规模数据(可能设置tol和max_iter),此求解器比“ Cholesky”更合适。 lsqr”使用专用的正则化最小二乘例程scipy.sparse.linalg.lsqr。它是最快的,并且使用迭代过程。“ sag”使用随机平均梯度下降,“ saga”使用其改进的无偏版本SAGA。两种方法都使用迭代过程,并且当n_samples和n_features都很大时,通常比其他求解器更快。请注意,只有在比例大致相同的要素上才能确保“ sag”和“ saga”快速收敛。您可以使用sklearn.preprocessing中的缩放器对数据进行预处理。最后五个求解器均支持密集和稀疏数据。但是,当fit_intercept为True时,仅’sag’和’sparse_cg’支持稀疏输入。
from sklearn import linear_model
reg_rid = linear_model.Ridge(alpha=.5)
reg_rid.fit(X,y)
reg_rid.score(X,y)
0.739957023371629
Lasso实例分享:
class sklearn.linear_model.Lasso(alpha=1.0, *, fit_intercept=True, normalize=False, precompute=False, copy_X=True, max_iter=1000, tol=0.0001, warm_start=False, positive=False, random_state=None, selection=‘cyclic’)
https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.Lasso.html?highlight=lasso#sklearn.linear_model.Lasso
- 参数:
alpha:正则化强度,1.0代表标准最小二乘。
fit_intercept:是否计算模型截距。默认true。
normalize:是否标准化,默认false。
positive:是否强制系数为正,默认false。
from sklearn import linear_model
reg_lasso = linear_model.Lasso(alpha = 0.5)
reg_lasso.fit(X,y)
reg_lasso.score(X,y)
0.7140164719858566
(5) 对模型超参数进行调优(调参):
在刚刚的讨论中,我们似乎对模型的优化都是对模型算法本身的改进,比如:岭回归对线性回归的优化在于在线性回归的损失函数中加入L2正则化项从而牺牲无偏性降低方差。但是,大家是否想过这样的问题:在L2正则化中参数 λ \lambda λ应该选择多少?是0.01、0.1、还是1?到目前为止,我们只能凭经验或者瞎猜,能不能找到一种方法找到最优的参数 λ \lambda λ?事实上,找到最佳参数的问题本质上属于最优化的内容,因为从一个参数集合中找到最佳的值本身就是最优化的任务之一,我们脑海中浮现出来的算法无非就是:梯度下降法、牛顿法等无约束优化算法或者约束优化算法,但是在具体验证这个想法是否可行之前,我们必须先认识两个最本质概念的区别。
- 参数与超参数:
我们很自然的问题就是岭回归中的参数 λ \lambda λ和参数w之间有什么不一样?事实上,参数w是我们通过设定某一个具体的 λ \lambda λ后使用类似于最小二乘法、梯度下降法等方式优化出来的,我们总是设定了 λ \lambda λ是多少后才优化出来的参数w。因此,类似于参数w一样,使用最小二乘法或者梯度下降法等最优化算法优化出来的数我们称为参数,类似于 λ \lambda λ一样,我们无法使用最小二乘法或者梯度下降法等最优化算法优化出来的数我们称为超参数。
模型参数是模型内部的配置变量,其值可以根据数据进行估计。- 进行预测时需要参数。
- 它参数定义了可使用的模型。
- 参数是从数据估计或获悉的。
- 参数通常不由编程者手动设置。
- 参数通常被保存为学习模型的一部分。
- 参数是机器学习算法的关键,它们通常由过去的训练数据中总结得出 。
模型超参数是模型外部的配置,其值无法从数据中估计。 - 超参数通常用于帮助估计模型参数。
- 超参数通常由人工指定。
- 超参数通常可以使用启发式设置。
- 超参数经常被调整为给定的预测建模问题。
我们前面(4)部分的优化都是基于模型本身的具体形式的优化,那本次(5)调整的内容是超参数,也就是取不同的超参数的值对于模型的性能有不同的影响。
- 网格搜索GridSearchCV():
网格搜索:https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html?highlight=gridsearchcv#sklearn.model_selection.GridSearchCV
网格搜索结合管道:https://scikit-learn.org/stable/auto_examples/compose/plot_compare_reduction.html?highlight=gridsearchcv
网格搜索的思想非常简单,比如你有2个超参数需要去选择,那你就把所有的超参数选择列出来分别做排列组合。举个例子: λ = 0.01 , 0.1 , 1.0 \lambda = 0.01,0.1,1.0 λ=0.01,0.1,1.0和 α = 0.01 , 0.1 , 1.0 \alpha = 0.01,0.1,1.0 α=0.01,0.1,1.0,你可以做一个排列组合,即:{[0.01,0.01],[0.01,0.1],[0.01,1],[0.1,0.01],[0.1,0.1],[0.1,1.0],[1,0.01],[1,0.1],[1,1]} ,然后针对每组超参数分别建立一个模型,然后选择测试误差最小的那组超参数。换句话说,我们需要从超参数空间中寻找最优的超参数,很像一个网格中找到一个最优的节点,因此叫网格搜索。 - 随机搜索 RandomizedSearchCV() :
https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.RandomizedSearchCV.html?highlight=randomizedsearchcv#sklearn.model_selection.RandomizedSearchCV
网格搜索相当于暴力地从参数空间中每个都尝试一遍,然后选择最优的那组参数,这样的方法显然是不够高效的,因为随着参数类别个数的增加,需要尝试的次数呈指数级增长。有没有一种更加高效的调优方式呢?那就是使用随机搜索的方式,这种方式不仅仅高校,而且实验证明,随机搜索法结果比稀疏化网格法稍好(有时候也会极差,需要权衡)。参数的随机搜索中的每个参数都是从可能的参数值的分布中采样的。与网格搜索相比,这有两个主要优点:- 可以独立于参数数量和可能的值来选择计算成本。
- 添加不影响性能的参数不会降低效率。
下面我们使用SVR的例子,结合管道来进行调优:
# 我们先来对未调参的SVR进行评价:
from sklearn.svm import SVR # 引入SVR类
from sklearn.pipeline import make_pipeline # 引入管道简化学习流程
from sklearn.preprocessing import StandardScaler # 由于SVR基于距离计算,引入对数据进行标准化的类
from sklearn.model_selection import GridSearchCV # 引入网格搜索调优
from sklearn.model_selection import cross_val_score # 引入K折交叉验证
from sklearn import datasets
boston = datasets.load_boston() # 返回一个类似于字典的类
X = boston.data
y = boston.target
features = boston.feature_names
pipe_SVR = make_pipeline(StandardScaler(),
SVR())
score1 = cross_val_score(estimator=pipe_SVR,
X = X,
y = y,
scoring = 'r2',
cv = 10) # 10折交叉验证
print("CV accuracy: %.3f +/- %.3f" % ((np.mean(score1)),np.std(score1)))
CV accuracy: 0.187 +/- 0.649
# 下面我们使用网格搜索来对SVR调参:
from sklearn.pipeline import Pipeline
pipe_svr = Pipeline([("StandardScaler",StandardScaler()),
("svr",SVR())])
param_range = [0.0001,0.001,0.01,0.1,1.0,10.0,100.0,1000.0]
param_grid = [{
"svr__C":param_range,"svr__kernel":["linear"]}, # 注意__是指两个下划线,一个下划线会报错的
{
"svr__C":param_range,"svr__gamma":param_range,"svr__kernel":["rbf"]}]
gs = GridSearchCV(estimator=pipe_svr,
param_grid = param_grid,
scoring = 'r2',
cv = 10) # 10折交叉验证
gs = gs.fit(X,y)
print("网格搜索最优得分:",gs.best_score_)
print("网格搜索最优参数组合:\n",gs.best_params_)
网格搜索最优得分: 0.6081303070817233
网格搜索最优参数组合:
{'svr__C': 1000.0, 'svr__gamma': 0.001, 'svr__kernel': 'rbf'}
# 下面我们使用随机搜索来对SVR调参:
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import uniform # 引入均匀分布设置参数
pipe_svr = Pipeline([("StandardScaler",StandardScaler()),
("svr",SVR())])
distributions = dict(svr__C=uniform(loc=1.0, scale=4), # 构建连续参数的分布
svr__kernel=["linear","rbf"], # 离散参数的集合
svr__gamma=uniform(loc=0, scale=4))
rs = RandomizedSearchCV(estimator=pipe_svr,
param_distributions = distributions,
scoring = 'r2',
cv = 10) # 10折交叉验证
rs = rs.fit(X,y)
print("随机搜索最优得分:",rs.best_score_)
print("随机搜索最优参数组合:\n",rs.best_params_)
随机搜索最优得分: 0.30021249798866756
随机搜索最优参数组合:
{'svr__C': 1.4195029566223933, 'svr__gamma': 1.8683733769303625, 'svr__kernel': 'linear'}
经过我们不懈的努力,从收集数据集并选择合适的特征、选择度量模型性能的指标、选择具体的模型并进行训练以优化模型到评估模型的性能并调参,我们认识到了如何使用sklearn构建简单回归模型。在本章的最后,我们会给出一个具体的案例,整合回归的内容。下面我们来看看机器学习另外一类大问题:分类。与回归一样,分类问题在机器学习的地位非常重要,甚至有的地方用的比回归问题还要多,因此分类问题是十分重要的!