"web/git@developer.sourcefind.cn:chenpangpang/ComfyUI.git" did not exist on "c39cf7fff09c728d573835a48106e2133c446e11"
Unverified Commit 035d58bc authored by SparkSnail's avatar SparkSnail Committed by GitHub
Browse files

Merge pull request #121 from Microsoft/master

merge master
parents b633c265 8e732f2c
#########################################
Neural Network Intelligence(NNI)文档
#########################################
********
内容
********
.. toctree::
:caption: 目录
:maxdepth: 2
:titlesonly:
概述<Overview>
入门<QuickStart>
教程<Tutorials>
样例<Examples>
参考<Reference>
常见问答<FAQ>
贡献<Contribution>
版本日志<RELEASE>
\ No newline at end of file
# MNIST 样例
在深度学习中,用 CNN 来分类 MNIST 数据,就像介绍编程语言中的 `hello world` 样例。 因此,NNI 将 MNIST 作为样例来介绍功能。 样例如下:
- [MNIST 中使用 NNI API](#mnist)
- [MNIST 中使用 NNI 标记(annotation)](#mnist-annotation)
- [在 Keras 中使用 MNIST](#mnist-keras)
- [MNIST -- 用批处理调参器来调优](#mnist-batch)
- [MNIST -- 用 hyperband 调优](#mnist-hyperband)
- [MNIST -- 用嵌套搜索空间调优](#mnist-nested)
- [用 Kubeflow 运行分布式的 MNIST (tensorflow)](#mnist-kubeflow-tf)
- [用 Kubeflow 运行分布式的 MNIST (PyTorch)](#mnist-kubeflow-pytorch)
<a name="mnist"></a>
**MNIST 中使用 NNI API**
这是个简单的卷积网络,有两个卷积层,两个池化层和一个全连接层。 调优的超参包括 dropout 比率,卷积层大小,隐藏层(全连接层)大小等等。 它能用 NNI 中大部分内置的调参器来调优,如 TPE,SMAC,Random。 样例的 YAML 文件也启用了评估器来提前终止一些中间结果不好的尝试。
`代码目录: examples/trials/mnist/`
<a name="mnist-annotation"></a>
**MNIST 中使用 NNI 标记(annotation)**
此样例与上例类似,上例使用的是 NNI API 来指定搜索空间并返回结果,而此例使用的是 NNI 标记。
`代码目录: examples/trials/mnist-annotation/`
<a name="mnist-keras"></a>
**在 Keras 中使用 MNIST**
此样例由 Keras 实现。 这也是 MNIST 数据集的网络,包括两个卷积层,一个池化层和两个全连接层。
`代码目录: examples/trials/mnist-keras/`
<a name="mnist-batch"></a>
**MNIST -- 用批处理调参器来调优**
此样例演示了如何使用批处理调参器。 只需要在搜索空间文件中列出所有要尝试的配置, NNI 会逐个尝试。
`代码目录: examples/trials/mnist-batch-tune-keras/`
<a name="mnist-hyperband"></a>
**MNIST -- 用 hyperband 调优**
此样例演示了如何使用 hyperband 来调优模型。 在尝试收到的配置中,有个主键叫做 `STEPS`,尝试要用它来控制运行多长时间(例如,控制迭代的次数)。
`代码目录: examples/trials/mnist-hyperband/`
<a name="mnist-nested"></a>
**MNIST -- 用嵌套搜索空间调优**
此样例演示了 NNI 如何支持嵌套的搜索空间。 搜索空间文件示了如何定义嵌套的搜索空间。
`代码目录: examples/trials/mnist-cascading-search-space/`
<a name="mnist-kubeflow-tf"></a>
**用 Kubeflow 运行分布式的 MNIST (tensorflow)**
此样例展示了如何通过 NNI 来在 Kubeflow 上运行分布式训练。 只需要简单的提供分布式训练代码,并在配置文件中指定 kubeflow 模式。 例如,运行 ps 和 worker 的命令行,以及各自需要的资源。 此样例使用了 Tensorflow 来实现,因而,需要使用 Kubeflow 的 tf-operator。
`代码目录: examples/trials/mnist-distributed/`
<a name="mnist-kubeflow-pytorch"></a>
**用 Kubeflow 运行分布式的 MNIST (PyTorch)**
与前面的样例类似,不同之处是此样例是 Pytorch 实现的,因而需要使用 Kubeflow 的 pytorch-operator。
`代码目录: examples/trials/mnist-distributed-pytorch/`
\ No newline at end of file
## 创建多阶段的 Experiment
通常情况下,每个 Trial 作业只从 Tuner 获得一组配置(如超参),然后运行 Experiment。也就是说,通过这组超参来训练模型,并返回结果给 Tuner。 有时候,可能需要在一个 Trial 作业中训练多个模型,并在它们之间共享信息,或者通过创建更少的 Trial 任务来节省资源。例如:
1. 在一个 Trial 作业中依次训练多个模型。这样,后面的模型可以利用先前模型的权重和其它信息,并可以使用不同的超参组合。
2. 在有限的资源上训练大量的模型,将多个模型放到一个 Trial 作业中训练,能够节约系统创建 Trial 作业的时间。
3. 还有的情况,希望在一个 Trial 任务中训练多个需要不同超参的模型。注意,如果为一个 Trial 作业分配多个 GPU,并且会并发训练模型,需要在代码中正确分配 GPU 资源。
在上述情况中,可利用 NNI 的多阶段 Experiment 来在同一个 Trial 任务中训练具有不同超参的多个模型。
多阶段 Experiment,是指 Trial 作业会从 Tuner 请求多次超参,并多次返回最终结果。
参考以下步骤来使用多阶段 Experiment:
1. 实现 nni.multi_phase.MultiPhaseTuner。 例如,[ENAS tuner](https://github.com/countif/enas_nni/blob/master/nni/examples/tuners/enas/nni_controller_ptb.py) 就是一个实现了 nni.multi_phase.MultiPhaseTuner 的 Tuner。 在实现多阶段 Tuner 时,可能要用 generate_parameters 中的 trial_job_id 参数来为每个 Trial 作业生成超参。
2. 设置 `multiPhase` 的值为 `true`,并将第一步中实现的 Tuner 作为自定义 Tuner 进行配置,例如:
```yaml
...
multiPhase: true
tuner:
codeDir: tuners/enas
classFileName: nni_controller_ptb.py
className: ENASTuner
classArgs:
say_hello: "hello"
...
```
3. 根据需要,在 Trial 代码中可多次调用 nni.get_next_parameter() API,例如:
```python
for i in range(5):
# 从 Tuner 中获得参数
tuner_param = nni.get_next_parameter()
# 使用参数
# ...
# 为上面获取的参数返回最终结果
nni.report_final_result()
# ...
```
\ No newline at end of file
sphinx==1.8.3
sphinx-argparse==0.2.5
sphinx-markdown-tables==0.0.9
sphinx-rtd-theme==0.4.2
sphinxcontrib-websupport==1.1.0
recommonmark==0.5.0
nni==0.5
\ No newline at end of file
###########################
Python API 参考
###########################
Trial
------------------------
.. autofunction:: nni.get_next_parameter
.. autofunction:: nni.get_current_parameter
.. autofunction:: nni.report_intermediate_result
.. autofunction:: nni.report_final_result
.. autofunction:: nni.get_sequence_id
Tuner
------------------------
.. autoclass:: nni.tuner.Tuner
:members:
.. autoclass:: nni.hyperopt_tuner.hyperopt_tuner.HyperoptTuner
:members:
.. autoclass:: nni.evolution_tuner.evolution_tuner.EvolutionTuner
:members:
.. autoclass:: nni.gridsearch_tuner.gridsearch_tuner.GridSearchTuner
:members:
.. autoclass:: nni.smac_tuner.smac_tuner.SMACTuner
:members:
Assessor
------------------------
.. autoclass:: nni.assessor.Assessor
:members:
.. autoclass:: nni.curvefitting_assessor.curvefitting_assessor.CurvefittingAssessor
:members:
.. autoclass:: nni.medianstop_assessor.medianstop_assessor.MedianstopAssessor
:members:
Advisor
------------------------
.. autoclass:: nni.hyperband_advisor.hyperband_advisor.Hyperband
\ No newline at end of file
# NNI 中使用 scikit-learn
[scikit-learn](https://github.com/scikit-learn/scikit-learn) (sklearn) 是数据挖掘和分析的流行工具。 它支持多种机器学习模型,如线性回归,逻辑回归,决策树,支持向量机等。 提高 scikit-learn 的效率是非常有价值的课题。
NNI 支持多种调优算法,可以为 scikit-learn 搜索最佳的模型和超参,并支持本机、远程服务器组、云等各种环境。
## 1. 如何运行此样例
安装 NNI 包,并使用命令行工具 `nnictl` 来启动 Experiment。 有关安装和环境准备的内容,参考[这里](QuickStart.md)。 安装完 NNI 后,进入相应的目录,输入下列命令即可启动 Experiment:
```bash
nnictl create --config ./config.yml
```
## 2. 样例概述
### 2.1 分类
此样例使用了数字数据集,由 1797 张 8x8 的图片组成,每张图片都是一个手写数字。目标是将这些图片分到 10 个类别中。
在此样例中,使用了 SVC 作为模型,并选择了一些参数,包括 `"C", "keral", "degree", "gamma" 和 "coef0"`。 关于这些参数的更多信息,可参考[这里](https://scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html)
### 2.2 回归
此样例使用了波士顿房价数据,数据集由波士顿各地区房价所组成,还包括了房屋的周边信息,例如:犯罪率 (CRIM),非零售业务的面积 (INDUS),房主年龄 (AGE) 等等。这些信息可用来预测波士顿的房价。 本例中,尝试了不同的回归模型,包括 `"LinearRegression", "SVR", "KNeighborsRegressor", "DecisionTreeRegressor"` 和一些参数,如 `"svr_kernel", "knr_weights"`。 关于这些模型算法和参数的更多信息,可参考[这里](https://scikit-learn.org/stable/supervised_learning.html#supervised-learning)
## 3. 如何在 NNI 中使用 sklearn
只需要如下几步,即可在 sklearn 代码中使用 NNI。
* **第一步**
准备 search_space.json 文件来存储选择的搜索空间。 例如,如果要在不同的模型中选择:
```json
{
"model_name":{"_type":"choice","_value":["LinearRegression", "SVR", "KNeighborsRegressor", "DecisionTreeRegressor"]}
}
```
如果要选择不同的模型和参数,可以将它们放到同一个 search_space.json 文件中。
```json
{
"model_name":{"_type":"choice","_value":["LinearRegression", "SVR", "KNeighborsRegressor", "DecisionTreeRegressor"]},
"svr_kernel": {"_type":"choice","_value":["linear", "poly", "rbf"]},
"knr_weights": {"_type":"choice","_value":["uniform", "distance"]}
}
```
在 Python 代码中,可以将这些值作为一个 dict,读取到 Python 代码中。
* **第二步**
在代码最前面,加上 `import nni` 来导入 NNI 包。 首先,要使用 `nni.get_next_parameter()` 函数从 NNI 中获取参数。 然后在代码中使用这些参数。 例如,如果定义了如下的 search_space.json:
```json
{
"C": {"_type":"uniform","_value":[0.1, 1]},
"keral": {"_type":"choice","_value":["linear", "rbf", "poly", "sigmoid"]},
"degree": {"_type":"choice","_value":[1, 2, 3, 4]},
"gamma": {"_type":"uniform","_value":[0.01, 0.1]},
"coef0 ": {"_type":"uniform","_value":[0.01, 0.1]}
}
```
就会获得像下面一样的 dict:
```python
params = {
'C': 1.0,
'keral': 'linear',
'degree': 3,
'gamma': 0.01,
'coef0': 0.01
}
```
就可以使用这些变量来编写 scikit-learn 的代码。
* **第三步**
完成训练后,可以得到模型分数,如:精度,召回率,均方差等等。 NNI 会将分数发送给 Tuner 算法,并据此生成下一组参数,所以需要将分数返回给 NNI。NNI 会开始下一个 Trial 任务。
只需要在训练结束后调用 `nni.report_final_result(score)`,就可以将分数传给 NNI。 如果训练过程中有中间分数,也可以使用 `nni.report_intemediate_result(score)` 返回给 NNI。 注意, 可以不返回中间分数,但必须返回最终的分数。
\ No newline at end of file
NNI 支持的训练平台介绍
=====================================
.. toctree::
本机<tutorial_1_CR_exp_local_api>
远程<RemoteMachineMode>
OpenPAI<PAIMode>
Kubeflow<KubeflowMode>
FrameworkController<FrameworkControllerMode>
\ No newline at end of file
#################
Tuner(调参器)
#################
NNI 能用简单快速的方法来配置超参调优算法,称之为 **Tuner**。
Tuner 从 Trial 接收指标结果,来评估一组超参或网络结构的性能。 然后 Tuner 会将下一组超参或网络结构的配置发送给新的 Trial。
在 NNI 中,有两种方法来选择调优算法:可以使用内置的 Tuner,也可以自定义 Tuner。 另外,也可以使用 Advisor,它同时支持 Tuner 和 Assessor 的功能。
详细信息,参考以下教程:
.. toctree::
内置 Tuner<Builtin_Tuner>
自定义 Tuner<Customize_Tuner>
自定义 Advisor<Customize_Advisor>
\ No newline at end of file
# **教程:使用 NNI API 在本地创建和运行 Experiment**
本教程会使用 [~/examples/trials/mnist] 样例来解释如何在本地使用 NNI API 来创建并运行 Experiment。
> 在开始前
要有一个使用卷积层对 MNIST 分类的代码,如 `mnist_before.py`
> 第一步:更新模型代码
对代码进行以下改动来启用 NNI API:
1.1 声明 NNI API
在 Trial 代码中通过 `import nni` 来导入 NNI API。
1.2 获取预定义的参数
参考下列代码片段:
RECEIVED_PARAMS = nni.get_next_parameter()
来获得 Tuner 分配的超参值。 `RECEIVED_PARAMS` 是一个对象,例如:
{"conv_size": 2, "hidden_size": 124, "learning_rate": 0.0307, "dropout_rate": 0.2029}
1.3 返回结果
使用 API:
`nni.report_intermediate_result(accuracy)`
返回 `accuracy` 的值给 Assessor。
使用 API:
`nni.report_final_result(accuracy)`
返回 `accuracy` 的值给 Tuner。
将改动保存到 `mnist.py` 文件中。
**注意**
accuracy - 如果使用 NNI 内置的 Tuner/Assessor,那么 `accuracy` 必须是数值(如 float, int)。在定制 Tuner/Assessor 时 `accuracy` 可以是任何类型的 Python 对象。
Assessor(评估器)- 会根据 Trial 的历史值(即其中间结果),来决定这次 Trial 是否应该提前终止。
Tuner(调参器) - 会根据探索的历史(所有 Trial 的最终结果)来生成下一组参数、架构。
> 第二步:定义搜索空间
`Step 1.2 获取预定义的参数` 中使用的超参定义在 `search_space.json` 文件中:
{
"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]},
"learning_rate":{"_type":"uniform","_value":[0.0001, 0.1]}
}
参考 [SearchSpaceSpec.md](./SearchSpaceSpec.md) 进一步了解搜索空间。
> 第三步:定义 Experiment
>
> > 3.1 启用 NNI API 模式
要启用 NNI 的 API 模式,需要将 useAnnotation 设置为 *false*,并提供搜索空间文件的路径(即第一步中定义的文件):
useAnnotation: false
searchSpacePath: /path/to/your/search_space.json
在 NNI 中运行 Experiment,只需要:
* 可运行的 Trial 的代码
* 实现或选择 Tuner
* 准备 YAML 的 Experiment 配置文件
* (可选) 实现或选择 Assessor
**准备 Trial**
> 在克隆代码后,可以在 ~/nni/examples 中找到一些样例,运行 `ls examples/trials` 查看所有 Trial 样例。
先从 NNI 提供的简单 Trial 样例,如 MNIST 开始。 NNI 样例在代码目录的 examples 中,运行 `ls ~/nni/examples/trials` 可以看到所有 Experiment 的样例。 执行下面的命令可轻松运行 NNI 的 mnist 样例:
python ~/nni/examples/trials/mnist-annotation/mnist.py
上面的命令会写在 YAML 文件中。 参考[这里](Trials.md)来写出自己的 Experiment 代码。
**准备 Tuner**: NNI 支持多种流行的自动机器学习算法,包括:Random Search(随机搜索),Tree of Parzen Estimators (TPE),Evolution(进化算法)等等。 也可以实现自己的 Tuner(参考[这里](Customize_Tuner.md))。下面使用了 NNI 内置的 Tuner:
tuner:
builtinTunerName: TPE
classArgs:
optimize_mode: maximize
*builtinTunerName* 用来指定 NNI 中的 Tuner,*classArgs* 是传入到 Tuner的参数(内置 Tuner 在[这里](Builtin_Tuner.md)),*optimization_mode* 表明需要最大化还是最小化 Trial 的结果。
**准备配置文件**:实现 Trial 的代码,并选择或实现自定义的 Tuner 后,就要准备 YAML 配置文件了。 NNI 为每个 Trial 样例都提供了演示的配置文件,用命令`cat ~/nni/examples/trials/mnist-annotation/config.yml` 来查看其内容。 大致内容如下:
authorName: your_name
experimentName: auto_mnist
# 并发运行数量
trialConcurrency: 2
# Experiment 运行时间
maxExecDuration: 3h
# 可为空,即数量不限
maxTrialNum: 100
# 可选值为: local, remote
trainingServicePlatform: local
# 可选值为: true, false
useAnnotation: true
tuner:
builtinTunerName: TPE
classArgs:
optimize_mode: maximize
trial:
command: python mnist.py
codeDir: ~/nni/examples/trials/mnist-annotation
gpuNum: 0
因为这个 Trial 代码使用了 NNI Annotation 的方法(参考[这里](AnnotationSpec.md) ),所以*useAnnotation* 为 true。 *command* 是运行 Trial 代码所需要的命令,*codeDir* 是 Trial 代码的相对位置。 命令会在此目录中执行。 同时,也需要提供每个 Trial 进程所需的 GPU 数量。
完成上述步骤后,可通过下列命令来启动 Experiment:
nnictl create --config ~/nni/examples/trials/mnist-annotation/config.yml
参考[这里](NNICTLDOC.md)来了解 *nnictl* 命令行工具的更多用法。
## 查看 Experiment 结果
Experiment 应该一直在运行。 除了 *nnictl* 以外,还可以通过 NNI 的网页来查看 Experiment 进程,进行控制和其它一些有意思的功能。
## 使用多个本地 GPU 加快搜索速度
下列步骤假设本机有 4 块 NVIDIA GPUs,参考 [tensorflow with GPU support](https://www.tensorflow.org/install/gpu)。 演示启用了 4 个并发的 Trial 任务,每个 Trial 任务使用了 1 块 GPU。
**准备配置文件**:NNI 提供了演示用的配置文件,使用 `cat examples/trials/mnist-annotation/config_gpu.yml` 来查看。 trailConcurrency 和 gpuNum 与基本配置文件不同:
...
# 可同时运行的 Trial 数量
trialConcurrency: 4
...
trial:
command: python mnist.py
codeDir: ~/nni/examples/trials/mnist-annotation
gpuNum: 1
用下列命令运行 Experiment:
nnictl create --config ~/nni/examples/trials/mnist-annotation/config_gpu.yml
可以用 *nnictl* 命令行工具或网页界面来跟踪训练过程。 *nvidia_smi* 命令行工具能在训练过程中查看 GPU 使用情况。
\ No newline at end of file
# 自定义 Assessor
*Assessor 从 Trial 中接收中间结果,并决定此 Trial 是否应该终止。 一旦 Trial 满足提前终止条件,Assessor 将终止此 Trial。*
因此,如果要自定义 Assessor,需要:
**1) 继承于 Assessor 基类,创建 Assessor 类**
```python
from nni.assessor import Assessor
class CustomizedAssessor(Assessor):
def __init__(self, ...):
...
```
**2) 实现评估 Trial 的函数**
```python
from nni.assessor import Assessor, AssessResult
class CustomizedAssessor(Assessor):
def __init__(self, ...):
...
def assess_trial(self, trial_history):
"""
决定是否应该终止 Trial。 必须重载。
trial_history: 中间结果列表对象。
返回 AssessResult.Good 或 AssessResult.Bad。
"""
# 代码实现于此处。
...
```
**3) 实现脚本来运行 Assessor**
```python
import argparse
import CustomizedAssessor
def main():
parser = argparse.ArgumentParser(description='parse command line parameters.')
# 在这里解析 Assessor 的参数。
...
FLAGS, unparsed = parser.parse_known_args()
tuner = CustomizedAssessor(...)
tuner.run()
main()
```
注意 2) 中, 对象 `trial_history``report_intermediate_result` 函数返回给 Assessor 的完全一致。
也可以重载 Assessor 的 `run` 函数来控制过程逻辑。
更多样例,可参考:
> - [Base-Assessor](https://msrasrg.visualstudio.com/NeuralNetworkIntelligenceOpenSource/_git/Default?_a=contents&path=%2Fsrc%2Fsdk%2Fpynni%2Fnni%2Fassessor.py&version=GBadd_readme)
\ No newline at end of file
# 如何在 NNI 中实现 Trial 的代码?
*Trial 从 Tuner 中接收超参和架构配置,并将中间结果发送给 Assessor,最终结果发送给Tuner 。*
当用户需要在 NNI 上运行 Trial 时,需要:
**1) 写好原始的训练代码**
Trial 的代码可以是任何能在本机运行的机器学习代码。 这里使用 `mnist-keras. py` 作为样例:
```python
import argparse
import logging
import keras
import numpy as np
from keras import backend as K
from keras.datasets import mnist
from keras.layers import Conv2D, Dense, Flatten, MaxPooling2D
from keras.models import Sequential
K.set_image_data_format('channels_last')
H, W = 28, 28
NUM_CLASSES = 10
def create_mnist_model(hyper_params, input_shape=(H, W, 1), num_classes=NUM_CLASSES):
layers = [
Conv2D(32, kernel_size=(3, 3), activation='relu', input_shape=input_shape),
Conv2D(64, (3, 3), activation='relu'),
MaxPooling2D(pool_size=(2, 2)),
Flatten(),
Dense(100, activation='relu'),
Dense(num_classes, activation='softmax')
]
model = Sequential(layers)
if hyper_params['optimizer'] == 'Adam':
optimizer = keras.optimizers.Adam(lr=hyper_params['learning_rate'])
else:
optimizer = keras.optimizers.SGD(lr=hyper_params['learning_rate'], momentum=0.9)
model.compile(loss=keras.losses.categorical_crossentropy, optimizer=optimizer, metrics=['accuracy'])
return model
def load_mnist_data(args):
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train = (np.expand_dims(x_train, -1).astype(np.float) / 255.)[:args.num_train]
x_test = (np.expand_dims(x_test, -1).astype(np.float) / 255.)[:args.num_test]
y_train = keras.utils.to_categorical(y_train, NUM_CLASSES)[:args.num_train]
y_test = keras.utils.to_categorical(y_test, NUM_CLASSES)[:args.num_test]
return x_train, y_train, x_test, y_test
class SendMetrics(keras.callbacks.Callback):
def on_epoch_end(self, epoch, logs={}):
pass
def train(args, params):
x_train, y_train, x_test, y_test = load_mnist_data(args)
model = create_mnist_model(params)
model.fit(x_train, y_train, batch_size=args.batch_size, epochs=args.epochs, verbose=1,
validation_data=(x_test, y_test), callbacks=[SendMetrics()])
_, acc = model.evaluate(x_test, y_test, verbose=0)
def generate_default_params():
return {
'optimizer': 'Adam',
'learning_rate': 0.001
}
if __name__ == '__main__':
PARSER = argparse.ArgumentParser()
PARSER.add_argument("--batch_size", type=int, default=200, help="batch size", required=False)
PARSER.add_argument("--epochs", type=int, default=10, help="Train epochs", required=False)
PARSER.add_argument("--num_train", type=int, default=1000, help="Number of train samples to be used, maximum 60000", required=False)
PARSER.add_argument("--num_test", type=int, default=1000, help="Number of test samples to be used, maximum 10000", required=False)
ARGS, UNKNOWN = PARSER.parse_known_args()
PARAMS = generate_default_params()
train(ARGS, PARAMS)
```
**2) 从 Tuner 获取配置**
导入 `NNI` 并用 `nni.get_next_parameter()` 来接收参数。 注意代码中的 **10**, **24****25** 行。
```python
import argparse
import logging
import keras
import numpy as np
from keras import backend as K
from keras.datasets import mnist
from keras.layers import Conv2D, Dense, Flatten, MaxPooling2D
from keras.models import Sequential
import nni
...
if __name__ == '__main__':
PARSER = argparse.ArgumentParser()
PARSER.add_argument("--batch_size", type=int, default=200, help="batch size", required=False)
PARSER.add_argument("--epochs", type=int, default=10, help="Train epochs", required=False)
PARSER.add_argument("--num_train", type=int, default=1000, help="Number of train samples to be used, maximum 60000", required=False)
PARSER.add_argument("--num_test", type=int, default=1000, help="Number of test samples to be used, maximum 10000", required=False)
ARGS, UNKNOWN = PARSER.parse_known_args()
PARAMS = generate_default_params()
RECEIVED_PARAMS = nni.get_next_parameter()
PARAMS.update(RECEIVED_PARAMS)
train(ARGS, PARAMS)
```
**3) 发送中间结果**
`nni.report_intermediate_result` 将中间结果发送给 Assessor。 注意第 **5** 行。
```python
...
class SendMetrics(keras.callbacks.Callback):
def on_epoch_end(self, epoch, logs={}):
nni.report_intermediate_result(logs)
def train(args, params):
x_train, y_train, x_test, y_test = load_mnist_data(args)
model = create_mnist_model(params)
model.fit(x_train, y_train, batch_size=args.batch_size, epochs=args.epochs, verbose=1,
validation_data=(x_test, y_test), callbacks=[SendMetrics()])
_, acc = model.evaluate(x_test, y_test, verbose=0)
...
```
**4) 发送最终结果**
`nni.report_final_result` 将最终结果发送给 Tuner。 注意第 **15** 行。
```python
...
class SendMetrics(keras.callbacks.Callback):
def on_epoch_end(self, epoch, logs={}):
nni.report_intermediate_result(logs)
def train(args, params):
x_train, y_train, x_test, y_test = load_mnist_data(args)
model = create_mnist_model(params)
model.fit(x_train, y_train, batch_size=args.batch_size, epochs=args.epochs, verbose=1,
validation_data=(x_test, y_test), callbacks=[SendMetrics()])
_, acc = model.evaluate(x_test, y_test, verbose=0)
nni.report_final_result(acc)
...
```
这是完整的样例:
```python
import argparse
import logging
import keras
import numpy as np
from keras import backend as K
from keras.datasets import mnist
from keras.layers import Conv2D, Dense, Flatten, MaxPooling2D
from keras.models import Sequential
import nni
LOG = logging.getLogger('mnist_keras')
K.set_image_data_format('channels_last')
H, W = 28, 28
NUM_CLASSES = 10
def create_mnist_model(hyper_params, input_shape=(H, W, 1), num_classes=NUM_CLASSES):
'''
创建简单的卷积模型
'''
layers = [
Conv2D(32, kernel_size=(3, 3), activation='relu', input_shape=input_shape),
Conv2D(64, (3, 3), activation='relu'),
MaxPooling2D(pool_size=(2, 2)),
Flatten(),
Dense(100, activation='relu'),
Dense(num_classes, activation='softmax')
]
model = Sequential(layers)
if hyper_params['optimizer'] == 'Adam':
optimizer = keras.optimizers.Adam(lr=hyper_params['learning_rate'])
else:
optimizer = keras.optimizers.SGD(lr=hyper_params['learning_rate'], momentum=0.9)
model.compile(loss=keras.losses.categorical_crossentropy, optimizer=optimizer, metrics=['accuracy'])
return model
def load_mnist_data(args):
'''
加载 MNIST 数据集
'''
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train = (np.expand_dims(x_train, -1).astype(np.float) / 255.)[:args.num_train]
x_test = (np.expand_dims(x_test, -1).astype(np.float) / 255.)[:args.num_test]
y_train = keras.utils.to_categorical(y_train, NUM_CLASSES)[:args.num_train]
y_test = keras.utils.to_categorical(y_test, NUM_CLASSES)[:args.num_test]
LOG.debug('x_train shape: %s', (x_train.shape,))
LOG.debug('x_test shape: %s', (x_test.shape,))
return x_train, y_train, x_test, y_test
class SendMetrics(keras.callbacks.Callback):
'''
Keras 回调来返回中间结果给 NNI
'''
def on_epoch_end(self, epoch, logs={}):
'''
在每个 epoch 结束时运行
'''
LOG.debug(logs)
nni.report_intermediate_result(logs)
def train(args, params):
'''
训练模型
'''
x_train, y_train, x_test, y_test = load_mnist_data(args)
model = create_mnist_model(params)
model.fit(x_train, y_train, batch_size=args.batch_size, epochs=args.epochs, verbose=1,
validation_data=(x_test, y_test), callbacks=[SendMetrics()])
_, acc = model.evaluate(x_test, y_test, verbose=0)
LOG.debug('Final result is: %d', acc)
nni.report_final_result(acc)
def generate_default_params():
'''
生成默认超参
'''
return {
'optimizer': 'Adam',
'learning_rate': 0.001
}
if __name__ == '__main__':
PARSER = argparse.ArgumentParser()
PARSER.add_argument("--batch_size", type=int, default=200, help="batch size", required=False)
PARSER.add_argument("--epochs", type=int, default=10, help="Train epochs", required=False)
PARSER.add_argument("--num_train", type=int, default=1000, help="Number of train samples to be used, maximum 60000", required=False)
PARSER.add_argument("--num_test", type=int, default=1000, help="Number of test samples to be used, maximum 10000", required=False)
ARGS, UNKNOWN = PARSER.parse_known_args()
try:
# 从 Tuner 中获取参数
RECEIVED_PARAMS = nni.get_next_parameter()
LOG.debug(RECEIVED_PARAMS)
PARAMS = generate_default_params()
PARAMS.update(RECEIVED_PARAMS)
# 训练
train(ARGS, PARAMS)
except Exception as e:
LOG.exception(e)
raise
```
\ No newline at end of file
此样例需要安装 Pytorch。 Pytorch 安装包需要选择所基于的 Python 和 CUDA 版本。
以下是 python==3.5 和 cuda == 8.0 下的环境样例,使用下列命令来安装 Pytorch: python3 -m pip install http://download.pytorch.org/whl/cu80/torch-0.4.1-cp35-cp35m-linux_x86_64.whl python3 -m pip install torchvision
\ No newline at end of file
# 在阅读理解上使用自动模型架构搜索
该样例展示了如何使用遗传算法为阅读理解任务找到好的模型架构。
## 搜索空间
对于阅读理解项目,注意力和循环神经网络(RNN)模块已经被证明非常有效。 使用的搜索空间如下:
1. IDENTITY (Effectively 表示继续训练)。
2. INSERT-RNN-LAYER (插入 LSTM。 在 Experiment 中比较了 GRU 和 LSTM 的性能后,我们决定在这里采用 LSTM。)
3. REMOVE-RNN-LAYER
4. INSERT-ATTENTION-LAYER (插入注意力层。)
5. REMOVE-ATTENTION-LAYER
6. ADD-SKIP (在随机层之间一致).
7. REMOVE-SKIP (移除随机跳过).
![ga-squad-logo](../../../../examples/trials/ga_squad/ga_squad.png)
## 新版本
另一个时间更快,性能更好的版本正在开发中。 很快将发布。
# 如何运行此样例?
## 在本机或远程上运行此样例
### 使用下载脚本来下载数据
执行下列命令来下载所需要的数据:
chmod +x ./download.sh
./download.sh
### 手动下载
1. 在 https://rajpurkar.github.io/SQuAD-explorer/ 下载 "dev-v1.1.json" 和 "train-v1.1.json"。
```bash
wget https://rajpurkar.github.io/SQuAD-explorer/dataset/train-v1.1.json
wget https://rajpurkar.github.io/SQuAD-explorer/dataset/dev-v1.1.json
```
2. 在 https://nlp.stanford.edu/projects/glove/ 下载 "glove.840B.300d.txt"。
```bash
wget http://nlp.stanford.edu/data/glove.840B.300d.zip
unzip glove.840B.300d.zip
```
### 更新配置
修改 `nni/examples/trials/ga_squad/config.yml`,以下是默认配置:
authorName: default
experimentName: example_ga_squad
trialConcurrency: 1
maxExecDuration: 1h
maxTrialNum: 1
#可选项: local, remote
trainingServicePlatform: local
#可选项: true, false
useAnnotation: false
tuner:
codeDir: ~/nni/examples/tuners/ga_customer_tuner
classFileName: customer_tuner.py
className: CustomerTuner
classArgs:
optimize_mode: maximize
trial:
command: python3 trial.py
codeDir: ~/nni/examples/trials/ga_squad
gpuNum: 0
在 "trial" 部分中,如果需要使用 GPU 来进行架构搜索,可将 `gpuNum``0` 改为 `1`。 根据训练时长,可以增加 `maxTrialNum``maxExecDuration`
`trialConcurrency` 是并发运行的 Trial 的数量。如果将 `gpuNum` 设置为 1,则需要与 GPU 数量一致。
### 提交任务
nnictl create --config ~/nni/examples/trials/ga_squad/config.yml
## 在 OpenPAI 上运行此样例
根据上传大小的限制,仅上传源代码,并在训练过程中下载数据。 本 Experiment 需要的内存 `memoryMB >= 32G`,训练过程可能需要数小时。
### 更新配置
修改 `nni/examples/trials/ga_squad/config_pai.yml`,以下是默认配置:
authorName: default
experimentName: example_ga_squad
trialConcurrency: 1
maxExecDuration: 1h
maxTrialNum: 10
#可选项: local, remote, pai
trainingServicePlatform: pai
#可选项: true, false
useAnnotation: false
# nni_manager 的 ip
nniManagerIp: 10.10.10.10
tuner:
codeDir: ../../tuners/ga_customer_tuner
classFileName: customer_tuner.py
className: CustomerTuner
classArgs:
optimize_mode: maximize
trial:
command: chmod +x ./download.sh && ./download.sh && python3 trial.py
codeDir: .
gpuNum: 0
cpuNum: 1
memoryMB: 32869
#在 OpenPAI 上运行 NNI 任务的 Docker 映像
image: msranni/nni:latest
#在 OpenPAI 的 hdfs 目录上存储数据的目录,如:'hdfs://host:port/directory'
dataDir: hdfs://10.10.10.10:9000/username/nni
#在 OpenPAI 的 hdfs 目录上存储输出的目录,如:'hdfs://host:port/directory'
outputDir: hdfs://10.10.10.10:9000/username/nni
paiConfig:
#登录 OpenPAI 的用户名
userName: username
#登录 OpenPAI 的密码
passWord: password
# OpenPAI 的 RESTful 服务器地址
host: 10.10.10.10
将默认值改为个人账户和服务器信息。 包括 `nniManagerIp`, `dataDir`, `outputDir`, `userName`, `passWord``host`
在 "trial" 部分中,如果需要使用 GPU 来进行架构搜索,可将 `gpuNum``0` 改为 `1`。 根据训练时长,可以增加 `maxTrialNum``maxExecDuration`
`trialConcurrency` 是并发运行的 Trial 的数量。如果将 `gpuNum` 设置为 1,则需要与 GPU 数量一致。
### 提交任务
nnictl create --config ~/nni/examples/trials/ga_squad/config_pai.yml
# 关于此 Trial 的技术细节
## 实现方法
基于进化算法架构的问答和其它样例一样,有两个部分:Trial 和 Tuner。
### Trial
Trial 有大量的文件、函数和类。 这里只简单介绍最重要的文件:
* `attention.py` 包含了 Tensorflow 注意力算法的实现。
* `data.py` 包含了数据处理函数。
* `evaluate.py` 包含了评估脚本。
* `graph.py` 包含了计算图的定义。
* `rnn.py` 包含了 TensorFlow 的 GRU 实现。
* `train_model.py` 是整个文档模型的封装。
这些文件中,`trial.py``graph_to_tf.py` 非常特别。
`graph_to_tf.py` 有一个叫做 `graph_to_network`的函数,其框架代码如下:
def graph_to_network(input1,
input2,
input1_lengths,
input2_lengths,
graph,
dropout_rate,
is_training,
num_heads=1,
rnn_units=256):
topology = graph.is_topology()
layers = dict()
layers_sequence_lengths = dict()
num_units = input1.get_shape().as_list()[-1]
layers[0] = input1*tf.sqrt(tf.cast(num_units, tf.float32)) + \
positional_encoding(input1, scale=False, zero_pad=False)
layers[1] = input2*tf.sqrt(tf.cast(num_units, tf.float32))
layers[0] = dropout(layers[0], dropout_rate, is_training)
layers[1] = dropout(layers[1], dropout_rate, is_training)
layers_sequence_lengths[0] = input1_lengths
layers_sequence_lengths[1] = input2_lengths
for _, topo_i in enumerate(topology):
if topo_i == '|':
continue
if graph.layers[topo_i].graph_type == LayerType.input.value:
# ......
elif graph.layers[topo_i].graph_type == LayerType.attention.value:
# ......
# 处理更多层
正如我们看到的,这个函数实际上是个编译器。它将内部模型的 DAG 配置`图`(在`模型配置格式`章节介绍)转换为 Tensorflow 的计算图。
topology = graph.is_topology()
将内部图表示进行拓扑排序,代码在下列循环中:
for _, topo_i in enumerate(topology):
执行实际转换,将每层映射为 TensorFlow 计算图中的一部分。
### Tuner
Tuner 比 Trial 代码简单很多。 它们共用了同样的 `graph.py`。 此外,Tuner 有 `customer_tuner.py`,其中最重要的类是 `CustomerTuner`
class CustomerTuner(Tuner):
# ......
def generate_parameters(self, parameter_id):
"""将一组 Trial 图配置作为序列化对象返回。
parameter_id : int
"""
if len(self.population) <= 0:
logger.debug("the len of poplution lower than zero.")
raise Exception('The population is empty')
pos = -1
for i in range(len(self.population)):
if self.population[i].result == None:
pos = i
break
if pos != -1:
indiv = copy.deepcopy(self.population[pos])
self.population.pop(pos)
temp = json.loads(graph_dumps(indiv.config))
else:
random.shuffle(self.population)
if self.population[0].result > self.population[1].result:
self.population[0] = self.population[1]
indiv = copy.deepcopy(self.population[0])
self.population.pop(1)
indiv.mutation()
graph = indiv.config
temp = json.loads(graph_dumps(graph))
# ......
重载函数 `generate_parameters` 实现了简单的变异算法。 代码如下:
if self.population[0].result > self.population[1].result:
self.population[0] = self.population[1]
indiv = copy.deepcopy(self.population[0])
控制突变过程。 它会在种群中随机取出两个个体,对更好结果的一个保留数据,并突变另一个。
## 模型配置格式
这是模型配置的样例,在架构搜索过程中,从 Tuner 传入 Trial 的代码。
{
"max_layer_num": 50,
"layers": [
{
"input_size": 0,
"type": 3,
"output_size": 1,
"input": [],
"size": "x",
"output": [4, 5],
"is_delete": false
},
{
"input_size": 0,
"type": 3,
"output_size": 1,
"input": [],
"size": "y",
"output": [4, 5],
"is_delete": false
},
{
"input_size": 1,
"type": 4,
"output_size": 0,
"input": [6],
"size": "x",
"output": [],
"is_delete": false
},
{
"input_size": 1,
"type": 4,
"output_size": 0,
"input": [5],
"size": "y",
"output": [],
"is_delete": false
},
{"Comment": "More layers will be here for actual graphs."}
]
}
每个模型配置都有一个 "layers" 部分,这是层定义的 JSON 列表。 每层的定义也是一个 JSON 对象:
* `type` 是层的类型。 0, 1, 2, 3, 4 对应注意力、自注意力、RNN、输入和输出层。
* `size` 是输出的长度。 "x", "y" 对应文档长度和问题长度。
* `input_size` 是该层的输入数量。
* `input` 表示输入层的索引。
* `output` 是输出层的索引,该层会作为这些层的输入。
* `is_delete` 表示此层是否可用。
\ No newline at end of file
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