之前的讨论中,对模型的优化都是对模型算法本身的改进,比如:岭回归对线性回归的优化在于在线性回归的损失函数中加入L2正则化项从而牺牲无偏性降低方差。但是对于这其中参数的选取并没有给出很好的解决方法,那么能不能找到一种方法找到最优的参数 λ \lambda λ?这实际上涉及到最优化内容。
首先要区分参数与超参数,如果要用一句话概括:
以我接触过的高斯分布混合模型(GMM)来说,其中模型的均值、方差实际上都可以通过数据进行迭代学习得到,这属于参数;但是GMM的分量个数,就属于超参数了,你实际上并不能通过数据在不借助其他准则(例如AIC、BIC准则)的情况下直接得到这个个数。同理K均值当中的K,也是超参数,一般都是人为进行制定或者说是遍历看效果来选取。
当然目前下面介绍几种超参数学习的算法:
1.网格搜索GridSearchCV():
sklearn库的链接:网格搜索 网格搜索结合管道
网格搜索非常简单,本质就是一种穷举法。
比如你有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]} ,然后针对每组超参数分别建立一个模型,然后选择测试误差最小的那组超参数。换句话说,我们需要从超参数空间中寻找最优的超参数,很像一个网格中找到一个最优的节点,因此叫网格搜索。
2.随机搜索 RandomizedSearchCV() :
sklearn库的链接:随机搜索
网格搜索相当于暴力地从参数空间中每个都尝试一遍,然后选择最优的那组参数,这样显然是不够高效的。而采用随机搜索的方式,不仅仅高效,而且实验证明,随机搜索法结果比稀疏化网格法稍好(有时候也会极差,需要权衡)。参数的随机搜索中的每个参数都是从可能的参数值的分布中采样的。与网格搜索相比,这有两个主要优点:
- 可以独立于参数数量和可能的值来选择计算成本。
- 添加不影响性能的参数不会降低效率。
下面我们使用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.6081303070817127
网格搜索最优参数组合:
{'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.3046244976868293
随机搜索最优参数组合:
{'svr__C': 1.040579963881545, 'svr__gamma': 1.008649319233331, 'svr__kernel': 'linear'}
3.贝叶斯优化方法 Bayesian Optimization:
前面两种方法能够针对单独超参数组合模型进行训练,并评估各自的性能。每个模型都是独立的,因此很易于进行并行计算。但是每个模型都是独立的,也导致模型之间不具有指导意义,前一模型的计算结果并不能影响后一模型的超参数选择。 而贝叶斯优化方法(顺序优化方法的一种,sequential model-besed optimization, SMBO)则可以借鉴已有的结果进而影响后续的模型超参数选择。
这也限制了模型训练评估的计算次数,因为只有有望提高模型性能的超参数组合才会被进行计算。
贝叶斯优化是通过构造一个函数的后验分布(高斯过程)来工作的,该后验分布最好地描述了要优化的函数。随着观测次数的增加,后验分布得到改善,算法更加确定参数空间中哪些区域值得探索,哪些区域不值得探索。
当反复迭代时,算法会在考虑到它对目标函数的了解的情况下,平衡它的探索和开发需求。在每个步骤中,高斯过程被拟合到已知的样本(先前探索的点),后验分布与探索策略(例如UCB(上置信限,upper confidence bound)或EI(预期改善, expected improvement))被用于确定下一个应该探索的点。
通过贝叶斯优化,可以更高效得探索超参数变量空间,降低优化时间。
4.启发式算法:
启发式算法其实是相对于最优化算法提出的,是基于直观或者经验构造的算法,在可接受的开销(时间和空间)内给出待解决组合优化问题的一个可行解。目前比较通用的启发式算法一般有模拟退火算法(SA)、遗传算法(GA)、蚁群算法(ACO)、人工神经网络(ANN)等。
这些算法比较多,常被用来寻找其他技术不易求解的近似解。优化问题往往没有一个精确的解决方案,因为它可能太耗时并且计算资源占用很大。在这种情况下,启发式算法通常可以用来寻找一个足够的近似最优解。
下一章将介绍机器学习另外一类大问题:分类。