使用随机搜索自动超参数优化

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

0. 前言

我们已经通过函数逼近问题学习了深度学习 ( Deep learning, DL) 模型手动超参数优化 (Hyperparameter Optimization, HPO) 的方法,并使用不同的超参数生成比较结果。我们已经知道了,手动超参数优化极其耗时。目前,有许多工具可以自动执行 HPO,我们可以使用这些工具与进化计算 (Evolutionary Computation, EC) 方法的基准比较,但是为了深入了解它们背后的原理,我们将详细介绍自动搜索过程。

1. 随机搜索

随机搜索超参数优化 (Hyperparameter Optimization, HPO) ,是从给定范围内的已知超参数集中随机取样值,然后评估有效性的过程。随机搜索的目标是最终找到最佳或所需的解决方案。这个过程类似于有人戴上眼罩扔飞镖,希望能命中靶心。戴眼罩的人可能不会在几次投掷中命中靶心,但是在多次投掷中,也可能会命中靶心。

2. 将随机搜索应用于超参数优化

在本节中,使用简单的随机搜索算法自动超参数搜索。

(1) 首先,定义希望深度学习 ( Deep learning, DL) 网络近似的问题函数,生成用于对网络进行训练的输入和目标数据点样本集:

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 random

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")

(2) 接下来,使用的基础模型/类 Net,作为我们要学习近似此函数的网络:

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

(3) 自动 HPO 的过程使用类来管理搜索。对于随机搜索,Hyperparameters类如下,初始化函数接受输入超参数并使用 update 方法将其转换为类属性。使用这个类时,首先设置基本属性为输入,然后对于每个超参数属性,定义一个生成器,提供下一个值。 在Hyperparameters 对象上调用 next 函数会生成一个新的生成对象,该对象用于单次评估:

class Hyperparameters(object):
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)
    
    def __str__(self):
        out = ""
        for d in self.__dict__:
            ds = self.__dict__[d]
            out += f"{d} = {ds}   "
        return out

    def next(self):
        dict = {}
        for d in self.__dict__:
            dict[d] = next(self.__dict__[d])
        return Hyperparameters(**dict)

(4) Hyperparameters 类在内部使用 Python 生成器模式循环遍历所有属性以创建一个新实例。对于随机搜索方法,使用名为 sampler 的函数生成器,sampler 函数用于在 minmax 设置的范围内连续取样给定函数的值。Python 支持两种形式的生成器——本节使用 yield 关键字中断循环并返回值。要执行生成器,需要将函数包装在 next 方法中:

def sampler(func, min, max):
    while True:
        yield func(min,max)

def static(val):
    while True:
        yield val

(5) 在初始设置或父 Hyperparameters 对象时将这些部分组合在一起。在父对象中,将每个输入定义为由各种函数和 min / max 范围定义的采样器生成器。需要注意的是,采样函数从 random.ranint 更改为 random.uniform,两个函数都根据均匀分布生成随机变量。调用 next函数会生成一个子超参数对象,该参数可用于试验评估:

hp = Hyperparameters(
    epochs = static(200),
    middle_layer = sampler(random.randint, 8, 64),  
    learning_rate = sampler(random.uniform,3.5e-01,3.5e-03),
    batch_size = static(16)  
)

#sample
print(hp.next())

epochs = 200   middle_layer = 43   learning_rate = 0.33428019768013584   batch_size = 16   

(6) 在训练函数 train_function 中,首先调用 hp.next() 生成子对象。然后,通过在对象上使用名称作为属性在训练算法中使用这些值。由于我们使用带有随机评估器的 sampler 函数,因此每次调用 hp.next() 时,输出都是一组随机超参数:

def sample_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]
    y = y.reshape(-1, 1)
    plt.plot(X, y, 'o', color='black')

sample_train_function(hp)

(7) 最后,自动执行 HPO,由于我们已经封装了所有随机采样与超参数类,因此其余代码非常简单。由于这是一个最小化问题,我们想要调整超参数以最小化目标网络的损失,因此我们将起始最佳值设置为无穷大。然后,在由 ·runs 定义的循环中,使用父超参数对象调用 train_function。在训练函数内部,HP生成超参数的新随机实例,并使用它们评估网络损失。通过对模型中所有点进行预测来评估整体适应度:

loss_fn = nn.MSELoss()
#@title The Main Training Function
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)
    
    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))
            y_batch = Variable(torch.Tensor(y))                   
            # 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)
plt.plot(history)
print(min(history).item())

**(8)**最后,绘制输出,首先绘制最后一次运行的损失和函数逼近程度,最后一个图是按学习率和中间层超参数绘制的所有运行历史的六边形散点图。随着自动 HPO 的运行,此图随时间而变化:

runs = 10000
best = float("inf")
best_hp = None
run_history = []
for i in range(runs):
    span, history, model, hp_out = train_function(hp)
    y_ = model(torch.Tensor(Xi))   
    fitness = loss_fn(y_, torch.Tensor(yi)).data.item() 
    run_history.append([fitness,*hp_out.__dict__.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"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().numpy(), 'r') 
    ax2.set_xlabel("X") 
    ax2.set_ylabel("Y")
    
    rh = np.array(run_history)    
    hexbins = ax3.hexbin(rh[:, 2], rh[:, 3], C=rh[:, 0], 
                            bins=25, gridsize=25, cmap=cm.get_cmap('gray'))
    ax3.set_xlabel("middle_layer")
    ax3.set_ylabel("learning_rate")    
        
    plt.show()
    time.sleep(1)

3. 结果分析

下图显示了使用随机搜索进行自动 HPO 的输出结果,顶部显示了最佳整体适应性及超参数,从左到右分别显示了网络训练的损失、模型对函数的逼近程度以及所有评估的映射情况。其中,最右侧的评估图表显示了最佳适应度的灰度输出,其中黑色六边形代表评估的最佳适应度。

随机搜索

训练过程可能持续数小时,可以清楚地看到最佳结果的位置。在这种情况下,我们只使用了两个超参数进行 HPO,所以我们可以清晰地在二维空间中可视化结果。当然,我们也可以在多个变量上使用所有这些技术,但这可能需要更多的运行时间。在之后的学习中,我们将介绍更高级的技术来可视化和跟踪多个超参数。
随机搜索适用于快速寻找问题解,但这种方法的问题在于随机方法就是随机的。我们无法知道是否越来越接近一个解决方案,或者可能的最佳解决方案会是什么样子。有多种统计方法可以跟踪进度并提供更好的解决方案,但这些方法仍然需要数百次甚至数千次迭代。
在本节中,为简单起见,我们只管理两个超参数,并且范围相对较小。这意味着在相对较短的时间内,我们可以得到一个相当好的解决方案。然而,虽然它是在一定数量的随机抽样中得到的最好解决方案之外,但无法得知其与最优解的接近程度。

小结

神经网络超参数优化的随机搜索是一种用于优化神经网络超参数的方法,适用于参数空间较大或者优化目标函数表现不规则的情况。在本节中,我们学习了随机搜索的基本原理,并利用随机搜索执行神经网络自动超参数优化。

系列链接

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

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

盼小辉丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值