Advertisement

华电软工非全研究生-玩深度学习之加速调参的方法

阅读量:

耗时和曲折的调参之路

最近阶段我在忙于毕业论文中的实验环节,在这段时间里我特意选择了四个数据集,并设计了一种神经网络架构与现有研究中性能最优的模型进行了对比分析。这些数据集中最小的数据量约为10,000条左右(即约1万),而最大则达到了百万级别规模。为了实现超越现有研究中的最佳模型的目标,在网络架构的设计上采用了较为先进的结构安排,并将参数优化视为一项精细的艺术(略带比喻色彩)。在实际操作过程中我对超参数进行了全面系统的调整包括通道数量、学习率设置、窗口大小选择、卷积通道数量以及Dropout比例等多个关键参数进行了反复试验最终使得模型性能得到了显著提升但这个过程实在太过漫长以至于令我疲惫不堪有时甚至觉得难以承受(有点像炼丹一般的过程)。每一轮参数调试都像是在进行一场复杂的实验操作有时甚至会让人感到不知所措结果往往难以预测令人不禁感叹是否真的能够突破现有的研究瓶颈这是一个充满挑战但也充满趣味的过程(其中'炼丹'这一概念让我感到新奇并且也由此深入探究了相关背景知识)。

"炼丹"一词在网络语境中常被用来形容深度学习的过程。

这主要是因为在实际应用中实现有效的模型训练都需要经过大量复杂的超参调试与模型架构选择等环节。

同样地,在实际应用中实现有效的模型训练也需要通过反复试验与微调。

这种复杂性和不确定性使得许多研究者认为这一探索过程与传统的化学实验相类比。

此外,

因为许多研究者对这一领域的基本原理尚不完全理解,

所以"炼丹"这一比喻也反映了人们对其本质属性的一种直觉认知。

但值得注意的是,

虽然我们可以通过大量计算资源训练出性能优异的模型,

但对其预测机制的理解却相对不足,

类似于我们在不清楚炼丹具体工艺的情况下依然能够欣赏其神秘色彩。

因此将这一探索过程比作炼丹更为贴切。

跑题了不好意思先到这就有些手忙脚乱不过经过一个月的模型训练也还算是小有心得因此统计显示训练次数高达72次约为一百万级别的样本数据每隔一百个样本算作一次EPOCH循环总共约耗时一小时完成了大约一百次EPOCH循环如果采用LSTM或Transformer架构由于其浮点操作数与参数数量均超过CNN水平预计耗时将大幅增加具体原因将在后文中详细说明下图是我整理出来的损失函数曲线

image.png

这些实验均是在利用业余时间进行的。例如,在路上思考一个网络架构,在回家后进行参数优化试验。然而社会人士与学生不同,因此不宜长时间停留在电脑前。提升效率成为当务之急,在参数优化过程中一直致力于寻找提升效率的方法。下面将介绍我针对这一问题提出的解决办法及逐步改进的过程。

家人的质疑

因为投入了大量的时间和精力在这一领域上

image.png

好了不说了,下面介绍我升级迭代的方案吧

1.引入earlystopping

痛点

作为新手学习者,在刚开始阶段持续监控TensorBoard中损失函数的变化趋势是非常重要的。为了区分模型出现过拟合还是欠拟合的情况,我们需要特别关注损失函数的表现。但值得注意的是,每个训练周期至少耗时约一小时。这个过程虽然耗时较长但确能帮助我们更好地理解机器学习的基本原理。

解决办法

通过查阅相关资料后,我引入了早停法这一技术,并开始详细介绍相关内容。
早停法(Early Stopping)是一种用于防止机器学习模型过拟合的技术。这种现象通常发生在模型过于复杂的情况下,在这种情况下,模型可能会过度地适应训练数据中的细节和噪声信息。为了防止这种情况发生,在训练过程中我们可以通过监控验证集的表现来决定何时停止训练。如果在连续几个周期内验证集的表现不再提升,则认为模型已接近最优状态并停止训练。这种方法本质上也是一种正则化手段,在实际应用中可以灵活地进行调整以提高模型性能。例如,在某些实现中我们可以设定一个"耐ience"参数来允许模型在验证性能下降之后继续迭代一定数量的周期以避免因随机波动而提前停止训练的机会。此外,在某些情况下我们还可以结合学习率退火等其他优化策略以进一步提升训练效果。总的来说,早停法是一种简单有效的正则化技术,在深度学习任务中得到了广泛应用

复制代码
    import torch
    import torch.nn as nn
    import torch.optim as optim
    from torch.autograd import Variable
    from sklearn.model_selection import train_test_split
    from sklearn.datasets import load_iris
    
    # EarlyStopping 类定义
    class EarlyStopping:
    def __init__(self, patience=2, verbose=False):
        self.patience = patience
        self.verbose = verbose
        self.counter = 0
        self.best_score = None
        self.early_stop = False
        self.val_loss_min = float('inf')
    
    def __call__(self, val_loss, model):
        score = -val_loss
    
        if self.best_score is None:
            self.best_score = score
        elif score < self.best_score:
            self.counter += 1
            if self.verbose:
                print(f'EarlyStopping counter: {self.counter} out of {self.patience}')
            if self.counter >= self.patience:
                self.early_stop = True
        else:
            self.best_score = score
            self.counter = 0
    
    # 加载数据
    iris = load_iris()
    X = iris.data
    y = iris.target
    X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42)
    X_train = torch.from_numpy(X_train).float()
    y_train = torch.from_numpy(y_train).long()
    X_val = torch.from_numpy(X_val).float()
    y_val = torch.from_numpy(y_val).long()
    
    # 创建模型
    model = nn.Sequential(nn.Linear(4, 16),
                      nn.ReLU(),
                      nn.Linear(16, 3))
    
    # 设定损失函数和优化器
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.Adam(model.parameters(), lr=0.01)
    
    # 初始化 EarlyStopping 对象
    early_stopping = EarlyStopping(patience=3, verbose=True)
    
    for epoch in range(100):  
    # 前向传播
    y_pred = model(X_train)
    # 计算损失
    loss = criterion(y_pred, y_train)
    # 反向传播
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    
    # 在验证集上计算损失
    with torch.no_grad():
        y_val_pred = model(X_val)
        val_loss = criterion(y_val_pred, y_val)
    
    print(f'Epoch {epoch}, Train Loss: {loss.item()}, Validation Loss: {val_loss.item()}')
    
    # 检查是否需要早停
    early_stopping(val_loss.item(), model)
    
    if early_stopping.early_stop:
        print("Early stopping")
        break
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

在这个例子中,我们开发了一个自定义的EarlyStopping类来实现早停机制。在每个epoch结束后,系统都会评估验证集的损失值。如果这个损失值优于之前记录的最佳结果,则会替代最佳分数并重置早停计数器。若验证集的损失未见改善,则会增加早停计数器。一旦该计数值达到设定好的"耐心"(patience)阈值,则算法将终止当前训练过程

2.引入开源免费远程桌面rustdesk

痛点

将深度学习设备放置在家里;无需在家时通过TeamView远程查看模型运行状态;遗憾地发现TeamView进行了升级并需付费。

解决办法

探索各种开源远程桌面工具的过程中, 自己尝试着构建一个基础服务器系统。随后通过深入研究发现rustdesk平台, 并且具备了自行搭建中级服务器的能力。优化后运行速度更快捷, 安全性也更有保障。这里就不详细讲解具体操作步骤了, 教程链接如下:
https://rustdesk.com/zh/server/

3.自动化超参数的选择(我还没试)?

痛点

例如调节一个dropout率时关注其损失值的变化情况。当 dropout率过高或过低时会导致较大的损失值波动,并呈现出类似钟形曲线的趋势。这个过程十分令人沮丧,因为每隔一段时间就需要检查模型是否收敛并评估其表现。这样的工作量非常大……

解决办法

网格搜索和贝叶斯优化是常见的超参数优化技术。

  1. 网格搜索 (Grid Search): 这种方法直接且机械地遍历所有可能的超参数组合,并从中选出性能最佳的那一组参数配置。其显著优势在于能够确保找到全局最优解;然而这一特点也带来了高昂的计算成本,在实际应用中尤其当超参数空间维度较高时尤为明显。
  2. 贝叶斯优化 (Bayesian Optimization):这种方法相较于传统方法更为高效与智能。它通过构建一个surrogate model(又称为 metamodel)来模拟超参数与模型性能之间的关系网络;随后在该模型表面系统地探索潜在的极值点,并通过实际验证逐步筛选出表现优异的候选点;每一次验证结果都会反哺模型更新过程中的概率分布函数估计值。

注意,在使用任何方法时,请记住它们只是工具而非万能武器(银弹)。这些工具可以帮助我们更高效地执行搜索操作,但最终的结果往往受到我们选择的超参数范围、模型架构等多个因素的影响。
此外,在深度学习领域中进行超参数调优是一项复杂的工作过程。我们需要综合考虑数据集特性、模型架构、优化器、学习率以及正则化等多方面的因素才能获得满意的效果。对于Dropout参数的选择而言,在一定范围内进行尝试通常是合理的策略(例如0.1到0.5之间),具体数值需根据验证集的表现来确定。
好的,请让我为你展示一个简单的例子。我们可以利用 sklearnGridSearchCVskoptBayesSearchCV 来实现网格搜索与贝叶斯优化等技术手段,并结合 sklearnEstimator 接口兼容性来进行超参数调优工作。
以下是一个具体的实施示例:
在本例中我们采用了一个非常简单的模型架构仅包含一层隐藏层和一个 Dropout 层为了探索不同 dropout 比例的影响我们将通过系统地调整 dropout 参数来优化模型性能

复制代码
    import torch
    from torch.autograd import Variable
    from torch.optim import SGD
    from skopt import BayesSearchCV
    from sklearn.model_selection import StratifiedKFold
    from sklearn.base import BaseEstimator, ClassifierMixin
    from sklearn.exceptions import NotFittedError
    
    class TorchClassifier(BaseEstimator, ClassifierMixin):
    def __init__(self, hidden_layer_size=20, dropout=0.5, epochs=10, lr=0.01):
        self.hidden_layer_size = hidden_layer_size
        self.dropout = dropout
        self.epochs = epochs
        self.lr = lr
        self.fitted_ = False
    
    def fit(self, X, y):
        self.model_ = torch.nn.Sequential(
            torch.nn.Linear(X.shape[1], self.hidden_layer_size),
            torch.nn.ReLU(),
            torch.nn.Dropout(self.dropout),
            torch.nn.Linear(self.hidden_layer_size, len(set(y)))
        )
    
        loss_fn = torch.nn.CrossEntropyLoss()
        optimizer = SGD(self.model_.parameters(), lr=self.lr)
    
        for _ in range(self.epochs):
            inputs = Variable(torch.from_numpy(X.astype(np.float32)))
            targets = Variable(torch.from_numpy(y.astype(np.int)))
    
            optimizer.zero_grad()
            outputs = self.model_(inputs)
            loss = loss_fn(outputs, targets)
            loss.backward()
            optimizer.step()
    
        self.fitted_ = True
        return self
    
    def predict(self, X):
        if not self.fitted_:
            raise NotFittedError()
    
        inputs = Variable(torch.from_numpy(X.astype(np.float32)))
        outputs = self.model_(inputs)
        _, preds = torch.max(outputs.data, 1)
        return preds.numpy()
    
    # 然后我们可以用 `BayesSearchCV` 来寻找最优的 dropout 参数:
    search_space = {"dropout": (0.1, 0.5)}
    cv = StratifiedKFold(n_splits=3)  # 假设你的数据是平衡的
    
    torch_model = TorchClassifier(epochs=10)
    bayes_search = BayesSearchCV(torch_model, search_space, n_iter=32, cv=cv)
    bayes_search.fit(X, y)  # 假设 X, y 是你的数据
    
    print("Best parameters found: ", bayes_search.best_params_)
    
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

4.模型训练完成自动通知

痛点

要时不时的去看下模型训练是否结束,然后好开展下面的测试工作。

解决办法

在代码中嵌入邮件或短信提醒功能,在手机收到提醒后即可安心处理其他事务;同时也可以利用Gitee的CI/CD管道脚本,在项目完成时发送提醒信息。这种简单的方法无需提供具体实现细节

5.大胆的想法,让chatgpt给你调参

痛点

调参耗时

解决办法

请以自然语言的形式向chatgpt说明以下参数设置:search_space = {“dropout”: (0.1, 0.5)}的具体范围,并要求逐步进行微调尝试;随后,请提供一个详细的分析结果报告。这个方案听起来过于激进,请问这样的方法能否实现?

transformer agent

image.png

微软的贾维斯

https://github.com/microsoft/JARVIS

总结

本文并非旨在展示技术的高超,在于记录攻读在职研究生过程中所经历的各种磨难与挑战。从实验中遭遇困境到通过解决困难来不断提升自我,在科研道路上不断突破自我、追求卓越的过程中完成一次次蜕变与成长。也期待学长学姐们能够克服困难、勇攀学术高峰!

全部评论 (0)

还没有任何评论哟~