作者 | Pierre Paul Ipolito Marina
来源 | Medium
编辑 | 代码医生团队
介绍
机器学习模型由两种不同类型的参数组成:
- Hyperparameters =是用户在开始训练之前可以任意设置的所有参数(例如,Random Forest中的估计量)。
- 取而代之的是在模型训练期间学习模型参数=(例如,神经网络中的权重,线性回归)。
模型参数定义了如何使用输入数据来获得所需的输出,并在训练时进行学习。相反,超参数首先确定了模型的结构。
机器学习模型调整是一种优化问题。有一组超参数,目标是找到它们的值的正确组合,这可以帮助找到函数的最小值(例如,损耗)或最大值(例如,精度)(图1)。
当比较不同的机器学习模型对数据集的执行方式时,这尤其重要。实际上,例如将具有最佳超参数的SVM模型与尚未优化的随机森林模型进行比较将是不公平的。
在这篇文章中,将说明以下超参数优化方法:
- 手动搜寻
- 随机搜寻
- 网格搜索
- 自动超参数调整(贝叶斯优化,遗传算法)
- 人工神经网络(ANN)调整
图1:机器学习优化工作流程[1]
为了演示如何在Python中执行超参数优化,决定对信用卡欺诈检测Kaggle数据集执行完整的数据分析。本文的目的是正确分类哪些应标记为欺诈或真实(二进制分类)。该数据集在分发前已被匿名化,因此,大多数功能的含义尚未公开。
https://www.kaggle.com/mlg-ulb/creditcardfraud
在这种情况下,决定仅使用数据集的一个子集,以加快训练时间并确保在两个不同的类之间实现完美的平衡。另外,仅使用了有限的功能来使优化任务更具挑战性。最终数据集如下图所示(图2)。
GitHub存储库和Kaggle Profile中提供了本文中使用的所有代码。
https://github.com/pierpaolo28/Kaggle-Challenges/blob/master/credit-card-fraud-model-tuning.ipynb
https://www.kaggle.com/pierpaolo28/credit-card-fraud-model-tuning
机器学习
首先,需要将数据集分为训练集和测试集。
代码语言:javascript复制import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
X = df[['V17', 'V9', 'V6', 'V12']]
Y = df['Class']
X = StandardScaler().fit_transform(X)
X_Train, X_Test, Y_Train, Y_Test = train_test_split(X, Y, test_size = 0.30,
random_state = 101)
在本文中,将使用随机森林分类器作为模型进行优化。
随机森林模型由大量不相关的决策树形成,这些决策树共同构成一个整体。在随机森林中,每个决策树都进行自己的预测,并且将整体模型输出选择为最常出现的预测。
现在,可以从计算基本模型的准确性开始。
代码语言:javascript复制from sklearn.metrics import classification_report,confusion_matrix
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
model = RandomForestClassifier(random_state= 101).fit(X_Train,Y_Train)
predictionforest = model.predict(X_Test)
print(confusion_matrix(Y_Test,predictionforest))
print(classification_report(Y_Test,predictionforest))
acc1 = accuracy_score(Y_Test,predictionforest)
代码语言:javascript复制[[110 6]
[ 6 118]]
precision recall f1-score support
0 0.95 0.95 0.95 116
1 0.95 0.95 0.95 124
accuracy 0.95 240
macro avg 0.95 0.95 0.95 240
weighted avg 0.95 0.95 0.95 240
使用具有默认scikit-learn参数的随机森林分类器可达到95%的总体准确性。现在看看是否应用一些优化技术可以提高精度。
手动搜寻
使用“手动搜索”时,会根据判断/经验选择一些模型超参数。然后训练模型,评估模型的准确性并重新开始该过程。重复该循环,直到获得令人满意的精度为止。
随机森林分类器使用的主要参数是:
- criterion =用于评估分割质量的函数。
- max_depth =每棵树中允许的最大级别数。
- max_features =分割节点时考虑的最大特征数。
- min_samples_leaf =可以存储在树叶中的最小样本数。
- min_samples_split =节点中导致节点分裂所需的最小样本数。
- n_estimators =集成树的数量。
可以在scikit-learn 文档中找到有关随机森林参数的更多信息。
作为手动搜索的示例,尝试指定模型中的估计量。不幸的是,这并没有导致准确性的提高。
代码语言:javascript复制model = RandomForestClassifier(n_estimators=10, random_state= 101).fit(X_Train,Y_Train)
predictionforest = model.predict(X_Test)
print(confusion_matrix(Y_Test,predictionforest))
print(classification_report(Y_Test,predictionforest))
acc2 = accuracy_score(Y_Test,predictionforest)
代码语言:javascript复制[[110 6]
[ 6 118]]
precision recall f1-score support
0 0.95 0.95 0.95 116
1 0.95 0.95 0.95 124
accuracy 0.95 240
macro avg 0.95 0.95 0.95 240
weighted avg 0.95 0.95 0.95 240
随机搜寻
在随机搜索中,创建一个超参数网格,并仅基于这些超参数的某些随机组合来训练/测试模型。在此示例中,另外决定对训练集执行交叉验证。
在执行机器学习任务时,通常将数据集分为训练集和测试集。这样做是为了在训练模型后测试模型(通过这种方式,可以在处理看不见的数据时检查其性能)。使用交叉验证时,将训练集划分为其他N个分区,以确保模型不会过度拟合数据。
最常用的交叉验证方法之一是K折验证。在K-Fold中,将训练集划分为N个分区,然后使用N-1个分区迭代地训练模型,并使用剩余分区进行测试(在每次迭代中,都会更改剩余分区)。一旦对模型进行了N次训练,就可以平均每次迭代获得的训练结果,从而获得整体训练效果结果(图3)。
图3:K折交叉验证[2]
在实现超参数优化时使用交叉验证非常重要。这样可以避免使用一些对训练数据非常有效但对测试数据不太好的超参数。
现在,可以通过首先定义一个超参数网格来开始实现随机搜索,在调用RandomizedSearchCV()时将随机采样该超参数网格。对于此示例,决定将训练集划分为4折(cv = 4),并选择80作为要采样的组合数(n_iter = 80)。然后,使用scikit-learn best_estimator_属性,可以检索在训练过程中表现最佳的超参数集,以测试模型。
代码语言:javascript复制import numpy as np
from sklearn.model_selection import RandomizedSearchCV
from sklearn.model_selection import cross_val_score
random_search = {'criterion': ['entropy', 'gini'],
'max_depth': list(np.linspace(10, 1200, 10, dtype = int)) [None],
'max_features': ['auto', 'sqrt','log2', None],
'min_samples_leaf': [4, 6, 8, 12],
'min_samples_split': [5, 7, 10, 14],
'n_estimators': list(np.linspace(151, 1200, 10, dtype = int))}
clf = RandomForestClassifier()
model = RandomizedSearchCV(estimator = clf, param_distributions = random_search, n_iter = 80,
cv = 4, verbose= 5, random_state= 101, n_jobs = -1)
model.fit(X_Train,Y_Train)
训练好模型后,可以可视化更改其某些超参数如何影响整体模型的准确性(图4)。在这种情况下,决定观察改变估计量和准则的数量如何影响随机森林准确性。
代码语言:javascript复制import seaborn as sns
table = pd.pivot_table(pd.DataFrame(model.cv_results_),
values='mean_test_score', index='param_n_estimators',
columns='param_criterion')
sns.heatmap(table)
图4:准则vs N估计器精度热图
然后,可以使可视化更具交互性,从而使这一步骤更进一步。在下面的图表中,可以检查(使用滑块)在模型中考虑估计的min_split和min_leaf参数时,改变估计量的数量如何影响模型的整体准确性。
通过更改n_estimators参数,放大和缩小图表,更改其方向以及将鼠标悬停在单个数据点上来获取有关它们的更多信息,可以随意使用下面的图表!
https://chart-studio.plot.ly/create/?fid=pierpaolo28:13
现在,可以使用随机搜索评估模型的性能。在这种情况下,与基本模型相比,使用随机搜索会导致准确性不断提高。
代码语言:javascript复制predictionforest = model.best_estimator_.predict(X_Test)
print(confusion_matrix(Y_Test,predictionforest))
print(classification_report(Y_Test,predictionforest))
acc3 = accuracy_score(Y_Test,predictionforest)
代码语言:javascript复制[[115 1]
[ 6 118]]
precision recall f1-score support
0 0.95 0.99 0.97 116
1 0.99 0.95 0.97 124
accuracy 0.97 240
macro avg 0.97 0.97 0.97 240
weighted avg 0.97 0.97 0.97 240
网格搜索
在“网格搜索”中,建立了一个超参数网格,并在每种可能的组合上训练/测试模型。
为了选择在Grid Search中使用的参数,现在可以查看哪些参数与Random Search一起使用效果最好,并根据它们形成一个网格,以查看是否可以找到更好的组合。
可以使用scikit-learn GridSearchCV()函数在Python中实现网格搜索。同样在这种情况下,决定将训练集分为4倍(cv = 4)。
使用网格搜索时,将尝试网格中所有可能的参数组合。在这种情况下,训练期间将使用128000个组合(2×10×4×4×4×10)。相反,在前面的“网格搜索”示例中,仅使用了80种组合。
代码语言:javascript复制from sklearn.model_selection import GridSearchCV
grid_search = {
'criterion': [model.best_params_['criterion']],
'max_depth': [model.best_params_['max_depth']],
'max_features': [model.best_params_['max_features']],
'min_samples_leaf': [model.best_params_['min_samples_leaf'] - 2,
model.best_params_['min_samples_leaf'],
model.best_params_['min_samples_leaf'] 2],
'min_samples_split': [model.best_params_['min_samples_split'] - 3,
model.best_params_['min_samples_split'],
model.best_params_['min_samples_split'] 3],
'n_estimators': [model.best_params_['n_estimators'] - 150,
model.best_params_['n_estimators'] - 100,
model.best_params_['n_estimators'],
model.best_params_['n_estimators'] 100,
model.best_params_['n_estimators'] 150]
}
clf = RandomForestClassifier()
model = GridSearchCV(estimator = clf, param_grid = grid_search,
cv = 4, verbose= 5, n_jobs = -1)
model.fit(X_Train,Y_Train)
predictionforest = model.best_estimator_.predict(X_Test)
print(confusion_matrix(Y_Test,predictionforest))
print(classification_report(Y_Test,predictionforest))
acc4 = accuracy_score(Y_Test,predictionforest)
代码语言:javascript复制[[115 1]
[ 7 117]]
precision recall f1-score support
0 0.94 0.99 0.97 116
1 0.99 0.94 0.97 124
accuracy 0.97 240
macro avg 0.97 0.97 0.97 240
weighted avg 0.97 0.97 0.97 240
与随机搜索相比,网格搜索速度较慢,但由于它可以遍历整个搜索空间,因此总体上更有效。取而代之的是,随机搜索可以更快更快,但是可能会错过搜索空间中的一些重要点。
自动超参数调整
使用自动超参数调整时,将使用以下技术来标识要使用的模型超参数:贝叶斯优化,梯度下降和进化算法。
贝叶斯优化
贝叶斯优化可以使用Hyperopt库在Python中执行。贝叶斯优化使用概率来找到函数的最小值。最终目的是找到函数的输入值,该函数可以为我们提供尽可能低的输出值。
贝叶斯优化已被证明比随机,网格或手动搜索更有效。因此,贝叶斯优化可以提高测试阶段的性能并减少优化时间。
在Hyperopt中,可以实现贝叶斯优化,为函数fmin()提供3个三个主要参数。
- 目标函数 =定义要最小化的损失函数。
- 域空间 =定义要测试的输入值的范围(在贝叶斯优化中,该空间为每个使用的超参数创建概率分布)。
- 优化算法 =定义用于选择在每个新迭代中使用的最佳输入值的搜索算法。
此外,还可以在fmin()中定义要执行的最大评估数。
贝叶斯优化可以通过考虑过去的结果来选择输入值,从而减少搜索迭代的次数。这样,可以从一开始就将搜索集中在更接近所需输出的值上。
现在,可以使用fmin()函数运行贝叶斯优化器。首先创建一个Trials()对象,以便稍后可视化fmin()函数运行时正在发生的事情(例如,损失函数的变化方式以及如何使用超参数)。
代码语言:javascript复制from hyperopt import hp, fmin, tpe, STATUS_OK, Trials
space = {'criterion': hp.choice('criterion', ['entropy', 'gini']),
'max_depth': hp.quniform('max_depth', 10, 1200, 10),
'max_features': hp.choice('max_features', ['auto', 'sqrt','log2', None]),
'min_samples_leaf': hp.uniform ('min_samples_leaf', 0, 0.5),
'min_samples_split' : hp.uniform ('min_samples_split', 0, 1),
'n_estimators' : hp.choice('n_estimators', [10, 50, 300, 750, 1200])
}
def objective(space):
model = RandomForestClassifier(criterion = space['criterion'],
max_depth = space['max_depth'],
max_features = space['max_features'],
min_samples_leaf = space['min_samples_leaf'],
min_samples_split = space['min_samples_split'],
n_estimators = space['n_estimators'],
)
accuracy = cross_val_score(model, X_Train, Y_Train, cv = 4).mean()
# We aim to maximize accuracy, therefore we return it as a negative value
return {'loss': -accuracy, 'status': STATUS_OK }
trials = Trials()
best = fmin(fn= objective,
space= space,
algo= tpe.suggest,
max_evals = 80,
trials= trials)
Best
代码语言:javascript复制100%|██████████| 80/80 [03:07<00:00, 2.02s/it, best loss: -0.9339285714285713]
{'criterion': 1,
'max_depth': 120.0,
'max_features': 2,
'min_samples_leaf': 0.0006380325074247448,
'min_samples_split': 0.06603114636418073,
'n_estimators': 1}
现在,可以检索识别出的最佳参数集,并使用训练过程中创建的最佳字典来测试模型。一些参数已使用索引以数字方式存储在最佳字典中,因此,需要先将它们转换回字符串,然后再将其输入到随机森林中。
代码语言:javascript复制crit = {0: 'entropy', 1: 'gini'}
feat = {0: 'auto', 1: 'sqrt', 2: 'log2', 3: None}
est = {0: 10, 1: 50, 2: 300, 3: 750, 4: 1200}
trainedforest = RandomForestClassifier(criterion = crit[best['criterion']],
max_depth = best['max_depth'],
max_features = feat[best['max_features']],
min_samples_leaf = best['min_samples_leaf'],
min_samples_split = best['min_samples_split'],
n_estimators = est[best['n_estimators']]
).fit(X_Train,Y_Train)
predictionforest = trainedforest.predict(X_Test)
print(confusion_matrix(Y_Test,predictionforest))
print(classification_report(Y_Test,predictionforest))
acc5 = accuracy_score(Y_Test,predictionforest)
代码语言:javascript复制[[114 2]
[ 11 113]]
precision recall f1-score support
0 0.91 0.98 0.95 116
1 0.98 0.91 0.95 124
accuracy 0.95 240
macro avg 0.95 0.95 0.95 240
weighted avg 0.95 0.95 0.95 240
遗传算法
遗传算法试图将自然选择机制应用于机器学习环境。它们受到达尔文自然选择过程的启发,因此通常也称为进化算法。
假设创建了具有一些预定义超参数的N个机器学习模型。然后,可以计算每个模型的准确性,并决定仅保留一半模型(性能最好的模型)。现在,可以生成具有与最佳模型相似的超参数的后代,以便再次获得N个模型的种群。在这一点上,可以再次计算每个模型的准确性,并在定义的世代中重复该循环。这样,只有最好的模型才能在流程结束时生存下来。
为了在Python中实现遗传算法,可以使用TPOT自动机器学习库。TPOT建立在scikit-learn库上,可用于回归或分类任务。
代码语言:javascript复制from tpot import TPOTClassifier
parameters = {'criterion': ['entropy', 'gini'],
'max_depth': list(np.linspace(10, 1200, 10, dtype = int)) [None],
'max_features': ['auto', 'sqrt','log2', None],
'min_samples_leaf': [4, 12],
'min_samples_split': [5, 10],
'n_estimators': list(np.linspace(151, 1200, 10, dtype = int))}
tpot_classifier = TPOTClassifier(generations= 5, population_size= 24, offspring_size= 12,
verbosity= 2, early_stop= 12,
config_dict=
{'sklearn.ensemble.RandomForestClassifier': parameters},
cv = 4, scoring = 'accuracy')
tpot_classifier.fit(X_Train,Y_Train)
以下代码片段显示了使用遗传算法确定的培训报告和最佳参数。
代码语言:javascript复制Generation 1 - Current best internal CV score: 0.9392857142857143
Generation 2 - Current best internal CV score: 0.9392857142857143
Generation 3 - Current best internal CV score: 0.9392857142857143
Generation 4 - Current best internal CV score: 0.9392857142857143
Generation 5 - Current best internal CV score: 0.9392857142857143
Best pipeline: RandomForestClassifier(CombineDFs(input_matrix, input_matrix), criterion=entropy, max_depth=406, max_features=log2, min_samples_leaf=4, min_samples_split=5, n_estimators=617)
随机森林遗传算法优化模型的整体准确性如下所示。
代码语言:javascript复制acc6 = tpot_classifier.score(X_Test, Y_Test)
print(acc6)
0.9708333333333333
人工神经网络(ANN)调整
使用KerasClassifier包装器,可以像使用scikit-learn机器学习模型时一样,对深度学习模型应用网格搜索和随机搜索。在以下示例中,将尝试优化一些ANN参数,例如:在每个层中使用多少个神经元,以及使用哪个激活函数和优化器。
代码语言:javascript复制from keras.models import Sequential
from keras.layers import Dense, Dropout
from keras.wrappers.scikit_learn import KerasClassifier
def DL_Model(activation= 'linear', neurons= 5, optimizer='Adam'):
model = Sequential()
model.add(Dense(neurons, input_dim= 4, activation= activation))
model.add(Dense(neurons, activation= activation))
model.add(Dropout(0.3))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer= optimizer, metrics=['accuracy'])
return model
# Definying grid parameters
activation = ['softmax', 'relu', 'tanh', 'sigmoid', 'linear']
neurons = [5, 10, 15, 25, 35, 50]
optimizer = ['SGD', 'Adam', 'Adamax']
param_grid = dict(activation = activation, neurons = neurons, optimizer = optimizer)
clf = KerasClassifier(build_fn= DL_Model, epochs= 80, batch_size=40, verbose= 0)
model = GridSearchCV(estimator= clf, param_grid=param_grid, n_jobs=-1)
model.fit(X_Train,Y_Train)
print("Max Accuracy Registred: {} using {}".format(round(model.best_score_,3),
model.best_params_))
Max Accuracy Registred: 0.932 using {'activation': 'relu', 'neurons': 35, 'optimizer': 'Adam'}
使用人工神经网络(ANN)评分的总体准确性可以在下面看到。
代码语言:javascript复制predictionforest = model.predict(X_Test)
print(confusion_matrix(Y_Test,predictionforest))
print(classification_report(Y_Test,predictionforest))
acc7 = accuracy_score(Y_Test,predictionforest)
代码语言:javascript复制[[115 1]
[ 8 116]]
precision recall f1-score support
0 0.93 0.99 0.96 116
1 0.99 0.94 0.96 124
accuracy 0.96 240
macro avg 0.96 0.96 0.96 240
weighted avg 0.96 0.96 0.96 240
评估
现在,可以比较在此给定练习中如何执行所有不同的优化技术。总体而言,随机搜索和进化算法的效果最佳。
代码语言:javascript复制print('Base Accuracy vs Manual Search {:0.4f}%.'.format( 100 * (acc2 - acc1) / acc1))
print('Base Accuracy vs Random Search {:0.4f}%.'.format( 100 * (acc3 - acc1) / acc1))
print('Base Accuracy vs Grid Search {:0.4f}%.'.format( 100 * (acc4 - acc1) / acc1))
print('Base Accuracy vs Bayesian Optimization Accuracy {:0.4f}%.'
.format( 100 * (acc5 - acc1) / acc1))
print('Base Accuracy vs Evolutionary Algorithms {:0.4f}%.'
.format( 100 * (acc6 - acc1) / acc1))
print('Base Accuracy vs Optimized ANN {:0.4f}%.'.format( 100 * (acc7 - acc1) / acc1))
Base Accuracy vs Manual Search 0.0000%.
Base Accuracy vs Random Search 2.1930%.
Base Accuracy vs Grid Search 1.7544%.
Base Accuracy vs Bayesian Optimization Accuracy -0.4386%.
Base Accuracy vs Evolutionary Algorithms 2.1930%.
Base Accuracy vs Optimized ANN 1.3158%.
获得的结果高度依赖于所选的网格空间和所使用的数据集。因此,在不同情况下,不同的优化技术将比其他技术表现更好。
参考书目
[1] 超参数优化:自动化算法的解释,Dawid Kopczyk。访问:
https://dkopczyk.quantee.co.uk/hyperparameter-optimization/
[2] 选型,ethen8181。访问以下网址:
http://ethen8181.github.io/machine-learning/model_selection/model_selection.html