学习原因:
通过学习“魔镜杯”风控大赛金奖获得者的代码,发现在模型建立完毕之后,可以使用hyperopt包进行自动化参数调优。而不需要人工不停输入参数进行判断,尤其在参数组合较多的情况下,很好用。
参数调优常用的工具包:
常用的调参方式有 grid search 和 random search ,grid search 是全空间扫描,所以比较慢,random search 虽然快,但可能错失空间上的一些重要的点,精度不够,于是,贝叶斯优化出现了。
hyperopt是一种通过贝叶斯优化(贝叶斯优化简介)来调整参数的工具,对于像XGBoost这种参数比较多的算法,可以用它来获取比较好的参数值。
使用方法
fmin应该是最重要的一个方法了,下面要介绍的都是在fmin中可以设置的参数。全文是通过对fmin参数的介绍和使用来进行搜索模型的最优化参数的。
Hyheropt四个重要的因素:指定需要最小化的函数,搜索的空间,采样的数据集(trails database)(可选),搜索的算法(可选)。
先来一个简单的例子,然后根据这个列子进行讲解和扩展
from hyperopt import hp, fmin, rand, tpe, space_eval
space = [hp.uniform(’x’, 0, 1), hp.normal(’y’, 0, 1)]
def q (args) :
x, y = args
return x ∗∗ 2 + y ∗∗ 2
best = fmin(q, space, algo=rand.suggest,max_evals=100)
print space_eval(space, best)
以上面的函数为例,fmin寻找最佳匹配的 space ,使 fn 的函数返回值最小,采用了 tpe.suggest (tree of Parzen estimators) 的算法,反复尝试100次,最终得到的结果类似于 {'x': 0.000269455723739237}
最小化目标函数
首先,定义一个目标函数,接受一个变量,计算后返回一个函数的损失值,比如要最小化函数q(x,y) = x2 + y2。注意,一定是loss内涵的min函数,不要是score那种的max函数
搜索空间
定义一个参数空间,比如x在0-1区间内取值,y是实数,所以在上面的代码中为
hp.uniform(label,start,end)
搜索空间表现形式
搜索空间可以含有list和dictionary.
from hyperopt import hp
list_space = [
hp.uniform(’a’, 0, 1),
hp.loguniform(’b’, 0, 1)]
tuple_space = (
hp.uniform(’a’, 0, 1),
hp.loguniform(’b’, 0, 1))
dict_space = {
’a’: hp.uniform(’a’, 0, 1),
’b’: hp.loguniform(’b’, 0, 1)}
使用sample函数从参数空间内采样:
from hyperopt.pyll.stochasti import sample
print sample(list_space)
# => [0.13, .235]
print sample(nested_space)
# => [[{’case’: 1, ’a’, 0.12‘}, {’case’: 2, ’b’: 2.3}],
# ’extra_literal_string’,
# 3]
可选参数
- hp.choice 可以返回list中的值,还可以构成tuple.options,用于组成条件参数
num_leaves=hp.choice('num_leaves',range(10,100,10))
- hp.pchoice(label,p_options)以一定的概率返回一个p_options的一个选项。这个选项使得函数在搜索过程中对每个选项的可能性不均匀。
- hp.uniform(label,low,high)参数在low和high之间均匀分布。
- hp.quniform(label,low,high,q),参数的取值round(uniform(low,high)/q)*q,适用于那些离散的取值。
- hp.loguniform(label,low,high) 返回根据 exp(uniform(low,high)) 绘制的值,以便返回值的对数是均匀分布的。
优化时,该变量被限制在[exp(low),exp(high)]区间内。 - hp.randint(label,upper) 返回一个在[0,upper)前闭后开的区间内的随机整数。
- hp.normal(label, mu, sigma) where mu and sigma are the mean and standard deviation σ , respectively. 正态分布,返回值范围没法限制。
- hp.qnormal(label, mu, sigma, q)
- hp.lognormal(label, mu, sigma)
- hp.qlognormal(label, mu, sigma, q)
注意事项
- 如果需要枚举从[1, 100],那么用choice,而不应该用quniform hp.randint(label, upper) 返回从[0, upper)的随机整数,一般用作随机数的种子值。
- hp.quniform(label, low, high) where low and high are the lower and upper bounds on the range. 但只取整数(round)的float形式,返回可能是 1.0 这样的数值,如果模型参数类型有Interger的限制,需要显式做一个 int()的转换。
- hp.loguniform(label, low, high) 返回的值在 [elow,ehigh] 之间,属于log uniform分布,取值偏聚集于前部,概率上类似于抛物线
一个比较复杂的搜索空间
from hyperopt import hp
space = hp.choice('classifier_type', [
{
'type': 'naive_bayes',
},
{
'type': 'svm',
'C': hp.lognormal('svm_C', 0, 1),
'kernel': hp.choice('svm_kernel', [
{'ktype': 'linear'},
{'ktype': 'RBF', 'width': hp.lognormal('svm_rbf_width', 0, 1)},
]),
},
{
'type': 'dtree',
'criterion': hp.choice('dtree_criterion', ['gini', 'entropy']),
'max_depth': hp.choice('dtree_max_depth',
[None, hp.qlognormal('dtree_max_depth_int', 3, 1, 1)]),
'min_samples_split': hp.qlognormal('dtree_min_samples_split', 2, 1, 1),
},
])
搜索算法
algo指定搜索算法,目前支持以下算法:
①随机搜索(hyperopt.rand.suggest)
②模拟退火(hyperopt.anneal.suggest)
③TPE算法(hyperopt.tpe.suggest,算法全称为Tree-structured Parzen Estimator Approach)
其他有用的参数和方法
Trials
Trials只是用来记录每次eval的时候,具体使用了什么参数以及相关的返回值。这时候,fn的返回值变为dict,除了loss,还有一个status。Trials对象将数据存储为一个BSON对象,可以利用MongoDB做分布式运算。
from hyperopt import fmin, tpe, hp, STATUS_OK, Trials
fspace = {
'x': hp.uniform('x', -5, 5)
}
def f(params):
x = params['x']
val = x**2
return {'loss': val, 'status': STATUS_OK}
trials = Trials()
best = fmin(fn=f, space=fspace, algo=tpe.suggest, max_evals=50, trials=trials)
print('best:', best)
print('trials:')
for trial in trials.trials[:2]:
print(trial)
对于STATUS_OK的返回,会统计它的loss值,而对于STATUS_FAIL的返回,则会忽略。
可以通过这里面的值,把一些变量与loss的点绘图,来看匹配度。或者tid与变量绘图,看它搜索的位置收敛(非数学意义上的收敛)情况。
trials有这几种:
- trials.trials - a list of dictionaries representing everything about the search
- trials.results - a list of dictionaries returned by ‘objective’ during the search
- trials.losses() - a list of losses (float for each ‘ok’ trial) trials.statuses() - a list of status strings
通过交叉验证的方式确定最佳的参数
cross_val_score
对衡量的estimator,它默认返回的是一个array,包含K folder情况下的各次的评分,一般采用mean()。 需要确定这个estimator默认的 scoring 是什么,它的值是越大越匹配还是越小越匹配。如果自己指定了scoring,一定要确定这个scoring值的意义,切记切记! 而如果用户不指定,一般对于Classification类的estimator,使用accuracy,它是越大越好,那么,hyperopt里面的loss的值就应该是对这个值取负数,因为hyperopt通过loss最小取找最佳匹配。 可以把feature的normalize或者scale作为一个choice,然后看看是否更合适。如果更合适,best里面就会显示 normalize 为1。
from sklearn.datasets import load_iris
from sklearn import datasets
from sklearn.preprocessing import normalize, scale
from hyperopt import fmin, tpe, hp, STATUS_OK, Trials
iris = load_iris()
X = iris.data
y = iris.target
def hyperopt_train_test(params):
X_ = X[:]
# 因为下面的两个参数都不属于KNeighborsClassifier支持的参数,故使用后直接删除
if 'normalize' in params:
if params['normalize'] == 1:
X_ = normalize(X_)
del params['normalize']
if 'scale' in params:
if params['scale'] == 1:
X_ = scale(X_)
del params['scale']
clf = KNeighborsClassifier(**params)
return cross_val_score(clf, X_, y).mean()
space4knn = {
'n_neighbors': hp.choice('n_neighbors', range(1,50)),
'scale': hp.choice('scale', [0, 1]), # 必须是choice,不要用quniform
'normalize': hp.choice('normalize', [0, 1])
}
def f(params):
acc = hyperopt_train_test(params)
return {'loss': -acc, 'status': STATUS_OK} #注意这里的负号
trials = Trials()
best = fmin(f, space4knn, algo=tpe.suggest, max_evals=100, trials=trials)
print best
参考的网页们:
hyperopt的gitlab主页
hyperopt的中文翻译文档主页
调参神器:Hyperopt
Hyperopt的使用注意点
python机器学习模型选择&调参工具Hyperopt-sklearn(1)——综述&分类问题
Hyperopt中文文档:FMin
使用sklearn的数据进行一次测试
#coding:utf-8
from hyperopt import fmin, tpe, hp, rand
import numpy as np
from sklearn.metrics import accuracy_score
from sklearn import svm
from sklearn import datasets
# SVM的三个超参数:C为惩罚因子,kernel为核函数类型,gamma为核函数的额外参数(对于不同类型的核函数有不同的含义)
# 有别于传统的网格搜索(GridSearch),这里只需要给出最优参数的概率分布即可,而不需要按照步长把具体的值给一个个枚举出来
parameter_space_svc ={
# loguniform表示该参数取对数后符合均匀分布
'C':hp.loguniform("C", np.log(1), np.log(100)),
'kernel':hp.choice('kernel',['rbf','poly']),
'gamma': hp.loguniform("gamma", np.log(0.001), np.log(0.1)),
}
# 鸢尾花卉数据集,是一类多重变量分析的数据集
# 通过花萼长度,花萼宽度,花瓣长度,花瓣宽度4个属性预测鸢尾花卉属于(Setosa,Versicolour,Virginica)三个种类中的哪一类
iris = datasets.load_digits()
#--------------------划分训练集和测试集--------------------
train_data = iris.data[0:1300]
train_target = iris.target[0:1300]
test_data = iris.data[1300:-1]
test_target = iris.target[1300:-1]
#-----------------------------------------------------------
# 计数器,每一次参数组合的枚举都会使它加1
count = 0
def function(args):
print(args)
# **可以把dict转换为关键字参数,可以大大简化复杂的函数调用
clf = svm.SVC(**args)
# 训练模型
clf.fit(train_data,train_target)
# 预测测试集
prediction = clf.predict(test_data)
global count
count = count + 1
score = accuracy_score(test_target,prediction)
print("第%s次,测试集正确率为:" % str(count),score)
# 由于hyperopt仅提供fmin接口,因此如果要求最大值,则需要取相反数
return -score
# algo指定搜索算法,目前支持以下算法:
# ①随机搜索(hyperopt.rand.suggest)
# ②模拟退火(hyperopt.anneal.suggest)
# ③TPE算法(hyperopt.tpe.suggest,算法全称为Tree-structured Parzen Estimator Approach)
# max_evals指定枚举次数上限,即使第max_evals次枚举仍未能确定全局最优解,也要结束搜索,返回目前搜索到的最优解
best = fmin(function, parameter_space_svc, algo=tpe.suggest, max_evals=100)
# best["kernel"]返回的是数组下标,因此需要把它还原回来
kernel_list = ['rbf','poly']
best["kernel"] = kernel_list[best["kernel"]]
print("最佳参数为:",best)
clf = svm.SVC(**best)
print(clf)
输出结果如下:
{'gamma': 0.0010051585652497248, 'kernel': 'poly', 'C': 29.551164584073586}
第1次,测试集正确率为: 0.959677419355
{'gamma': 0.006498482991283678, 'kernel': 'rbf', 'C': 6.626826808981864}
第2次,测试集正确率为: 0.834677419355
{'gamma': 0.008192671915044216, 'kernel': 'poly', 'C': 34.48947180442318}
第3次,测试集正确率为: 0.959677419355
{'gamma': 0.001359874432712413, 'kernel': 'rbf', 'C': 1.6402360233244775}
第98次,测试集正确率为: 0.971774193548
{'gamma': 0.0029328466160223813, 'kernel': 'poly', 'C': 1.6328276445108112}
第99次,测试集正确率为: 0.959677419355
{'gamma': 0.0015786919481979775, 'kernel': 'rbf', 'C': 4.669133703622153}
第100次,测试集正确率为: 0.969758064516
最佳参数为: {'gamma': 0.00101162002595069, 'kernel': 'rbf', 'C': 21.12514792460218}
SVC(C=21.12514792460218, cache_size=200, class_weight=None, coef0=0.0,
decision_function_shape=None, degree=3, gamma=0.00101162002595069,
kernel='rbf', max_iter=-1, probability=False, random_state=None,
shrinking=True, tol=0.001, verbose=False)