这篇文章主要记录的是在学习过程中,根据老师所讲解的有关于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)
到此,此次实验就结束啦,如果觉得有用就帮助博主点个赞吧。