MLP自动调参——Raytune实例

这篇文章主要记录的是在学习过程中,根据老师所讲解的有关于Raytune自动化调参的实例的记录。

Ray tune调参

主要对pytorch的神经网络进行自动调参

定义实验对象

在进行自动调参之前,需要创建一个实验对象的函数,该函数内需要给Ray tune提交调参监控指标,如正确率,误差平方等。

import ray
from ray import tune
from ray.tune.schedulers import AsyncHyperBandScheduler

首先,定义一个可根据传入参数动态调整的网络结构,其中的hidden_size用来控制隐藏层的神经元个数。

import os
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import DataLoader
import torch.utils.data as Data
from torchvision import transforms,datasets

import matplotlib.pyplot as plt
import numpy as np
from tqdm.notebook import tqdm
class MNISTNet(nn.Module):
    def __init__(self,hidden_size = 512):
        # 定义网络使用的结构
        super(MNISTNet,self).__init__()
        self.fc1 = nn.Linear(28*28,hidden_size) 
        self.fc2 = nn.Linear(hidden_size,10)
        
    def forward(self,x):
        # 定义网络计算全过程
        # x(batch_size,channel,height,width)
        batch_size = x.shape[0]
        x = torch.squeeze(x,1).reshape(batch_size,-1) #因为mnist图像时灰度图,故channel为1,需要squeeze降维
        a1 = F.relu(self.fc1(x))
        z2 = self.fc2(a1)
        #最后的softmax函数包含在了损失函数中,因此不需要在此计算
        return z2

其次,需要向训练pytorch神经网络一样,编写训练以及测试时的代码,同时需要在每次训练完成添加一个保存参数的步骤,并将所有过程封装程函数调用。

def train(model,optimizer,train_loader,loss_function=None,device = "cpu",epoch = 0):
#     print("当前使用的训练设备为:",device)
    
    model.train()
    for data in train_loader:
        images,labels = data
        images,labels = images.to(device),labels.to(device) #将数据切换到指定设备上
        optimizer.zero_grad() #清空之前的梯度
        
        # images(batch,28,28)
        # outputs(batch_size,10)
        outputs = model(images) #根据输入得到网络输出结果
        loss = loss_function(outputs,labels) #计算损失
        loss.backward() #反向传播
        optimizer.step() #更新参数
    
    #保存训练好的模型参数
    with tune.checkpoint_dir(epoch) as checkpoint_dir:
        path = os.path.join(checkpoint_dir,"checkpoint") #保存路径
        # 保存网络参数以及优化器参数
        torch.save((model.state_dict(),optimizer.state_dict()),path)
        
def test(model,test_loader,device = "cpu"):
    # 检验模型在测试机上的性能
    # eval 表示不改变如dropout和bn等变换的参数
    model.eval()
    acc = 0.0
    with torch.no_grad(): #以下计算不会在反向传播中被记录
        for test_data in test_loader:
            test_images,test_labels = test_data
            test_images,test_labels = test_images.to(device),test_labels.to(device) 
            
            outputs = model(test_images)
            predict_y = torch.argmax(outputs,dim=1)
            acc += torch.eq(predict_y,test_labels).sum().item()
            
        # 计算准确率
        test_accurate = acc / len(test_loader.dataset)
    # 返回准确率作为 Ray tune 的监控指标
    return test_accurate

准备MNIST数据集,从官方的60000个数据样本集划分50000个数据作为训练集和10000个数据作为验证集,测试集。

def load_array(data_arrays,batch_size,is_train = True):
    """构造一个Pytorch数据迭代器"""
    dataset = Data.TensorDataset(*data_arrays)
    return DataLoader(dataset,batch_size,shuffle = is_train,drop_last=True)

def get_train_and_val_loaders(batch_size = 64):
    # 将数据转换为Tensor形式
    pipeline = transforms.Compose([transforms.ToTensor()])
    
    #放在data文件夹下,第一次下载时需要download设为True,之后便可以设置为False
    #train为True加载训练集,False加载测试集
    train_set = datasets.MNIST("./data",train = True,transform = pipeline,download=True)
    
    #划分数据集
    x_train = train_set.data[:-10000]/255
    x_val = train_set.data[-10000:]/255
    
    y_train = train_set.targets[:-10000]
    y_val = train_set.targets[-10000:]
    
    # 将数据转换为可迭代的batch形式
    train_loader = load_array((x_train,y_train),batch_size = batch_size,is_train = True)
    val_loader = load_array((x_val,y_val),batch_size = batch_size,is_train = True)
    
    return  train_loader,val_loader

def get_test_loaders(batch_size = 64):
    # 将数据转换为Tensor形式
    pipeline = transforms.Compose([transforms.ToTensor()])
    
    #放在data文件夹下,第一次下载时需要download设为True,之后便可以设置为False
    #train为True加载训练集,False加载测试集
    test_set = datasets.MNIST("./data",train = False,transform = pipeline,download=True)
    # 将数据转换为可迭代的batch形式
    test_loader = DataLoader(test_set,batch_size = batch_size,shuffle = False,drop_last=True)
    return test_loader

最后,定义tune调参的实验对象,该对象的参数为字典形式的超参数空间config,字典的key为需要调整的参数,value为tune提供的api实现的参数范围。

def train_mnist(config):
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    train_loader ,val_loader = get_train_and_val_loaders()
    model = MNISTNet(config["hidden_size"]).to(device) # 根据传入神经元的个数生成网络结构
    loss_function = nn.CrossEntropyLoss() #交叉熵损失函数,包含softmax
    optimizer = optim.Adam(model.parameters(),lr = config["lr"]) #根据传入的学习率创建Adam优化器
    
    # 每个实验对象最后进行5次迭代
    for epoch in range(5):
        # 在训练集上训练网格参数
        train(model,optimizer,train_loader,loss_function,device,epoch)
        # 在验证集上验证模型性能
        val_acc = test(model,val_loader,device)
        # 将监控指标传给 Ray tune
        tune.report(val_accuracy = val_acc)

在train_mnist这个函数中我们定义了一张个pytorch的训练过程,包含了数据的获取get_train_and_val_loaders,创建神经网络MNISTNet,定义损失函数loss_function以及学习率优化器optimizer。在这个过程中我们希望调整隐藏层的神经元数量hidden_size,确定优化器学习率参数。

搜索最优超参数组合

定义好实验对象后,我们利用Ray tune提供的tune.run函数来实现超参数的搜索,其主要参数如下:

  • run_or_experiment:传入定义好的实验对象,即我们定义好的train_mnist
  • metric:指定监控的指标,以该指标来判断实验效果的好坏
  • mode:[‘min’,‘max’]选择一个,表示求最大或最小化监控指标metric
  • stop:根据传入的字典形式条件,在符合条件时停止训练迭代
  • config:传入需要调整的超参数空间
  • num_samples:从超参数空间中采样的次数。如果超参数为grid_search的网格形式,则表示对网格的所有可能组合,重复的次数
  • local_dir:保证调参结果的路径,只保存超参数结果,不保存模型网络参数
# 训练模块,在这里最少进行3个epoch的计算
sched = AsyncHyperBandScheduler(grace_period = 3)

analysis = tune.run(
    run_or_experiment = train_mnist, # 实验对象
    metric = "val_accuracy", # 在tune.report中定义的监控指标val_accuracy
    mode = "max", #最大化metric
    scheduler = sched,
    num_samples = 1, # 从超参数空间中采样的次数
    resources_per_trial = {
        "cpu":1,
        "gpu":1 if torch.cuda.is_available() else 0
    },
    config = { #需要优化的参数
        # grid_search,即网格调参会计算每种可能的超参数组合
        "hidden_size" : tune.grid_search([128,256,512]), #隐藏神经元的个数
        "lr":tune.grid_search([0.01,0.001,0.0001]),
    },
    local_dir = "./data")

训练状态图
通过训练就可得到如上的训练表格(只进行了部分截图),从图表我们就可以看到每一次训练的参数状态,最后的准确率等。

保存传入超参数组合的实验结果
print("Best config is :",analysis.best_config)
保存传入超参数组合的实验结果¶
result = pd.DataFrame(analysis.results_df)
result.to_csv("./results_df.csv")

加载最优模型

搜索结束之后,可以检索最佳模型,并将最优的参数组合的模型参数加载出来

# 检索最优的实验对象
best_trial = analysis.get_best_trial()

# 根据最优的超参数组合创建网络
best_trained_model = MNISTNet(best_trial.config["hidden_size"])
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
best_trained_model.to(device)

# 根据最优实验对象的模型保存路径
# 加载保存好的网络参数

best_checkpoint = analysis.get_best_checkpoint(trial=best_trial, metric="val_accuracy", mode="max")
best_checkpoint_dir = best_checkpoint.to_directory(path="directory")
model_state,optimizer_state = torch.load(os.path.join(best_checkpoint_dir,"checkpoint"))
best_trained_model.load_state_dict(model_state)

# 加载保存好的优化器参数
optimizer = optim.Adam(best_trained_model.parameters(),lr = 0.001) #
optimizer.load_state_dict(optimizer_state)
# 在测试集上测试最优超参数组合的模型性能
test_loader = get_test_loaders()
test_acc = test(best_trained_model,test_loader,device)
print("test_acc:",test_acc)

到此,此次实验就结束啦,如果觉得有用就帮助博主点个赞吧。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值