「爱与否科技」怎么用最快的速度解决算法问题,机器学习、深度学习的基础已打好( 三 )


实际上在这种策略迭代过程的情况下 , 优先考虑是存在bug 。 比如用了BERT-uncased预训练模型 , 却忘了对输入文本进行lowercase处理;用了个char-level的模型 , 却给文本切了词;万事俱备后 , bash脚本里却忘了载入预训练模型等 。
有时候调参和使用一些算法策略可以缓解bug带来的影响 , 导致小白误以为继续卖力地调参和疯狂试错就一定能把这个鸿沟填平 。 实际上 , 比起算法和超参 , bug往往致命得多 。 当然了 , 对于一些特殊的算法问题(比如众所周知的RL问题) , 超参数确实极其敏感 , 需要具体问题具体分析 。

摆脱“洁癖” , 提高写代码速度
算法探索具有极强的不确定性 , 很可能你写了半天的代码最后由于不work而完全废弃 , 因此 , 从代码风格上来说 , 一定要避免把代码写成系统 , 各种面向对象的封装技巧一顿乱怼是非常不必要的 。 允许一些“垃圾代码”的存在可以大大提高实验迭代的效率 。
问题来了 , 假如你生产了一堆“垃圾代码片段” , 该怎么高效利用它们呢?直接丢掉 , 还是复制粘贴重构代码?
最简单的方法是直接使用粘合剂“BashScript” 。 即将功能零散的代码片段通过bash管道命令连接起来 , 这样还能通过“&”+wait命令的组合拳实现对大规模数据集的(多进程)并行处理 。
对shell实在不熟悉的小伙伴也可以使用jupyternotebook来进行粘合 。 不过 , 强烈建议每个NLP算法工程师熟练使用bash和vim , 相当多的数据处理和分析是不需要使用python的 , 习惯了之后这些bash命令和vim技巧会对炼丹效率有非常明显的提升 。
而对于更加碎片化的小代码(比如边分析边修改逻辑生成一个小字典) , 则可以考虑使用ipython , 完成任务后一条magic命令%save就让这些碎片代码变得可复用了 。
不仅在生产代码上可以大大提速 , 在debug和调参问题上依然是有学问的 。
分规模验证 , 快速完成实验
这个问题写出来时感觉很白痴 , 但是据我观察 , 大部分新手都存在这个问题 。 如果你给他100万规模的训练集 , 他就会拿整个训练集去调试;你给他1000万规模的训练集 , 他还是拿整个训练集去调试 , 甚至不忘抱怨“数据载入太费时间了 , 调试好花时间呀” 。
「爱与否科技」怎么用最快的速度解决算法问题,机器学习、深度学习的基础已打好
文章图片
亲 , debug也是分阶段的呀....
第一阶段:调通代码 。 这时候象征性的挂几百条样本就够了 , 修正语法错误和严重的逻辑错误 。
第二阶段:验证收敛性 。 很多bug不会报错 , 但会导致训练完全崩溃或者压根就没在训练 。 可以对几百条或者几千条样本进行训练 , 看看若干epoch之后训练loss是否能降低到接近0 。
第三阶段:小规模实验 。 在万级或十万级别的小样本集上验证模型表现 , 分析超参数敏感性 。 这一阶段在数据规模不大时(比如几十万或一二百万)其实可有可无 , 当训练数据极其庞大时(十亿级甚至百亿级的话)还是必要的 。 有一些很细微的bug虽然不会影响收敛 , 却会显著影响最终模型的表现 。 此外也有助于发现一些离谱的超参数设置 。
第四阶段:大规模实验 。 即 , 有多少训练数据 , 就上多少 , 甚至多训练几个epoch 。 进行到第四阶段时 , 应当绝对保证代码是高度靠谱的 , 基本无需调参的 , 否则试错代价往往难以承受 。
理性调参 , 把算力和时间留给策略探索
初学者喜欢把什么都作为超参数来调 。
“文本截断长度不确定设置多少?挂10组实验调一调”“官方代码里有个warmup不知道有啥用?挂10组实验调一调”“听说batchsize对性能有影响?挂10组实验调一调”一切尽在调参中╮(╯▽╰)╭
这种做法无疑是极其浪费时间和计算资源的 , 有的超参数完全可以算出来合理范围 , 有的取决于其他超参数和所处环境 , 有的与网络结构和预训练模型强耦合等等 。 因此 , 调参的第一步 , 也是最重要的一步是进行超参数敏感性分析 , 找到对当前任务性能影响最大的几个超参数之后再进行精调 。