对于机器学习工程师,要选择使用的模型,也要调整各个模型的参数,才找到最好的匹配。即使模型还可以,如果它的参数设置不匹配,同样无法输出好的结果。
常用的调参方式有 grid search 和 random search ,grid search 是全空间扫描,所以比较慢,random search 虽然快,但可能错失空间上的一些重要的点,精度不够,于是,贝叶斯优化出现了。
hyperopt是一种通过贝叶斯优化来调整参数的工具,对于像XGBoost这种参数比较多的算法,可以用它来获取比较好的参数值。
pip install hyperopt
它会安装 networkx,如果运行异常,碰到 TypeError: 'generator' object is not subscriptable
的话,可以卸载并换成老版本来解决
pip uninstall networkx
pip install networkx==1.11
from hyperopt import fmin, tpe, hp
best = fmin(
fn=lambda x: x,
space=hp.uniform('x', 0, 1),
algo=tpe.suggest,
max_evals=100)
print best
以上面的函数为例,fmin寻找最佳匹配的 space ,使 fn 的函数返回值最小,采用了 tpe.suggest (tree of Parzen estimators) 的算法,反复尝试100次,最终得到的结果类似于 {'x': 0.000269455723739237}
对于变量的变化范围与取值概率,一般定义有这么几个
示例:
space = {
'x': hp.uniform('x', 0, 1),
'y': hp.normal('y', 0, 1),
'name': hp.choice('name', ['alice', 'bob']),
}
整体变化比较多,感觉欠缺的点是:
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的返回,则会忽略。输出类似于
best: {'x': 0.014420181637303776}
trials:
{'refresh_time': None, 'book_time': None, 'misc': {'tid': 0, 'idxs': {'x': [0]}, 'cmd': ('domain_attachment', 'FMinIter_Domain'), 'vals': {'x': [1.9646918559786162]}, 'workdir': None}, 'state': 2, 'tid': 0, 'exp_key': None, 'version': 0, 'result': {'status': 'ok', 'loss': 3.8600140889486996}, 'owner': None, 'spec': None}
{'refresh_time': None, 'book_time': None, 'misc': {'tid': 1, 'idxs': {'x': [1]}, 'cmd': ('domain_attachment', 'FMinIter_Domain'), 'vals': {'x': [-3.9393509404526728]}, 'workdir': None}, 'state': 2, 'tid': 1, 'exp_key': None, 'version': 0, 'result': {'status': 'ok', 'loss': 15.518485832045357}, 'owner': None, 'spec': None}
可以通过这里面的值,把一些变量与loss的点绘图,来看匹配度。或者tid与变量绘图,看它搜索的位置收敛(非数学意义上的收敛)情况。
trials有这几种:
accuracy
,它是越小越好,那么,hyperopt里面的loss
的值就应该是对这个值取负数,因为hyperopt通过loss
最小取找最佳匹配。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
space = hp.choice('classifier_type', [
{
'type': 'naive_bayes', # BernoulliNB
'alpha': hp.uniform('alpha', 0.0, 2.0)
},
{
'type': 'svm', # SVC
'C': hp.uniform('C', 0, 10.0),
'kernel': hp.choice('kernel', ['linear', 'rbf']),
'gamma': hp.uniform('gamma', 0, 20.0)
},
{
'type': 'randomforest', # RandomForestClassifier
'max_depth': hp.choice('max_depth', range(1,20)),
'max_features': hp.choice('max_features', range(1,5)),
'n_estimators': hp.choice('n_estimators', range(1,20)),
'criterion': hp.choice('criterion', ["gini", "entropy"]),
'scale': hp.choice('scale', [0, 1]),
'normalize': hp.choice('normalize', [0, 1])
},
{
'type': 'knn', # KNeighborsClassifier
'n_neighbors': hp.choice('knn_n_neighbors', range(1,50))
}
])
Hyperas 是一个wrapper,便于使用。好处是,模型定义的时候,直接通过 {{uniform(0, 1)}}
{{choice(['relu', 'sigmoid'])}}
等赋值,可以不需要一个太大的space定义和一个复杂的fn(fn里面需要做参数的del和赋值等操作)。对于 NN这类模型,会显得更高效一些。
示例代码如下:
from hyperas.distributions import uniform
def create_model(x_train, y_train, x_test, y_test):
model = Sequential()
model.add(Dense(512, input_shape=(784,)))
model.add(Activation('relu'))
model.add(Dropout({{uniform(0, 1)}}))
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout({{uniform(0, 1)}}))
model.add(Dense(10))
model.add(Activation('softmax'))
# ... model fitting
score = model.evaluate(x_test, y_test, verbose=0)
accuracy = score[1]
return {'loss': -accuracy, 'status': STATUS_OK, 'model': model}