NNI 超参搜索介绍
NNI(Neural Network Intelligence)是微软开源的一个 AutoML 工具包,用于超参数优化、神经架构搜索、模型压缩和特征工程。
NNI 的超参搜索支持更多的算法。同时在 pipeline 中也支持了"边训练边超参搜索"的模板。只需要用户的代码能接受超参数作为输入,同时上报作为超参算法的目标值即可。
每个用户可以启动多个超参搜索的实例,通过 url prefix(/nni/<实验名>/)作为前端的路由(路由前缀实现见 myapp/views/view_nni.py:434,nni web 服务监听容器内 8888 端口)。
可以参考 NNI 官网 的书写方式。
NNI 超参搜索
1、编写超参搜索代码
参数接收
启动超参搜索时,会根据用户配置的超参搜索算法选择好超参的可选值,并将选择值传递给用户的容器。例如下面的超参定义会在用户 docker 运行时传递下面的参数。所以用户不需要在启动命令或参数中添加这些变量,系统会自动添加,用户只需要在自己的业务代码中接收这些参数,并根据这些参数输出值即可。
--lr=0.021593113434583065 --num-layers=5 --optimizer=ftrl
结果上报
业务方容器和代码启动接收超参进行迭代计算,通过主动上报结果来进行迭代。示例如下:用户代码需要能接受超参可取值为输入参数,同时每次迭代通过 nni.report_intermediate_result 上报每次 epoch 的结果值,并使用 nni.report_final_result 上报每次实例的最终结果值。
import os
import argparse
import logging, random, time
import nni
from nni.utils import merge_parameter
logger = logging.getLogger('mnist_AutoML')
def main(args):
test_acc = random.randint(30, 50)
for epoch in range(1, 11):
test_acc_epoch = random.randint(3, 5)
time.sleep(3)
test_acc += test_acc_epoch
# 上报当前迭代目标值
nni.report_intermediate_result(test_acc)
# 上报最终目标值
nni.report_final_result(test_acc)
def get_params():
# 必须接收超参数为输入参数
parser = argparse.ArgumentParser(description='PyTorch MNIST Example')
parser.add_argument('--batch_size', type=int, default=64, help='input batch size for training (default: 64)')
args, _ = parser.parse_known_args()
return args
if __name__ == '__main__':
try:
# get parameters form tuner
tuner_params = nni.get_next_parameter()
params = vars(merge_parameter(get_params(), tuner_params))
print(tuner_params, params)
main(params)
except Exception as exception:
logger.exception(exception)
raise
2、添加一个超参搜索实验

创建实验时需要配置:搜索算法、目标函数类型(maximize/minimize)、目标指标名、超参数(搜索空间)、任务镜像、工作目录、启动命令、资源(cpu/memory/gpu)、任务并行数、最大任务数、最大运行时长、任务并行方式等。
平台实际可选的搜索算法(
myapp/views/view_nni.py:191-206):TPE、Random、Anneal、Evolution、SMAC、BatchTuner、GridSearch、Hyperband、NetworkMorphism、MetisTuner、BOHB、GPTuner、PPOTuner、PBTTuner。任务并行方式(
parallel_trial_type)有两种(view_nni.py:132-138):
multi-process:单机多进程,平台创建单个 nni Pod 运行(make_nnijob);multi-machine:多机多进程,平台创建 Volcano Job 拉起 master + 多个 worker(make_volcanojob)。
不同超参算法支持不同的超参空间
下表为 NNI 各 Tuner 对不同搜索空间类型的支持情况(参考 NNI 官方文档):
| Tuner | choice | choice(nested) | randint | uniform | quniform | loguniform | qloguniform | normal | qnormal | lognormal | qlognormal |
|---|---|---|---|---|---|---|---|---|---|---|---|
| TPE Tuner | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Random Search Tuner | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Anneal Tuner | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Evolution Tuner | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| SMAC Tuner | ✓ | ✓ | ✓ | ✓ | ✓ | ||||||
| Batch Tuner | ✓ | ||||||||||
| Grid Search Tuner | ✓ | ✓ | ✓ | ||||||||
| Hyperband Advisor | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |
| Metis Tuner | ✓ | ✓ | ✓ | ✓ | |||||||
| GP Tuner | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
超参空间
必须是标准的 json。示例:
{
"batch_size": {"_type":"choice", "_value": [16, 32, 64, 128]},
"hidden_size":{"_type":"choice","_value":[128, 256, 512, 1024]},
"lr":{"_type":"choice","_value":[0.0001, 0.001, 0.01, 0.1]},
"momentum":{"_type":"uniform","_value":[0, 1]}
}
该示例与平台内置示例
core.nni_parameters_demo()一致(myapp/utils/core.py:574)。
3、启动超参搜索
点击 运行 | 容器 | 日志 | 清理 中的 运行 按钮:
- 运行按钮:启动搜索实验(部署 nni 服务,接口
/nni_modelview/api/run/<nni_id>,view_nni.py:791); - 容器按钮:查看搜索实验容器运行状况;
- 日志按钮:查看平台日志(这里不是业务代码日志,接口
/nni_modelview/api/log/<nni_id>,view_nni.py:925); - 清理按钮:删除清理实验(接口
/nni_modelview/api/stop/<nni_id>,view_nni.py:776)。
4、查看超参搜索进度和结果
等待全部容器运行中时,点击名称,进入 web 界面查看实验进度和日志(跳转到 /nni/<实验名>/,view_nni.py:938-954)。
web 查看搜索效果
总览界面可以看到实验的 id 和当前实例运行的状态。


可以看每次 trial 的运行情况、计算出来的目标值。

也可以看某次 trial 中每次 epoch 得到的结果值。
分布式超参搜索日志查看
由于是分布式进行超参数搜索,日志保存在分布式存储。想看某次搜索运行实例的日志:

在 web 界面看到的日志地址为:
Trial stdout: file://test-worker-2.test:/tmp/nni-experiments/$实验名/envs/$任务名
则对应分布式存储日志路径地址为:
/mnt/$用户名/nni/$nni名称/log/$实验名/envs/$任务名
说明:nni 代码、配置和 log 都在
/mnt/${username}/nni/目录下。实验工作目录为/mnt/<username>/nni/<name>/log/,容器内/tmp/nni-experiments/挂载到该 log 目录(view_nni.py:393/489/834)。

5、清理超参搜索实验
点击 运行 | 容器 | 日志 | 清理 中的 清理 按钮。运行结束后记得主动清理,以释放集群资源。