利用Optuna对PyTorch模型进行自动调参

利用Optuna对PyTorch模型进行自动调参

Optuna是一个自动超参数优化框架,可以动态地构造超参数的搜索空间,可以针对模型帮助我们搜索最佳参数。

1368f912ecd07e9548f5a353307dc2f

1. Optuna安装

# pip
pip install optuna

# conda
conda install optuna

2. Optuna使用流程

c7e82695e344c7db89dce7a9b4e542c

通常,Optuna是用于优化超参数的,但是作为例子,这里来优化一个简单的二次函数: ( x − 2 ) 2 (x-2)^2 (x2)2.

  • 首先,导入optuna。
import optuna
  • 在 Optuna 中,待优化函数一般被命名为 objective。
def objective(trial):
    x = trial.suggest_uniform("x", -10, 10)
    return (x - 2) ** 2

该函数的返回值是 ( x − 2 ) 2 (x-2)^2 (x2)2,我们的目标是找到一个 x,使 objective 函数的输出最小。这被称为 “optimization” (优化)。 在优化过程中,Optuna 反复调用目标函数,在不同的 x 下对其进行求值。

一个 Trial 对应着目标函数的单次执行。在每次调用该函数的时候,它都被内部实例化一次。而 suggest API (例如 suggest_uniform()) 在目标函数内部被调用。它被用于获取单个 trial 的参数。在上面的例子中,suggest_uniform() 在给定的范围(-1010)内均匀地选择参数。

当 Optuna 被用于机器学习时,目标函数通常返回模型的损失或者准确度

  • 为了开始优化过程,我们将创建一个 study 对象,并将目标函数传递给它的一个方法 optimize()
study = optuna.create_study()
# study = optuna.create_study(direction='minimize')  # 默认 direction='minimize'
# study = optuna.create_study(sampler=optuna.samplers.RandomSampler())  # 切换采样器

study.optimize(objective, n_trials=100)
# study.optimize(objective, n_trials=100, show_progress_bar=True)  # 输出美观一点
  • 最后,获取最佳参数组合。
best_params = study.best_params
found_x = best_params["x"]
print("Found x: {}, (x - 2)^2: {}".format(found_x, (found_x - 2) ** 2))

在 Optuna 中,我们用 study 对象来管理优化过程。 create_study() 方法会返回一个 study 对象。该对象包含若干有用的属性,可以用于分析优化结果。

study属性解释
study.best_params获得参数名和参数值的字典
study.best_value获得最佳目标函数值
study.best_trial获得最佳 trial
study.trials获得所有 trials
len(study.trials)获得 trial 的数目
  • 再次执行 optimize(),可以继续优化过程。
study.optimize(objective, n_trials=100)

3. 超参数采样的搜索空间

通过可选的 steplog 参数,可以对整形或者浮点型参数进行离散化或者取对数操作。

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)

4. 优化算法

4.1 采样算法

利用 suggested 参数值和评估的目标值的记录,采样器基本上不断缩小搜索空间,直到找到一个最佳的搜索空间,其产生的参数会带来 更好的目标函数值。

Optuna 提供了下列采样算法:

4.2 切换采样器

默认情况下, Optuna 这样使用 TPESampler.

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

如果你希望使用其他采样器,比如 RandomSamplerCmaEsSampler,

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__}")

4.3 剪枝算法

Pruners 自动在训练的早期(也就是自动化的 early-stopping)终止无望的 trial.

Optuna 提供以下剪枝算法:

4.4 激活 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 集成模块的完整列表,参见 optuna.integration.

5. Optuna Dashboard的使用

Optuna仪表板是Optuna的实时web仪表板。我们可以在图表中查看优化历史、超参数重要性等。(👀:Optuna Dashboard支持Python 3.7或更新版本。)

5.1 Optuna Dashboard安装

# 安装optuna-dashboard
pip install optuna-dashboard

5.2 使用流程

  • 第一步:在study中指定storage。storage 文件默认存储在当前路径下。
import optuna

def objective(trial):
    x = trial.suggest_float("x", -100, 100)
    y = trial.suggest_categorical("y", [-1, 0, 1])
    return x**2 + y

study = optuna.create_study(
    storage="sqlite:///db.sqlite3",  # 在这里指定存储的URL,db.sqlite3文件默认存储到当前目录下
    study_name="quadratic-simple"
)
study.optimize(objective, n_trials=100)
print(f"Best value: {study.best_value} (params: {study.best_params})")
  • 第二步:在命令行界面使用如下命令打开Optuna Dashboard。
optuna-dashboard sqlite:///db.sqlite3

Alt

6. 案例一:使用 Optuna 的 XGBoost 模型调参(Scikit-Learn框架)

  • 导入必要的包。
import optuna
import xgboost as xgb
from catboost import CatBoostRegressor
import numpy as np
import pandas as pd
from sklearn.model_selection import KFold
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
  • 数据清洗。
train = pd.read_csv('./train.csv')
test  = pd.read_csv('./test.csv')
sub = pd.read_csv('./sample_submission.csv')

train.head()

columns = [col for col in train.columns.to_list() if col not in ['id','target']]

data=train[columns]
target=train['target']
  • 构建 Optuna 的 XGBoost。
def objective(trial,data=data,target=target):
    
    train_x, test_x, train_y, test_y = train_test_split(data, target, test_size=0.15,random_state=42)
    param = {
        # 这个参数意味着在训练模型时使用GPU来加速训练过程
        'tree_method':'gpu_hist',  
        #  表示在 [1e-3,1e1) 范围内,对数均匀分布取值
        'lambda': trial.suggest_loguniform('lambda', 1e-3, 10.0), 
        'alpha': trial.suggest_loguniform('alpha', 1e-3, 10.0),
        # 表示从 [0.3,0.4,0.5,0.6,0.7,0.8,0.9, 1.0] 取值
        'colsample_bytree': trial.suggest_categorical('colsample_bytree', [0.3,0.4,0.5,0.6,0.7,0.8,0.9, 1.0]),
        'subsample': trial.suggest_categorical('subsample', [0.4,0.5,0.6,0.7,0.8,1.0]),
        'learning_rate': trial.suggest_categorical('learning_rate', [0.008,0.01,0.012,0.014,0.016,0.018, 0.02]),
        'n_estimators': 1000,
        'max_depth': trial.suggest_categorical('max_depth', [5,7,9,11,13,15,17]),
        'random_state': trial.suggest_categorical('random_state', [2020]),
        # 表示从 [1,300] 范围内,整数取值
        'min_child_weight': trial.suggest_int('min_child_weight', 1, 300),
    }
    model = xgb.XGBRegressor(**param) 
    
    model.fit(train_x,train_y,eval_set=[(test_x,test_y)],early_stopping_rounds=100,verbose=False)
    
    preds = model.predict(test_x)
    
    rmse = mean_squared_error(test_y, preds,squared=False)
    
    return rmse
  • 运行,寻找最优的超参数组合。
study = optuna.create_study(direction='minimize')
study.optimize(objective, n_trials=30)
print('Number of finished trials:', len(study.trials))
print('Best trial:', study.best_trial.params)

7. 案例二:使用 Optuna 的 MLP 神经网络调参(Pytorch框架)👍

"""
coding:utf-8
* @Author:FHTT-Tian
* @name:Optuna_MLP.py
* @Time:2024/10/22 星期二 15:45
* @Description: 使用Optuna优化MLP示例。在这个例子中,使用PyTorch和MINST。优化了神经网络的结构和优化器的配置。并使用optuna-dashboard进行了可视化。
"""

import torch
import torchvision.datasets as dataset
import torchvision.transforms as transforms
from torch import nn, optim
from torch.utils.data import DataLoader
import optuna
from optuna.trial import TrialState

# 超参数设置
input_size = 28 * 28
hidden_size = 256
output_size = 10
batch_size = 128
learning_rate = 0.0001
total_epochs = 10

device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')

"""
第一步: 定义模型
"""


class Model(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super().__init__()
        self.flatten = nn.Flatten()
        # 线性层1,输入层和隐藏层之间的线性层
        self.layer1 = nn.Linear(input_size, hidden_size)
        # 激活函数
        self.relu = nn.ReLU()
        # 线性层2,隐藏层和输出层之间的线性层
        self.layer2 = nn.Linear(hidden_size, output_size)

    def forward(self, x):
        # 将输入的图片数据x展平成一维数据
        x = self.flatten(x).to(device)
        # 前向传播
        x = self.layer1(x)
        x = self.relu(x)
        x = self.layer2(x)
        return x


"""
第二步: 定义搜索空间和目标函数
* 加载数据集
* 定义超参数搜索空间
* 定义目标函数
* 添加剪枝
"""


def objective(trial):
    # 第一步:加载数据集-构建训练数据和测试数据各自的Dataloader。
    # 对图片进行预处理,将图片数据转换为张量,并进行归一化
    transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,), )])
    # 下载数据集并预处理
    train_dataset = dataset.MNIST(root="./MINST", train=True, download=True, transform=transform)
    test_dataset = dataset.MNIST(root="./MINST", train=False, download=True, transform=transform)
    # 构建dataloader,实现小批量的数据读取
    train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
    test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size, shuffle=False)

    # 第二步:定义超参数搜索空间
    param = {
        "hidden_size": trial.suggest_int("hidden_size", 32, 256),
        "learning_rate": trial.suggest_float("learning_rate", 1e-5, 1e-1, log=True),
        "optimizer_name": trial.suggest_categorical("optimizer", ['Adam', 'SGD'])
    }

    # 创建模型
    model = Model(input_size, param["hidden_size"], output_size)
    model.to(device)

    # 选择优化器
    if param["optimizer_name"] == "Adam":
        optimizer = optim.Adam(model.parameters(), lr=param["learning_rate"])
    elif param["optimizer_name"] == "SGD":
        optimizer = optim.SGD(model.parameters(), lr=param["learning_rate"])
    else:
        raise NotImplementedError

    # 定义损失
    criterion = nn.CrossEntropyLoss()

    # 训练模型
    for epoch in range(total_epochs):

        model.train()

        for batch_idx, (data, label) in enumerate(train_loader):
            # 前向传播
            output = model(data).to(device)
            loss = criterion(output, label.to(device))
            # 反向传播
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

        # 验证模型
        model.eval()
        correct = 0
        with torch.no_grad():
            for data, label in test_loader:
                output = model(data).to(device)
                correct += torch.sum(output.argmax(1) == label.to(device)).item()
        accuracy = correct / len(test_dataset)

        # 第四步: 添加剪枝
        # 报告每个epoch的验证准确率
        trial.report(accuracy, epoch)

        # 根据中间值处理修剪
        if trial.should_prune():
            raise optuna.exceptions.TrialPruned()

    return accuracy


if __name__ == "__main__":
    """
    第三步: 超参数优化
    """

    # 创建study对象
    study = optuna.create_study(
        study_name="mlp_optuna",
        storage="sqlite:///db.sqlite3",
        direction='maximize',
        sampler=optuna.samplers.TPESampler(),
        pruner=optuna.pruners.MedianPruner()
    )
    study.optimize(objective, n_trials=10)

    """
    第四步: 打印最佳参数
    """
    pruned_trials = study.get_trials(deepcopy=False, states=[TrialState.PRUNED])
    complete_trials = study.get_trials(deepcopy=False, states=[TrialState.COMPLETE])

    print("Study statistics: ")
    print("Number of finished trials: ", len(study.trials))
    print("Number of pruned trials: ", len(pruned_trials))
    print("Number of complete trials: ", len(complete_trials))

    print("Best trial:")
    trial = study.best_trial

    print(f"Accuracy: {trial.value}")

    print("Best hyperparameters:", trial.params)

image-20241023215239365

image-20241023222049173

参考

😃😃😃

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值