深度学习项目搭建与训练技巧


0 核心思想

0.1 尽可能地用计算机代替人工完成任务

虽然在短期需要一些额外的工作,但是长远开看可以节省很多的时间和成本。

0.2 当出现问题时,不用凑合的办法修正,去找到出现问题的原因,或许会有意外的收获

0.3 在工程上,先解决80%的问题,在慢慢解决剩下20%的问题

0.4 科学炼丹——快、准、稳

原文链接:别再喊我调参侠!夕小瑶”科学炼丹”手册了解一下

0.4.1 小白如何找准科研方向

  1. 查找大厂的职位描述,大厂的职位描述某种程度上代表了工业可落地的方向,首先不至于毕业找不到工作,其次可落地的更具发展前景
  2. 查看当年以及去年顶会论文,如果一两年都没有顶会文章产出,某种程度上代表研究比较困难,不适合小白深入

0.4.1 “准”字诀

  1. 做好调研,找准起点

在开始调研之前,先定位算法问题所对口的学术会议或期刊,如下:

  • query-doc相关性匹配:SIGIR,CIKM等IR强相关的会议,而不是NLP的会议;
  • NLI、问答、对话等语义匹配问题:ACL、EMNLP、NAACL、COLING等NLP会议,而不是IR会议;
  • 改进算法模型问题:ICLR、NIPS等深度学习、神经网络会议
  • 无法找到对口的问题:AAAI和IJCAI会议

但是定位会议是为寻找相关模型服务的,在定位好对口的期刊或会议后,寻找几篇近两年内发表的跟待解决问题相接近的paper,了解这些paper后,追根朔源,通过这些paper里的related work与实验的baselines章节,了解到该算法问题更早的工作。

PS:一定要充分阅读相关文献,一方面这些文章在数据处理与实验上会有很多的经验,可以避免踩坑,另一方面,这些模型富含丰富的策略,值得参考。

  1. 构建策略迭代闭环,找准努力方向

一个比较通用的迭代闭环是:

数据集分析 - 预处理策略 - 算法策略 - 模型评价 - case study

(1)数据集分析

在开始之前对数据集做个简单的分析,可能有助于大大降低之后的体力劳动(提前排除不靠谱的策略和不敏感的超参数),并大大降低
初次接触新任务时犯致命错误的概率。

(2)预处理策略与算法策略

这个环节最直接方法就是搬运上一节的调研结果,将一些paper中比较有效的策略搬过来进行验证。不过,尤其是注意一个meaningful的问题,即搬运这个策略,甚至设计一个新的策略,目的是什么?要解决什么问题?毕竟很多paper中的策略的适用场景是很局限的,毫无目的的搬运可能会大大增加无用功。同时,这也是避免“结果主义”(魔改的模型结果好再去找原因)炼丹。因此在调研时,面对不同的模型,也要学会思考背后的理论原理或数学逻辑,才能深刻理解模型。也可以方便自己思考新的解决方案

(3)模型评价

业务中可能有一些模棱两可的算法任务,如何无偏地评价一个模型的好坏,就需要在大规模开搞之前仔细设计清楚了。没有一个客观、无偏且自动的评价指标,策略迭代无疑会非常缓慢甚至到后期推翻重来。

即便是有一些常用的指标,也要经过你自己的思考之后再决定是否采用。

(4)case study

简单的说,case study就是针对犯的错误,进行反思和总结,分析原因,吸取经验和教训,给出改进措施,保证以后不再犯相同的错误或者类似的错误。
每一次尝试之后都要做case study,同时做好记录(可见后文),这样可以不断减少无用功。

  1. 重视bug,找准翻车原因

实际上,在策略迭代过程中,刚实现一个策略时,非常容易出现bug,当初步模型效果不好时,优先考虑是存在bug。一方面可以通过打印中间过程结果检查,一方面可以通过观察模型效果检查。
有时候调参和使用一些算法策略可以缓解bug带来的影响,导致小白误以为继续卖力的调参和疯狂试错就一定能把这个鸿沟填平。实际上,比起算法和超参,bug往往致命的多。当然了,对于一些特殊的算法问题(比如众所周知的RL问题),超参数确实极其敏感,需要具体问题具体分析。

0.4.2 “快”字诀

  1. 摆脱“洁癖”,提高写代码速度

算法探索具有极强的不确定性,很可能你写了半天的代码最后由于不work而完全废弃,因此,从代码风格上来说,一定要避免把代码写成系统,各种面向对象的封装技巧一顿乱怼是非常不必要的。
允许一些”垃圾代码“的存在可以大大提高实验迭代的效率。

  • 如何利用”垃圾代码片段“

(1)最简单的方法是直接使用粘合剂”Bash Script”。即将功能零散的代码片段通过bash管道命令连接起来,这样还能通过”&”+wait命令的组合拳实现对大规模数据集的(多进程)并行处理。

(2)对shell实在不熟悉的也可以使用jupyter notebook来进行粘合。不过,强烈建议每个NLP算法工程师熟练使用bash和vim,相当多的数据处理和分析是不需要使用python的,习惯了之后这些bash命令和vim技巧会对炼丹效率有非常明显的提升。

(3)对于更加碎片化的小代码(比如边分析边修改逻辑生成一个小字典),则可以考虑使用ipython,完成任务后一条magic命令%save就让这些碎片代码变得可复用了。

  1. 分规模验证,快速完成实验

Debug的四个阶段:

第一阶段:调通代码。可以通过人为捏造有规律的少量几条数据,修正代码的语法错误与逻辑错误。

第二阶段:验证收敛性。在数据集中取出几百或几千条样本进行训练,看看若干epoch之后训练loss是否能降低到接近0。

第三阶段:小规模实验。在万级或十万级别的小样本集上验证模型表现,分析超参数敏感性,并且调整超参数取值。这一阶段在数据规模不大时(比如几十万或者一二百万)其实可有可无,当训练数据集非常大(亿级别)还是有必要的。

第四阶段:大规模实验。即有多少训练数据就上多少,甚至多训练几个epoch。此时应该保证代码是高度靠谱,基本无需调参的,否则试错代价往往难以承受。

  1. 理性调参,把算力和时间留给策略探索

超参数和所处环境,有的与网络结构和预训练模型强耦合等等。因此,调参的第一步, 也是最重要的一步是进行超参数敏感性分析,找到对当前任务性能影响最大的几个超参数之后再进行精调。

要确定各个超参数的敏感性,一方面可以根据自身经验来定,一方面可以根据各paper中的取值(差异大的超参数可能是敏感超参,大家都取值相同的一般不敏感),实在不确定, 跑两三组实验就够确定敏感性了,完全没有必要来个“网格搜索”。

0.4.3 ”稳“字诀

  1. 实验管理与代码管理

实验管理就是要记录下来每一次实验的策略名和对应的实验结果,一般以表格的形式记录。这里可以用excel、markdown编辑器等记录,当然更建议使用支持云端同步的工具来记录(比如语雀、石墨文档、印象笔记或内网的相关工具等),以防电脑被偷、文件误删等意外导致的悲剧。
但是,有时候实验着急,对策略的描述不够仔细怎么办?比如某次实验同时改变了具体策略、还改了超参数、预训练模型等一堆东西, 不能用一个名字概括全部,怎么办呢?
最简单的做法就是与版本管理工具配合,再也不用担心未来settings丢失、 模型无法复现、模型无法追溯环境等问题了。而要实现版本管理,也很简单,Git自 然是不二之选。

  • 怎么用Git管理版本和实验迭代呢?

首先,务必保证训练日志、eval日志是以文件的形式存了下来,而不是打印到屏幕上变成过眼云烟了;此外,需要保证每一次运行时的settings (比如超参数、数据集版本、ckpt存储路径等)都能保存到日志文件中,且尽量封装一个run.sh来维护训练任务的启动环境。

接下来看每个人自己的习惯,此处引用原文作者的习惯:

(1)主线策略每成功推进一步,就调用git tag打个tag。这里的tag即策略名,与实验管理的表格中的策略名对齐。

(2)如果要在某个策略的基础上尝试一个很不靠谱的探索,那么可以在当前策略的基础上拉一个分支出来,在这个分支上完成相应事情后切回主分支。当然啦,万一这个分支上的策略work了,就可以考虑将其转正,合入主分支并打上相关tag。

最后一定要记得做好备份工作,即周期性的将环境中的关键代码push到github等远程仓库。当然了,对于ckpt、数据集这种大型文件,可以写入.gitignore文件中以免把仓库撑爆,这些大型文件的最佳归宿当然就是hadoop集群啦。

更多Git使用技巧参考:Git从入门到进阶,你想要的全在这里

0.5 实验运行前牢记

0.5.1 seed是否可以保证实验可复现

设置seed的正确方式,并且必须写在初始化模型之前

def set_seed(offset=0):
  seed_num = args.seed + offset
  random.seed(seed_num)
  np.random.seed(seed_num)
  torch.manual_seed(seed_num)
  torch.cuda.manual_seed(seed_num) # 让显卡产生的随机数一致
  torch.cuda.manual_seed_all(seed_num) # 多卡模式下,让所有显卡生成的随机数一致?这个待验证
  os.environ['PYTHONHASHSEED'] = str(seed_num)
  torch.backends.cudnn.deterministic = True
  torch.backends.cudnn.benchmark = False

0.5.2 是否在可公开数据集上运行

数据集太大时,迭代idea的时候可以进行采样,在小数据集上运行

0.5.3 是否已经运行对比模型

0.5.4 idea是否可行

有了新的idea不要急于去实现,而是要反复思考其中的合理性与预期的有效性,最好是隔一天或者半天再去实现

0.5.5 idea是否是在最优模型上的改进

一定要在当前最优的模型的基础上进行修改,因为提出的改进可能在最优模型上无效

0.5.6 代码是否正确

实现代码之后不要马上实验,而是要一步一步地验证代码的正确性,防止出错,可以在一些baseline的基础上进行实验,还可以进行一些必要的可视化

0.6 Idea有效性的判断

0.6.1 有没有类似工作,比如设计某种结构解决某类问题

0.6.2 符不符合人的直觉

0.6.3 有效的方向往往是简单的,容易理解的

0.7 研究推进

0.7.1 一定要有严格的ddl观念

0.7.2 一定要有周期性地汇报

0.7.3 一定要管理好进度

1 Pytorch使用

1.1 torch.sparse.mm 无法反向传播

1.2 drpout一般发生在training阶段,Pytorch模型可通过self.training获取状态

  • self.training通过model.train() / model.eval() 设置值

1.3 Pytorch的tensor数值与一般的int等基本数据类型不同,注意用.item()统一类型

### train_label 为 doK_matrix (0,0) 1.0
### users=tensor([0,0,0])
(0, 0) in train_label # True
(users[0], 0) in train_label  # False
(users[0].item(), 0) in train_label  # True

1.4 Debug

在设置断点后,在此处运行部分添加参数,即可像命令行一样运行,同时可以进行Debug。不添加参数则默认无参数运行,一般会报错切无法Debug。

PyCharm_DebugConfig

2 调整超参

2.1 模型第一次完整运行注意打印关键信息,否则可能长时间无响应

2.2 为节省时间,避免用循环,尽量用矩阵操作

循环操作虽然只增加2-3秒,但是在大数据集下,会显得尤为突出,2-3秒在很多个batch下会被放大

2.3 模型的内容尽量写在forward里,避免不断地调用函数

当数据集大时会非常损耗时间,本来1秒的运行时间可能会因为循环调用函数变成10+秒,甚至重复操作也比重复调用函数来得快

2.6 train data一定要shuffle,使得loss更新

2.7 报错 WARNING:root:NaN or Inf found in input tensor.

2.7.1 寻找梯度消失的位置与原因

  • 大概Debug步骤
  1. 通过打印梯度,找到最先出现nan值的地方
  2. 找到出现nan值地方后,根据下面可能的原因一一排查
  • 通过如下代码可以打印梯度,可以快速找到梯度从哪里开始出现nan值
loss.backward()
	for name, parms in net.named_parameters():	
		print('-->name:', name, '-->grad_requirs:',parms.requires_grad, \
		 ' -->grad_value:',parms.grad)

原文链接:https://blog.csdn.net/Jee_King/article/details/103017077

2.7.2 可能的原因

  • 梯度消失或梯度爆炸
  1. 调小或调大学习率,如果涉及到多个损失函数,可以找出哪一项导致了梯度爆炸,通过减小其权重解决。
  2. 增大正则惩罚,如L1/L2正则化
  • 显存或内存不够
  1. 调小batch_size(训练与测试的batch size过大都有可能导致报错)
  • 代码错误:可以通过将loss设置为0检查
  1. 检查有无除法,将可能除0的地方,替换成1e-8
  • 数据集有问题:可以通过将loss设置为0检查
  1. 检查是否有越界数据或者Nan、Inf的数据
  • 代码原因(可能导致梯度反向传播的计算图出现问题,待证实 todo)
  1. 数学上成立的偏导数在PyTorch的计算图中可能会因为数据集过大出现梯度消失的情况,比如如下公式:

梯度消失代码原因的示例-1

  • 出现坏样本

设置训练data loader的batch size为1(根据数据规模而定,当运行时间很长时,可以设置较大的batch size,然后确定大致范围再一步一步缩小batch size),逐个样本送入网络,同时设置shuffle为False,也就是不打乱顺序。出现loss为NaN表示遇到了坏样本,此时迭代的批次数,就是该坏样本在数据集的位置,从而定位了坏样本出现位置。排除后重新训练,继续观察loss的情况,直到能正常训练一个epoch,表示所有的坏样本均被去掉,可以开始正常的训练了。

另外也可以在数据集读取样本时保留其原始文件名,同样设置batch size为1,遇到loss变为NaN就输出文件名,更加方便一些。

  • 不合适的损失函数带来的数学问题

采用一些涉及到除法、对数函数等的损失函数时,要注意除零、对数输入负数等情况,这样同样会导致loss变为NaN。这种情况的出现,一般是在开始训练就出现,不存在loss逐渐增大到爆炸的过程。

一般通过对网络输出值域以及损失函数定义的数学分析可以进行判断,并进行相应地调整。

原文链接:训练过程中Loss突然变为NaN的可能原因与解决

2.8 留点数据用于最终验证

如果数据还算充裕,最好能留⼀部分数据,在训练结束之后再⽆偏验证⼀下模型们的表现。

2.9 网格搜索寻找最优超参

  • 通过设置超参数的范围(有些超参数彼此影响需要共同搜索,比如batch size与learning rate),在多个超参数取值下逐个遍历尝试,即为网格搜索

  • 在进行网格搜索前,首先需要了解相关模型一般的敏感超参数以及其范围,这些可以通过去了解对比模型的论文参数设置收集

  • 其次是封装好遍历运行脚本,并记录好相关结果,最好是对每一次搜索的结果都即时记录,以防一次完整搜索出现问题就要从头开始重新搜索

  • 在运行脚本前,最好对模型的执行效率进行检查,确保最大程度利用显卡性能,减少实验的时间

  • 对于与迭代epoch次数无关的超参数,比如模型自身的参数设置等,可以设置较少的epoch,这些参数在较少的epoch下即可分出优劣,但是如学习率等超参数必须设置较大的epoch才能对比出结果,有时还需要结合loss图像观察,比如观察学习率是否合适

PS:更多的超参数调整信息,可以查看以下博客

  1. 深度学习 超参数调整
  2. 【DL碎片4】深度学习中的的超参数调节

3 权重初始化

3.1 Xavier

对于每个经过的层,我们希望方差保持相同,这有助于防止梯度爆炸或消失。换句话说,我们需要以使 x 和 y 的方差保持相同的方式来初始化权重,此初始化过程称为Xavier初始化(Xavier initialization)。(Xavier 读音 zeivier)

4 评价指标

4.1 多分类问题的准确率、召回率与F1分数

把每个类别单独视为”正“,所有其它类型视为”负“,考虑使用混淆矩阵,分别计算各个类别的精确率与召回率。

4.2 相同评价指标与相同规模数据集下评价指标在不同量级,找数据集设置

当相关研究文章里的模型效果与自己运行相同模型的效果相差很大时,需要不断去找这些文章对输入数据集的设置,一定要不断地去找,直至效果的量级相差不大。可以分为几个可能。

  1. 对数据集的不同筛选条件导致,可能是随机筛选,也可能有条件筛选
  2. 数据集并非全部输入模型的训练过程,可能采取了采样来减少输入,以平衡训练效率与时间
  3. 测试集的规模不一致,有些测试集很多,但是自己设置的数据集较小
  4. 可能是没有去除原始数据集中的重复项,但是训练集与测试集划分后会有重复
  5. 对于一些采用负采样方法的测试集,负采样的个数很大程度上决定了模型的性能,负采样个数越大,干扰性越强,模型的效果就会降低

4.3 对于模型的重复实验,应该是设置种子来固定每一次结果,设置多个种子实现重复实验

# 通用设置随机数种子函数,在每次随机过程之前调用
def set_seed(args):
    random.seed(args.seed)
    np.random.seed(args.seed)
    torch.manual_seed(args.seed)
    if args.n_gpu > 0:
        torch.cuda.manual_seed_all(args.seed)

5 模型收敛

5.1 避免陷入“高原平坦区”

以下内容节选自:《你的模型真的陷⼊局部最优点了吗?》

在⾼维空间⾥(深度学习问题上)真正可怕的不是局部最优也不是鞍点问题,⽽是⼀些特殊地形。⽐如⼤⾯积的平坦区域。

在平坦区域,虽然导数不为0但是却不⼤。虽然是在不断下降但是路程却⾮常⻓。对于优化算法来说,它需要⾛很多很多步才有可能⾛过这⼀⽚平坦区域。甚⾄在这段地形的⼆阶导数过于特殊的情况下,⼀阶优化算法⾛⽆穷多步也⾛不出去(设想⼀下,如果终点在⼀⽶外,但是你第⼀次⾛0.5⽶,后续每⼀步都是前⼀步的⼀半⻓度,那么你永远也⾛不到⾯前的⼀⽶终点处)。

所以相⽐于栽到最优点和鞍点上,优化算法更有可能载到这种类似平坦区的地形中(如果这个平坦区⼜是“⾼原地带”,即loss值很⾼的地带,那么恭喜你悲剧了)。更糟糕的是,由于⾼维地形难以可视化,还有很多更复杂的未知地形会导致假收敛,⼀旦陷⼊到这些危险地形中,⼏乎是⽆解的。

所以说,在深度学习中,与其担忧模型陷⼊局部最优点怎么跳出来,更不如去好好考虑:

  1. 去设计⼀个尽量没有“平坦区”等危险地形的loss空间,即着⼿于loss函数的设计以及深度学习模型的设计;

  2. 尽量让模型的初始化点远离空间中的危险地带,让最优化游戏开始于简单模式,即着⼿于模型参数的初始化策略;

  3. 让最优化过程更智能⼀点,该加速冲时加速冲,该⼤胆跳跃时就⼤胆跳,该慢慢踱步时慢慢⾛,对危险地形有⼀定的判断⼒,如梯度截断策略;

  4. 开外挂,本来下⼀步要⾛向死亡的,结果被外挂给拽回了安全区,如batch normalization策略等

5.2 Pytoch梯度反向传播

对于梯度反向传播的理解,参考下面两篇博客即可,博客内容很详细也很容易理解,此处不赘述。

6 减少训练时间

6.1 用列表生成式代替循环

[x * x for x in range(1, 11)]
  • 列表生成式用C语言实现,效率非常高

6.2 遍历list远快于遍历tensor,避免遍历tensor

import torch
import time

num = 1000000
a = torch.ones(num)
b = range(num)

start_time1 = time.time()
for i in a:
    pass
elapse_time1 = time.time() - start_time1

start_time2 = time.time()
for i in b:
    pass
elapse_time2 = time.time() - start_time2

print('Traverside Tensor Elapse Time: ', elapse_time1)
print('Traverside List Elapse Time: ', elapse_time2)
# Traverside Tensor Elapse Time:  2.491483449935913
# Traverside List Elapse Time:  0.036934614181518555

6.3 多线程(含线程同步)示例

# 多线程处理代码(含线程同步)
import numpy as np

global_graph_data_list = []

# 由_acquire解锁执行后释放_release锁
def genegrate_graph_data(feature_tensor, edge_index, acquire_mutex: Lock, release_mutex: Lock) -> None:
    # 不需要同步操作
    part_data_list = [(x, edge_index) for x in feature_tensor]
    # 同步操作
    acquire_mutex.acquire()
    global_graph_data_list.extend(part_data_list)  # 进程同步以便按顺序插入
    release_mutex.release()

n_thread = 4
n_user = 100
graph_edge_index = [[2,2,2],[2,2,2]]
node_user_embeddings = np.zeros((n_user,3))
# 此代码用于检验是否同步成功
for i in range(node_user_embeddings.shape[0]):
    node_user_embeddings[i][0] = i

# 创建n_thread个锁供n_thread个线程使用
mutex = [Lock() for _ in range(n_thread)]
print(mutex)
# 定义n_thread个线程
step = n_user // n_thread
threads = []
for i in range(n_thread):
    feature_tensors = node_user_embeddings[step * i: step * (i + 1)] if i < n_thread else node_user_embeddings[step * i:]
    thread = Thread(target=genegrate_graph_data, args=(feature_tensors, graph_edge_index, mutex[i], mutex[(i + 1) % n_thread]))
    threads.append(thread)

# 把除了第1个的进程锁上
[mutex[i].acquire() for i in range(1, n_thread)]
# 接下来只有线程1可以先执行是因为mutex[0]并没有被占用
# 之后的分别等待着前一个锁的释放才能继续执行
[thread.start() for thread in threads]
[thread.join() for thread in threads]

print(global_graph_data_list)

原文链接:浅谈Python3多线程之间的执行顺序问题

6.4 减少函数调用

函数的开销很大。尽量把循环放在函数内进行。而不要让每次迭代都调用函数。

6.5 尽量使用内置方法

尽量使用内置方法,因为内置的是C写的,效率肯定高很多。

6.6 用xrange代替range

6.7 对于可迭代对象尽量直接使用

6.8 复制可迭代对象使用自带的构造函数,不要使用列表生成式

6.9 对列表的每一个元素都调用函数,应该使用map(f(x), L)

如果想对列表的每一个元素都调用函数,应该用L1=map(f,L),而不是L1=[f(x) for x in L],这里使用map的原因是涉及到重复调用函数,列表列表生成式会更慢,如果不是函数调用,则可以用列表生成式。

6.10 生成器与列表生成式的选择

  • 如果你希望使用整个列表,则使用列表推导,因为他会节省生成器带来的系统开销;
  • 如果你只想用列表的一部分,那么使用生成器吧。

6.11 shuffle()+取前n项,获取列表随机元素

随机取出列表里的元素应该先用random.shuffle(list)把元素随机的打乱,然后顺序的取每一项即可。如果不想取到重复的,可以用list.pop(),既取出来了,又把这个元素从列表中删除。而且内置的pop方法,非常快速。

6.4 - 6.11 节选自他人博客,原文链接:python列表生成式和map效率_python提高效率(优化)的心得总结(不断补充)

6.12 查看GPU即时使用情况,watch -n 1 nvidia-smi

  • 1代表1秒

  • 可以通过nvidia-smi查看当前GPU使用情况

7 搭建模型

7.0 一般模型提出步骤

  1. 阅读他人文献,复现其模型,一定要在论文中提供的相同数据集上达到几乎差不多的性能才说明复现成功,才能用于对比
  2. 提出自己的模型框架,每一添加一个子部分,先要进行验证是否有性能提升,消融实验是中期检查手段,而不仅仅是写在论文里的文字
  3. 当他人模型效果无法复现出一致结果时,运行其公布的源码,看效果是否一致,如果一致则是复现代码的问题,否则是他人模型有其他的tricks未被发现。较长时间都无法达成一致,可以更换对比算法或者直接用他人论文中的数据,但是如果数据集不一致只能更换对比模型。

7.1 一定要在人为构造的极小数据集上验证模型每个模块的正确性

为了避免在验证模型策略时正确有效,必须保证依据模型策略搭建的模型是正确的,必须事先人为构造极小的数据集进行测试,每一步人工计算可能的结果,然后打印验证。有时可以简单设置为tensor shape的验证,但是最好通过计算具体值来验证,保证万无一失。

7.2 在搭建自己模型之前一定需要先复现他人模型,以保证自己模型与对比模型具有保持一致的数据集处理、评价指标等,才能进行性能对比

  • 这一点要求广泛的阅读其他对比模型,因为不同对比模型可能具有不同的数据处理方案,综合取最合理的处理方案
  • 搭建好他人模型后,一定要训练到与他人模型结果相差不大,即代表复现成功,可以用于性能对比

7.3 设计模型的子结构的时候,一定要先检验这部分结构添加上后能否提升性能,即消融实验是检验手段,应该在前面做

为了验证论文的创新点,需要进行相应的消融实验(无须重复实验,设置相同seed即可),一定要把该消融实验做完整,验证正确,再开始其他的实验(如超参数调整等),因为有了这部分的消融实验结果,才能证明论文创新点的正确性,才能保证一切准备就绪的时候不会因为消融实验失败,一切推倒重来。

上述的消融实验是简单的验证,在进行超参数调整之后,需要重新重复实验,这样才能使用最佳结果进行消融比较,才能在论文中进行论述。


文章作者: fdChen
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 fdChen !
评论
  目录
加载中...