当前位置: 首页 > article >正文

遗传算法与深度学习实战(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 ParameterGrid

def 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 = 0
        if 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] = row
        grid = ParameterGrid(grid)    
        return grid

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

    def reset(self):
        self.gidx = 0

    def next(self):
        self.gidx += 1
        if self.gidx > self.grid_size-1:
            self.gidx = 0
        return 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) / step
    for i in grid:
        yield i, int(len)

def grid_int(min, max, step): 
    grid = cycle(range(min, max, step))
    len = (max-min) / step
    for 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.Tensor

loss_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 tensor
    tensor_y = torch.Tensor(y)
    
    dataset = TensorDataset(tensor_x,tensor_y) # create your datset
    dataloader = DataLoader(dataset, batch_size= hp.batch_size, shuffle=True) # create your dataloader

    model = 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 variables
            x_batch = Variable(torch.Tensor(X).type(Tensor))
            y_batch = Variable(torch.Tensor(y).type(Tensor))                   
            # forward pass
            y_pred = model(x_batch)        
            # compute and print loss
            loss = loss_fn(y_pred, y_batch)  
            ll = loss.data
            history.append(ll.item())                   
            # reset gradients
            optimizer.zero_grad()        
            # backwards pass
            loss.backward()        
            # step the optimizer - update the weights
            optimizer.step()  
    end = time.time() - start
    return end, history, model, hp

best = 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 = fitness
        best_hp = hp_out
    clear_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)——使用随机搜索自动超参数优化


http://www.kler.cn/news/354092.html

相关文章:

  • 吴恩达深度学习笔记(4)---加速神经网络训练速度的优化算法
  • 【Python爬虫实战】正则:从基础字符匹配到复杂文本处理的全面指南
  • 2023-06 GESP C++三级试卷
  • Spring MVC的运行流程
  • Flume面试整理-Flume的故障排除与监控
  • 阿里云国际站DDoS高防增值服务怎么样?
  • 个人 Mac 常用配置记录
  • 特征提取:传统算法 vs 深度学习
  • 科学家们设计了一种新型胰岛素,能够根据血液中的葡萄糖水平自动开启或关闭
  • php常用设计模式之工厂模式
  • Spring Boot助力:图书进销存管理效率提升
  • A-【项目开发知识管理】Android AIDL跨进程通信
  • git add操作,文件数量太多卡咋办呢,
  • Java项目:158 springboot球队训练信息管理系统(含论文)
  • 04 设计模式-创造型模式-建造者模式
  • VS Code对齐NoteBook和Terminal的Python环境
  • Axure基本元件库——基本元件、表单和菜单表格
  • 串口(UART)的FPGA设计(接收与发送模块)
  • CSS之一
  • Whisper 音视频转写