超参数优化
调参
在训练模型的过程中,为了达到更好的结果,免不了要优化超参数,又称调参。虽然感觉提升并不显著,但提高一点算一点嘛。
在Amazon介绍的超参数调优的介绍中,在传统的机器学习中一般有三种调优方式(其实就两种,网格搜索和随机搜索没什么区别):
- 网格搜索(Grid search)
- 随机搜索(Random search)
- 贝叶斯优化(Bayesian optimization):
为了演示,这里使用Random Forest Classifier。
0. 数据
随机在kaggle上找了个Dry Bean数据集, 我也没仔细看具体是什么,大概就是不同豆子的种类吧,应该和iris差不多。
import numpy as np
import pandas as pd
from sklearn.metrics import confusion_matrix, classification_report, accuracy_score
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
X = df.drop('Class', axis=1)
y = df['Class']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
用默认参数跑一遍
rfm_default = RandomForestClassifier()
rfm_default.fit(X_train , y_train)
rfm_default_pred = rfm_default.predict(X_test)
# print(classification_report(y_test , rfm_default_pred))
print("Accuracy: ", accuracy_score(y_test, rfm_default_pred))
# Accuracy: 0.9217774513404333
1. 网格搜索
这种方法简单来说就是把所有的超参数组合试一遍,和一拍脑袋手动试并无二致,不过网格搜索在给定范围内自动运行,不用手动一个一个试。
为了演示方便,只调 n_estimators 和 min_samples_leaf,并只给两个取值。事实上可以无限多个。
from sklearn.model_selection import GridSearchCV
param_grid = {
'n_estimators': [100, 200],
# 'max_depth': [None, 10, 20],
# 'min_samples_split': [5, 10],
'min_samples_leaf': [2, 4],
# 'bootstrap': [True, False]
}
rfm_grid = RandomForestClassifier(random_state=42)
# 开始调参
grid_search = GridSearchCV(estimator=rfm_grid, param_grid=param_grid, cv=4, verbose=1)
grid_search.fit(X_train, y_train)
# 打印最好参数和结果
print("Best parameters found: ", grid_search.best_params_)
print("Best score: ", grid_search.best_score_)
#Best parameters found: {'min_samples_leaf': 2, 'n_estimators': 100}
#Best score: 0.9231263776634827
# 在测试集上测试
predictions = grid_search.predict(X_test)
#print("Classification report:\n", classification_report(y_test, predictions))
print("Accuracy: ", accuracy_score(y_test, predictions))
#Accuracy: 0.9232464193903782
可以看到,调参过后准确率略有提升(0.92178 –> 0.92325)。这种方法理论上可以找到最好的超参数组合,但是太慢了!因为每一个组合都要拟合测试一遍。
2. 随机搜索
顾名思义,就是在超参数组合中随机取点拟合模型,保留在有限次循环 (n_iter) 中最好结果。
牺牲一点准确度,但是解决了网格搜索过慢的痛点。
from sklearn.model_selection import RandomizedSearchCV
# 速度较快,我们可以加大超参数取值范围,但为了演示依旧只调n_estimators 和 min_samples_leaf
param_dist = {
'n_estimators': [int(x) for x in np.linspace(start=100, stop=300, num=10)],
# 'max_depth': [int(x) for x in np.linspace(10, 110, num=11)] + [None],
# 'min_samples_split': [2, 5, 10],
'min_samples_leaf': [1, 2, 4],
# 'bootstrap': [True, False]
}
rfm_random = RandomForestClassifier()
# 开始调参,注意 n_iter 为循环次数
random_search = RandomizedSearchCV(estimator=rfm_random, param_distributions=param_dist, n_iter=5, cv=3, verbose=2, random_state=42)
random_search.fit(X_train , y_train)
# 打印最好参数和结果
print("Best parameters found: ", random_search.best_params_)
print("Best score: ", random_search.best_score_)
# 在测试集上测试
predictions = random_search.predict(X_test)
# print("Classification report:\n", classification_report(y_test, predictions))
print("Accuracy: ", accuracy_score(y_test, predictions))
# Accuracy: 0.9243481454278369
这里准确率依旧有提升,仅仅是因为范围比较大。
3. 贝叶斯优化 ⭐️⭐️⭐️
贝叶斯优化假设超参数和损失函数有某种关系,但是由于函数未知等原因,无法使用凸优化的方法。取而代之,用数值分析的方法找到最优解。简单来说,在每一次选取新的超参数组合时,记录了前几次的信息,所以效果理论上会比随机搜索好。
pip install scikit-optimize
from skopt import BayesSearchCV
# 开始调参
opt = BayesSearchCV(
estimator=RandomForestClassifier(),
search_spaces=param_dist, #和前面范围一致
n_iter=5, # 循环次数
cv=3, # 3折交叉验证
scoring='accuracy',
verbose = 2
)
opt.fit(X_train , y_train)
# 打印最好参数和结果
print("Best parameters found: ", opt.best_params_)
print("Best score: ", opt.best_score_)
# 在测试集上测试
predictions = opt.predict(X_test)
# print("Classification report:\n", classification_report(y_test, predictions))
print("Accuracy: ", accuracy_score(y_test, predictions))
# Accuracy: 0.9254498714652957
可以看到,贝叶斯优化由于是有规律的选取超参数,表现较好,并且耗时较短。
结语
调参在一定程度上可以提升正确率。根据实际情况,每个模型需要调的超参数不同。现如今,我们也可以使用一些自动调参的包,类似于H2O AutoML, 这个甚至可以从几个基础模型中选择最好的。
调参的过程需要时间和耐心(和玄学),最离谱的是调random seed的都有。在Torch.manual_seed(3407) is all you need这篇文章中,作者居然说3407这个种子在主流预训练的计算机视觉模型中表现最好,娘希匹,这都能水文章。太变态了。
总之,学再多的模型,也逃不了调参的命运,真是不幸。
附录:基础模型的常见超参数
Random Forest | Gradient Boosting | SVM | Logistic Regression | KNN | Decision Trees |
---|---|---|---|---|---|
n_estimators: 树的数量 | n_estimators: 迭代次数,即要构建的树的数量 | C: 正则化参数,控制误差项的惩罚强度 | C: 正则化参数,控制误差项的惩罚强度 | n_neighbors: 邻居的数量 | max_depth: 树的最大深度 |
max_depth: 树的最大深度 | learning_rate: 学习率,用以控制每棵树对最终结果的贡献 | kernel: 核函数类型,用于转换输入数据空间 | penalty: 用于指定惩罚中使用的标准(如L1或L2正则化) | weights: 预测中使用的权重函数 | min_samples_split: 分割内部节点所需的最小样本数 |
min_samples_split: 分割内部节点所需的最小样本数 | max_depth: 树的最大深度 | gamma: 核函数的系数(对于‘rbf’,‘poly’和‘sigmoid’) | solver: 用于优化问题的算法 | algorithm: 用于计算最近邻居的算法 | min_samples_leaf: 叶节点所需的最小样本数 |
min_samples_leaf: 叶节点所需的最小样本数 | min_samples_split: 分割内部节点所需的最小样本数 | criterion: 用于测量分割质量的函数 | |||
bootstrap: 是否在构建树时使用bootstrap样本(即重抽样) | min_samples_leaf: 叶节点所需的最小样本数 |