使用optuna对模型的超参数进行自动优化

使用前先安装optuna.
pip install optuna
optuna适用于多种机器学习框架包括pytorch,tensorflow等。可以optuna examples github查看所有支持的框架的教程。

使用optuna优化pytorch的模型

先有基本的pytorch经验,可以更快的理解下面的代码。这里使用了gpu,如果没有GPU,可以修改最开始的DEVICE=torch.device("cuda")DEVICE=torch.device("cpu")

import os
import optuna
from optuna.trial import TrialState
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torch.utils.data
from torchvision import datasets
from torchvision import transforms


"""
Optuna example that optimizes multi-layer perceptrons using PyTorch.

In this example, we optimize the validation accuracy of fashion product recognition using
PyTorch and FashionMNIST. We optimize the neural network architecture as well as the optimizer
configuration. As it is too time consuming to use the whole FashionMNIST dataset,
we here use a small subset of it.

"""


DEVICE = torch.device("cuda")
BATCHSIZE = 128 #每次训练时数据被分为小批次的大小
CLASSES = 10 #
DIR = os.getcwd()
EPOCHS = 10 #所有的小批次完成后,即为1个epoch,此处是设置总的训练的epoch次数
N_TRAIN_EXAMPLES = BATCHSIZE * 30 #训练集的样本数量
N_VALID_EXAMPLES = BATCHSIZE * 10 #测试集的样本数量

#定义神经网络模型,在里面使用了2个自优化超参数
def define_model(trial):
    # We optimize the number of layers, hidden units and dropout ratio in each layer.
    n_layers = trial.suggest_int("n_layers", 1, 3) #定义一个自由化超参数:训练的层数,从1到3
    layers = []

    in_features = 28 * 28
    #循环构建卷积块
    for i in range(n_layers):
        out_features = trial.suggest_int("n_units_l{}".format(i), 4, 128)#定义一个自优化超参数out_features,是输出feature的维度,从4到128
        layers.append(nn.Linear(in_features, out_features))#使用了上面定义的输出特征维度超参数
        layers.append(nn.ReLU())#层添加上激活函数ReLU
        p = trial.suggest_float("dropout_l{}".format(i), 0.2, 0.5)#定义一个自优化超参数p,是丢弃率,从0.2到0.5
        layers.append(nn.Dropout(p))#添加上上面的丢弃层
        in_features = out_features#再把最后的输出层维度赋值给输出层
    layers.append(nn.Linear(in_features, CLASSES))#最后添加上线性输出层
    layers.append(nn.LogSoftmax(dim=1))#再转为0-1分布函数,此处是分类模型,所以需要这个函数,转成概率值
    return nn.Sequential(*layers)#返回的就是一个多层神经网络的所有层

#在线加载FashionMNIST数据集,pytorch的基本操作
def get_mnist():
    # Load FashionMNIST dataset.
    train_loader = torch.utils.data.DataLoader(
        datasets.FashionMNIST(DIR, train=True, download=True, transform=transforms.ToTensor()),
        batch_size=BATCHSIZE,
        shuffle=True,
    )
    valid_loader = torch.utils.data.DataLoader(
        datasets.FashionMNIST(DIR, train=False, transform=transforms.ToTensor()),
        batch_size=BATCHSIZE,
        shuffle=True,
    )
    return train_loader, valid_loader


def objective(trial):
    # Generate the model.
    model = define_model(trial).to(DEVICE)

    # Generate the optimizers.
    optimizer_name = trial.suggest_categorical("optimizer", ["Adam", "RMSprop", "SGD"])#定义自优化的超参数:模型优化器
    lr = trial.suggest_float("lr", 1e-5, 1e-1, log=True) #定义自由化的超参数,学习率,从1e-5到1
    optimizer = getattr(optim, optimizer_name)(model.parameters(), lr=lr)#创建优化器对象

    # Get the FashionMNIST dataset.使用在线获取FashionMNIST数据集,前者是训练数据集,后者是测试数据集
    train_loader, valid_loader = get_mnist()

    # Training of the model.
    for epoch in range(EPOCHS):
        model.train()#训练模型
        for batch_idx, (data, target) in enumerate(train_loader):
            # 当训练的样本总数超过我们最开始设置的训练样本数之后,就停止训练
            if batch_idx * BATCHSIZE >= N_TRAIN_EXAMPLES:
                break

            #转换为特定硬件设备的张量,view是用于改变张量的形状,data.size(0)表示第1维度的大小,-1,表示自动计算其他维度的大小
            data, target = data.view(data.size(0), -1).to(DEVICE), target.to(DEVICE)

            optimizer.zero_grad()#清空优化器的梯度
            output = model(data)#得到模型的输出结果
            loss = F.nll_loss(output, target)#使用负对数似然损失(nll_loss)作为损失函数
            loss.backward()#反向传播
            optimizer.step()#根据计算得到的梯度,通过优化器更新模型的参数

        # 测试集,评估模型
        model.eval()
        correct = 0
        with torch.no_grad():
            for batch_idx, (data, target) in enumerate(valid_loader):
                # 如果测试数据量大于最开始设置的测试数据量就停止测试
                if batch_idx * BATCHSIZE >= N_VALID_EXAMPLES:
                    break
                data, target = data.view(data.size(0), -1).to(DEVICE), target.to(DEVICE)
                output = model(data)
                # 获取最大值的索引,后面的keepdim是在原来的张量相同的维度上保存。
                pred = output.argmax(dim=1, keepdim=True)
                #累加计算结果正确的所有的次数,
                correct += pred.eq(target.view_as(pred)).sum().item()
        #这里是计算准确率,
        accuracy = correct / min(len(valid_loader.dataset), N_VALID_EXAMPLES)

        trial.report(accuracy, epoch)#将当前的准确率和迭代次数传递给optuna

        #optuna评估是否需要剪枝,如果需要剪枝(说明模型的参数性能不佳),则抛出TrialPruned异常。
        if trial.should_prune():
            raise optuna.exceptions.TrialPruned()
    return accuracy

#判断当前脚本是否是作为主程序运行,只有作为主程序时,才会执行下面的代码,作为模块被引入时,不会执行下面的代码
if __name__ == "__main__":
    #创建一个optuna研究对象,名字是maxmize
    study = optuna.create_study(direction="maximize")
    #对目标函数进行优化,objective是要优化的目标函数,n_trials=100表示最多进行100次实验,timeout=600表示优化过程的超时时间为600秒。
    study.optimize(objective, n_trials=100, timeout=600)
    #获取所有被提前停止的实验
    pruned_trials = study.get_trials(deepcopy=False, states=[TrialState.PRUNED])
    #获取所有成功的实验,deepcopy=False表示返回的是原始对象的引用,而不是复制,这可以节省内存
    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("  Value: ", trial.value)
    print("  Params: ")
    for key, value in trial.params.items():
        print("    {}: {}".format(key, value))

程序最终输出内容如下:

Study statistics: 
  Number of finished trials:  100
  Number of pruned trials:  61
  Number of complete trials:  39
Best trial:
  Value:  0.85078125
  Params: 
    n_layers: 1
    n_units_l0: 81
    dropout_l0: 0.22233836180755426
    optimizer: Adam
    lr: 0.0037253244556814374

即最优的超参数组合的模型的准确率是0.85078125,最优超参数设置是:

  • 层数是1,
  • 输出维度是81,
  • 损失函数是0.22233836180755426,
  • 模型优化器是Adam,
  • 学习率是 0.0037253244556814374
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,230评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,261评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,089评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,542评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,542评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,544评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,922评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,578评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,816评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,576评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,658评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,359评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,920评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,859评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,381评论 2 342

推荐阅读更多精彩内容