欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 教育 > 高考 > 遗传算法与深度学习实战(18)——使用网格搜索自动超参数优化

遗传算法与深度学习实战(18)——使用网格搜索自动超参数优化

2024/10/25 16:22:30 来源:https://blog.csdn.net/LOVEmy134611/article/details/142911856  浏览:    关键词:遗传算法与深度学习实战(18)——使用网格搜索自动超参数优化

遗传算法与深度学习实战(18)——使用网格搜索自动超参数优化

    • 0. 前言
    • 1. 网格搜索
    • 2. 使用网格搜索自动超参数优化
    • 小结
    • 系列链接

0. 前言

我们已经学习了如何使用随机搜索获得较好的超参数优化 (Hyperparameter Optimization, HPO) 结果,但它耗时过长,为了寻找快速且准确的自动 HPO,需要使用更高级的技术。一种简单有效的技术是网格搜索,特别适用于参数空间较小且相对离散的情况。在本节中,我们将介绍网格搜索的基本原理,并实现网格搜索自动超参数优化。

1. 网格搜索

网格搜索 (Grid Search) 的工作原理是将搜索区域按照网格模式划分,并系统地遍历网格中的每个单元。网格搜索在二维空间中易于进行可视化,但该技术对于任何维数的问题都是有效的。
下图展示了随机搜索和网格搜索在超参数空间中的比较,图中展示了一种可能的网格遍历模式,在每个单元格中评估学习率和中间层变量。网格搜索是一种有效的方法,可以以有条不紊且高效的方式评估一系列可能的组合。

网格搜索

2. 使用网格搜索自动超参数优化

在本节中,我们将修改随机搜索自动超参数优化,使用更复杂的网格搜索技术。虽然这种技术更强大和高效,但它仍受限于网格的大小,使用较大的网格单元通常会将结果限制在局部最小值或最大值,而较小的网格单元虽然可以找到全局最小值或最大值,但搜索空间也会增加。
接下来,基于随机搜索代码,实现网格搜索。代码的主要区别在于超参数对象需要跟踪一个参数网格。

(1) 首先,导入所需库,并定义相关超参数:

import numpy as np
import torch
import torch.nn as nn
from torch.autograd import Variable
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import TensorDataset, DataLoader
from matplotlib import pyplot as plt
from matplotlib import cm
from IPython.display import clear_output
import time
import math
import types
from itertools import cycle
from sklearn.model_selection import ParameterGriddef function(x):return (2*x + 3*x**2 + 4*x**3 + 5*x**4 + 6*x**5 + 10) data_min = -5
data_max = 5
data_step = .5
Xi = np.reshape(np.arange(data_min, data_max, data_step), (-1, 1))
yi = function(Xi)
inputs = Xi.shape[1]
yi = yi.reshape(-1, 1)
plt.plot(Xi, yi, 'o', color='black')
plt.plot(Xi,yi, color="red")class Net(nn.Module):def __init__(self, inputs, middle):super().__init__()self.fc1 = nn.Linear(inputs,middle)    self.fc2 = nn.Linear(middle,middle)    self.out = nn.Linear(middle,1)def forward(self, x):x = F.relu(self.fc1(x))     x = F.relu(self.fc2(x))    x = self.out(x)return x

(2) 实现 HyperparametersGrid 类和 __init__() 函数,将输入参数的名称提取到 self.hparms 中,然后测试第一个输入是否指向一个生成器,如果为真,就使用 self.create_grid 生成一个参数网格;否则,该实例只是一个子超参数容器:

class HyperparametersGrid(object):def __init__(self, **kwargs):self.__dict__.update(kwargs)     self.hparms = [d for d in self.__dict__] self.grid = {}   self.gidx = 0if isinstance(self.__dict__[self.hparms[0]], types.GeneratorType):        self.grid = self.create_grid()self.grid_size = len(self.grid)      def __str__(self):out = ""for d in self.hparms:ds = self.__dict__[d]out += f"{d} = {ds} "return out def values(self):vals = []for d in self.hparms:vals.append(self.__dict__[d])return vals

接下来,在 self.create_grid() 函数中构建参数网格。函数首先创建一个空的网格字典 grid,然后循环遍历超参数列表,调用超参数生成器,使用 next 返回一个值和总值数。然后再次通过生成器循环,提取每个唯一值并将其附加到行列表 row 中。之后,将行附加到网格中,最后通过将网格注入 ParameterGrid 类来完成。ParameterGridscikit-learn 中的一个辅助类,它以输入字典和值列表作为输入,并构造一个网格,其中每个单元格表示各种超参数组合。虽然我们在本示例中,使用两个超参数在二维网格上运行,但 ParameterGrid 可以处理任意维数的问题:

    def create_grid(self):grid = {}for d in self.hparms:v,len = next(self.__dict__[d])row = []for i in range(len):v,_ = next(self.__dict__[d])row.append(v)grid[d] = rowgrid = ParameterGrid(grid)    return grid

拥有包含所有超参数组合的内部参数网格后,更新 next 函数。reset 函数用于将索引复位为参数网格。每次调用 next 都会增加索引,并从参数网格 (self.grid) 中提取下一个值,使用 ** 操作将网格值作为输入解包到 HyperparametersGrid 的新实例中:

    def reset(self):self.gidx = 0def next(self):self.gidx += 1if self.gidx > self.grid_size-1:self.gidx = 0return HyperparametersGrid(**self.grid[self.gidx])

(3) 使用网格超参数类还需要用于控制超参数创建的生成器。为了简单起见,我们定义了两个函数:一个用于浮点数,另一个用于整数。在每个函数内部,我们从最小值 min 到最大值 max 以步长间隔 step 创建一个名为 grid 的值数组,遍历值列表,得到一个新的值和总列表长度。有了总列表长度,就可以通过迭代生成器来创建参数网格:

def grid(min, max, step):  grid = cycle(np.arange(min, max, step))len = (max-min) / stepfor i in grid:yield i, int(len)def grid_int(min, max, step): grid = cycle(range(min, max, step))len = (max-min) / stepfor i in grid:yield i, int(len)

(4) 接下来,使用网格超参数类 HyperparametersGrid 和生成器函数来创建父 hp 对象。使用网格生成器函数。在初始化类之后,将创建一个内部参数网格,可以查询关于网格的信息,如获取组合或值的总数。然后,还可以调用父 hp 对象上的 next 来生成一对子对象。可以通过将每个超参数的值数相乘来计算网格组合的数量。在示例中,middle_layer9 个值,learning_rate10 个值,epochs1 个值,batch_size1 个值,总共有 90 个值,即 10×9×1×1=90。当处理多个变量和较小的步长时,网格大小可能会迅速增大:

hp = HyperparametersGrid(middle_layer = grid_int(8, 64, 6),learning_rate = grid(3.5e-02,3.5e-01, 3e-02),batch_size = grid_int(16, 20, 4),    epochs = grid_int(200,225,25)  
)print(hp.grid_size)
print(hp.grid.param_grid)
print(hp.next())
print(hp.next())

(5) 使用 GPU 进行训练。runshp.grid_size 定义,并创建一个名为 grid_size 的新变量,它由 runs 的数量定义,第二个变量用于定义在适应度评估图上绘制的网格单元格的大小:

cuda = True if torch.cuda.is_available() else False
print("Using CUDA" if cuda else "Not using CUDA")
Tensor = torch.cuda.FloatTensor if cuda else torch.Tensorloss_fn = nn.MSELoss()  
if cuda:loss_fn.cuda()def train_function(hp):hp = hp.next()X = np.reshape(np.arange(data_min, data_max, data_step), (-1, 1))y = function(X)inputs = X.shape[1]tensor_x = torch.Tensor(X) # transform to torch tensortensor_y = torch.Tensor(y)dataset = TensorDataset(tensor_x,tensor_y) # create your datsetdataloader = DataLoader(dataset, batch_size= hp.batch_size, shuffle=True) # create your dataloadermodel = Net(inputs, hp.middle_layer)  optimizer = optim.Adam(model.parameters(), lr=hp.learning_rate)if cuda:model.cuda()    history=[]  start = time.time()for i in range(hp.epochs):        for X, y in iter(dataloader):# wrap the data in variablesx_batch = Variable(torch.Tensor(X).type(Tensor))y_batch = Variable(torch.Tensor(y).type(Tensor))                   # forward passy_pred = model(x_batch)        # compute and print lossloss = loss_fn(y_pred, y_batch)  ll = loss.datahistory.append(ll.item())                   # reset gradientsoptimizer.zero_grad()        # backwards passloss.backward()        # step the optimizer - update the weightsoptimizer.step()  end = time.time() - startreturn end, history, model, hpbest = float("inf")
span, history, model, hp_out = train_function(hp)
print(hp_out)
plt.plot(history)
print(min(history).item())

(6) 最后,输出评估图,根据计算的变量设置 grid_size。使用六边图将适应度值自动映射为颜色,然后,根据组合的数量设置 grid_size。在示例中,我们假设参数的网格是正方形的,但这可能并不总是准确的:

for i in range(runs):  span, history, model, hp_out = train_function(hp)y_ = model(torch.Tensor(Xi).type(Tensor))  fitness = loss_fn(y_, torch.Tensor(yi).type(Tensor)).data.item() run_history.append([fitness,*hp_out.values()]) if fitness < best:best = fitnessbest_hp = hp_outclear_output()    fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(18,6))    fig.suptitle(f"Best Fitness {best} \n{best_hp}")fig.text(0,0,f"Run {i+1}/{runs} Current Fitness {fitness} \n{hp_out}")ax1.plot(history)ax1.set_xlabel("iteration") ax1.set_ylabel("loss")ax2.plot(Xi, yi, 'o', color='black') ax2.plot(Xi,y_.detach().cpu().numpy(), 'r') ax2.set_xlabel("X") ax2.set_ylabel("Y")rh = np.array(run_history)      hexbins = ax3.hexbin(rh[:, 1], rh[:, 2], C=rh[:, 0], bins=25, gridsize=grid_size, cmap=cm.get_cmap('gray'))ax3.set_xlabel("middle_layer")ax3.set_ylabel("learning_rate")    plt.show()time.sleep(1)

下图显示了输出结果,显然比随机搜索要快得多,但不够准确。最终适应度(约 12,000 )是随机搜索中适应度的三分之一(约 57,000)。因此,网格搜索的结果不够准确,但更快更高效。我们可以将搜索范围缩小到较小的范围,并减小步长以提高准确性。

在这里插入图片描述

网格搜索是一种优秀的技术,在需要系统地查看各种超参数组合时,它非常有用。然而,需要特别的是,在输出图中,最佳适应度(暗色区域)与最差适应度(浅色区域)之间相差只有两个单元。然而,我们可以看到在这个浅色区域周围有很多具有良好适应度的区域,这表明我们很可能错过了全局最小值和/或最大值。解决此问题的方法是缩小网格范围,只覆盖两到三个单元的区域,以更好地确定最佳超参数。

小结

网格搜索的优势在于其能够完全覆盖预定义的参数空间,确保找到最优解,然而随着参数空间的增大,网格搜索的计算成本会显著增加,因为它需要评估每个可能的参数组合。在本节中,我们介绍了网格搜索的基本原理,并学习了如何通过网格搜索自动超参数优化。

系列链接

遗传算法与深度学习实战(1)——进化深度学习
遗传算法与深度学习实战(2)——生命模拟及其应用
遗传算法与深度学习实战(3)——生命模拟与进化论
遗传算法与深度学习实战(4)——遗传算法(Genetic Algorithm)详解与实现
遗传算法与深度学习实战(5)——遗传算法中常用遗传算子
遗传算法与深度学习实战(6)——遗传算法框架DEAP
遗传算法与深度学习实战(7)——DEAP框架初体验
遗传算法与深度学习实战(8)——使用遗传算法解决N皇后问题
遗传算法与深度学习实战(9)——使用遗传算法解决旅行商问题
遗传算法与深度学习实战(10)——使用遗传算法重建图像
遗传算法与深度学习实战(11)——遗传编程详解与实现
遗传算法与深度学习实战(12)——粒子群优化详解与实现
遗传算法与深度学习实战(13)——协同进化详解与实现
遗传算法与深度学习实战(14)——进化策略详解与实现
遗传算法与深度学习实战(15)——差分进化详解与实现
遗传算法与深度学习实战(16)——神经网络超参数优化
遗传算法与深度学习实战(17)——使用随机搜索自动超参数优化

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com