"Resource/vscode:/vscode.git/clone" did not exist on "be0d1a01c5bbb4d2a778d0276471037bc6e21938"
Unverified Commit 8c203f30 authored by SparkSnail's avatar SparkSnail Committed by GitHub
Browse files

Merge pull request #211 from microsoft/master

merge master
parents 7c1ab114 483232c8
......@@ -14,6 +14,6 @@ Metis 属于基于序列的贝叶斯优化 (SMBO) 的类别,它也基于贝叶
它会标识出下一个超参的候选项。 这是通过对隐含信息的探索、挖掘和重采样来实现的。
注意,搜索空间仅支持 `choice`, `quniform`, `uniform``randint`
此 Tuner 搜索空间仅接受 `quniform``uniform``randint` 和数值的 `choice` 类型
更多详情,参考论文:https://www.microsoft.com/en-us/research/publication/metis-robustly-tuning-tail-latencies-cloud-systems/
\ No newline at end of file
# NNI 中的 PPO Tuner
## PPOTuner
这是通常用于 NAS 接口的 NNI Tuner,使用了 [PPO 算法](https://arxiv.org/abs/1707.06347)。 此实现继承了[这里](https://github.com/openai/baselines/tree/master/baselines/ppo2)的主要逻辑,(即 OpenAI 的 PPO2),并为 NAS 场景做了适配。
它能成功调优 [mnist-nas 示例](https://github.com/microsoft/nni/tree/master/examples/trials/mnist-nas),结果如下:
![](../../img/ppo_mnist.png)
我们也使用 NAS 接口和 PPO Tuner 调优了[ ENAS 论文中为图片分类所做的宏分类](https://github.com/microsoft/nni/tree/master/examples/trials/nas_cifar10)(Trial 中 Epoch 限定为 8)。 [enas 论文](https://arxiv.org/pdf/1802.03268.pdf)中的图 7 展示了搜索空间:
![](../../img/enas_search_space.png)
上图是某个选定的架构,用来展示搜索空间。 每个方块是一层,其操作可从 6 个操作中选择。 每条虚线是直通连接,每个方块都可以有 0 或 1 条直通连接获得前面层的输出。 **注意**,在原始的宏搜索空间中,每个方块层可选择任意条直通连接,在此实现中,仅允许 0 或 1条。
结果如下图所示([配置文件](https://github.com/microsoft/nni/blob/master/examples/trials/nas_cifar10/config_ppo.yml)):
![](../../img/ppo_cifar10.png)
\ No newline at end of file
......@@ -2,12 +2,10 @@
创建 Experiment 所需要的配置文件。 配置文件的路径会传入 `nnictl` 命令。 配置文件的格式为 YAML。 本文介绍了配置文件的内容,并提供了一些示例和模板。
- [Experiment(实验)配置参考](#Experiment-config-reference)
- [模板](#Template)
- [说明](#Configuration-spec)
- [样例](#Examples)
<a name="Template"></a>
- [Experiment(实验)配置参考](#experiment-config-reference)
- [模板](#template)
- [说明](#configuration-spec)
- [样例](#examples)
## 模板
......@@ -19,27 +17,27 @@ experimentName:
trialConcurrency:
maxExecDuration:
maxTrialNum:
#可选项: local, remote, pai, kubeflow
# 可选项: local, remote, pai, kubeflow
trainingServicePlatform:
searchSpacePath:
#可选项: true, false, 默认值: false
# 可选项: true, false, 默认值: false
useAnnotation:
#可选项: true, false, 默认值: false
# 可选项: true, false, 默认值: false
multiPhase:
#可选项: true, false, 默认值: false
# 可选项: true, false, 默认值: false
multiThread:
tuner:
#可选项: TPE, Random, Anneal, Evolution
# 可选项: TPE, Random, Anneal, Evolution
builtinTunerName:
classArgs:
#可选项: maximize, minimize
# 可选项: maximize, minimize
optimize_mode:
gpuNum:
gpuIndices:
trial:
command:
codeDir:
gpuNum:
#在本地使用时,machineList 可为空
# 在本机模式下,machineList 可为空
machineList:
- ip:
port:
......@@ -70,18 +68,18 @@ tuner:
classArgs:
#可选项: maximize, minimize
optimize_mode:
gpuNum:
gpuIndices:
assessor:
#可选项: Medianstop
builtinAssessorName:
classArgs:
#可选项: maximize, minimize
optimize_mode:
gpuNum:
gpuIndices:
trial:
command:
codeDir:
gpuNum:
gpuIndices:
#在本地使用时,machineList 可为空
machineList:
- ip:
......@@ -112,18 +110,18 @@ tuner:
classArgs:
#可选项: maximize, minimize
optimize_mode:
gpuNum:
gpuIndices:
assessor:
#可选项: Medianstop
builtinAssessorName:
classArgs:
#可选项: maximize, minimize
optimize_mode:
gpuNum:
gpuIndices:
trial:
command:
codeDir:
gpuNum:
gpuIndices:
#在本地使用时,machineList 可为空
machineList:
- ip:
......@@ -132,8 +130,6 @@ machineList:
passwd:
```
<a name="Configuration"></a>
## 说明
- **authorName**
......@@ -264,11 +260,11 @@ machineList:
- **builtinTunerName**
**builtinTunerName** 指定系统 Tuner 的名,NNI SDK 提供了多 Tuner,如:{**TPE**, **Random**, **Anneal**, **Evolution**, **BatchTuner**, **GridSearch**}
**builtinTunerName** 指定系统 Tuner 的名,NNI SDK 提供了多个内置 Tuner,详情参考[这里](../Tuner/BuiltinTuner.md)
- **classArgs**
**classArgs** 指定了 Tuner 算法的参数。 如果 **builtinTunerName** 是{**TPE**, **Random**, **Anneal**, **Evolution**},用户需要设置 **optimize_mode**
**classArgs** 指定了 Tuner 算法的参数。 参考[此文件](../Tuner/BuiltinTuner.md)来了解内置 Tuner 的配置参数
- **codeDir**, **classFileName**, **className****classArgs**
......@@ -288,11 +284,9 @@ machineList:
**classArgs** 指定了 Tuner 算法的参数。
- **gpuNum**
__gpuNum__ 指定了运行 Tuner 进程的 GPU 数量。 此字段的值必须是正整数。 如果此字段没有设置,NNI不会在脚本中添加 `CUDA_VISIBLE_DEVICES` (也就是说,不会通过 `CUDA_VISIBLE_DEVICES` 来控制 GPU 在 Trial 中是否可见),也不会管理 GPU 资源。
- **gpuIndices**
注意: 只能使用一种方法来指定 Tuner,例如:设置 {tunerName, optimizationMode} 或 {tunerCommand, tunerCwd},不能同时设置两者
__gpuIndices__ 指定 Tuner 进程可使用的 GPU。 可以指定单个或多个 GPU 索引,多个索引间使用逗号(,)隔开,例如:`1``0,1,3`。 如果没设置此字段,脚本中的 `CUDA_VISIBLE_DEVICES` 会为空 '',即 Tuner 中找不到 GPU
- **includeIntermediateResults**
......@@ -300,6 +294,8 @@ machineList:
如果 __includeIntermediateResults__ 为 true,最后一个 Assessor 的中间结果会被发送给 Tuner 作为最终结果。 __includeIntermediateResults__ 的默认值为 false。
注意:用户只能用一种方法来指定 Tuner,指定 `builtinTunerName``classArgs`,或指定 `codeDir``classFileName``className` 以及 `classArgs`
- **Assessor**
- 说明
......@@ -310,7 +306,7 @@ machineList:
- **builtinAssessorName**
**builtinAssessorName** 指定了系统 Assessor 的名称, NNI 内置的 Assessor 有 {**Medianstop**,等等}
**builtinAssessorName** 指定了内置 Assessor 的名称,NNI SDK 提供了多个内置的 Assessor,详情参考[这里](../Assessor/BuiltinAssessor.md)
- **classArgs**
......@@ -334,11 +330,48 @@ machineList:
**classArgs** 指定了 Assessor 算法的参数。
- **gpuNum**
注意:用户只能用一种方法来指定 Assessor,指定 `builtinAssessorName``classArgs`,或指定 `codeDir``classFileName``className` 以及 `classArgs`。 如果不需要使用 Assessor,此字段可为空。
- **Advisor**
- 说明
**Advisor** 指定了 Experiment 的 Advisor 算法。有两种方法可设置 Advisor。 一种方法是使用 SDK 提供的 Advisor ,需要设置 **builtinAdvisorName****classArgs**。 另一种方法,是使用用户自定义的 Advisor,需要设置 **codeDirectory****classFileName****className****classArgs**
- **builtinAdvisorName****classArgs**
- **builtinAdvisorName**
**builtinAdvisorName** 指定了内置 Advisor 的名称,NNI SDK 提供了多个[内置的 Advisor](../Tuner/BuiltinTuner.md)
- **classArgs**
**classArgs** 指定了 Advisor 算法的参数。 参考[此文件](../Tuner/BuiltinTuner.md)来了解内置 Advisor 的配置参数。
- **codeDir**, **classFileName**, **className****classArgs**
- **codeDir**
**codeDir** 指定 Advisor 代码的目录。
- **classFileName**
**classFileName** 指定 Advisor 文件名。
- **className**
**className** 指定 Advisor 类名。
- **classArgs**
**classArgs** 指定了 Advisor 算法的参数。
- **gpuIndices**
__gpuIndices__ 指定了 Advisor 进程可使用的 GPU。 可以指定单个或多个 GPU 索引,多个索引间使用逗号(,)隔开,例如:`1``0,1,3`。 如果没设置此字段,脚本中的 `CUDA_VISIBLE_DEVICES` 会为空 '',即 Tuner 中找不到 GPU。
**gpuNum** 指定了运行 Assessor 进程的 GPU 数量。 此字段的值必须是正整数。
注意: 只能使用一种方法来指定 Assessor,例如:设置 {assessorName, optimizationMode} 或 {assessorCommand, assessorCwd},不能同时设置。如果不需要使用 Assessor,可将其置为空
注意:用户只能用一种方法来指定 Advisor ,指定 `builtinAdvisorName``classArgs`,或指定 `codeDir``classFileName``className` 以及 `classArgs`
- **trial (local, remote)**
......@@ -568,8 +601,6 @@ machineList:
**host** 是 OpenPAI 的主机地址。
<a name="Examples"></a>
## 样例
- **本机模式**
......@@ -592,7 +623,6 @@ machineList:
classArgs:
#可选项: maximize, minimize
optimize_mode: maximize
gpuNum: 0
trial:
command: python3 mnist.py
codeDir: /nni/mnist
......@@ -618,14 +648,12 @@ machineList:
classArgs:
#可选项: maximize, minimize
optimize_mode: maximize
gpuNum: 0
assessor:
#可选项: Medianstop
builtinAssessorName: Medianstop
classArgs:
#可选项: maximize, minimize
optimize_mode: maximize
gpuNum: 0
trial:
command: python3 mnist.py
codeDir: /nni/mnist
......@@ -652,7 +680,6 @@ machineList:
classArgs:
#可选项: maximize, minimize
optimize_mode: maximize
gpuNum: 0
assessor:
codeDir: /nni/assessor
classFileName: myassessor.py
......@@ -660,7 +687,6 @@ machineList:
classArgs:
#choice: maximize, minimize
optimize_mode: maximize
gpuNum: 0
trial:
command: python3 mnist.py
codeDir: /nni/mnist
......@@ -688,7 +714,6 @@ machineList:
classArgs:
#可选项: maximize, minimize
optimize_mode: maximize
gpuNum: 0
trial:
command: python3 mnist.py
codeDir: /nni/mnist
......@@ -762,16 +787,16 @@ machineList:
trialConcurrency: 1
maxExecDuration: 1h
maxTrialNum: 1
#可选项: local, remote, pai, kubeflow
# 可选项: local, remote, pai, kubeflow
trainingServicePlatform: kubeflow
searchSpacePath: search_space.json
#可选项: true, false
# 可选项: true, false
useAnnotation: false
tuner:
#可选项: TPE, Random, Anneal, Evolution
# 可选项: TPE, Random, Anneal, Evolution
builtinTunerName: TPE
classArgs:
#可选项: maximize, minimize
# 可选项: maximize, minimize
optimize_mode: maximize
trial:
codeDir: .
......@@ -797,23 +822,22 @@ machineList:
trialConcurrency: 1
maxExecDuration: 1h
maxTrialNum: 1
#可选项: local, remote, pai, kubeflow
# 可选项: local, remote, pai, kubeflow
trainingServicePlatform: kubeflow
searchSpacePath: search_space.json
#可选项: true, false
# 可选项: true, false
useAnnotation: false
#nniManagerIp: 10.10.10.10
tuner:
#可选项: TPE, Random, Anneal, Evolution
# 可选项: TPE, Random, Anneal, Evolution
builtinTunerName: TPE
classArgs:
#可选项: maximize, minimize
# 可选项: maximize, minimize
optimize_mode: maximize
assessor:
builtinAssessorName: Medianstop
classArgs:
optimize_mode: maximize
gpuNum: 0
trial:
codeDir: .
worker:
......
......@@ -10,6 +10,7 @@ nnictl 支持的命令:
* [nnictl create](#create)
* [nnictl resume](#resume)
* [nnictl view](#view)
* [nnictl stop](#stop)
* [nnictl update](#update)
* [nnictl trial](#trial)
......@@ -104,6 +105,35 @@ nnictl 支持的命令:
nnictl resume [experiment_id] --port 8088
```
<a name="view"></a>
![](https://placehold.it/15/1589F0/000000?text=+) `nnictl view`
* 说明
使用此命令查看已停止的 Experiment。
* 用法
```bash
nnictl view [OPTIONS]
```
* 选项
| 参数及缩写 | 是否必需 | 默认值 | 说明 |
| ---------- | ----- | --- | -------------------------------- |
| id | True | | 要查看的 Experiment 标识 |
| --port, -p | False | | 要查看的 Experiment 使用的 RESTful 服务端口 |
* 示例
> 在指定的端口 8088 上查看 Experiment
```bash
nnictl view [experiment_id] --port 8088
```
<a name="stop"></a>
![](https://placehold.it/15/1589F0/000000?text=+) `nnictl stop`
......@@ -125,7 +155,7 @@ nnictl 支持的命令:
| --port, -p | False | | 要停止的 Experiment 使用的 RESTful 服务端口 |
| --all, -a | False | | 停止所有 Experiment |
* 详细信息及
* 详细信息及
1. 如果没有指定 id,并且当前有运行的 Experiment,则会停止该 Experiment,否则会输出错误信息。
......@@ -183,7 +213,7 @@ nnictl 支持的命令:
| id | False | | 需要设置的 Experiment 的 id |
| --filename, -f | True | | 新的搜索空间文件名 |
*
*
`使用 'examples/trials/mnist/search_space.json' 来更新 Experiment 的搜索空间`
......@@ -207,7 +237,7 @@ nnictl 支持的命令:
| 参数及缩写 | 是否必需 | 默认值 | 说明 |
| ----------- | ----- | --- | --------------------- |
| id | False | | 需要设置的 Experiment 的 id |
| id | False | | 需要设置的 Experiment 的 ID |
| --value, -v | True | | 允许同时运行的 Trial 的数量 |
* 样例
......@@ -233,11 +263,11 @@ nnictl 支持的命令:
* 选项
| 参数及缩写 | 是否必需 | 默认值 | 说明 |
| ----------- | ----- | --- | ----------------------------------------------------------------------- |
| id | False | | 需要设置的 Experiment 的 id |
| --value, -v | True | | Experiment 持续时间如没有单位,则为秒。 后缀可以为 's' 即秒 (默认值), 'm' 即分钟, 'h' 即小时或 'd' 即天。 |
| ----------- | ----- | --- | ------------------------------------------------------------------------ |
| id | False | | 需要设置的 Experiment 的 ID |
| --value, -v | True | | Experiment 持续时间如没有单位,则为秒。 后缀可以为 's' 即秒 (默认值), 'm' 即分钟, 'h' 即小时或 'd' 即天。 |
*
*
> 修改 Experiment 的执行时间
......@@ -264,7 +294,7 @@ nnictl 支持的命令:
| id | False | | 需要设置的 Experiment 的 id |
| --value, -v | True | | 需要设置的 maxtrialnum 的数量 |
*
*
> 更新 Experiment 的 Trial 数量
......@@ -312,7 +342,7 @@ nnictl 支持的命令:
| id | False | | Trial 的 Experiment ID |
| --trial_id, -T | True | | 需要终止的 Trial 的 ID。 |
*
*
> 结束 Trial 任务
......@@ -378,7 +408,7 @@ nnictl 支持的命令:
| 参数及缩写 | 是否必需 | 默认值 | 说明 |
| ----- | ----- | --- | --------------------- |
| id | False | | 需要设置的 Experiment 的 id |
| id | False | | 需要设置的 Experiment 的 ID |
* **nnictl experiment list**
......@@ -439,7 +469,7 @@ nnictl 支持的命令:
| --filename, -f | True | | 文件的输出路径 |
| --type | True | | 输出文件类型,仅支持 "csv" 和 "json" |
*
*
> 将 Experiment 中所有 Trial 数据导出为 JSON 格式
......@@ -478,7 +508,7 @@ nnictl 支持的命令:
]
```
最顶层列表的每个元素都是一个例。 对于内置的 Tuner 和 Advisor,每个样本至少需要两个主键:`parameter``value``parameter` 必须与 Experiment 的搜索空间相匹配,`parameter` 中的所有的主键(或超参)都必须与搜索空间中的主键相匹配。 否则, Tuner 或 Advisor 可能会有无法预期的行为。 `Value` 应当遵循与 `nni.report_final_result` 的输入值一样的规则,即要么时一个数字,或者是包含 `default` 主键的 dict。 对于自定义的 Tuner 或 Advisor,根据实现的不同,此文件可以是任意的 JSON 内容(例如,`import_data`)。
最顶层列表的每个元素都是一个例。 对于内置的 Tuner 和 Advisor,每个样本至少需要两个主键:`parameter``value``parameter` 必须与 Experiment 的搜索空间相匹配,`parameter` 中的所有的主键(或超参)都必须与搜索空间中的主键相匹配。 否则, Tuner 或 Advisor 可能会有无法预期的行为。 `Value` 应当遵循与 `nni.report_final_result` 的输入值一样的规则,即要么时一个数字,或者是包含 `default` 主键的 dict。 对于自定义的 Tuner 或 Advisor,根据实现的不同,此文件可以是任意的 JSON 内容(例如,`import_data`)。
也可以用 [nnictl experiment export](#export) 命令导出 Experiment 已经运行过的 Trial 超参和结果。
......@@ -489,7 +519,7 @@ nnictl 支持的命令:
内置 Advisor: BOHB
```
*如果要将数据导入到 BOHB Advisor,建议像 NNI 一样,增加 "TRIAL_BUDGET" 参数,否则,BOHB 会使用 max_budget 作为 "TRIAL_BUDGET"。 例如下:*
*如果要将数据导入到 BOHB Advisor,建议像 NNI 一样,增加 "TRIAL_BUDGET" 参数,否则,BOHB 会使用 max_budget 作为 "TRIAL_BUDGET"。 例如下:*
```json
[
......@@ -497,7 +527,7 @@ nnictl 支持的命令:
]
```
*
*
> 将数据导入运行中的 Experiment
......@@ -563,7 +593,7 @@ nnictl 支持的命令:
| 参数及缩写 | 是否必需 | 默认值 | 说明 |
| ---------- | ----- | --- | --------------------- |
| id | False | | 需要设置的 Experiment 的 ID |
| id | False | | 需要设置的 Experiment 的 id |
| --head, -h | False | | 显示 stdout 开始的若干行 |
| --tail, -t | False | | 显示 stdout 结尾的若干行 |
| --path, -p | False | | 显示 stdout 文件的路径 |
......@@ -592,7 +622,7 @@ nnictl 支持的命令:
| 参数及缩写 | 是否必需 | 默认值 | 说明 |
| ---------- | ----- | --- | --------------------- |
| id | False | | 需要设置的 Experiment 的 ID |
| id | False | | 需要设置的 Experiment 的 id |
| --head, -h | False | | 显示 stderr 开始的若干行 |
| --tail, -t | False | | 显示 stderr 结尾的若干行 |
| --path, -p | False | | 显示 stderr 文件的路径 |
......@@ -641,7 +671,7 @@ nnictl 支持的命令:
| 参数及缩写 | 是否必需 | 默认值 | 说明 |
| -------------- | ----- | ---- | --------------------- |
| id | False | | 需要设置的 Experiment 的 id |
| --trial_id, -T | False | | Trial 的 id |
| --trial_id, -T | False | | Trial 的 ID |
| --port | False | 6006 | Tensorboard 进程的端口 |
* 详细说明
......
......@@ -101,7 +101,7 @@
已知的局限:
* 注意 Metis Tuner 当前仅支持在 `choice`使用数值。
* GP Tuner 和 Metis Tuner 的搜索空间只支持**数值**,(`choice` 类型在其它 Tuner 中可以使用数值,如:字符串等)。 GP Tuner 和 Metis Tuner 都使用了高斯过程的回归(Gaussian Process Regressor, GPR)。 GPR 基于计算不同点距离的和函数来进行预测,其无法计算非数值值的距离
* 请注意,对于嵌套搜索空间:
......
......@@ -50,6 +50,9 @@ Assessor(评估器)
Advisor
------------------------
.. autoclass:: nni.msg_dispatcher_base.MsgDispatcherBase
:members:
.. autoclass:: nni.hyperband_advisor.hyperband_advisor.Hyperband
:members:
......
......@@ -20,7 +20,7 @@ configure_list = [{
'start_epoch': 0,
'end_epoch': 10,
'frequency': 1,
'op_type': 'default'
'op_types': ['default']
}]
pruner = AGP_Pruner(configure_list)
```
......
# 运行模型压缩示例
以 PyTorch 剪枝为例:
```bash
python main_torch_pruner.py
```
此示例使用了 AGP Pruner。 初始化 Pruner 需要通过以下两种方式来提供配置。
- 读取 `configure_example.yaml`,这样代码会更整洁,但配置会比较复杂。
- 直接在代码中配置
此例在代码中配置了模型压缩:
```python
configure_list = [{
'initial_sparsity': 0,
'final_sparsity': 0.8,
'start_epoch': 0,
'end_epoch': 10,
'frequency': 1,
'op_types': ['default']
}]
pruner = AGP_Pruner(configure_list)
```
当调用 `pruner(model)` 时,模型会被嵌入掩码操作。 例如,某层以权重作为输入,可在权重和层操作之间插入一个操作,此操作以权重为输入,并将其应用掩码后输出。 因此,计算过程中,只要通过此操作,就会应用掩码。 还可以**不做任何改动**,来对模型进行微调。
```python
for epoch in range(10):
# update_epoch 来让 Pruner 知道 Epoch 的数量,从而能够在训练过程中调整掩码。
pruner.update_epoch(epoch)
print('# Epoch {} #'.format(epoch))
train(model, device, train_loader, optimizer)
test(model, device, test_loader)
```
微调完成后,被修剪过的权重可通过以下代码获得:
```
masks = pruner.mask_list
layer_name = xxx
mask = masks[layer_name]
```
......@@ -6,4 +6,4 @@ AGPruner:
frequency: 1
initial_sparsity: 0.05
final_sparsity: 0.8
op_type: 'default'
op_types: ['default']
......@@ -91,7 +91,7 @@ def main():
'start_epoch': 0,
'end_epoch': 10,
'frequency': 1,
'op_type': 'default'
'op_types': ['default']
}]
pruner = AGP_Pruner(configure_list)
# if you want to load from yaml file
......
......@@ -82,7 +82,7 @@ def main():
'''you can change this to DoReFaQuantizer to implement it
DoReFaQuantizer(configure_list).compress(tf.get_default_graph())
'''
configure_list = [{'q_bits':8, 'op_type':'default'}]
configure_list = [{'q_bits':8, 'op_types':['default']}]
quantizer = QAT_Quantizer(configure_list)
quantizer(tf.get_default_graph())
# you can also use compress(model) or compress_default_graph()
......
......@@ -76,7 +76,7 @@ def main():
'start_epoch': 0,
'end_epoch': 10,
'frequency': 1,
'op_type': 'default'
'op_types': ['default']
}]
pruner = AGP_Pruner(configure_list)
......
......@@ -68,7 +68,7 @@ def main():
'''you can change this to DoReFaQuantizer to implement it
DoReFaQuantizer(configure_list).compress(model)
'''
configure_list = [{'q_bits':8, 'op_type':'default'}]
configure_list = [{'q_bits':8, 'op_types':['default']}]
quantizer = QAT_Quantizer(configure_list)
quantizer(model)
# you can also use compress(model) method
......
**在 NNI 中运行神经网络架构搜索**
===
# 在 NNI 中运行神经网络架构搜索
参考 [NNI-NAS-Example](https://github.com/Crysple/NNI-NAS-Example),来使用贡献者提供的 NAS 接口。
谢谢可爱的贡献者!
此目录中包含了 Trial 代码,并提供了示例的配置文件来展示如何使用 PPO Tuner 来调优此 Trial 代码。
欢迎越来越多的人加入我们!
\ No newline at end of file
运行下列代码来准备数据集 `cd data && . download.sh`.
感谢可爱的志愿者,欢迎更多的人加入我们!
\ No newline at end of file
......@@ -19,16 +19,14 @@
# ==================================================================================================
# pylint: disable=wildcard-import
from .trial import *
from .smartparam import *
from .nas_utils import training_update
class NoMoreTrialError(Exception):
def __init__(self,ErrorInfo):
def __init__(self, ErrorInfo):
super().__init__(self)
self.errorinfo=ErrorInfo
self.errorinfo = ErrorInfo
def __str__(self):
return self.errorinfo
......@@ -27,9 +27,10 @@ import logging
import json
import importlib
from .common import enable_multi_thread, enable_multi_phase
from .constants import ModuleName, ClassName, ClassArgs, AdvisorModuleName, AdvisorClassName
from nni.common import enable_multi_thread, enable_multi_phase
from nni.msg_dispatcher import MsgDispatcher
from .msg_dispatcher import MsgDispatcher
logger = logging.getLogger('nni.main')
logger.debug('START')
......@@ -44,7 +45,7 @@ def augment_classargs(input_class_args, classname):
input_class_args[key] = value
return input_class_args
def create_builtin_class_instance(classname, jsonstr_args, is_advisor = False):
def create_builtin_class_instance(classname, jsonstr_args, is_advisor=False):
if is_advisor:
if classname not in AdvisorModuleName or \
importlib.util.find_spec(AdvisorModuleName[classname]) is None:
......@@ -130,6 +131,31 @@ def main():
if args.advisor_class_name:
# advisor is enabled and starts to run
_run_advisor(args)
else:
# tuner (and assessor) is enabled and starts to run
tuner = _create_tuner(args)
if args.assessor_class_name:
assessor = _create_assessor(args)
else:
assessor = None
dispatcher = MsgDispatcher(tuner, assessor)
try:
dispatcher.run()
tuner._on_exit()
if assessor is not None:
assessor._on_exit()
except Exception as exception:
logger.exception(exception)
tuner._on_error()
if assessor is not None:
assessor._on_error()
raise
def _run_advisor(args):
if args.advisor_class_name in AdvisorModuleName:
dispatcher = create_builtin_class_instance(
args.advisor_class_name,
......@@ -147,10 +173,9 @@ def main():
except Exception as exception:
logger.exception(exception)
raise
else:
# tuner (and assessor) is enabled and starts to run
tuner = None
assessor = None
def _create_tuner(args):
if args.tuner_class_name in ModuleName:
tuner = create_builtin_class_instance(
args.tuner_class_name,
......@@ -161,11 +186,12 @@ def main():
args.tuner_class_filename,
args.tuner_class_name,
args.tuner_args)
if tuner is None:
raise AssertionError('Failed to create Tuner instance')
return tuner
if args.assessor_class_name:
def _create_assessor(args):
if args.assessor_class_name in ModuleName:
assessor = create_builtin_class_instance(
args.assessor_class_name,
......@@ -178,20 +204,8 @@ def main():
args.assessor_args)
if assessor is None:
raise AssertionError('Failed to create Assessor instance')
return assessor
dispatcher = MsgDispatcher(tuner, assessor)
try:
dispatcher.run()
tuner._on_exit()
if assessor is not None:
assessor._on_exit()
except Exception as exception:
logger.exception(exception)
tuner._on_error()
if assessor is not None:
assessor._on_error()
raise
if __name__ == '__main__':
try:
......
......@@ -31,7 +31,6 @@ class AssessResult(Enum):
Bad = False
class Assessor(Recoverable):
# pylint: disable=no-self-use,unused-argument
def assess_trial(self, trial_job_id, trial_history):
"""Determines whether a trial should be killed. Must override.
......@@ -46,21 +45,20 @@ class Assessor(Recoverable):
trial_job_id: identifier of the trial (str).
success: True if the trial successfully completed; False if failed or terminated.
"""
pass
def load_checkpoint(self):
"""Load the checkpoint of assessr.
path: checkpoint directory for assessor
"""
checkpoin_path = self.get_checkpoint_path()
_logger.info('Load checkpoint ignored by assessor, checkpoint path: %s' % checkpoin_path)
_logger.info('Load checkpoint ignored by assessor, checkpoint path: %s', checkpoin_path)
def save_checkpoint(self):
"""Save the checkpoint of assessor.
path: checkpoint directory for assessor
"""
checkpoin_path = self.get_checkpoint_path()
_logger.info('Save checkpoint ignored by assessor, checkpoint path: %s' % checkpoin_path)
_logger.info('Save checkpoint ignored by assessor, checkpoint path: %s', checkpoin_path)
def _on_exit(self):
pass
......
......@@ -100,7 +100,7 @@ class BatchTuner(Tuner):
data:
a list of dictionarys, each of which has at least two keys, 'parameter' and 'value'
"""
if len(self.values) == 0:
if not self.values:
logger.info("Search space has not been initialized, skip this data import")
return
......
......@@ -51,7 +51,7 @@ def create_parameter_id():
int
parameter id
"""
global _next_parameter_id # pylint: disable=global-statement
global _next_parameter_id
_next_parameter_id += 1
return _next_parameter_id - 1
......@@ -80,7 +80,7 @@ def create_bracket_parameter_id(brackets_id, brackets_curr_decay, increased_id=-
return params_id
class Bracket(object):
class Bracket:
"""
A bracket in BOHB, all the information of a bracket is managed by
an instance of this class.
......@@ -98,7 +98,7 @@ class Bracket(object):
max_budget : float
The largest budget to consider. Needs to be larger than min_budget!
The budgets will be geometrically distributed
:math:`a^2 + b^2 = c^2 \sim \eta^k` for :math:`k\in [0, 1, ... , num\_subsets - 1]`.
:math:`a^2 + b^2 = c^2 \\sim \\eta^k` for :math:`k\\in [0, 1, ... , num\\_subsets - 1]`.
optimize_mode: str
optimize mode, 'maximize' or 'minimize'
"""
......@@ -169,7 +169,7 @@ class Bracket(object):
If we have generated new trials after this trial end, we will return a new trial parameters.
Otherwise, we will return None.
"""
global _KEY # pylint: disable=global-statement
global _KEY
self.num_finished_configs[i] += 1
logger.debug('bracket id: %d, round: %d %d, finished: %d, all: %d',
self.s, self.i, i, self.num_finished_configs[i], self.num_configs_to_run[i])
......@@ -377,8 +377,10 @@ class BOHB(MsgDispatcherBase):
if self.curr_s < 0:
logger.info("s < 0, Finish this round of Hyperband in BOHB. Generate new round")
self.curr_s = self.s_max
self.brackets[self.curr_s] = Bracket(s=self.curr_s, s_max=self.s_max, eta=self.eta,
max_budget=self.max_budget, optimize_mode=self.optimize_mode)
self.brackets[self.curr_s] = Bracket(
s=self.curr_s, s_max=self.s_max, eta=self.eta,
max_budget=self.max_budget, optimize_mode=self.optimize_mode
)
next_n, next_r = self.brackets[self.curr_s].get_n_r()
logger.debug(
'new SuccessiveHalving iteration, next_n=%d, next_r=%d', next_n, next_r)
......@@ -643,14 +645,14 @@ class BOHB(MsgDispatcherBase):
"""
_completed_num = 0
for trial_info in data:
logger.info("Importing data, current processing progress %s / %s" %(_completed_num, len(data)))
logger.info("Importing data, current processing progress %s / %s", _completed_num, len(data))
_completed_num += 1
assert "parameter" in trial_info
_params = trial_info["parameter"]
assert "value" in trial_info
_value = trial_info['value']
if not _value:
logger.info("Useless trial data, value is %s, skip this trial data." %_value)
logger.info("Useless trial data, value is %s, skip this trial data.", _value)
continue
budget_exist_flag = False
barely_params = dict()
......@@ -662,7 +664,7 @@ class BOHB(MsgDispatcherBase):
barely_params[keys] = _params[keys]
if not budget_exist_flag:
_budget = self.max_budget
logger.info("Set \"TRIAL_BUDGET\" value to %s (max budget)" %self.max_budget)
logger.info("Set \"TRIAL_BUDGET\" value to %s (max budget)", self.max_budget)
if self.optimize_mode is OptimizeMode.Maximize:
reward = -_value
else:
......
......@@ -28,7 +28,6 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import logging
import traceback
import ConfigSpace
import ConfigSpace.hyperparameters
......@@ -39,7 +38,7 @@ import statsmodels.api as sm
logger = logging.getLogger('BOHB_Advisor')
class CG_BOHB(object):
class CG_BOHB:
def __init__(self, configspace, min_points_in_model=None,
top_n_percent=15, num_samples=64, random_fraction=1/3,
bandwidth_factor=3, min_bandwidth=1e-3):
......@@ -77,8 +76,8 @@ class CG_BOHB(object):
self.min_points_in_model = len(self.configspace.get_hyperparameters())+1
if self.min_points_in_model < len(self.configspace.get_hyperparameters())+1:
logger.warning('Invalid min_points_in_model value. Setting it to %i'%(len(self.configspace.get_hyperparameters())+1))
self.min_points_in_model =len(self.configspace.get_hyperparameters())+1
logger.warning('Invalid min_points_in_model value. Setting it to %i', len(self.configspace.get_hyperparameters()) + 1)
self.min_points_in_model = len(self.configspace.get_hyperparameters()) + 1
self.num_samples = num_samples
self.random_fraction = random_fraction
......@@ -107,9 +106,9 @@ class CG_BOHB(object):
self.kde_models = dict()
def largest_budget_with_model(self):
if len(self.kde_models) == 0:
return(-float('inf'))
return(max(self.kde_models.keys()))
if not self.kde_models:
return -float('inf')
return max(self.kde_models.keys())
def sample_from_largest_budget(self, info_dict):
"""We opted for a single multidimensional KDE compared to the
......@@ -162,11 +161,11 @@ class CG_BOHB(object):
val = minimize_me(vector)
if not np.isfinite(val):
logger.warning('sampled vector: %s has EI value %s'%(vector, val))
logger.warning("data in the KDEs:\n%s\n%s"%(kde_good.data, kde_bad.data))
logger.warning("bandwidth of the KDEs:\n%s\n%s"%(kde_good.bw, kde_bad.bw))
logger.warning("l(x) = %s"%(l(vector)))
logger.warning("g(x) = %s"%(g(vector)))
logger.warning('sampled vector: %s has EI value %s', vector, val)
logger.warning("data in the KDEs:\n%s\n%s", kde_good.data, kde_bad.data)
logger.warning("bandwidth of the KDEs:\n%s\n%s", kde_good.bw, kde_bad.bw)
logger.warning("l(x) = %s", l(vector))
logger.warning("g(x) = %s", g(vector))
# right now, this happens because a KDE does not contain all values for a categorical parameter
# this cannot be fixed with the statsmodels KDE, so for now, we are just going to evaluate this one
......@@ -181,19 +180,15 @@ class CG_BOHB(object):
best_vector = vector
if best_vector is None:
logger.debug("Sampling based optimization with %i samples failed -> using random configuration"%self.num_samples)
logger.debug("Sampling based optimization with %i samples failed -> using random configuration", self.num_samples)
sample = self.configspace.sample_configuration().get_dictionary()
info_dict['model_based_pick'] = False
else:
logger.debug('best_vector: {}, {}, {}, {}'.format(best_vector, best, l(best_vector), g(best_vector)))
for i, hp_value in enumerate(best_vector):
if isinstance(
self.configspace.get_hyperparameter(
self.configspace.get_hyperparameter_by_idx(i)
),
ConfigSpace.hyperparameters.CategoricalHyperparameter
):
logger.debug('best_vector: %s, %s, %s, %s', best_vector, best, l(best_vector), g(best_vector))
for i, _ in enumerate(best_vector):
hp = self.configspace.get_hyperparameter(self.configspace.get_hyperparameter_by_idx(i))
if isinstance(hp, ConfigSpace.hyperparameters.CategoricalHyperparameter):
best_vector[i] = int(np.rint(best_vector[i]))
sample = ConfigSpace.Configuration(self.configspace, vector=best_vector).get_dictionary()
......@@ -224,12 +219,12 @@ class CG_BOHB(object):
# If no model is available, sample from prior
# also mix in a fraction of random configs
if len(self.kde_models.keys()) == 0 or np.random.rand() < self.random_fraction:
if not self.kde_models.keys() or np.random.rand() < self.random_fraction:
sample = self.configspace.sample_configuration()
info_dict['model_based_pick'] = False
if sample is None:
sample, info_dict= self.sample_from_largest_budget(info_dict)
sample, info_dict = self.sample_from_largest_budget(info_dict)
sample = ConfigSpace.util.deactivate_inactive_hyperparameters(
configuration_space=self.configspace,
......@@ -245,10 +240,10 @@ class CG_BOHB(object):
for i in range(array.shape[0]):
datum = np.copy(array[i])
nan_indices = np.argwhere(np.isnan(datum)).flatten()
while(np.any(nan_indices)):
while np.any(nan_indices):
nan_idx = nan_indices[0]
valid_indices = np.argwhere(np.isfinite(array[:,nan_idx])).flatten()
if len(valid_indices) > 0:
valid_indices = np.argwhere(np.isfinite(array[:, nan_idx])).flatten()
if valid_indices:
# pick one of them at random and overwrite all NaN values
row_idx = np.random.choice(valid_indices)
datum[nan_indices] = array[row_idx, nan_indices]
......@@ -260,8 +255,8 @@ class CG_BOHB(object):
else:
datum[nan_idx] = np.random.randint(t)
nan_indices = np.argwhere(np.isnan(datum)).flatten()
return_array[i,:] = datum
return(return_array)
return_array[i, :] = datum
return return_array
def new_result(self, loss, budget, parameters, update_model=True):
"""
......@@ -305,7 +300,7 @@ class CG_BOHB(object):
# a) if not enough points are available
if len(self.configs[budget]) <= self.min_points_in_model - 1:
logger.debug("Only %i run(s) for budget %f available, need more than %s \
-> can't build model!"%(len(self.configs[budget]), budget, self.min_points_in_model+1))
-> can't build model!", len(self.configs[budget]), budget, self.min_points_in_model+1)
return
# b) during warnm starting when we feed previous results in and only update once
if not update_model:
......@@ -345,5 +340,5 @@ class CG_BOHB(object):
}
# update probs for the categorical parameters for later sampling
logger.debug('done building a new model for budget %f based on %i/%i split\nBest loss for this budget:%f\n'
%(budget, n_good, n_bad, np.min(train_losses)))
logger.debug('done building a new model for budget %f based on %i/%i split\nBest loss for this budget:%f\n',
budget, n_good, n_bad, np.min(train_losses))
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment