NNI(Neural Network Intelligence) 是微软开源的自动机器学习工具包,它可以帮助优化在机器学习建模中耗时耗力的大量尝试各种超参组合和寻找最佳模型的过程。NNI着力解决超参调试过程的挑战,通过内置的超参选择算法、算力的强大支持和便捷的交互方式来加速和简化超参搜索的过程。更多详细内容和代码可以参考NNI的GitHub地址。
开篇博文“微软开源自动机器学习工具 – NNI安装与使用”是快速入门篇,提到了NNI的安装和通过简单三步使用NNI训练mnist样例,本篇是NNI (Neural Network Intelligence) 系列文章的第二篇,本篇会介绍NNI内置的三个工具和使用方式来介绍NNI是如何提高自动机器学习的效率。首先介绍使用两种方式定义搜索空间来提高训练效率,帮助节省时间和减少代码,然后介绍NNI的内置命令行工具nnictl来快速控制实验,最后通过内置前端界面webUI介绍NNI的高效能和可视化管理实验。后续系列文章还将从NNI的分布式——支持OpenPAI等多端部署、NNI的可扩展性——支持多种tuner和assessor等特点介绍NNI。
搜索空间是通过定义变量名称、变量的类型和候选值使得变量在一定的空间内可以做组合的搜索,它通过简单的定义方式可以定义大量的、丰富种类的搜索组合,而无需用户手动去多次更改代码,这样可以节省大量的时间,并减少代码出错的可能性,并可以快速尝试多种超参的组合。
一是通过无侵入的代码注释方式进行定义,这种方式通过在代码中对其变量加入注释行的方式定义变量的取值范围和取值方式,而不对代码本身做出修改,不改变代码本来的功能;NNI兼容已有代码,通过注释方式支持 NNI 后,代码还可以单独运行。
#==========================use annotation example=================================
@nni.get_next_parameter() //开头
@nni.variable(nni.choice(2,3,5,7),name=self.conv_size) //变量定义
@nni.report_intermediate_result(test_acc) //中间结果
@nni.report_final_result(test_acc) //最终结果
@nni.function_choice(max_pool(h_conv1, self.pool_size),avg_pool(h_conv1, self.pool_size),name=max_pool)
//函数选择
另一种是通过配置文件的方式进行定义,这种方式在代码外部定义单独文件,与代码文件分离,可以清晰查看、简单编辑。
#===========================use json file example====================================
{
"dropout_rate":{"_type":"uniform","_value":[0.1,0.5]},
"conv_size":{"_type":"choice","_value":[2,3,5,7]},
"hidden_size":{"_type":"choice","_value":[124, 512, 1024]},
"batch_size":{"_type":"choice","_value":[50, 250, 500]},
"learning_rate":{"_type":"uniform","_value":[0.0001, 0.1]}
}
NNI帮助用户提高训练效率并节省时间,不同于在需要多个超参搜索训练时定义多个并行的训练任务而进行重复的、存在冗余的、有拼写错误可能的、占用一定内存的、耗费时间的、实验间难以管理的训练重写,NNI通过少量代码定义清晰的搜索空间即可批量提交规定数量的训练任务,使用户能高效、便捷地进行训练任务
#===========================use json file example====================================
{
"lr":{"_type":"choice", "_value":[0.1, 0.01, 0.001, 0.0001]},
"optimizer":{"_type":"choice", "_value":["SGD", "Adadelta", "Adagrad", "Adam", "Adamax"]},
"model":{"_type":"choice", "_value":["vgg", "resnet18", "googlenet", "densenet121", "mobilenet", "dpn92", "senet18"]}
}
以NNI中内置的例子cifar10-pytorch为例说明,在此实验中搜索空间的定义包含:学习率有0.1、0.01、0.001、0.0001四种;优化器选择有SGD、Adadelta、Adagrad、Adam、Adamax五种;模型选择有vgg、resnet18、googlenet、densenet121、mobilenet、dpn92、senet18七种,如果需要将这三个变量的选择遍历一次需要4*5*7=140个训练任务,则需要提交不同变量组合产生的140个任务,在此过程中会消耗大量时间且有很多拼写错误的可能。而使用NNI的搜索空间可以用几行代码阐述这种变量遍历可以提高效率、节省时间、减少错误的可能性。
NNI中规定了丰富的搜索空间的取值类型,例如从列表中选择、规定上下界的随机数、规定均值和标准差的sigma分布等等。下面用一个表格介绍NNI的注释方式和json文件方式分别如何定义取值以及它们所代表的含义。
注释形式 | json文件形式 | 代表含义 |
---|---|---|
@nni.variable(nni.choice(option1,option2,…,optionN),name=variable) | {"_type":“choice”,"_value":options} | 从列表中选择 |
@nni.variable(nni.randint(upper),name=variable) | {"_type":“randint”,"_value":[upper]} | 选择[0,upper]中的随机整数 |
@nni.variable(nni.uniform(low, high),name=variable) | {"_type":“uniform”,"_value":[low, high]} | 选择[low,high]中的随机值 |
@nni.variable(nni.quniform(low, high, q),name=variable) | {"_type":“quniform”,"_value":[low, high, q]} | round(uniform(low, high) / q) * q |
@nni.variable(nni.loguniform(low, high),name=variable) | {"_type":“loguniform”,"_value":[low, high]} | 选择(uniform(low, high) / q) * q |
@nni.variable(nni.qloguniform(low, high, q),name=variable) | {"_type":“qloguniform”,"_value":[low, high, q]} | 选择[exp(low),exp(high)]中的值 |
@nni.variable(nni.normal(label, mu, sigma),name=variable) | {"_type":“normal”,"_value":[label, mu, sigma]} | 用均值μ和标准差sigma分布 |
@nni.variable(nni.qnormal(label, mu, sigma, q),name=variable) | {"_type":“qnormal”,"_value":[label, mu, sigma, q]} | round(normal(mu, sigma) / q) * q |
@nni.variable(nni.lognormal(label, mu, sigma),name=variable) | {"_type":“lognormal”,"_value":[label, mu, sigma]} | exp(normal(mu, sigma)) |
@nni.variable(nni.qlognormal(label, mu, sigma, q),name=variable) | {"_type":“qlognormal”,"_value":[label, mu, sigma, q]} | round(exp(normal(mu, sigma)) / q) * q |
NNI中内置的这两类定义搜索空间的方式都十分简单,您可以动手尝试在自己的代码中以注释或json文件的形式规定您所需要的变量和超参取值类型及范围,开始属于您的个人定制版自动机器学习任务。
Tuners是NNI为用户提供的通过超参优化库快速实现自动调参的函数库,NNI通过直接调用多种开源库或复现流行调参算法为用户提供便捷简单的算法选择功能,例如NNI支持随机查找(random search)、模拟退火(simulated annealing)和Tree-of-Parzen-Estimators等等。在NNI中众多的tuners将训练的中间结果作为矩阵接受来评估特定的参数或者网络结构配置的性能,并返回给训练任务下一个超参数或网络结构的配置内容,从而实现自动化调参的过程。
不同于以往使用超参优化算法的方式,NNI使用一种简单的方式来设置使用多种tuner,在配置文件中的参数中输入对应提供的优化算法名称即可尝试各种不同的tuner算法,而不用每次进行代码的整合和编译,这样可以省时省力。在NNI中不仅可以使用内置的tuner也可以自己定制tuner,NNI提供规范的定制tuner的方式便于用户根据自己的需求个性化定制tuner。
NNI支持了目前已有的多数tuner,用户可以在配置文件中直接调用,而无需每次都和自己的代码相整合,只需要在配置文件中修改使用tuner的变量名就可以尝试多种tuner,为自己的训练任务选择最合适的tuner算法。目前NNI内置的tuner如下所示。
TPE算法: Tree-structured Parzen Estimator (TPE)基于序列模型的优化(SMBO)方法,适用于多种场景,在计算资源有限仅能进行少量实验时,经验表明其效能优于random search。这种方法通过依次构建基于历史计算来近似超参数性能,然后模型基于新的超参数进行测试。
Random Search算法:随机搜索在不了解超参数的先验分布时表现良好,通过可以使用随机搜索的结果作为baseline,这种搜索方式适用于每次实验占用的时间较少且计算资源充足的情况。
Anneal算法:退火算法适用于每次实验占用的时间较少且计算资源充足的情况,或者搜索空间的变量可能来自于某些先验分布时使用。简单的退火算法先进行先验采样,随着时间推移越来越接近观察到的最佳采样点。
Naive Evolution算法:进化算法对计算资源的要求较高且需要庞大的计算量来避免达到局部最优解,它适用于含有评估算法且训练时长较短的情况,还适用于训练代码支持权重转移的情况。进化算法先随机初始化,然后逐代更迭,通过对每一代进行更改超参数、增删层数等变异方式获得下一代,每一次选择更好的一代。
SMAC算法:SMAC算法适用于计算资源优先且大多数超参数为离散值的情况,它基于序列模型的优化(SMBO算法),它适应以前的高斯随机过程模型,并引入随机森林的模型来处理分类参数。
Batch tuner算法:Batch tuner算法适用于已经确定了要尝试的搜索空间,用户为代码提供几种超参数选择的配置,它通过批量实验尝试不同的配置空间。
Grid Search算法:网格搜索通过搜索空间文件中定义的超参数空间的手动指定子集执行穷举搜索,它适用于当搜索空间比较小的情况,此时穷举遍历是可行的。
Hyperband算法:Hyperband使用有限的资源探索尽可能多的配置,并找出有希望获得最终结果的配置。基本思想是生成许多配置,通过少数几步运行产生的中间结果选出最有希望的配置,然后进一步训练那些有希望的配置以得到最终结果。这种算法在中间结果的表现在某种程度能够反应最终结果的性能。
Network Morphism算法:网络态射适用于计算机视觉领域且有自己的数据集的情况下寻找较优的网络结构,它具有了自动搜索深度学习模型架构的功能。每个子网络都从其父网络继承知识,并变形为不同类型的网络,包括深度,宽度和skip-connection的变化。接下来,它使用历史架构和度量对估计子网络的价值,最后选择最有前途的一个网络训练。
Metis Tuner算法:Metis是微软研究的一种tuner算法,它可以在训练中提供当前预测的最佳配置和下一次实验的配置建议,会给出建议是否需要对某一特定超参数进行重新采样。
NNI提供多种Tuners并内置于工具中以便用户使用,用户使用tuner的方式非常简单,只需在配置文件中的tuner栏目中填入相关参数即可,最常用的参数是buildintTunerName和optimize_mode。buildintTunerName可以根据实际训练任务选择上面提供的多种tuners名称,也可以使用自己定制的tuner,optimize_mode有两种模式maximize和minimize。此处以cifar10-pytorch为例来看看如何使用TPE算法来配置文件使其自动调参。用户可以在NNI的examples目录中查看这个例子的全部代码。
authorName: default
experimentName: example_pytorch_cifar10
trialConcurrency: 1
maxExecDuration: 100h
maxTrialNum: 10
#choice: local, remote, pai
trainingServicePlatform: local
searchSpacePath: search_space.json
#choice: true, false
useAnnotation: false
tuner:
#choice: TPE, Random, Anneal, Evolution, BatchTuner
#SMAC (SMAC should be installed through nnictl)
builtinTunerName: TPE
classArgs:
#choice: maximize, minimize
optimize_mode: maximize
trial:
command: python3 main.py
codeDir: .
gpuNum: 1
NNI使用简单的方式供用户选择tuner算法,此处在builtinTunerName处填入TPE,代表这个训练使用Tree-structured Parzen Estimator算法进行自动调参,在optimize_mode处填入maximize,代表算法返回的结果是具有更大期望的超参数集合。如果使用NNI中内置的算法仅仅通过几行代码的修改就可以完成不同算法的尝试,而无需做代码的整合,能极大地提高自动化机器学习的效率和便捷程度,如同一个黑盒,只用简单调用即可。如果用户想尝试特定的功能,NNI也支持用户进行个性化的tuner定制,并提供代码规范以便调用算法。
本文描述了NNI如何通过定义搜索空间和内置Tuner算法来加速训练,提高训练效率,后续系列文章还将从NNI的分布式——支持OpenPAI等多端部署、NNI的可扩展性——支持多种tuner和assessor等特点介绍NNI。看到这里,是不是想尝试自己的多种超参组合和多种tuner算法自动调参了?安装NNI体验这一过程吧,它将助你实现你的无限想法。更多详细内容和代码可以参考NNI的GitHub地址。
1.NNI github Repo
2.博文:微软开源自动机器学习工具 – NNI安装与使用
1.如何在你的机器上安装nni?
2.如何使用nnictl工具命令?
3.如何使用web UI?
4.如何定义搜索空间?
5.如何配置定义实验?
6.如何使用注释定义搜索空间?
7.如何写训练实验?
1.如何在本地训练任务?
2.如何在多个机器上训练任务?
3.如何在OpenPAI上训练任务?
4.如何在远程服务器训练任务?
5.尝试更多不同的tuners和assessors
6.自己定义一个tuner
7.自己定义一个assessor
1.如何创建NNI环境?
2.如何贡献代码?
3.如何debug?