調參神器optuna學習筆記


介紹

optuna作為調參工具適合絕大多數的機器學習框架,sklearn,xgb,lgb,pytorch等。

調參原理

主要的調參原理如下:
1 采樣算法
利用 suggested 參數值和評估的目標值的記錄,采樣器基本上不斷縮小搜索空間,直到找到一個最佳的搜索空間,
其產生的參數會帶來 更好的目標函數值。

  • optuna.samplers.TPESampler 實現的 Tree-structured Parzen Estimator 算法
  • optuna.samplers.CmaEsSampler 實現的 CMA-ES 算法
  • optuna.samplers.GridSampler 實現的網格搜索
  • optuna.samplers.RandomSampler 實現的隨機搜索
    默認TPE
study = optuna.create_study()
print(f"Sampler is {study.sampler.__class__.__name__}")

study = optuna.create_study(sampler=optuna.samplers.RandomSampler())
print(f"Sampler is {study.sampler.__class__.__name__}")

study = optuna.create_study(sampler=optuna.samplers.CmaEsSampler())
print(f"Sampler is {study.sampler.__class__.__name__}")

2 剪枝算法
自動在訓練的早期(也就是自動化的 early-stopping)終止無望的 trial

  • optuna.pruners.SuccessiveHalvingPruner 實現的 Asynchronous Successive Halving 算法。
  • optuna.pruners.HyperbandPruner 實現的 Hyperband 算法。
  • optuna.pruners.MedianPruner 實現的中位數剪枝算法
  • optuna.pruners.ThresholdPruner 實現的閾值剪枝算法

激活 Pruner
要打開剪枝特性的話,你需要在迭代式訓練的每一步后調用 report() 和 should_prune(). report() 定期監控目標函數的中間值. should_prune() 確定終結那些沒有達到預先設定條件的 trial.

import logging
import sys
import sklearn.datasets
import sklearn.linear_model
import sklearn.model_selection

def objective(trial):
    iris = sklearn.datasets.load_iris()
    classes = list(set(iris.target))
    train_x, valid_x, train_y, valid_y = sklearn.model_selection.train_test_split(
        iris.data, iris.target, test_size=0.25, random_state=0)

    alpha = trial.suggest_float("alpha", 1e-5, 1e-1, log=True)
    clf = sklearn.linear_model.SGDClassifier(alpha=alpha)

    for step in range(100):
        clf.partial_fit(train_x, train_y, classes=classes)

        # Report intermediate objective value.
        intermediate_value = 1.0 - clf.score(valid_x, valid_y)
        trial.report(intermediate_value, step)

        # Handle pruning based on the intermediate value.
        if trial.should_prune():
            raise optuna.TrialPruned()

    return 1.0 - clf.score(valid_x, valid_y)

# Add stream handler of stdout to show the messages
optuna.logging.get_logger("optuna").addHandler(logging.StreamHandler(sys.stdout))
study = optuna.create_study(pruner=optuna.pruners.MedianPruner())
study.optimize(objective, n_trials=20)

對 optuna.samplers.RandomSampler 而言 optuna.pruners.MedianPruner 是最好的。
對於 optuna.samplers.TPESampler 而言 optuna.pruners.Hyperband 是最好的。

當 Optuna 被用於機器學習時,目標函數通常返回模型的損失或者准確度。

1. Study 對象

  • Trial: 目標函數的單次調用
  • Study: 一次優化過程,包含一系列的 trials.
  • Parameter: 待優化的參數.
    在 Optuna 中,我們用 study 對象來管理優化過程。 create_study() 方法會返回一個 study 對象。該對象包含若干有用的屬性,可以用於分析優化結果。
    獲得參數名和參數值的字典:
    study.best_params
    獲得最佳目標值:
    study.best_values

2.超參數采樣

  • optuna.trial.Trial.suggest_categorical() 用於類別參數
  • optuna.trial.Trial.suggest_int() 用於整形參數
  • optuna.trial.Trial.suggest_float() 用於浮點型參數

通過可選的 steplog 參數,我們可以對整形或者浮點型參數進行離散化或者取對數操作。
這里的step比較好理解,對於整型就是步長,對於float就是離散化程度(分箱)

log開始不是特別理解,查看了optuna的源碼:
對於float:If log is true, the value is sampled from the range in the log domain.
Otherwise, the value is sampled from the range in the linear domain.
還是很懵逼,看看numpy里面是怎么搞的,numpy里面有三種抽樣方式:
logspace
Similar to geomspace, but with endpoints specified using log and base.
linspace
Similar to geomspace, but with arithmetic instead of geometric progression.
geomspace
Similar to logspace, but with endpoints specified directly.
舉個例子比較直觀:

np.linspace(0.02, 2.0, num=20)
np.geomspace(0.02, 2.0, num=20)
np.logspace(0.02, 2.0, num=20)

linspace是一列等差數列,

[ 0.02  0.12421053  0.22842105  0.33263158  0.43684211  0.54105263
  0.64526316  0.74947368  0.85368421  0.95789474  1.06210526  1.16631579
  1.27052632  1.37473684  1.47894737  1.58315789  1.68736842  1.79157895
  1.89578947  2. ]

geomspace是一列等比數列

[0.02 ,  0.0254855 ,  0.03247553,  0.04138276,  0.05273302,
 0.06719637,  0.08562665,  0.1091119 ,  0.13903856,  0.17717336,
 0.22576758,  0.28768998,  0.36659614,  0.46714429,  0.59527029,
 0.75853804,  0.96658605,  1.23169642,  1.56951994,  2.]

logspace會計算默認計算一個\(base^{start}\)\(base^{end}\), base默認為10,計算了start和end

\[start=10^{0.02} =1.047, end=10^{2} =100. \]

[  1.04712855    1.33109952    1.69208062    2.15095626    2.73427446
    3.47578281    4.41838095    5.61660244    7.13976982    9.07600522
   11.53732863   14.66613875   18.64345144   23.69937223   30.12640904
   38.29639507   48.68200101   61.88408121   78.6664358   100.  ]

代碼示例:

import optuna
def objective(trial):
    # Categorical parameter
    optimizer = trial.suggest_categorical("optimizer", ["MomentumSGD", "Adam"])

    # Integer parameter
    num_layers = trial.suggest_int("num_layers", 1, 3)

    # Integer parameter (log)
    num_channels = trial.suggest_int("num_channels", 32, 512, log=True)

    # Integer parameter (discretized)
    num_units = trial.suggest_int("num_units", 10, 100, step=5)

    # Floating point parameter
    dropout_rate = trial.suggest_float("dropout_rate", 0.0, 1.0)

    # Floating point parameter (log)
    learning_rate = trial.suggest_float("learning_rate", 1e-5, 1e-2, log=True)

    # Floating point parameter (discretized)
    drop_path_rate = trial.suggest_float("drop_path_rate", 0.0, 1.0, step=0.1)

定義參數空間

在 Optuna 中,我們使用和 Python 語法類似的方式來定義搜索空間,其中包含條件和循環語句。
類似地,你也可以根據參數值采用分支或者循環。

# 分支
import sklearn.ensemble
import sklearn.svm

def objective(trial):
    classifier_name = trial.suggest_categorical("classifier", ["SVC", "RandomForest"])
    if classifier_name == "SVC":
        svc_c = trial.suggest_float("svc_c", 1e-10, 1e10, log=True)
        classifier_obj = sklearn.svm.SVC(C=svc_c)
    else:
        rf_max_depth = trial.suggest_int("rf_max_depth", 2, 32, log=True)
        classifier_obj = sklearn.ensemble.RandomForestClassifier(max_depth=rf_max_depth)
# 循環
import torch
import torch.nn as nn
def create_model(trial, in_size):
    n_layers = trial.suggest_int("n_layers", 1, 3)

    layers = []
    for i in range(n_layers):
        n_units = trial.suggest_int("n_units_l{}".format(i), 4, 128, log=True)
        layers.append(nn.Linear(in_size, n_units))
        layers.append(nn.ReLU())
        in_size = n_units
    layers.append(nn.Linear(in_size, 10))

    return nn.Sequential(*layers)

關於參數個數的注意事項
隨着參數個數的增長,優化的難度約呈指數增長。也就是說,當你增加參數的個數的時候,優化所需要的 trial 個數會呈指數增長。因此我們不推薦增加不必要的參數。

Reference:
1.官網
2.github examples
3.Difference in output between numpy linspace and numpy logspace
4.np.geomspace


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM