超参数优化


调参

在训练模型的过程中,为了达到更好的结果,免不了要优化超参数,又称调参。虽然感觉提升并不显著,但提高一点算一点嘛。

Amazon介绍的超参数调优的介绍中,在传统的机器学习中一般有三种调优方式(其实就两种,网格搜索和随机搜索没什么区别):

  1. 网格搜索(Grid search)
  2. 随机搜索(Random search)
  3. 贝叶斯优化(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 ForestGradient BoostingSVMLogistic RegressionKNNDecision 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: 叶节点所需的最小样本数

文章作者: Chupeng Zheng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Chupeng Zheng !
  目录