精华内容
下载资源
问答
  • 网格搜索参数优化原理
    千次阅读
    2019-06-25 17:37:40

    1.交叉验证

    1.1 基本概念

           交叉验证的基本思想是将数据集分割成N份,依次使用其中1份作为测试集,其他N1份整合到一起作为训练集,将训练好的模型用于测试集上,以得到模型好坏的判断或估计值,可以得到N个这样的值。交叉验证通常用于估计模型的误差,这里将N个对应的误差求平均作为对模型误差的估计。也可以根据这N个值,选岀拟合效果最好的模型,对应模型的参数也被认为是最优或接近最优的,因此交叉验证可以用来辅助确定参数。

    1.2 代码实现

    #本函数实现对样本的分割
    #df:data.frame对象
    #k:分割数量
    #return:带有I_kvalue属性的完整数据集
    #注意sample函数中的参数默认是“对位”的sample(x=x,size,replace=TRUE,prob=c(0.4,0.6))分别是数据集、尺寸、是否可重复、抽取的概率
    
    sampleSplit <- function(df,k)
    {
        df$I_kvalue <- sample(1+((1:dim(df)[1])%%k),dim(df)[1])  # dim(iris):150 5  dim(df)[1]=5
        return(df)
    }
    out=sampleSplit(iris,k=10)
    table(out$I_kvalue)
    
    #table是其中的数出现的次数
    ## 
    ##  1  2  3  4  5  6  7  8  9 10 
    ## 15 15 15 15 15 15 15 15 15 15
    
    # > out
    # Sepal.Length Sepal.Width Petal.Length Petal.Width    Species I_kvalue
    # 1            5.1         3.5          1.4         0.2     setosa        9
    # 2            4.9         3.0          1.4         0.2     setosa        1
    # 3            4.7         3.2          1.3         0.2     setosa        2
    # 4            4.6         3.1          1.5         0.2     setosa        2
    # 5            5.0         3.6          1.4         0.2     setosa        8
    # 6            5.4         3.9          1.7         0.4     setosa        4
    # 7            4.6         3.4          1.4         0.3     setosa        5
    # 8            5.0         3.4          1.5         0.2     setosa        3
    # 9            4.4         2.9          1.4         0.2     setosa        9
    
    # 1、使用交叉验证得到的参数
    set.seed(1234)
    k=10
    out=sampleSplit(iris,k)
    #初始化最小均方误差minError
    minError=100
    #初始化最佳拟合结果finalfit
    finalfit=NULL
    for(i in 1:k)
    {
        #选择第i个子样本之外的其它所有样本作为训练集
        trainset=out[out$I_kvalue!=i,1:(dim(out)[2]-2)]
        #选择第i个子样本作为测试集
        testset=out[out$I_kvalue==i,1:(dim(out)[2]-2)]
        #拟合线性回归模型
        lm.fit=lm(Petal.Width~Sepal.Length+Sepal.Width+Petal.Length,data=trainset)
        #基于测试集得出预测结果
        testset$pred=predict(lm.fit,testset)
        #计算均方误差
        error=mean((testset$Petal.Width-testset$pred)^2)
        #判断是否最小均方误差
        if(error<minError)
        {
            minError=error
            finalfit=lm.fit
        }
    }
    print(minError)
    ## [1] 0.009822179
    
    print(finalfit$coefficients)
    
    ##  (Intercept) Sepal.Length  Sepal.Width Petal.Length 
    ##   -0.2488242   -0.1977551    0.2176520    0.5157144
    
    # 2、使用一般方法得到的参数
    lm.fit=lm(Petal.Width~Sepal.Length+Sepal.Width+Petal.Length,data=iris)
    print(lm.fit$coefficients)
    
    ##  (Intercept) Sepal.Length  Sepal.Width Petal.Length 
    ##   -0.2403074   -0.2072661    0.2228285    0.5240831

    2. 网格搜索

    2.1 基本概念

           网格搜索的基本原理是将各参数变量值的区间划分为一系列的小区间,并按顺序计算出对应各参数变量值组合所确定的目标值(通常是误差),并逐一择优,以得到该区间内最小目标值及其对应的最佳参数值。该方法可保证所得的搜索解是全局最优或接近最优的,可避免产生重大的误差。

    2.2 代码实现

    minMSE=1000
    f_a0=NULL
    f_b0=NULL
    f_c0=NULL
    f_d0=NULL
    k=55
    for(a in 0:k)
    {
        for(b in 0:k)
        {
            for(c in 0:k)
            {
                for(d in 0:k)
                {
                    #参数实例化
                    a0<-(-1)+2*a/k
                    b0<-(-1)+2*b/k
                    c0<-(-1)+2*c/k
                    d0<-(-1)+2*d/k
                    #计算均方误差
                    y0<-a0+b0*iris$Sepal.Length+c0*iris$Sepal.Width+
                        d0*iris$Petal.Length
                    mse<-mean((iris$Petal.Width-y0)^2)
                    if(mse<minMSE)
                    {
                        minMSE<-mse
                        f_a0<-a0
                        f_b0<-b0
                        f_c0<-c0
                        f_d0<-d0
                    }
                }
            }
        }
    }
    print(minMSE)
    ## [1] 0.03607967
    
    print(c(f_a0,f_b0,f_c0,f_d0))
    ## [1] -0.3454545 -0.2000000  0.2363636  0.5272727

     

    更多相关内容
  • 参数优化网格搜索

    千次阅读 2020-06-07 21:20:15
    使用Python完成网格搜索法在机器学习和深度学习的小案例!

    网格搜索法在机器学习和深度学习中的使用

    1.项目简介

      在机器学习和深度学习中,经常需要进行超参数优化。其中,网格搜索法在小批量的超参数优化时被经常使用,该项目是一个多分类(26个类别)问题。
      使用Jupyter Notebook完成,代码和数据文件

    2.机器学习案例

    2.1导入相关库

    from sklearn import svm  # 支持向量机库
    import pandas as pd
    from sklearn.model_selection import train_test_split  # 拆分数据集
    from sklearn.model_selection import GridSearchCV  # 网格搜索法
    from sklearn import metrics  # 用于模型评估
    

    2.2导入数据

    filepath = 'E:/Jupyter/Mojin/超参数优化/data/letterdata.csv'
    letters = pd.read_csv(filepath)
    letters.shape
    letters.tail(10)  # 返回数据后10行
    

      运行结果:
    在这里插入图片描述
      可以看到,总共有2万个数据,17个指标,其中,letter为目标变量,其余16个指标为特征变量。

    2.3拆分数据集

    features = letters.columns[1:]
    trainX, valX, trainY, valY = train_test_split(letters[features], letters[['letter']], 
                                                  test_size=0.2, 
                                                  random_state=1234)
    

    2.4网格搜索法

    C = [0.05, 0.1, 0.15, 0.2]
    kernel = ['rbf', 'sigmoid']
    Hyperparameter = dict(C=C, kernel=kernel)  # 将超参数范围包成字典
    
    grid = GridSearchCV(estimator=svm.SVC(),  # 支持向量机中的SVC模型
                        param_grid=Hyperparameter)
    
    # 模型在训练数据集上的拟合
    grid_result = grid.fit(trainX, trainY)
    
    # 返回最佳参数组合
    print('Best:%f using %s' % (grid_result.best_score_, grid_result.best_params_))
    

      运行结果:
    在这里插入图片描述
      可以看到,最佳参数组合:C:‘0.2’,kernel:‘rbf’,准确率:0.914125。

    2.5使用最优参数重新训练模型

    SVC = svm.SVC(kernel='rbf', C=0.2, gamma='scale').fit(trainX, trainY)
    pred = SVC.predict(valX)  # 预测
    print('模型预测的准确率:{0}'.format(metrics.accuracy_score(valY, pred)))
    

      运行结果:
    在这里插入图片描述

    3.深度学习案例

    3.1导入相关库

    from keras.models import Sequential
    from keras.layers import Dense
    from keras.layers import Dropout
    import pandas as pd
    import random  # 随机库
    from sklearn.model_selection import GridSearchCV  # 网格搜索法
    from keras.wrappers.scikit_learn import KerasClassifier  # Keras库分类模型封装器
    from keras.utils.np_utils import to_categorical  # Keras库独热编码
    

      KerasClassifier是将深度模型包装传递给网格搜索法的接口,具体说明见官方文档

    3.2导入数据

    filepath = 'E:/Jupyter/Mojin/超参数优化/data/letterdata.csv'
    letters = pd.read_csv(filepath)
    letters.shape
    letters.tail(10)
    
    # 将letter指标数据类型转换成category
    letter = letters['letter'].astype('category')
    # 使用标签的编码作为真正的数据
    letters['letter'] = letter.cat.codes
    letters.tail(10)
    
    # 提取特征变量并转换成数组格式
    X = letters[letters.columns[1:]].values
    X.shape
    Y = letters[['letter']].values
    Y.shape
    

      运行结果:
    在这里插入图片描述

    # 独热编码
    Y = to_categorical(Y)
    Y.shape
    

      运行结果:
    在这里插入图片描述
      可以看到,经过独热编码Y的维度从1变为26。

    3.3拆分数据集

    random_index = random.sample(list(range(X.shape[0])), X.shape[0])
    train_size = int(X.shape[0] * 0.8)
    train_index = random_index[:train_size]
    test_index = random_index[train_size:]
    
    trainX = X[train_index]
    trainX.shape
    trainY = Y[train_index]
    
    testX = X[test_index]
    testY = Y[test_index]
    

    3.4构造模型

    def create_model(dropout=0.2, depth=2):
        """
        dropout: Dropout层丢弃的比例,一般在0.2~0.5之间
        depth: 模型隐藏层的层数
        """
        model = Sequential()
        if depth < 2:
            raise Exception('至少两层结构')
        else:
            model.add(Dense(units=32, 
                        input_shape=(16,),  # 特征指标个数:16(trainX.shape[1])
                        activation='relu'))
            model.add(Dropout(rate=dropout))  # 防止过拟合
            for i in range(depth - 2):
                model.add(Dense(units=32,
                                    activation='relu'))
            model.add(Dense(units=26,
                            activation='softmax'))
            
            model.compile(loss='categorical_crossentropy',
                      optimizer='rmsprop', 
                      metrics=['accuracy'])
            model.summary()
        return model
    
    model = KerasClassifier(build_fn=create_model)
    

    3.5网格搜索法

    # 构建需要优化的超参数范围
    depth = [3, 4, 5]
    epochs = [30, 50]
    batch_size = [100]
    param_grid = dict(depth=depth,
                      epochs=epochs,
                      batch_size=batch_size)
              
    grid = GridSearchCV(estimator=model, 
                        param_grid=param_grid)  # 默认是cv=3,即3折交叉验证
    grid.fit(trainX, trainY)
    
    # 返回最佳参数组合
    print('Best:%f using %s' % (grid.best_score_, grid.best_params_))
    

      运行结果:
    在这里插入图片描述

    3.6使用最优参数重新训练模型

    model = create_model(depth=4)
    model.fit(trainX, trainY, epochs=50, batch_size=100)
    predict = model.predict(testX)
    loss, acc = model.evaluate(testX, testY)
    print('模型预测的准确率:{0}'.format(acc))
    

      运行结果:
    在这里插入图片描述

    展开全文
  • 【小白从小学Python、C、Java】 【Python全国计算机等级考试】 【Python数据分析考试必会题】 ...1.网格搜索(Grid Search)是一种调参方法,也称为穷举搜索搜索的是参数,在所有候选的参数选择中,

    【小白从小学Python、C、Java】

    【Python全国计算机等级考试】

    【Python数据分析考试必会题】

    ● 标题与摘要

    Python的数据分析中

    超参数调优方法:网格搜索

    ● 选择题

    以下说法错误的是:

    A 网格搜索是一种调参方法

    B 网格搜索会遍历几乎所有参数组合

    C 网格搜索的最大优点就是搜索速度快

    D 网格搜索在面对大数据集时会非常耗时

    ● 问题解析

    1.网格搜索(Grid Search)是一种调参方法,也称为穷举搜索,搜索的是参数,在所有候选的参数选择中,按步长依次调整参数,通过循环遍历,尝试每一种可能性,从所有的参数中找到在验证集上精度最高的参数,表现最好的参数就是最终的结果,原理就像是在数组里找到最大值。

    2.网格搜索可以保证在指定的参数范围内找到精度最高的参数,因为网格搜索会遍历所有可能参数的组合,在面对大数据集和多参数的情况下会非常耗时。

    3.网格搜索语法:

    Class sklearn.model_selection.GridSearchCV(estimator,param_grid,scoring=None,fit_params=None,n_jobs=None,iid=’warn’,refit=True,cv=’warn’,verbose=0,pre_dispatch=‘2*n_jobs’,error_score=’raise-deprecating’,return_train_score=’warn’)

    网格搜索参数说明:

    (1)estimator:选择使用的分类器,并且传入需要确定最佳的参数之外的其他参数。

    (2)param_grid:需要最优化的参数的取值,值为字典或者列表。

    (3)scoring=None:模型评价标准,默认为None。

    (4)n_jobs=1,n_jobs表示并行数,int为个数,-1表示与CPU核数一致,1为默认值。

    (5)iid=True,iid默认为True,当为True时,默认为各个样本fold概率分布一致,误差估计为所有样本之和,而不是各个fold的平均。

    (6)refit=True默认为True。在搜索参数结束后,用最佳参数结果再次拟合一遍全部数据集

    (7)cv=None,交叉验证参数,默认None,使用三折交叉验证。

    (8)verbose=0,scoring=None,verbose表示日志冗长度,0表示不输出训练过程,1表示偶尔输出,>1表示对每个子模型都输出。

    (9)pre_dispatch='2*n_jobs'表示指定总共发的并行任务数。

    4.网格搜索常用方法及属性:

    (1)grid.fit()表示运行网格搜索

    (2)grid.score()表示运行网格搜索后模型得分

    (3)grid_scores_表示给出不同参数情况下的评价结果

    (4)best_params_表示描述已取得最佳结果的参数的组合

    (5)best_score_表示提供优化过程期间观察到的最好评分

    5.利用决策树进行网格搜索的代码举例如图1所示,最终网格搜索的最优结果为max_depth=4。

    ● 附图 

    图1 网格搜索预测乳腺癌寻找最优参数

    ● 附图代码

    from sklearn.model_selection import GridSearchCV,KFold,train_test_split

    from sklearn.metrics import make_scorer , accuracy_score

    from sklearn.tree import DecisionTreeClassifier

    from sklearn.datasets import load_breast_cancer

    import warnings

    import pandas as pd

    from sklearn.neighbors import KNeighborsClassifier as KNN

    warnings.filterwarnings('ignore')

    data = load_breast_cancer()#load data

    #输出Python自带乳腺癌数据的维度

    print("Python自带乳腺癌数据的维度:",data.data.shape)

    X,y = data['data'] , data['target']

    #切分训练集、测试集数据

    X_train,X_test,y_train,y_test = train_test_split(X,y,train_size=0.8 , random_state=0)

    #决策树

    regressor = DecisionTreeClassifier(random_state=0)

    parameters = {'max_depth':range(1,6)}

    scorin_fnc = make_scorer(accuracy_score)

    kflod = KFold(n_splits=10)

    #网格搜索

    grid = GridSearchCV(regressor,parameters,scoring=scorin_fnc,cv=kflod)

    grid = grid.fit(X_train,y_train)

    reg = grid.best_estimator_

    #优化过程期间观察到的最好评分

    print('优化过程期间观察到的最好评分:%f'%grid.best_score_)

    for key in parameters.keys():

    #输出网格搜索最优结果

    print('最佳结果的参数:%s:%d'%(key,reg.get_params()[key]))

    ● 正确答案

    C

    欢迎大家转发,一起传播知识和正能量,帮助到更多人。期待大家提出宝贵改进建议,互相交流,收获更大。辛苦大家转发时注明出处(也是咱们公益编程交流群的入口网址),刘经纬老师共享知识相关文件下载地址为:http://liujingwei.cn

    展开全文
  • 参数优化 1 超参数优化 1.1 网格搜索类 1.1.1 枚举网格搜索 1.1.2 随机网格搜索 1.1.3 对半网格搜索(Halving Grid Search) 1.2 贝叶斯超参数优化(推荐) 原理 1.2.1 BayesOpt实现高斯过程GP(较慢) 1.2.1.1 ...

    1 超参数优化

    scoring:

    import sklearn
    sorted(sklearn.metrics.SCORERS.keys())
    

    1.1 网格搜索类

    1.1.1 枚举网格搜索

    classsklearn.model_selection.GridSearchCV(estimator, param_grid, , scoring=None, n_jobs=None, refit=True, cv=None, verbose=0, pre_dispatch='2n_jobs', error_score=nan, return_train_score=False)
    https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html#sklearn.model_selection.GridSearchCV

    NameDescription
    estimator调参对象,某评估器
    param_grid参数空间,可以是字典或者字典构成的列表,稍后介绍参数空间的创建方法
    scoring评估指标,支持同时输出多个参数
    n_jobs设置工作时参与计算的线程数
    refit挑选评估指标和最佳参数,在完整数据集上进行训练
    cv交叉验证的折数
    verbose输出工作日志形式
    pre_dispatch多任务并行时任务划分数量
    error_score当网格搜索报错时返回结果,选择’raise’时将直接报错并中断训练过程,其他情况会显示警告信息后继续完成训练
    return_train_score在交叉验证中是否显示训练集中参数得分
    • 主要是范围合适,枚举网格搜索能完成最优解的搜索,就是太费时间
    import time #计时模块time
    import pandas as pd
    from sklearn.ensemble import RandomForestRegressor as RFR
    from sklearn.model_selection import KFold, GridSearchCV
    def RMSE(cvresult,key):
        return (abs(cvresult[key])**0.5).mean()
    
    data = pd.read_csv( r"D:\学习资料\sklearn课件\AutoML与超参数优化——菜菜\train_encode.csv", index_col=0)
    X = data.iloc[:,:-1]
    y = data.iloc[:,-1]
    param_grid_simple = {'n_estimators': [*range(5,100,5)]
                         , 'max_depth': [*range(25,36,2)]
                         , "max_features": ["log2","sqrt","auto"]
                        }
    
    reg = RFR(random_state=1412,verbose=True,n_jobs=-1)
    
    cv = KFold(n_splits=5,shuffle=True,random_state=1412)
    
    search = GridSearchCV(estimator=reg
                         ,param_grid=param_grid_simple
                         ,scoring = "neg_mean_squared_error" #MSE
                         ,verbose = True
                         ,cv = cv
                         ,n_jobs=12)
    
    #=====【TIME WARNING: 5~10min】=====#
    start = time.time()
    search.fit(X,y)
    print(time.time() - start)
    
    search.best_estimator_
    """
    RandomForestRegressor(max_depth=33, max_features='log2', n_estimators=45,
                          n_jobs=-1, random_state=1412, verbose=True)
    """
    
    abs(search.best_score_)**0.5
    # 29840.676988215626
    

    1.1.2 随机网格搜索

    决定枚举网格搜索运算速度的因子一共有两个:

    1. 参数空间的大小:参数空间越大,需要建模的次数越多
    2. 数据量的大小:数据量越大,每次建模时需要的算力和时间越多

    所以解决方式就俩:

    • 调整搜索空间
    • 每次训练的数据

    在sklearn中,随机抽取参数子空间并在子空间中进行搜索的方法叫做随机网格搜索RandomizedSearchCV

    随机网格搜索在实际运行时,并不是先抽样出子空间,再对子空间进行搜索,而是逐个的,这种随机抽样是不放回的。我们可以控制随机网格搜索的迭代次数。

    class sklearn.model_selection.RandomizedSearchCV(estimator, param_distributions, *, n_iter=10, scoring=None, n_jobs=None, refit=True, cv=None, verbose=0, pre_dispatch='2*n_jobs', random_state=None, error_score=nan, return_train_score=False)

    https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.RandomizedSearchCV.html#sklearn.model_selection.RandomizedSearchCV

    NameDescription
    estimator调参对象,某评估器
    param_distributions全域参数空间,可以是字典或者字典构成的列表
    n_iter迭代次数,迭代次数越多,抽取的子参数空间越大
    scoring评估指标,支持同时输出多个参数
    n_jobs设置工作时参与计算的线程数
    refit挑选评估指标和最佳参数,在完整数据集上进行训练
    cv交叉验证的折数
    verbose输出工作日志形式
    pre_dispatch多任务并行时任务划分数量
    random_state随机数种子
    error_score当网格搜索报错时返回结果,选择’raise’时将直接报错并中断训练过程,其他情况会显示警告信息后继续完成训练
    return_train_score在交叉验证中是否显示训练集中参数得分
    import time #计时模块time
    import pandas as pd
    from sklearn.ensemble import RandomForestRegressor as RFR
    from sklearn.model_selection import KFold, RandomizedSearchCV
    
    #创造参数空间 - 使用与网格搜索时完全一致的空间
    param_grid_simple = {'n_estimators': [*range(5,100,5)]
                         , 'max_depth': [*range(25,36,2)]
                         , "max_features": ["log2","sqrt","auto"]
                        }
    
    #建立回归器、交叉验证
    reg = RFR(random_state=1412,verbose=True,n_jobs=12)
    cv = KFold(n_splits=5,shuffle=True,random_state=1412)
    
    #计算全域参数空间大小,因为要是不知道全域大小,就不知道在随机网格搜索中的迭代量
    len([*range(5,100,5)]) * len([*range(25,36,2)]) * 3
    # 342
    
    search = RandomizedSearchCV(estimator=reg
                                ,param_distributions=param_grid_simple
                                ,n_iter = 170 #子空间的大小是全域空间的约一半
                                ,scoring = "neg_mean_squared_error"
                                ,verbose = True
                                ,cv = cv
                                ,n_jobs=12)
    
    #训练随机搜索评估器
    #=====【TIME WARNING: 2~5min】=====#
    start = time.time()
    search.fit(X,y)
    print(time.time() - start)
    
    #查看模型结果
    search.best_estimator_
    """
    RandomForestRegressor(max_depth=35, max_features='log2', n_estimators=45,
                          n_jobs=12, random_state=1412, verbose=True)
    """
    
    abs(search.best_score_)**0.5
    # 29840.676988215626
    # 居然和枚举网格搜索一模一样。
    

    随机网格搜索能够有效的根本原因在于:

    • 抽样出的子空间反馈出与全域空间相似的分布
    • 子空间越大,子空间与全域空间越相似
    • 在与全域空间相似的空间中,随机搜索即便不能找到真正的最小值,也能找到与最小值高度接近的某一个次小值

    相似分布的直观形式:
    在这里插入图片描述
    在这里插入图片描述

    1.1.3 对半网格搜索(Halving Grid Search)

    在讲解随机网格搜索之前,我们梳理了决定枚举网格搜索运算速度的因子:

    1 参数空间的大小:参数空间越大,需要建模的次数越多

    2 数据量的大小:数据量越大,每次建模时需要的算力和时间越多

    面对枚举网格搜索过慢的问题,sklearn中呈现了两种优化方式:其一是调整搜索空间,其二是调整每次训练的数据。调整搜索空间的方法就是随机网格搜索,而调整每次训练数据的方法就是对半网格搜索。

    假设现在存在数据集 D D D,我们从数据集 D D D中随机抽样出一个子集 d d d。如果一组参数在整个数据集 D D D上表现较差,那大概率这组参数在数据集的子集 d d d上表现也不会太好。反之,如果一组参数在子集 d d d上表现不好,我们也不会信任这组参数在全数据集 D D D上的表现。参数在子集与全数据集上反馈出的表现一致,如果这一假设成立,那在网格搜索中,比起每次都使用全部数据来验证一组参数,或许我们可以考虑只带入训练数据的子集来对超参数进行筛选,这样可以极大程度地加速我们的运算。

    但在现实数据中,这一假设要成立是有条件的,即任意子集的分布都与全数据集D的分布类似。当子集的分布越接近全数据集的分布,同一组参数在子集与全数据集上的表现越有可能一致。根据之前在随机网格搜索中得出的结论,我们知道子集越大、其分布越接近全数据集的分布,但是大子集又会导致更长的训练时间,因此为了整体训练效率,我们不可能无限地增大子集。这就出现了一个矛盾:大子集上的结果更可靠,但大子集计算更缓慢。对半网格搜索算法设计了一个精妙的流程,可以很好的权衡子集的大小与计算效率问题,我们来看具体的流程:

    1、首先从全数据集中无放回随机抽样出一个很小的子集 d 0 d_0 d0,并在 d 0 d_0 d0上验证全部参数组合的性能。根据 d 0 d_0 d0上的验证结果,淘汰评分排在后1/2的那一半参数组合


    2、然后,从全数据集中再无放回抽样出一个比 d 0 d_0 d0大一倍的子集 d 1 d_1 d1,并在 d 1 d_1 d1上验证剩下的那一半参数组合的性能。根据 d 1 d_1 d1上的验证结果,淘汰评分排在后1/2的参数组合


    3、再从全数据集中无放回抽样出一个比 d 1 d_1 d1大一倍的子集 d 2 d_2 d2,并在 d 2 d_2 d2上验证剩下1/4的参数组合的性能。根据 d 2 d_2 d2上的验证结果,淘汰评分排在后1/2的参数组合……

    持续循环。如果使用S代表首次迭代时子集的样本量,C代表全部参数组合数,则在迭代过程中,用于验证参数的数据子集是越来越大的,而需要被验证的参数组合数量是越来越少的:

    迭代次数子集样本量参数组合数
    1SC
    22S 1 2 \frac{1}{2} 21C
    34S 1 4 \frac{1}{4} 41C
    48S 1 8 \frac{1}{8} 81C
    ……
    (当C无法被除尽时,则向上取整)

    备选参数组合只剩下一组,或剩余可用的数据不足,循环就会停下,具体地来说, 1 n \frac{1}{n} n1C <= 1或者nS > 总体样本量,搜索就会停止。在实际应用时,哪一种停止条件会先被触发,需要看实际样本量及参数空间地大小。同时,每次迭代时增加的样本量、以及每次迭代时不断减少的参数组合都是可以自由设定的。

    在这种模式下,只有在不同的子集上不断获得优秀结果的参数组合能够被留存到迭代的后期,最终选择出的参数组合一定是在所有子集上都表现优秀的参数组合。这样一个参数组合在全数据上表现优异的可能性是非常大的,同时也可能展现出比网格/随机搜索得出的参数更大的泛化能力。

    • 对半网格搜索的局限性

    然而这个过程当中会存在一个问题:子集越大时,子集与全数据集D的分布会越相似,但整个对半搜索算法在开头的时候,就用最小的子集筛掉了最多的参数组合。如果最初的子集与全数据集的分布差异巨大的化,在对半搜索开头的前几次迭代中,就可能筛掉许多对全数据集D有效的参数,因此对半网格搜索最初的子集一定不能太小。

    在对半网格搜索过程中,子集的样本量时呈指数级增长:

    对于对半网格搜索应用来说,最困难的部分就是决定搜索本身复杂的参数组合。在调参时,如果我们希望参数空间中的备选组合都能够被充分验证,则迭代次数不能太少(例如,只迭代3次),因此factor不能太大。但如果factor太小,又会加大迭代次数,同时拉长整个搜索的运行时间。因此,我们一般考虑以下两个点:

    1、min_resources的值不能太小,且迭代过程中使用尽量多的数据

    2、迭代完毕之后,剩余的验证参数组合不能太多,10~20之间即可接受

    对半网格搜索的类如下所示:
    class sklearn.model_selection.HalvingGridSearchCV(estimator, param_grid, *, factor=3, resource='n_samples', max_resources='auto', min_resources='exhaust', aggressive_elimination=False, cv=5, scoring=None, refit=True, error_score=nan, return_train_score=True, random_state=None, n_jobs=None, verbose=0)

    全部参数如下所示:

    NameDescription
    estimator调参对象,某评估器
    param_grid参数空间,可以是字典或者字典构成的列表
    factor每轮迭代中新增的样本量的比例,同时也是每轮迭代后留下的参数组合的比例
    resource设置每轮迭代中增加的验证资源的类型
    max_resources在一次迭代中,允许被用来验证任意参数组合的最大样本量
    min_resources首次迭代时,用于验证参数组合的样本量r0
    aggressive_elimination是否以全部数被使用完成作为停止搜索的指标,如果不是,则采取措施
    cv交叉验证的折数
    scoring评估指标,支持同时输出多个参数
    refit挑选评估指标和最佳参数,在完整数据集上进行训练
    error_score当网格搜索报错时返回结果,选择’raise’时将直接报错并中断训练过程
    其他情况会显示警告信息后继续完成训练
    return_train_score在交叉验证中是否显示训练集中参数得分
    random_state控制随机抽样数据集的随机性
    n_jobs设置工作时参与计算的线程数
    verbose输出工作日志形式

    https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.HalvingGridSearchCV.html#sklearn.model_selection.HalvingGridSearchCV

    NameDescription
    estimator调参对象,某评估器
    param_grid参数空间,可以是字典或者字典构成的列表
    factor每轮迭代中新增的样本量的比例,同时也是每轮迭代后留下的参数组合的比例
    resource设置每轮迭代中增加的验证资源的类型
    max_resources在一次迭代中,允许被用来验证任意参数组合的最大样本量
    min_resources首次迭代时,用于验证参数组合的样本量r0
    aggressive_elimination是否以全部数被使用完成作为停止搜索的指标,如果不是,则采取措施
    cv交叉验证的折数
    scoring评估指标,支持同时输出多个参数
    refit挑选评估指标和最佳参数,在完整数据集上进行训练
    error_score当网格搜索报错时返回结果,选择’raise’时将直接报错并中断训练过程
    其他情况会显示警告信息后继续完成训练
    return_train_score在交叉验证中是否显示训练集中参数得分
    random_state控制随机抽样数据集的随机性
    n_jobs设置工作时参与计算的线程数
    verbose输出工作日志形式

    在sklearn当中,我们可以使用HalvingGridSearchCV类来实现对半网格搜索。Halving搜索是sklearn 1.0.1版本才新增的功能,因此现在该功能还处于实验阶段,在导入该类的时候需要同时导入用以开启对半网格搜索的辅助功能enable_halving_search_cv。当且仅当该功能被导入时,HalvingGridSearchCV才能够被导入和使用。

    import re
    import sklearn
    import numpy as np
    import pandas as pd
    import matplotlib as mlp
    import matplotlib.pyplot as plt
    import time
    from sklearn.ensemble import RandomForestRegressor as RFR
    from sklearn.experimental import enable_halving_search_cv
    from sklearn.model_selection import KFold, HalvingGridSearchCV
    
    
    param_grid_simple = {'n_estimators': [*range(5,100,5)]
                         , 'max_depth': [*range(25,36,2)]
                         , "max_features": ["log2","sqrt",16,32,64,"auto"]
                        }
    #建立回归器、交叉验证
    reg = RFR(random_state=1412,verbose=True,n_jobs=12)
    cv = KFold(n_splits=5,shuffle=True,random_state=1412)
    
    """
    对半网格搜索(Halving Grid Search)参数的确定:
    (需要多尝试)
    factor = 1.3 # 每次变化量
    n_samples = X.shape[0]
    n_splits = 5
    min_resources = 100 # 初始样本量
    space = 342 # 这时param_grid_simple中每一项参数的个数乘积
    
    for i in range(100):
        if (min_resources*factor**i > n_samples) or (space/factor**i < 1):
            break
        print(i,"本轮迭代样本:{}".format(min_resources*factor**i)
              ,"本轮验证参数组合:{}".format(space//factor**i + 1))
              
    # 通常最后一次剩下的情况不能太大或太小10~20就差不多了。
    # 另外最后一次样本进行和训练集大小差不多
    """
    #定义随机搜索
    search = HalvingGridSearchCV(estimator=reg
                                ,param_grid=param_grid_simple
                                ,factor=1.3
                                ,min_resources=100
                                ,scoring = "neg_mean_squared_error"
                                ,verbose = True
                                ,random_state=1412
                                ,cv = cv
                                ,n_jobs=12)
    
    start = time.time()
    search.fit(X,y)
    print(time.time() - start)
    
    #查看最佳评估器
    search.best_estimator_
    """
    RandomForestRegressor(max_depth=31, max_features='sqrt', n_estimators=95,
                          n_jobs=12, random_state=1412, verbose=True)
    """
    
    #查看最佳评估器
    abs(search.best_score_)**0.5
    # 30915.40250783341
    

    1.2 贝叶斯超参数优化(推荐)

    贝叶斯优化具有随机性,所以一定要注意random_state等随机因素的确定,保证结果的可复现性。

    原理

    首先,我们不理会HPO的问题,先来看待下面的例子。假设现在我们知道一个函数 f ( x ) f(x) f(x)的表达式以及其自变量 x x x的定义域,现在,我们希望求解出 x x x的取值范围上 f ( x ) f(x) f(x)的最小值,你打算如何求解这个最小值呢?面对这个问题,无论是从单纯的数学理论角度,还是从机器学习的角度,我们都已经见过好几个通俗的思路:

    • 1 我们可以对 f ( x ) f(x) f(x)求导、令其一阶导数为0来求解其最小值

    函数 f ( x ) f(x) f(x)可微,且微分方程可以直接被求解

    • 2 我们可以通过梯度下降等优化方法迭代出 f ( x ) f(x) f(x)的最小值

    函数 f ( x ) f(x) f(x)可微,且函数本身为凸函数

    • 3 我们将全域的 x x x带入 f ( x ) f(x) f(x)计算出所有可能的结果,再找出最小值

    函数 f ( x ) f(x) f(x)相对不复杂、自变量维度相对低、计算量可以承受

    当我们知道函数 f ( x ) f(x) f(x)的表达式时,以上方法常常能够有效,但每个方法都有自己的前提条件。假设现在函数 f ( x ) f(x) f(x)是一个平滑均匀的函数,但它异常复杂、且不可微,我们无法使用上述三种方法中的任意一种方法求解,但我们还是想求解其最小值,可以怎么办呢?由于函数异常复杂,带入任意 x x x计算的所需的时间很长,所以我们不太可能将全域 x x x都带入进行计算,但我们还是可以从中随机抽样部分观测点来观察整个函数可能存在的趋势。于是我们选择在 x x x的定义域上随机选择了4个点,并将4个点带入 f ( x ) f(x) f(x)进行计算,得到了如下结果:

    01

    好了,现在有了这4个观测值,你能告诉我 f ( x ) f(x) f(x)的最小值在哪里吗?你认为最小值点可能在哪里呢?大部分人会倾向于认为,最小值点可能非常接近于已观测出4个 f ( x ) f(x) f(x)值中最小的那个值,但也有许多人不这么认为。当我们有了4个观测值,并且知道我们的函数时相对均匀、平滑的函数,那我们可能对函数的整体分布有如下猜测:

    02

    当我们对函数整体分布有一个猜测时,这个分布上一定会存在该函数的最小值。同时,不同的人可能对函数的整体分布有不同的猜测,不同猜测下对应的最小值也是不同的。

    03

    04

    现在,假设我们邀请了数万个人对该问题做出猜测,每个人所猜测的曲线如下图所示。不难发现,在观测点的附近,每个人猜测的函数值差距不大,但是在远离远侧点的地方,每个人猜测的函数值就高度不一致了。这也是当然的,因为观测点之间函数的分布如何完全是未知的,并且该分布离观测点越远时,我们越不确定真正的函数值在哪里,因此人们猜测的函数值的范围非常巨大。

    现在,我们将所有猜测求均值,并将任意均值周围的潜在函数值所在的区域用色块表示,可以得到一条所有人猜测的平均曲线。不难发现,色块所覆盖的范围其实就是大家猜测的函数值的上界和下界,而任意 x x x所对应的上下界差异越大,表示人们对函数上该位置的猜测值的越不确定。因此上下界差异可以衡量人们对该观测点的置信度,色块范围越大,置信度越低


    在观测点周围,置信度总是很高的,远离观测点的地方,置信度总是很低,所以如果我们能够在置信度很低的地方补充一个实际的观测点,我们就可以很快将众人的猜测统一起来。以下图为例,当我们在置信度很低的区间内取一个实际观测值时,围绕该区间的“猜测”会立刻变得集中,该区间内的置信度会大幅升高。

    当整个函数上的置信度都非常高时,我们可以说我们得出了一条与真实的 f ( x ) f(x) f(x)曲线高度相似的曲线 f ∗ f^* f,次数我们就可以将 f ∗ f^* f的最小值当作真实 f ( x ) f(x) f(x)的最小值来看待。自然,如果估计越准确, f ∗ f^* f越接近 f ( x ) f(x) f(x),则 f ∗ f^* f的最小值也会越接近于 f ( x ) f(x) f(x)的真实最小值。那如何才能够让 f ∗ f^* f更接近 f ( x ) f(x) f(x)呢?根据我们刚才提升置信度的过程,很明显——观测点越多,我们估计出的曲线会越接近真实的 f ( x ) f(x) f(x)。然而,由于计算量有限,我们每次进行观测时都要非常谨慎地选择观测点。那现在,如何选择观测点才能够最大程度地帮助我们估计出 f ( x ) f(x) f(x)的最小值呢?

    有非常多的方法,其中最简单的手段是使用最小值出现的频数进行判断。由于不同的人对函数的整体分布有不同的猜测,不同猜测下对应的最小值也是不同的,根据每个人猜测的函数结果,我们在 X X X轴上将定义域区间均匀划分为100个小区间,如果有某个猜测的最小值落在其中一个区间中,我们就对该区间进行计数(这个过程跟对离散型变量绘制直方图的过程完全一致)。当有数万个人进行猜测之后,我们同时也绘制了基于 X X X轴上不同区间的频数图,频数越高,说明猜测最小值在该区间内的人越多,反之则说明该猜测最小值在该区间内的人越少。该频数一定程度上反馈出最小值出现的概率,频数越高的区间,函数真正的最小值出现的概率越高

    当我们将 X X X轴上的区间划分得足够细后,绘制出的频数图可以变成概率密度曲线,曲线的最大值所对应的点是 f ( x ) f(x) f(x)的最小值的概率最高,因此很明显,我们应该将曲线最大值所对应的点确认为下一个观测点。根据图像,我们知道最小值最有可能在的区间就在x=0.7左右的位置。当我们不取新的观测点时,现在 f ( x ) f(x) f(x)上可以获得的可靠的最小值就是x=0.6时的点,但我们如果在x=0.7处取新的观测值,我们就很有可能找到比当前x=0.6的点还要小的 f m i n f_{min} fmin。因此,我们可以就此决定,在x=0.7处进行观测。


    当我们在x=0.7处取出观测值之后,我们就有了5个已知的观测点。现在,我们再让数万人根据5个已知的观测点对整体函数分布进行猜测,猜测完毕之后再计算当前最小值频数最高的区间,然后再取新的观测点对 f ( x ) f(x) f(x)进行计算。当允许的计算次数被用完之后(比如,500次),整个估计也就停止了。

    你发现了吗?在这个过程当中,我们其实在不断地优化我们对目标函数 f ( x ) f(x) f(x)的估计,虽然没有对 f ( x ) f(x) f(x)进行全部定义域上的计算,也没有找到最终确定一定是 f ( x ) f(x) f(x)分布的曲线,但是随着我们观测的点越来越多,我们对函数的估计是越来越准确的,因此也有越来越大的可能性可以估计出 f ( x ) f(x) f(x)真正的最小值。这个优化的过程,就是贝叶斯优化

    这个方法的基本思想还是通过计算一些点的情况,通过这些确定的点的情况判断附近的点的情况,将可能产生最小值得地方附近计算这里的模型值,通过这里的值进一步判断,(若我们计算了情况A,那么A附近的情况能大致模拟出来,离确定点越近判断结果越可靠,越远波动越大,越不可信。)

    在贝叶斯优化的数学过程当中,我们需要有以下步骤:

    1. 定义需要估计的 f ( x ) f(x) f(x)以及 x x x的定义域。
    2. 取出有线的 N 个 x N个x Nx上的值,求出这些值对应的 f ( x ) f(x) f(x)也就是求出观测模型的预测值。
    3. 根据有限的观测值对于函数进行估计,该假设被称为贝叶斯优化中的先验假设,通过该估计 f ∗ f* f上的目标值最大值或最小值。
    4. 定义某种规则以确定下一个需要计算的的观测点。

    并持续在 2 − 4 2-4 24 步中进行循环,直到假设分布上的目标值达到我们的标准,或者说所有计算资源被用完为止,例如最多观测 M M M 次或者最多运行 t t t 分钟。

    以上流程又被称为序贯模型优化(SMBO),是最为经典的贝叶斯优化方法。在实际的运算过程当中,尤其是超参数优化的过程当中,有以下具体细节需要注意:

    • 当贝叶斯优化不被用于HPO时,一般 f ( x ) f(x) f(x)可以是完全的黑盒函数(black box function,也译作黑箱函数,即只知道 x x x f ( x ) f(x) f(x)的对应关系,却丝毫不知道函数内部规律、同时也不能写出具体表达式的一类函数),因此贝叶斯优化也被认为是可以作用于黑盒函数估计的一类经典方法。但在HPO过程当中,需要定义的 f ( x ) f(x) f(x)一般是交叉验证的结果/损失函数的结果,而我们往往非常清楚损失函数的表达式,只是我们不了解损失函数内部的具体规律,因此HPO中的 f ( x ) f(x) f(x)不能算是严格意义上的黑盒函数。

    • 在HPO中,自变量 x x x就是超参数空间。在上述二维图像表示中, x x x为一维的,但在实际进行优化时,超参数空间往往是高维且极度复杂的空间。

    • 最初的观测值数量n、以及最终可以取到的最大观测数量m都是贝叶斯优化的超参数,最大观测数量m也决定了整个贝叶斯优化的迭代次数。

    • 在第3步中,根据有限的观测值、对函数分布进行估计的工具被称为概率代理模型(Probability Surrogate model),毕竟在数学计算中我们并不能真的邀请数万人对我们的观测点进行连线。这些概率代理模型自带某些假设,他们可以根据廖廖数个观测点估计出目标函数的分布 f ∗ f^* f(包括 f ∗ f^* f上每个点的取值以及该点对应的置信度)。在实际使用时,概率代理模型往往是一些强大的算法,最常见的比如高斯过程、高斯混合模型等等。传统数学推导中往往使用高斯过程,但现在最普及的优化库中基本都默认使用基于高斯混合模型的TPE过程。

    • 在第4步中用来确定下一个观测点的规则被称为采集函数(Aquisition Function),采集函数衡量观测点对拟合 f ∗ f^* f所产生的影响,并选取影响最大的点执行下一步观测,因此我们往往关注采集函数值最大的点。最常见的采集函数主要是概率增量PI(Probability of improvement,比如我们计算的频数)、期望增量(Expectation Improvement)、置信度上界(Upper Confidence Bound)、信息熵(Entropy)等等。上方gif图像当中展示了PI、UCB以及EI。其中大部分优化库中默认使用期望增量。

    在HPO中使用贝叶斯优化时,我们常常会看见下面的图像,这张图像表现了贝叶斯优化的全部基本元素,我们的目标就是在采集函数指导下,让 f ∗ f^* f尽量接近 f ( x ) f(x) f(x)

    贝叶斯优化是当今黑盒函数估计领域最为先进和经典的方法,在同一套序贯模型下使用不同的代理模型以及采集函数、还可以发展出更多更先进的贝叶斯优化改进版算法,因此,贝叶斯优化的其算法本身就多如繁星,实现各种不同种类的贝叶斯优化的库也是琳琅满目,几乎任意一个专业用于超参数优化的工具库都会包含贝叶斯优化的内容。我们可以在以下页面找到大量可以实现贝叶斯优化方法的HPO库:https://www.automl.org/automl/hpo-packages/ ,其中大部分库都是由独立团队开发和维护,因此不同的库之间之间的优劣、性格、功能都有很大的差异。在课程中,我们将介绍如下三个可以实现贝叶斯优化的库:bayesian-optimizationhyperoptoptuna

    HPO库优劣评价推荐指数
    bayes_opt✅实现基于高斯过程的贝叶斯优化
    ✅当参数空间由大量连续型参数构成时

    ⛔包含大量离散型参数时避免使用
    ⛔算力/时间稀缺时避免使用
    ⭐⭐
    hyperopt✅实现基于TPE的贝叶斯优化
    ✅支持各类提效工具
    ✅进度条清晰,展示美观,较少怪异警告或报错
    ✅可推广/拓展至深度学习领域

    ⛔不支持基于高斯过程的贝叶斯优化
    ⛔代码限制多、较为复杂,灵活性较差
    ⭐⭐⭐⭐
    optuna✅(可能需结合其他库)实现基于各类算法的贝叶斯优化
    ✅代码最简洁,同时具备一定的灵活性
    ✅可推广/拓展至深度学习领域

    ⛔非关键性功能维护不佳,有怪异警告与报错
    ⭐⭐⭐⭐

    1.2.1 BayesOpt实现高斯过程GP(较慢)

    from bayes_opt import BayesianOptimization
    

    这个开源比较早,代码简单,不过处理方式较为原始,缺乏阶的提效监控能力,对于算力要求比较高,但是当我们必须要实现基于高斯过程的贝叶斯优化算法的参数空间时并且带有大量的连续性参数时,我们才会考虑使用。

    • 过程无法复现,结果可以
    • 效率不足,无法提前停止,有时会出现训练相同的参数组合的情况

    1.2.1.1 定义目标函数

    就是我们首先需要定义一个函数我们给这个函数,一组超参数它会返回给我们该超参数下模型的效果,这个效果可以用损失或者得分来表示。但是有三个影响目标函数定义的规则。

    • 目标函数的输入必须是具体的超参数而不能是整个超参数空间,更不能是数据算法等超三数以外的元素。
    • 超参数的输入值只能是fud数不支持整数或字符串
    • 只支持寻找 f ( x ) f(x) f(x)最大值,不寻找最小值

    通俗点说就是贝叶斯优化会通过这个函数进行判断该参数模型的优劣程度

    def bayesopt_objective(n_estimators, max_depth, max_features, min_impurity_decrease):
        
        # 定义评估器
        # 需要调整的超参数等于目标函数的输入,不需要调整的超参数则直接等于固定值
        # 默认参数输入一定是浮点数,因此需要套上int做强制类型转换成整数
        reg = RFR(n_estimators=int(n_estimators),
                  max_depth=int(max_depth),
                  max_features=int(max_features),
                  min_impurity_decrease=min_impurity_decrease,
                  random_state=1412,
                  verbose=False,
                  n_jobs=-1)
        # 定义损失的输出,5折交叉验证的结果,输出为负根均方误差(-RMSE)
        # 注意,交叉验证需要使用数据,但是我们不能够让数据X,y成为目标函数的输入。
        cv = KFold(n_splits=5, shuffle=True, random_state=1412)
        validation_loss = cross_validate(reg, X, y,
                                         scoring="neg_root_mean_squared_error",
                                         cv=cv,
                                         verbose=False,
                                         n_jobs=-1,
                                         error_score="raise"
                                         # 如果交叉验证出错了。就会显示错误理由
                                         )
        # 交叉验证输出的评估指标是负均方误差,因此本来就是负的失误
        # 目标函数可直接输出该损失的均值
        return np.mean(validation_loss["test_score"])
    

    1.2.1.2 定义参数空间

    在任意超参数优化期中优化器中,将参数空格中的超参数组合作为备选组合,一组一组输入到算法中进行训练,在贝叶斯优化中,超参数组合会被输入到我们定义好的目标函数 f ( x ) f(x) f(x)中,在 b a y e s − o p t bayes-opt bayesopt中我们使用字典方式来定义参数空间,其中参数的名称为键,参数的值为范围值(元组),范围均为双向闭区间。

    就是我们网格搜索的范围

    # 注意是元组
    param_grid_simple = {
        "n_estimators": (80, 100),
        "max_depth": (10, 25),
        "max_feature": (10, 20),
        "min_impurity_decrease": (0, 1)
    }
    

    因为参数只支持参数,中间的上界和下界,不支持写步长等参数,所以会直接取闭区间任意的浮点数作为备选参数,因此在填入模型参数中是整数时,我们要强制类型转化成int类型。

    1.2.1.3 定义优化目标函数和具体流程

    在任意贝叶斯优化算法的实践过程中,一定都会有设计随机性的过程,例如随机抽取点作为观测点,随机抽取部分观测点进行采集函数的计算,在大部分的优化过程中,这种随机性无法控制。即使我们填写了随机种子,优化算法也不能够固定下来,因此我们可以尝试填写随机种子,但需要记住优化算法,每一次运行时都不会一样。
    虽然优化算法无法被复现,但是优化算法得出来的最佳超参数结果确实可以复现的,只要优化完毕之后,可以从优化算法的实例化对象中取出最佳参数组合以及最佳分数,该最佳分数组合被输入到交叉验证中是一定可以复现其最佳分数的,如果没有复现,最佳分数则交叉,验证过程的随机种子设置存在问题或者优化算法迭代流程存在问题。

    就是通过给定一个初始点量和迭代次数调用目标函数判断优劣程度并作出判断如何进行下一次迭代,最后返回最优值,实际上这部分不一定写成函数形式,不过函数形式好看。

    # 定义优化目标函数和具体流程
    def param_bayes_opt(param_grid_simple,init_points, n_iter):
    
        # 定义优化器,先实例化优化器
        opt = BayesianOptimization(f=bayes_objective,  # 需要优化的目标函数
                                   pbounds=param_grid_simple,  # 备选参数空间
                                   random_state=1412)  # 随机种子,但是从根本上来说无法控制
    
        # 使用优化器,记住bayes_opt只支持最大化
        opt.maximize(init_points=init_points,  # 抽取多少个初始观测值
                     n_iter=n_iter)  # 一共观测/迭代次数
    
        # 优化完成,取出最佳参数和最佳分数
        params_best = opt.max["params"]
        score_best = opt.max["target"]
        # 打印最佳参数和最佳分数
        print("\n", "\n", "best params :", params_best)
        print("\n", "\n", "best cvscore :", score_best)
        # 返回最佳参数和最佳分数
        return params_best, score_best
    

    1.2.1.4 验证(非必要)

    我们刚才提到最优化方法的最优化参数可以被复现,所以我们定一个函数取出最优化结果观察我们的结果通过结果来判断是否存在问题。

    就是将最有参数组复现出来,这个就特别简单

    def bayes_opt_validation(params_best):
        reg = RFR(n_estimators=int(params_best["n_estimators"]),
                  max_depth=int(params_best["max_depth"]),
                  max_features=int(params_best["max_features"]),
                  min_impurity_decrease=params_best["min_impurity_decrease"],
                  random_state=1412,
                  verbose=False,
                  n_jobs=-1)
        cv = KFold(n_splits=5, shuffle=True, random_state=1412)
        validation_loss = cross_validate(reg, X, y,
                                         scoring="neg_root_mean_squared_error",
                                         cv=cv,
                                         verbose=False,
                                         n_jobs=-1,
                                         error_score="raise"
                                         # 如果交叉验证出错了。就会显示错误理由
                                         )
        return np.mean(validation_loss["test_score"])
    

    1.2.1.5 执行

    import sklearn
    import numpy as np
    import pandas as pd 
    from sklearn.model_selection import KFold, cross_validate
    from sklearn.ensemble import RandomForestRegressor as RFR
    from bayes_opt import BayesianOptimization
    import hyperopt
    from hyperopt.early_stop import no_progress_loss
    import optuna
    
    data = pd.read_csv(
        r"D:\学习资料\sklearn课件\AutoML与超参数优化——菜菜\train_encode.csv", index_col=0)
    X = data.iloc[:, :-1]
    y = data.iloc[:, -1]
    
    start = time.time()
    # 运行贝叶斯
    params_best, score_best = param_bayes_opt(
        param_grid_simple, 20, 280)  # 初始化看了20个,观测值后面替代280次
    # 记录并计算时间
    print("训练时间:%s 分钟" % ((time.time()-start)/60))
    # 验证模型结果
    validation_score = bayes_opt_validation(params_best)
    # 显示最优化模型参数结果
    print("\n", "\n", "validation_score: ", validation_score)
    
    """
     best params : {'max_depth': 22.754861061950113, 
     				'max_features': 14.020522796997795,
     			    'min_impurity_decrease': 0.0,
     			    'n_estimators': 88.70408461644216}
    
     
     best cvscore : -28385.755008050553
      训练时间:7.725130772590637 分钟
    
     validation_score:  -28385.755008050553
    """
    

    会显示迭代过程
    在这里插入图片描述

    1.2.2 hyperopt贝叶斯方法(推荐)

    使用GBDT进行调参,先根据之前对数据的训练确定GBDT参数大致范围:

    在这里插入图片描述

    1.2.2.1 导包–导入数据–init初始学习器的建立

    import sklearn
    import numpy as np
    import pandas as pd
    from sklearn.model_selection import KFold, cross_validate
    from sklearn.ensemble import RandomForestRegressor as RFR, GradientBoostingRegressor as GBR
    import hyperopt
    from hyperopt import hp, tpe, Trials, partial, fmin
    from hyperopt.early_stop import no_progress_loss
    import optuna
    
    data = pd.read_csv(
        r"D:\学习资料\sklearn课件\AutoML与超参数优化——菜菜\train_encode.csv", index_col=0)
    X = data.iloc[:, :-1]
    y = data.iloc[:, -1]
    
    # init初始学习器的建立
    rf = RFR(n_estimators=22, max_depth=22, max_features=14,
             min_impurity_decrease=0, random_state=1412, verbose=False)
    

    1.2.2.2 定义目标函数

    # 这个和BayersOpt相同均是浮点数,若是整型数就要强制类型转换,若需要字符串或者浮点数就不用管
    # params是特定的参数空间字典格式,在参数空间的确定会提到
    # 若参数已经确定,就可直接填入参数
    def hyperopt_objective(params):
        reg = GBR(n_estimators=int(params["n_estimators"]),
                  learning_rate=params["learning_rate"],
                  criterion=params["criterion"],
                  loss=params["loss"],
                  max_depth=int(params["max_depth"]),
                  max_features=params["max_features"],
                  subsample=params["subsample"],
                  min_impurity_decrease=params["min_impurity_decrease"],
                  init=rf,
                  random_state=1412,
                  verbose=False)
    
        cv = KFold(n_splits=5, shuffle=True, random_state=1412)
        validation_loss = cross_validate(reg, X, y,
                                         scoring="neg_root_mean_squared_error",
                                         cv=cv,
                                         verbose=False,
                                         n_jobs=-1,
                                         error_score="raise")
        return np.mean(abs(validation_loss["test_score"]))# 这里和BayesOpt不同,这里使用了绝对值,因为hyperopt支持fmin,最小化
    

    1.2.2.3 参数空间的确定

    在任意超参数优化器中,优化器会将参数空格中的超参数组合作为备选组合,一组一组输入到算法中进行训练。在贝叶斯优化中,超参数组合会被输入我们定义好的目标函数 f ( x ) f(x) f(x)中。

    在hyperopt中,我们使用特殊的字典形式来定义参数空间,其中键值对上的键可以任意设置,只要与目标函数中索引参数的键一致即可,键值对的值则是hyperopt独有的hp函数,包括了:

    hp.quniform(“参数名称”, 下界, 上界, 步长) - 适用于均匀分布的浮点数

    hp.uniform(“参数名称”,下界, 上界) - 适用于随机分布的浮点数

    hp.randint(“参数名称”,上界) - 适用于[0,上界)的整数,区间为前闭后开

    hp.choice(“参数名称”,[“字符串1”,“字符串2”,…]) - 适用于字符串类型,最优参数由索引表示

    hp.choice(“参数名称”,[*range(下界,上界,步长)]) - 适用于整数型,最优参数由索引表示

    hp.choice(“参数名称”,[整数1,整数2,整数3,…]) - 适用于整数型,最优参数由索引表示

    hp.choice(“参数名称”,[“字符串1”,整数1,…]) - 适用于字符与整数混合,最优参数由索引表示

    在hyperopt的说明当中,并未明确参数取值范围空间的开闭,根据实验,如无特殊说明,hp中的参数空间定义方法应当都为前闭后开区间。我们依然使用在随机森林上获得最高分的随机搜索的参数空间:

    # 若是整数或者浮点数,并且是需要确定步长使用hp.quniform格式
    # 若是字符串形式,用hp.choice格式数据
    # hp.quniform("参数名", 起始位置, 结束位置, 步长)
    # hp.choice("参数名",参数列表)
    param_grid_simple = {'n_estimators': hp.quniform("n_estimators", 60, 140, 5),
                         "learning_rate": hp.quniform("learning_rate", 0.05, 1.05, 0.02),
                         "criterion": hp.choice("criterion", ["friedman_mse", "squared_error", "mse", "mae"]),
                         "loss": hp.choice("loss", ["squared_error"]),
                         "max_depth": hp.quniform("max_depth", 2, 12, 1),
                         "subsample": hp.quniform("subsample", 0.1, 0.6, 0.05),
                         "max_features": hp.choice("max_features", ["log2", "sqrt", 16,  17, 18, 19, 15, 14, "auto"]),
                         "min_impurity_decrease": hp.quniform("min_impurity_decrease", 0, 10, 0.5)
                         }
    

    1.2.2.4 定义优化目标函数

    def param_hyperopt(max_evals=100):
        # 保存迭代过程的对象
        trials = Trials()
        # 设置提前停止,100次迭代,结果都没有减小就停止
        early_stop_fn = no_progress_loss(100)
        # 定义代理模型
        params_best = fmin(hyperopt_objective,  # 目标函数
                           space=param_grid_simple,  # 参数空间
                           algo=tpe.suggest,  # 最优化代理模型
                           max_evals=max_evals,  # 最大迭代次数,默认100
                           verbose=True,  # 显示过程
                           trials=trials,  # 保存迭代过程
                           early_stop_fn=early_stop_fn  # 设置提前停止
                           )
        # 打印最优参数,fmin会自动打印最佳分数
        print("\n", "\n", "best params: ", params_best, "\n")
        # 返回最优参数和迭代过程
        return params_best, trials
    

    1.2.2.5 验证函数(非必要)

    def hyperopt_validation(params):
        reg = GBR(n_estimators=int(params["n_estimators"]),
                  learning_rate=params["learning_rate"],
                  criterion=params["criterion"],
                  loss=params["loss"],
                  max_depth=int(params["max_depth"]),
                  max_features=params["max_features"],
                  subsample=params["subsample"],
                  min_impurity_decrease=params["min_impurity_decrease"],
                  init=rf,
                  random_state=1412)  # GBR中的random_state只能够控制特征抽样,不能控制样本抽样, verbose=False
        cv = KFold(n_splits=5, shuffle=True, random_state=1412)
        validation_loss = cross_validate(reg, x, y,
                                         scoring="neg_root_mean_squared_error",
                                         cv=cv,
                                         verbose=False,
                                         n_jobs=-1)
        return np.mean(abs(validation_loss["test_score"]))
    

    1.2.2.6 执行和建议

    params_best,trials = param_hyperopt(100)
    # 一般使用小于0.1%的空间进行训练
    # 在多次调整模型参数范围时,获取最优参数值组时,若有些参数一直未发生变化,可能说明就是最优值,可以将对应参数值下的选项只保留最优的那一个,以缩小参数空间范围,提高效率
    # 注意,在经过上面的调整后,需要将目标函数hyperopt_objective中的模型进行调整,将模型参数设置成固定的值
    # 根据每一次结果调整参数范围,保证参数空间包含最优值
    # 再确定范围后,可以进一步缩小参数空间范围,减小步长或增大迭代次数以确定更加精细的参数范围
    
    结果:
    #损失 best loss: 27549.81165947049
    #最优参数:best params:  {'criterion': 3, 'learning_rate': 0.14, 'loss': 0, 'max_depth': 7.0, 'max_features': 0, 'min_impurity_decrease': 3.5, 'n_estimators': 220.0, 'subsample': 0.5} 
    #注意:若之前定义参数空间使用的是hp.choice,那么最优参数中的取值为索引(主要是整型,定是索引)
    
    
    
    
    # 将参数对应过后,调用验证函数 获取验证结果
    hyperopt_validation({'criterion': "mae",
                         'learning_rate': 0.14,
                         'loss': "squared_error",
                         'max_depth': 7,
                         'max_features': "log2",
                         'min_impurity_decrease': 3.5,
                         'n_estimators': 220,
                         'subsample': 0.5})
    # 27549.81165947049 和 之前一样
    

    1.2.3 基于Optuna实现多种贝叶斯优化(推荐)

    import optuna
    

    1.2.3.1 定义目标函数与参数空间

    Optuna的目标函数相当特别。在其他优化库中,我们需要单独输入参数或参数空间,优化器会在具体优化过程中将参数空间一一放入我们的目标函数进行优化,但在Optuna中,我们并不需要将参数或参数空间输入目标函数,而是需要直接在目标函数中定义参数空间。特别的是,Optuna优化器会生成一个指代备选参数的变量trial,该变量无法被用户获取或打开,但该变量在优化器中生存,并被输入目标函数。在目标函数中,我们可以通过变量trail所携带的方法来构造参数空间,具体如下所示:

    def optuna_objective(trial):
        #定义参数空间
        n_estimators = trial.suggest_int("n_estimators",80,100,1) #整数型,(参数名称,下界,上界,步长)
        max_depth = trial.suggest_int("max_depth",10,25,1)
        max_features = trial.suggest_int("max_features",10,20,1)
        #max_features = trial.suggest_categorical("max_features",["log2","sqrt","auto"]) #字符型
        min_impurity_decrease = trial.suggest_int("min_impurity_decrease",0,5,1)
        #min_impurity_decrease = trial.suggest_float("min_impurity_decrease",0,5,log=False) #浮点型
        
        #定义评估器
        #需要优化的参数由上述参数空间决定
        #不需要优化的参数则直接填写具体值
        reg = RFR(n_estimators = n_estimators
                  ,max_depth = max_depth
                  ,max_features = max_features
                  ,min_impurity_decrease = min_impurity_decrease
                  ,random_state=1412
                  ,verbose=False
                  ,n_jobs=-1
                 )
        
        #交叉验证过程,输出负均方根误差(-RMSE)
        #optuna同时支持最大化和最小化,因此如果输出-RMSE,则选择最大化
        #如果选择输出RMSE,则选择最小化
        cv = KFold(n_splits=5,shuffle=True,random_state=1412)
        validation_loss = cross_validate(reg,X,y
                                         ,scoring="neg_root_mean_squared_error"
                                         ,cv=cv #交叉验证模式
                                         ,verbose=False #是否打印进程
                                         ,n_jobs=-1 #线程数
                                         ,error_score='raise'
                                        )
        #最终输出RMSE,损失函数的最小值,需要在优化时最小化。
        return np.mean(abs(validation_loss["test_score"]))
    

    1.2.3.2 定义优化目标函数的具体流程

    在HyperOpt当中我们可以调整参数algo来自定义用于执行贝叶斯优化的具体算法,在Optuna中我们也可以。大部分备选的算法都集中在Optuna的模块sampler中,包括我们熟悉的TPE优化、随机网格搜索以及其他各类更加高级的贝叶斯过程,对于Optuna.sampler中调出的类,我们也可以直接输入参数来设置初始观测值的数量、以及每次计算采集函数时所考虑的观测值量。在Optuna库中并没有集成实现高斯过程的方法,但我们可以从scikit-optimize里面导入高斯过程来作为optuna中的algo设置,而具体的高斯过程相关的参数则可以通过如下方法进行设置:

    # n_trials表示迭代多少次
    def optimizer_optuna(n_trials, algo):
        
        #定义使用TPE或者GP(优化算法)
        if algo == "TPE":
            algo = optuna.samplers.TPESampler(n_startup_trials = 10 # 最开始有十个观测值
            								, n_ei_candidates = 24) # 每次计算采集函数时,随机采取24组参数组合进行预测
        elif algo == "GP":
            from optuna.integration import SkoptSampler # 使用关于optuna和其他包的混合库
            import skopt # sklearn-optuna优化库
            algo = SkoptSampler(skopt_kwargs={'base_estimator':'GP', #选择高斯过程
                                              'n_initial_points':10, #初始观测点10个
                                              'acq_func':'EI'} #选择的采集函数为EI,期望增量
                               )
        
        #实际优化过程,首先实例化优化器
        study = optuna.create_study(sampler = algo #要使用的具体算法
                                    , direction="minimize" #优化的方向,可以填写minimize或maximize
                                   )
        #开始优化,n_trials为允许的最大迭代次数
        #由于参数空间已经在目标函数中定义好,因此不需要输入参数空间
        study.optimize(optuna_objective #目标函数
                       , n_trials=n_trials #最大迭代次数(包括最初的观测值的)
                       , show_progress_bar=True #要不要展示进度条呀?
                      )
        
        #可直接从优化好的对象study中调用优化的结果
        #打印最佳参数与最佳损失值
        print("\n","\n","best params: ", study.best_trial.params,
              "\n","\n","best score: ", study.best_trial.values,
              "\n")
        
        return study.best_trial.params, study.best_trial.values
    

    1.2.3.3 执行实际优化流程

    Optuna库虽然是当今最为成熟的HPO方法之一,但当参数空间较小时,Optuna库在迭代中容易出现抽样BUG,即Optuna会持续抽到曾经被抽到过的参数组合,并且持续报警告说"算法已在这个参数组合上检验过目标函数了"。在实际迭代过程中,一旦出现这个Bug,那当下的迭代就无用了,因为已经检验过的观测值不会对优化有任何的帮助,因此对损失的优化将会停止。如果出现该BUG,则可以增大参数空间的范围或密度。或者使用如下的代码令警告关闭:

    import warnings
    warnings.filterwarnings('ignore', message='The objective has been evaluated at this point before.')
    
    optuna.logging.set_verbosity(optuna.logging.ERROR) #关闭自动打印的info,只显示进度条
    best_params, best_score = optimizer_optuna(10,"TPE") #默认打印迭代过程
    

    基于高斯过程的贝叶斯优化是比基于TPE的贝叶斯优化运行更加缓慢的.

    结果求出后,可继续减小参数空间范围,增大参数密度,进一步优化目标参数组合

    模型存储和读取

    import joblib # 导入库函数
    
    # 存储在本地文件,后缀dat,二进制文件
    joblib.dump(xgb_model, "D:\\code_management\\xgb_model.dat")
    # 读取文件
    load_model = joblib.load("D:\\code_management\\xgb_model.dat")
    

    感谢B站UP(菜菜的机器学习)

    展开全文
  • 网格搜索—调参数(与交叉验证一同使用) 如果有多个超参数:采用排列组合的方式,组合进行调参—穷举法 估计器就是算法 param中有多少个超参数需要调整就输入多少个字典 决策树 基础知识:信息论 通信的数学...
  • 【视频】支持向量机SVM、支持向量回归SVR和R语言网格搜索参数优化实例 支持向量机SVM、支持向量回归SVR和R语言网格搜索参数优化实例 ,时长07:24 假设我们有两个标签类的图,如下图所示: 你能决定分隔...
  • 为了进行超参数调优,我们一般会采用网格搜索、 随机搜索、贝叶斯优化等算法。 在具体介绍算法之前,需要明确超参数搜索算法一般包括哪几个要素。一是目标函数,即算法需要最大化/最小化的目标;二是搜索范围,一般...
  • 神经网络学习过程及参数更新方法,优化方法
  • 他们是: AdaBoost 集成算法 AdaBoost API 用于分类的 AdaBoost 用于回归的 AdaBoost AdaBoost 超参数 探索树的数量 探索弱学习 探索学习率 探索替代算法 网格搜索 AdaBoost 超参数 AdaBoost 集成算法 Boosting 是...
  • 一 超参数优化与枚举网格的理论极限 二 随机网格搜索RandomSearchCV   1 随机网格搜索的基本原理   2 随机网格搜索实现   3 随机网格的理论极限 三 Halving网格搜索HalvingSearchCV   1 对半网格搜索的基本流程 ...
  • 在深度学习中,有许多不同的深度网络结构,包括卷积神经网络(CNN或convnet)、长短期记忆...文章讨论了在卷积神经网络中,该如何调整超参数以及可视化卷积层。 为什么用卷积神经网络? 首先,我们想要计算机具有什么能
  • 在进行机器学习的过程中,最为核心的一个概念就是参数,而参数又分为模型参数与超参数。模型参数,顾名思义就是我们使用的模型根据训练数据的...通常情况下,常用的超参数调参的方法有:网格搜索,随机搜索与贝叶...
  • 网格搜索方法主要用于模型调参,也就是帮助我们找到一组最合适的模型设置参数,使得模型的预测达到更好的效果,这组参数于模型训练过程中学习到的参数不同,它是需要在训练前预设好的,我们称其为超参数。...
  • 支持向量机原理 寻求最优分类边界 正确:对大部分样本可以正确地划分类别。 泛化:最大化持向量间距。 公平:与支持向量等距。 简单:线性,直线或平面,分割超平面。 基于核函数的升维变换 通过名为核函数的...
  • 模型调参之网格搜索与随机搜索

    千次阅读 2021-05-17 11:22:01
    网格搜索是对参数进行搜索,在指定的参数范围内,按步长依次调整参数,利用调整的参在这里插入代码片数训练学习器,从所有的参数中找到在验证集上精度最高的参数,这也是一个训练和比较的过程。交叉验证根据cv参数的...
  • 面对枚举网格搜索过慢的问题,sklearn中呈现了两种优化方式:其一是调整搜索空间,其二是调整每次训练的数据。调整搜索空间的方法就是随机网格搜索,而调整每次训练数据的方法就是对半网格搜索。 假设现在存在数据...
  • ML模型超参数调节:网格搜索、随机搜索与贝叶斯优化 贝叶斯优化: 一种更好的超参数调优方式 Theroy: 贝叶斯优化/Bayesian Optimization Compare with code in detail: 用贝叶斯优化进行超参数调优 随机搜索...
  • 网格搜索

    万次阅读 多人点赞 2018-08-28 21:37:09
    网格搜索法是指定参数值的一种穷举搜索方法,通过将估计函数的参数通过交叉验证的方法进行优化来得到最优的学习算法。 即,将各个参数可能的取值进行排列组合,列出所有可能的组合结果生成“网格”。然后将各组合...
  • 网格搜索搜索的是参数,即在指定的参数范围内,按步长依次调整参数,利用调整的参数训练学习器,从所有的参数中找到在验证集上精度最高的参数,这其实是一个训练和比较的过程。 GridSearchCV可以
  • 文章目录一、LightGBM 原生接口重要参数训练参数预测方法绘制特征重要性分类例子回归例子二、LightGBM 的 sklearn 风格接口LGBMClassifier基本...例子LGBMRegressor基本使用例子三、LightGBM 调参思路四、参数网格搜索...
  • 网格搜索 C++实现 机器暴力参数优化: 参数设置和实现原理见下图,后面附带C++源码(个人原创),帮助解决C++程序参数优化问题。 一般放那跑一夜,最好的参数就出来了,哈哈,还是比较方便滴。 有问题记得留言...
  • 网格搜索(调参)与数据预处理

    千次阅读 2018-09-28 10:52:20
    网格搜索 ...以有两个参数的模型为例,参数a有3种可能,参数b有4种可能,把所有可能性列出来,可以表示成一个3*4的表格,其中每个cell就是一个网格,循环过程就像是在每个网格里遍历、搜索,所以叫grid ...
  • 文章目录Ⅰ.Grid Search/Random SearchⅡ.Bayesian Optimization Ⅰ.Grid Search/Random Search Grid Search:神经网络...虽然随机搜索得到的结果互相之间差异较大,但是实验证明随机搜索的确比网格搜索效果要好 Ⅱ.
  • 2.1网格搜索参数含义 class sklearn.model_selection.GridSearchCV(estimator, param_grid, scoring=None, fit_params=None, n_jobs=1, iid=True, refit=True, cv=None, verbose=0, pre_dispatch=‘2*n_jobs’, ...
  •  网格搜索算法是一种通过遍历给定的参数组合来优化模型表现的方法。以 决策树 为例,当我们确定了要使用决策树算法的时候,为了能够更好地拟合和预测,我们需要调整它的参数。在决策树算法中,我们通常选择的参数是...
  • 一篇关于蚁群算法优化SVM参数的论文,具有很好的参考价值
  • 积神经网络(CNN)的参数优化方法

    千次阅读 2020-12-22 12:42:57
    积神经网络(CNN)的参数优化方法from:http://blog.csdn.net/u010900574/article/details/51992156著名:本文是从 Michael Nielsen的电子书Neural Network and Deep Learning的深度学习那一章的卷积神经网络的参数...
  • gridSearchCV(网格搜索)的参数、方法及示例 1.简介 GridSearchCV存在的意义就是自动调参,只要把参数传递进去,就可以得出最优化参数和结果,但这种方法适合于数据量比较小的情况下,当存在大量的数据时就很难...
  • 背景: 由于深度学习和传统机器学习的超参数性质差异较大,二者的超参数优化算法原理和偏向也不同, 以下算法整理更适用于传统机器学习. 1.先盲目地搜索 网格搜索(Grid Search, GS) 和 随机搜索(Random Search,RS) 2.要...
  • 网格搜索优化算法二、建立模型1.逻辑回归模型2. KNN3. 决策森林4. 随机森林总结 前言 数据为二手手机的各个性能的数据,最后根据这些性能得到3个价格区间,作为这些二手手机售出的价格区间。 注意:该数据集已经...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 14,576
精华内容 5,830
关键字:

网格搜索参数优化原理