"ts/webui/src/static/style/App.scss" did not exist on "0a6c234a2ebb2c368d9bbfe2685e14ad12afc6ff"
Unverified Commit 91d9a797 authored by Yuge Zhang's avatar Yuge Zhang Committed by GitHub
Browse files

Adopt symbolic links in Chinese documentation (#4345)


Co-authored-by: default avatarjiahangxu <jiahangxu@microsoft.com>
parent 0e0ee86d
TPE, Random Search, Anneal Tuners
========================================
TPE
---
Tree-structured Parzen Estimator (TPE) 是一种 sequential model-based optimization(SMBO,即基于序列模型优化)的方法。 SMBO 方法根据历史指标数据来按顺序构造模型,来估算超参的性能,随后基于此模型来选择新的超参。 TPE 方法对 P(x|y) 和 P(y) 建模,其中 x 表示超参,y 表示相关的评估指标。 P(x|y) 通过变换超参的生成过程来建模,用非参数密度(non-parametric densities)代替配置的先验分布。 优化方法在论文 `Algorithms for Hyper-Parameter Optimization <https://papers.nips.cc/paper/4443-algorithms-for-hyper-parameter-optimization.pdf>`__ 中有详细描述。 ​
TPE 的并行优化
^^^^^^^^^^^^^^^^^^^^^^^^^
为了利用多个计算节点,TPE 方法是异步运行的,这样能避免浪费时间等待 Trial 评估的完成。 对原始算法设计进行了顺序计算优化。 如果要大并发的使用 TPE,性能将会较差。 通过 Constant Liar 算法优化了这种情况。 关于优化的原理,参考 `这里 <../CommunitySharings/ParallelizingTpeSearch.rst>`__。
用法
^^^^^
要使用 TPE,需要在 Experiment 的 YAML 配置文件进行如下改动:
.. code-block:: yaml
tuner:
builtinTunerName: TPE
classArgs:
optimize_mode: maximize
parallel_optimize: True
constant_liar_type: min
**classArgs 要求:**
* **optimize_mode** (*maximize 或 minimize, 可选项, 默认值为 maximize*) - 如果为 'maximize',表示 Tuner 会试着最大化指标。 如果为 'minimize',表示 Tuner 的目标是将指标最小化。
* **parallel_optimize** (*bool, 可选, 默认值为 False*) - 如果为 True,TPE 会使用 Constant Liar 算法来优化并行超参调优。 否则,TPE 不会区分序列或并发的情况。
* **constant_liar_type** (*min、max 或 mean, 可选, 默认值为 min*) - 使用的 constant liar 类型,会在 X 点根据 y 的取值来确定。对应三个值:min{Y}, max{Y}, 和 mean{Y}。
Random Search(随机搜索)
--------------------------------------
论文 `Random Search for Hyper-Parameter Optimization <http://www.jmlr.org/papers/volume13/bergstra12a/bergstra12a.pdf>`__ 中介绍了随机搜索惊人的简单和效果。 建议在不知道超参数的先验分布时,使用随机搜索作为基准。
Anneal(退火算法)
------------------------
这种简单的退火算法从先前的采样开始,会越来越靠近发现的最佳点取样。 此算法是随机搜索的简单变体,利用了反应曲面的平滑性。 退火率不是自适应的。
../../en_US/Tuner/HyperoptTuner.rst
\ No newline at end of file
Metis Tuner
==================
Metis Tuner
-----------
`Metis <https://www.microsoft.com/en-us/research/publication/metis-robustly-tuning-tail-latencies-cloud-systems/>`__ 相对于别的调优算法,有几个优势。 大多数调参工具仅仅预测最优配置,而 Metis 具有两个输出,最优配置的预测, 以及下一次 Trial 的建议。 不再需要随机猜测!
大多数工具假设训练集没有噪声数据,但 Metis 会知道是否需要对某个超参重新采样。
大多数工具都有着重于在已有结果上继续发展的问题,而 Metis 的搜索策略可以在探索,发展和重新采样(可选)中进行平衡。
Metis 属于基于序列的贝叶斯优化 (SMBO) 算法的类别,它也基于贝叶斯优化框架。 为了对超参-性能空间建模,Metis 同时使用了高斯过程(Gaussian Process)和高斯混合模型(GMM)。 由于每次 Trial 都可能有很高的时间成本,Metis 大量使用了已有模型来进行推理计算。 在每次迭代中,Metis 执行两个任务:
*
在高斯过程空间中找到全局最优点。 这一点表示了最佳配置。
*
它会标识出下一个超参的候选项。 这是通过对隐含信息的探索、挖掘和重采样来实现的。
此 Tuner 搜索空间仅接受 ``quniform,uniform,randint`` 和数值的 ``choice`` 类型。
更多详情,参考 `论文 <https://www.microsoft.com/en-us/research/publication/metis-robustly-tuning-tail-latencies-cloud-systems/>`__。
../../en_US/Tuner/MetisTuner.rst
\ No newline at end of file
Network Morphism Tuner
=============================
1. 介绍
---------------
`Autokeras <https://arxiv.org/abs/1806.10282>`__ 是使用 Network Morphism 算法的流行的自动机器学习工具。 Autokeras 的基本理念是使用贝叶斯回归来预测神经网络架构的指标。 每次都会从父网络生成几个子网络。 然后使用朴素贝叶斯回归,从网络的历史训练结果来预测它的指标值。 接下来,会选择预测结果最好的子网络加入训练队列中。 `此代码 <https://github.com/jhfjhfj1/autokeras>`__ 的启发下,我们在 NNI 中实现了 Network Morphism 算法。
要了解 Network Morphism Trial 的用法,参考 :githublink:`Readme <examples/trials/network_morphism/README.rst>`
2. 用法
--------
要使用 Network Morphism,需要如下配置 ``config.yml`` 文件:
.. code-block:: yaml
tuner:
#choice: NetworkMorphism
builtinTunerName: NetworkMorphism
classArgs:
#choice: maximize, minimize
optimize_mode: maximize
# 当前,此 Tuner 仅支持视觉领域。
task: cv
# 修改以适合输入图像的宽度
input_width: 32
# 修改以适合输入图像的通道数
input_channel: 3
# 修改以适合类数
n_output_node: 10
在训练过程中,会生成一个 JSON 文件来表示网络图。 可调用 ``json\_to\_graph()`` 函数来将 JSON 文件转化为 Pytoch Keras 模型。
.. code-block:: python
import nni
from nni.networkmorphism_tuner.graph import json_to_graph
def build_graph_from_json(ir_model_json):
"""从json构建pytorch模型
"""
graph = json_to_graph(ir_model_json)
model = graph.produce_torch_model()
return model
# Trial Network Morphism Tuner 获取下一个参数
RCV_CONFIG = nni.get_next_parameter()
# 调用此函数来构建pytorch模型或keras模型
net = build_graph_from_json(RCV_CONFIG)
# 训练过程
# ....
# 将最终精度返回给 NNI
nni.report_final_result(best_acc)
如果需要保存并读取 **最佳模型** ,推荐采用以下方法。
.. code-block:: python
# 1. 使用 NNI API
# WebUI 获得最佳模型 ID
# 或者 ``nni-experiments/experiment_id/log/model_path/best_model.txt``
# 从模型文件中读取 json 字符串,并用 NNI API 加载
with open("best-model.json") as json_file:
json_of_model = json_file.read()
model = build_graph_from_json(json_of_model)
# 2. # 使用框架 API
# 2.1 Keras API
# Trial 代码中使用 Keras API 保存模型
# 最好在 NNI 本地模式下保存 id
model_id = nni.get_sequence_id()
# 将模型序列化为 JSON
model_json = model.to_json()
with open("model-{}.json".format(model_id), "w") as json_file:
json_file.write(model_json)
# 将权重序列化为 HDF5
model.save_weights("model-{}.h5".format(model_id))
# 重用模型时,使用 Keras API 读取
# 读取 JSON 文件,并创建模型
model_id = "" # 需要重用的模型 ID
with open('model-{}.json'.format(model_id), 'r') as json_file:
loaded_model_json = json_file.read()
loaded_model = model_from_json(loaded_model_json)
# 将权重加载到新模型中
loaded_model.load_weights("model-{}.h5".format(model_id))
# 2.2 PyTorch API
# Trial 代码中使用 PyTorch API 保存
model_id = nni.get_sequence_id()
torch.save(model, "model-{}.pt".format(model_id))
# 重用模型时,使用 PyTorch API 读取
model_id = "" # id of the model you want to reuse
loaded_model = torch.load("model-{}.pt".format(model_id))
3. 文件结构
-----------------
Tuner 有大量的文件、函数和类。 这里简单介绍最重要的文件:
*
``networkmorphism_tuner.py`` 是使用 network morphism 算法的 Tuner
*
``bayesian.py`` 是用来基于已经搜索到的模型来预测未知模型指标的贝叶斯算法。
* ``graph.py`` 是元图数据结构。 Graph 表示了模型的神经网络图。
* Graph 从模型中抽取神经网络。
* 图中的每个节点都是层之间的中间张量。
* 在图中,边表示层。
* 注意,多条边可能会表示同一层。
*
``graph_transformer.py`` 包含了一些图转换,包括变宽,变深,或在图中增加跳跃连接。
*
``layers.py`` 包括模型中用到的所有层。
* ``layer_transformer.py`` 包含了一些层转换,包括变宽,变深,或在层中增加跳跃连接。
* ``nn.py`` 包括生成初始网络的类。
* ``metric.py`` 包括了一些指标类,如 Accuracy MSE
* ``utils.py`` 是使用 Keras 在数据集 ``cifar10`` 上搜索神经网络的示例。
4. 网络表示的 JSON 示例
------------------------------------------
这是样例定义的中间表示 JSON 文件,它会在架构搜索过程中从 Tuner 传到 Trial 可调用 Trial 代码中的 ``json_to_graph()`` 函数来将 JSON 文件转化为 Pytoch Keras 模型。
.. code-block:: json
{
"input_shape": [32, 32, 3],
"weighted": false,
"operation_history": [],
"layer_id_to_input_node_ids": {"0": [0],"1": [1],"2": [2],"3": [3],"4": [4],"5": [5],"6": [6],"7": [7],"8": [8],"9": [9],"10": [10],"11": [11],"12": [12],"13": [13],"14": [14],"15": [15],"16": [16]
},
"layer_id_to_output_node_ids": {"0": [1],"1": [2],"2": [3],"3": [4],"4": [5],"5": [6],"6": [7],"7": [8],"8": [9],"9": [10],"10": [11],"11": [12],"12": [13],"13": [14],"14": [15],"15": [16],"16": [17]
},
"adj_list": {
"0": [[1, 0]],
"1": [[2, 1]],
"2": [[3, 2]],
"3": [[4, 3]],
"4": [[5, 4]],
"5": [[6, 5]],
"6": [[7, 6]],
"7": [[8, 7]],
"8": [[9, 8]],
"9": [[10, 9]],
"10": [[11, 10]],
"11": [[12, 11]],
"12": [[13, 12]],
"13": [[14, 13]],
"14": [[15, 14]],
"15": [[16, 15]],
"16": [[17, 16]],
"17": []
},
"reverse_adj_list": {
"0": [],
"1": [[0, 0]],
"2": [[1, 1]],
"3": [[2, 2]],
"4": [[3, 3]],
"5": [[4, 4]],
"6": [[5, 5]],
"7": [[6, 6]],
"8": [[7, 7]],
"9": [[8, 8]],
"10": [[9, 9]],
"11": [[10, 10]],
"12": [[11, 11]],
"13": [[12, 12]],
"14": [[13, 13]],
"15": [[14, 14]],
"16": [[15, 15]],
"17": [[16, 16]]
},
"node_list": [
[0, [32, 32, 3]],
[1, [32, 32, 3]],
[2, [32, 32, 64]],
[3, [32, 32, 64]],
[4, [16, 16, 64]],
[5, [16, 16, 64]],
[6, [16, 16, 64]],
[7, [16, 16, 64]],
[8, [8, 8, 64]],
[9, [8, 8, 64]],
[10, [8, 8, 64]],
[11, [8, 8, 64]],
[12, [4, 4, 64]],
[13, [64]],
[14, [64]],
[15, [64]],
[16, [64]],
[17, [10]]
],
"layer_list": [
[0, ["StubReLU", 0, 1]],
[1, ["StubConv2d", 1, 2, 3, 64, 3]],
[2, ["StubBatchNormalization2d", 2, 3, 64]],
[3, ["StubPooling2d", 3, 4, 2, 2, 0]],
[4, ["StubReLU", 4, 5]],
[5, ["StubConv2d", 5, 6, 64, 64, 3]],
[6, ["StubBatchNormalization2d", 6, 7, 64]],
[7, ["StubPooling2d", 7, 8, 2, 2, 0]],
[8, ["StubReLU", 8, 9]],
[9, ["StubConv2d", 9, 10, 64, 64, 3]],
[10, ["StubBatchNormalization2d", 10, 11, 64]],
[11, ["StubPooling2d", 11, 12, 2, 2, 0]],
[12, ["StubGlobalPooling2d", 12, 13]],
[13, ["StubDropout2d", 13, 14, 0.25]],
[14, ["StubDense", 14, 15, 64, 64]],
[15, ["StubReLU", 15, 16]],
[16, ["StubDense", 16, 17, 64, 10]]
]
}
可将模型视为 `有向无环图 <https://zh.wikipedia.org/wiki/Directed_acyclic_graph>`__ 每个模型的定义都是一个 JSON 对象:
* ``input_shape`` 是整数的列表,不包括批量维度。
* ``weighted`` 表示是否权重和偏移值应该包含在此神经网络图中。
* ``operation_history`` 是保存了所有网络形态操作的列表。
* ``layer_id_to_input_node_ids`` 是字典,将层的标识映射到输入节点标识。
* ``layer_id_to_output_node_ids`` 是字典,将层的标识映射到输出节点标识。
* ``adj_list`` 是二维列表,是图的邻接表。 第一维是张量标识。 在每条边的列表中,元素是两元组(张量标识,层标识)。
* ``reverse_adj_list`` 是与 adj_list 格式一样的反向邻接列表。
* ``node_list`` 是一个整数列表。 列表的索引是标识。
*
``layer_list`` 是层的列表。 列表的索引是标识。
*
对于 ``StubConv(StubConv1d, StubConv2d, StubConv3d)``,后面的数字表示节点的输入 id(或 id 列表),节点输出 idinput_channelfilterskernel_sizestride padding
*
对于 ``StubDense``,后面的数字表示节点的输入 id (或 id 列表),节点输出 idinput_units units
*
对于 ``StubBatchNormalization (StubBatchNormalization1d, StubBatchNormalization2d, StubBatchNormalization3d)``,后面的数字表示节点输入 id(或 id 列表),节点输出 id,和特征数量。
*
对于 ``StubDropout(StubDropout1d, StubDropout2d, StubDropout3d)``,后面的数字表示节点的输入 id (或 id 列表),节点的输出 id dropout 率。
*
对于 ``StubPooling (StubPooling1d, StubPooling2d, StubPooling3d)`` 后面的数字表示节点的输入 id(或 id 列表),节点输出 idkernel_size, stride padding
*
对于其它层,后面的数字表示节点的输入 id(或 id 列表)以及节点的输出 id
5. TODO
-------
下一步,会将 API 从固定网络生成器,改为有更多可用操作的网络生成器。 会使用 ONNX 格式来替代 JSON 作为中间表示结果。
../../en_US/Tuner/NetworkmorphismTuner.rst
\ No newline at end of file
NNI 中的 PBTTuner
================================
PBTTuner
--------
Population Based Training (PBT,基于种群的训练) 来自于论文 `Population Based Training of Neural Networks <https://arxiv.org/abs/1711.09846v1>`__。 它是一种简单的异步优化算法,在固定的计算资源下,它能有效的联合优化一组模型及其超参来最大化性能。 重要的是,PBT 探索的是超参设置的规划,而不是通过整个训练过程中,来试图找到某个固定的参数配置。
.. image:: ../../img/pbt.jpg
:target: ../../img/pbt.jpg
:alt:
PBTTuner 使用多个 Trial 来初始化种群 (即,``population_size``)。 上图中有四步,每个 Trial 只运行一步。 每步运行的时长由 Trial 代码控制,如一个 Epoch。 当 Trial 开始时,会加载 PBTTuner 指定的检查点,并继续运行一步,然后将检查点保存到PBTTuner 指定的目录中,并退出。 种群中的 Trial 会同步的运行这些步骤,也就是说,所有 Trial 都完成了第 ``i`` 步后,``(i+1)`` 步才能开始。 PBT 的挖掘(Exploitation)和探索(Exploration)是两个连续的步骤。
提供检查点目录
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
为了让 Trial 读取其它 Trial 的检查点,需要提供能够被所有 Trial 访问到的目录 (即,``all_checkpoint_dir`` )。 本机模式下非常容易,直接使用默认目录,或指定本机的任意目录均可。 对于其他训练平台,需要根据 `训练平台文档 <../TrainingService/Overview.rst>`__ 来提供能共享的目录,如 NFS, Azure 存储。
修改 Trial 代码
^^^^^^^^^^^^^^^^^^^^^^
在运行步骤之前,Trial 需要读取检查点,检查点目录由 PBTTuner 的超参配置来决定,即 ``params['load_checkpoint_dir']``。 同样,保存检查点的目录也包含在配置中,即,``params['save_checkpoint_dir']``。 在这里,``all_checkpoint_dir`` 是 ``load_checkpoint_dir`` 和 ``save_checkpoint_dir``的根目录,格式为 ``all_checkpoint_dir/<population-id>/<step>``。
.. code-block:: python
params = nni.get_next_parameter()
# 读取检查点的路径
load_path = os.path.join(params['load_checkpoint_dir'], 'model.pth')
# 从 `load_path` 中读取检查点
...
# 运行一步
...
# 保存检查点的路径
save_path = os.path.join(params['save_checkpoint_dir'], 'model.pth')
# 将检查点保存到 `save_path`
...
完整的示例代码参考 :githublink:`这里 <examples/trials/mnist-pbt-tuner-pytorch>`。
Experiment 配置
^^^^^^^^^^^^^^^^^
以下是 PBTTuner 在 Experiment 配置文件中的示例。 **注意,如果使用了 PBTTuner,就不能使用 Assessor。**
.. code-block:: yaml
# config.yml
tuner:
builtinTunerName: PBTTuner
classArgs:
optimize_mode: maximize
all_checkpoint_dir: /the/path/to/store/checkpoints
population_size: 10
../../en_US/Tuner/PBTTuner.rst
\ No newline at end of file
SMAC Tuner
=================
SMAC
----
`SMAC <https://www.cs.ubc.ca/~hutter/papers/10-TR-SMAC.pdf>`__ 基于 Sequential Model-Based Optimization (SMBO). 它利用使用过的结果好的模型(高斯随机过程模型),并将随机森林引入到 SMBO 中,来处理分类参数。 NNI 的 SMAC 通过包装 `SMAC3 <https://github.com/automl/SMAC3>`__ 来支持。
NNI 中的 SMAC 只支持部分类型的 `搜索空间 <../Tutorial/SearchSpaceSpec.rst>`__\ ,包括 ``choice``\ , ``randint``\ , ``uniform``\ , ``loguniform``\ , 和 ``quniform``。
../../en_US/Tuner/SmacTuner.rst
\ No newline at end of file
NNI Annotation
==============
概述
--------
为了获得良好的用户体验并减少对以后代码的影响,NNI 设计了通过 Annotation(标记)来使用的语法。 通过 Annotation,只需要在代码中加入一些注释字符串,就能启用 NNI,完全不影响代码原先的执行逻辑。
示例如下:
.. code-block:: python
'''@nni.variable(nni.choice(0.1, 0.01, 0.001), name=learning_rate)'''
learning_rate = 0.1
此示例中,NNI 会从 (0.1, 0.01, 0.001) 中选择一个值赋给 learning_rate 变量。 第一行就是 NNI 的 Annotation,是 Python 中的一个字符串。 接下来的一行需要是赋值语句。 NNI 会根据 Annotation 行的信息,来给这一行的变量赋上相应的值。
通过这种方式,不需要修改任何代码,代码既可以直接运行,又可以使用 NNI 来调参。
Annotation 的类型:
--------------------
NNI 中,有 4 种类型的 Annotation;
1. 变量
^^^^^^^^^^^^^^^^^^^^^
``'''@nni.variable(sampling_algo, name)'''``
``@nni.variable`` is used in NNI to annotate a variable.
**参数**
* **sampling_algo**: 指定搜索空间的采样算法。 可将其换成 NNI 支持的其它采样函数,函数要以 ``nni.`` 开头。例如,``choice`` 或 ``uniform``, `SearchSpaceSpec <SearchSpaceSpec.rst>`__ 。
* **name**: 将被赋值的变量名称。 注意,此参数应该与下面一行等号左边的值相同。
NNI 支持如下 10 种类型来表示搜索空间:
* ``@nni.variable(nni.choice(option1,option2,...,optionN),name=variable)``
变量值会是列表中的 options 之一。options 的元素可以是嵌套的随机表达式。
* ``@nni.variable(nni.randint(lower, upper),name=variable)``
这表示变量值会类似于 round(uniform(low, high)) 目前,值的类型为 float。 如果要使用整数,需要显式转换。
* ``@nni.variable(nni.uniform(low, high),name=variable)``
变量是 low 和 high 之间均匀分布的值。
* ``@nni.variable(nni.quniform(low, high, q),name=variable)``
变量值为 clip(round(uniform(low, high) / q) * q, low, high),clip 操作用于约束生成值的边界。
* ``@nni.variable(nni.loguniform(low, high),name=variable)``
变量值为 exp(uniform(low, high)) 分布,范围值是对数的均匀分布。
* ``@nni.variable(nni.qloguniform(low, high, q),name=variable)``
变量值为 clip(round(loguniform(low, high) / q) * q, low, high),clip 操作用于约束生成值的边界。
* ``@nni.variable(nni.normal(mu, sigma),name=variable)``
变量值为实数,且为正态分布,均值为 mu,标准方差为 sigma。
* ``@nni.variable(nni.qnormal(mu, sigma, q),name=variable)``
这表示变量值会类似于 round(normal(mu, sigma) / q) * q
* ``@nni.variable(nni.lognormal(mu, sigma),name=variable)``
这表示变量值会类似于 exp(uniform(low, high))
* ``@nni.variable(nni.qlognormal(mu, sigma, q),name=variable)``
这表示变量值会类似于 round(exp(normal(mu, sigma)) / q) * q
示例如下:
.. code-block:: python
'''@nni.variable(nni.choice(0.1, 0.01, 0.001), name=learning_rate)'''
learning_rate = 0.1
2. 函数
^^^^^^^^^^^^^^^^^^^^^
``'''@nni.function_choice(*functions, name)'''``
``@nni.function_choice`` 可以从几个函数中选择一个来执行。
**参数**
* **functions**: 可选择的函数。 注意,必须是包括参数的完整函数调用。 例如 ``max_pool(hidden_layer, pool_size)``。
* **name**: 将被替换的函数名称。
例如:
.. code-block:: python
"""@nni.function_choice(max_pool(hidden_layer, pool_size), avg_pool(hidden_layer, pool_size), name=max_pool)"""
h_pooling = max_pool(hidden_layer, pool_size)
3. 中间结果
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
``'''@nni.report_intermediate_result(metrics)'''``
``@nni.report_intermediate_result`` 用来返回中间结果,这和 `在 NNI 中实现 Trial <../TrialExample/Trials.rst>`__ 中 ``nni.report_intermediate_result`` 的用法相同。
4. 最终结果
^^^^^^^^^^^^^^^^^^^^^^^^
``'''@nni.report_final_result(metrics)'''``
``@nni.report_final_result`` 用来返回最终结果,这和 `在 NNI 中实现 Trial <../TrialExample/Trials.rst>`__ 中 ``nni.report_final_result`` 的用法相同。
../../en_US/Tutorial/AnnotationSpec.rst
\ No newline at end of file
改进 Neural Network Intelligence (NNI)
=================================================
欢迎!! 我们非常欢迎贡献者,特别是代码贡献者。
首先,如果有什么不确定的事情,可随时提交问题或拉取请求。 不会有人因此而抱怨。:) 最有可能的是,会有礼貌的请求你修改一些内容。 我们会感激任何形式的贡献,不想用一堆规则来阻止这些贡献。
不管怎样,如果想要更有效的贡献代码,可以阅读以下内容。 本文档包括了所有在贡献中需要注意的要点,会加快合并代码、解决问题的速度。
查看 `快速入门 <QuickStart.rst>`__ 来初步了解。
下面是一些简单的贡献指南。
提交问题
--------------
在提出问题时,请说明以下事项:
* 按照问题模板的内容来填写安装的细节,以便评审者检查。
* 出现问题的场景 (尽量详细,以便重现问题)。
* 错误和日志消息。
* 其它可能有用的细节信息。
提交新功能建议
---------------------------------
*
在适配使用场景时,总会需要一些新的功能。 可以加入新功能的讨论,也可以直接提交新功能的拉取请求。
*
在自己的 github 账户下 fork 存储库。 在 fork 后, 对于 add, commit, push, 或 squash (如需要) 等改动都需要详细的提交消息。 然后就可以提交拉取请求了。
参与源代码和 Bug 修复
-----------------------------------------
拉取请求需要选好正确的标签,表明是 Bug 修复还是功能改进。 所有代码都需要遵循正确的命名约定和代码风格。
参考 `如何配置 NNI 的开发环境 <./SetupNniDeveloperEnvironment.rst>`__,来安装开发环境。
与 `快速入门类似 <QuickStart.rst>`__, 其它内容,参考 `NNI 文档 <http://nni.readthedocs.io>`__。
处理现有问题
---------------------
查看 `问题列表 <https://github.com/Microsoft/nni/issues>`__,找到需要贡献的问题。 可以找找有 ``good-first-issue`` 或 ``help-wanted`` 标签的来开始贡献。
修改问题的注释和指派人来表明此问题已经开始跟进。 如果上述问题在一周内没有拉取请求或更新状态,这个问题会重新开放给所有人。 高优先级的 Bug 和回归问题需在一天内响应。
代码风格和命名约定
--------------------------------
* NNI 遵循 `PEP8 <https://www.python.org/dev/peps/pep-0008/>`__ 的 Python 代码命名约定。在提交拉取请求时,请尽量遵循此规范。 可通过``flake8`` 或 ``pylint`` 的提示工具来帮助遵循规范。
* NNI 还遵循 `NumPy Docstring 风格 <https://www.sphinx-doc.org/en/master/usage/extensions/example_numpy.html#example-numpy>`__ 的 Python Docstring 命名方案。 Python API 使用了 `sphinx.ext.napoleon <https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html>`__ 来 `生成文档 <Contributing.rst#id5>`__。
* 有关 docstrings,参考 `numpydoc docstring 指南 <https://numpydoc.readthedocs.io/en/latest/format.html>`__ 和 `pandas docstring 指南 <https://python-sprints.github.io/pandas/guide/pandas_docstring.html>`__
* 函数的 docstring, **description**, **Parameters**, 和 **Returns Yields** 是必需的。
* 类的 docstring, **description**, **Attributes** 是必需的。
* 描述 ``dict`` 的 docstring 在超参格式描述中多处用到,请参考 `写作标准的内部准则 <https://ribokit.github.io/docs/text/>`__ 。
文档
-------------
文档使用了 :githublink:`sphinx <docs>` 来生成,
* 在提交文档改动前,请先 **在本地生成文档**:``cd docs/zh_CN && make html``,然后,可以在 ``docs/zh_CN/_build/html`` 目录下找到所有生成的网页。 请认真分析生成日志中的 **每个 WARNING**,这非常有可能是或 **空连接** 或其它问题。
*
需要链接时,尽量使用\ **相对路径**。 但如果文档是 Markdown 格式的,并且:
* 图片需要通过嵌入的 HTML 语法来格式化,则需要使用绝对链接,如 ``https://user-images.githubusercontent.com/44491713/51381727-e3d0f780-1b4f-11e9-96ab-d26b9198ba65.png``。可以通过将图片拖拽到 `Github Issue <https://github.com/Microsoft/nni/issues/new>`__ 框中来生成这样的链接。
* 如果不能被 sphinx 重新格式化,如源代码等,则需要使用绝对链接。 如果源码连接到本代码库,使用 ``https://github.com/Microsoft/nni/tree/master/`` 作为根目录 (例如 :githublink:`mnist.py <examples/trials/mnist-pytorch/mnist.py>` )。
../../en_US/Tutorial/Contributing.rst
\ No newline at end of file
Experiment 配置(遗产)
====================================
这是上一个版本(V1)的实验配置规范。 目前仍然支持,但我们建议用户使用 `新版实验配置(V2) <../reference/experiment_config.rst>`_。
创建 Experiment 所需要的配置文件。 配置文件的路径会传入 ``nnictl`` 命令。
配置文件的格式为 YAML。
本文介绍了配置文件的内容,并提供了一些示例和模板。
* `Experiment 配置参考 <#experiment-config-reference>`__
* `模板 <#template>`__
* `说明 <#configuration-spec>`__
* `authorName <#authorname>`__
* `experimentName <#experimentname>`__
* `trialConcurrency <#trialconcurrency>`__
* `maxExecDuration <#maxexecduration>`__
* `versionCheck <#versioncheck>`__
* `debug <#debug>`__
* `maxTrialNum <#maxtrialnum>`__
* `trainingServicePlatform <#trainingserviceplatform>`__
* `searchSpacePath <#searchspacepath>`__
* `useAnnotation <#useannotation>`__
* `multiThread <#multithread>`__
* `nniManagerIp <#nnimanagerip>`__
* `logDir <#logdir>`__
* `logLevel <#loglevel>`__
* `logCollection <#logcollection>`__
* `tuner <#tuner>`__
* `builtinTunerName <#builtintunername>`__
* `codeDir <#codedir>`__
* `classFileName <#classfilename>`__
* `className <#classname>`__
* `classArgs <#classargs>`__
* `gpuIndices <#gpuindices>`__
* `includeIntermediateResults <#includeintermediateresults>`__
* `assessor <#assessor>`__
* `builtinAssessorName <#builtinassessorname>`__
* `codeDir <#codedir-1>`__
* `classFileName <#classfilename-1>`__
* `className <#classname-1>`__
* `classArgs <#classargs-1>`__
* `advisor <#advisor>`__
* `builtinAdvisorName <#builtinadvisorname>`__
* `codeDir <#codedir-2>`__
* `classFileName <#classfilename-2>`__
* `className <#classname-2>`__
* `classArgs <#classargs-2>`__
* `gpuIndices <#gpuindices-1>`__
* `trial <#trial>`__
* `localConfig <#localconfig>`__
* `gpuIndices <#gpuindices-2>`__
* `maxTrialNumPerGpu <#maxtrialnumpergpu>`__
* `useActiveGpu <#useactivegpu>`__
* `machineList <#machinelist>`__
* `ip <#ip>`__
* `port <#port>`__
* `username <#username>`__
* `passwd <#passwd>`__
* `sshKeyPath <#sshkeypath>`__
* `passphrase <#passphrase>`__
* `gpuIndices <#gpuindices-3>`__
* `maxTrialNumPerGpu <#maxtrialnumpergpu-1>`__
* `useActiveGpu <#useactivegpu-1>`__
* `pythonPath <#pythonPath>`__
* `kubeflowConfig <#kubeflowconfig>`__
* `operator <#operator>`__
* `storage <#storage>`__
* `nfs <#nfs>`__
* `keyVault <#keyvault>`__
* `azureStorage <#azurestorage>`__
* `uploadRetryCount <#uploadretrycount>`__
* `paiConfig <#paiconfig>`__
* `userName <#username>`__
* `password <#password>`__
* `token <#token>`__
* `host <#host>`__
* `reuse <#reuse>`__
* `示例 <#examples>`__
* `本地模式 <#local-mode>`__
* `远程模式 <#remote-mode>`__
* `PAI 模式 <#pai-mode>`__
* `Kubeflow 模式 <#kubeflow-mode>`__
* `Kubeflow 中使用 Azure 存储 <#kubeflow-with-azure-storage>`__
模板
--------
* **简化版(不包含 Annotation(标记)和 Assessor)**
.. code-block:: yaml
authorName:
experimentName:
trialConcurrency:
maxExecDuration:
maxTrialNum:
# 可选项: local, remote, pai, kubeflow
trainingServicePlatform:
searchSpacePath:
# 可选项: true, false, default: false
useAnnotation:
# 可选项: true, false, default: false
multiThread:
tuner:
# 可选项: TPE, Random, Anneal, Evolution
builtinTunerName:
classArgs:
# 可选项: maximize, minimize
optimize_mode:
gpuIndices:
trial:
command:
codeDir:
gpuNum:
# 在本机模式下,machineList 可为空
machineList:
- ip:
port:
username:
passwd:
* **使用 Assessor**
.. code-block:: yaml
authorName:
experimentName:
trialConcurrency:
maxExecDuration:
maxTrialNum:
# 可选项: local, remote, pai, kubeflow
trainingServicePlatform:
searchSpacePath:
# 可选项: true, false, default: false
useAnnotation:
# 可选项: true, false, default: false
multiThread:
tuner:
# 可选项: TPE, Random, Anneal, Evolution
builtinTunerName:
classArgs:
# 可选项: maximize, minimize
optimize_mode:
gpuIndices:
assessor:
# 可选项: Medianstop
builtinAssessorName:
classArgs:
# 可选项: maximize, minimize
optimize_mode:
trial:
command:
codeDir:
gpuNum:
# 在本机模式下,machineList 可为空
machineList:
- ip:
port:
username:
passwd:
* **使用 Annotation**
.. code-block:: yaml
authorName:
experimentName:
trialConcurrency:
maxExecDuration:
maxTrialNum:
# 可选项: local, remote, pai, kubeflow
trainingServicePlatform:
# 可选项: true, false, default: false
useAnnotation:
# 可选项: true, false, default: false
multiThread:
tuner:
# 可选项: TPE, Random, Anneal, Evolution
builtinTunerName:
classArgs:
# 可选项: maximize, minimize
optimize_mode:
gpuIndices:
assessor:
# 可选项: Medianstop
builtinAssessorName:
classArgs:
# 可选项: maximize, minimize
optimize_mode:
trial:
command:
codeDir:
gpuNum:
# 在本机模式下,machineList 可为空
machineList:
- ip:
port:
username:
passwd:
说明
------------------
authorName
^^^^^^^^^^
必填。 字符串。
创建 Experiment 的作者的姓名。
*待定: 增加默认值。*
experimentName
^^^^^^^^^^^^^^
必填。 字符串。
创建的 Experiment 名称。
*待定: 增加默认值。*
trialConcurrency
^^^^^^^^^^^^^^^^
必填。 1 到 99999 之间的整数。
指定同时运行的 Trial 任务的最大数量。
注意:如果 trialGpuNum 大于空闲的 GPU 数量,并且并发的 Trial 任务数量还没达到 **trialConcurrency**,Trial 任务会被放入队列,等待分配 GPU 资源。
maxExecDuration
^^^^^^^^^^^^^^^
可选。 字符串。 默认值:999d。
**maxExecDuration** 指定实验的最大执行时间。 时间单位为 {**s**\ , **m**\ , **h**\ , **d**\ },其分别表示 {*秒*\ , *分钟*\ , *小时*\ , *天*\ }。
注意:maxExecDuration 设置的是 Experiment 执行的时间,不是 Trial 的。 如果 Experiment 达到了设置的最大时间,Experiment 不会停止,但不会再启动新的 Trial 作业。
versionCheck
^^^^^^^^^^^^
可选。 布尔。 默认值:true。
NNI 会校验 remote, pai 和 Kubernetes 模式下 NNIManager 与 trialKeeper 进程的版本。 如果需要禁用版本校验,versionCheck 应设置为 false。
debug
^^^^^
可选。 布尔。 默认值:false。
调试模式会将 versionCheck 设置为 False,并将 logLevel 设置为 'debug'。
maxTrialNum
^^^^^^^^^^^
可选。 1 到 99999 之间的整数。 默认值:99999。
指定 NNI 创建的最大 Trial 任务数,包括成功和失败的任务。
trainingServicePlatform
^^^^^^^^^^^^^^^^^^^^^^^
必填。 字符串。
指定运行 Experiment 的平台,包括 **local**\ , **remote**\ , **pai**\ , **kubeflow**\ , **frameworkcontroller**。
*
**local** 在本机的 ubuntu 上运行 Experiment。
*
**remote** 将任务提交到远程的 ubuntu 上,必须用 **machineList** 来指定远程的 SSH 连接信息。
*
**pai** 提交到微软开源的 `OpenPAI <https://github.com/Microsoft/pai>`__ 上。 更多 OpenPAI 配置,参考 `PAI 模式指南 <../TrainingService/PaiMode.rst>`__。
*
**kubeflow** 提交任务到 `kubeflow <https://www.kubeflow.org/docs/about/kubeflow/>`__\ , NNI 支持基于 kubernetes 的 kubeflow,以及 `azure kubernetes <https://azure.microsoft.com/zh-cn/services/kubernetes-service/>`__。 详情参考 `Kubeflow Docs <../TrainingService/KubeflowMode.rst>`__。 详情参考 `Kubeflow Docs <../TrainingService/KubeflowMode.rst>`__。
*
**adl** 提交任务到 `AdaptDL <https://www.kubeflow.org/docs/about/kubeflow/>`__\ , NNI 支持 Kubernetes 集群上的 AdaptDL。 详情参考 `AdaptDL Docs <../TrainingService/AdaptDLMode.rst>`__。
*
TODO:解释 FrameworkController。
searchSpacePath
^^^^^^^^^^^^^^^
可选。 现有文件的路径。
指定搜索空间文件的路径,此文件必须在运行 nnictl 的本机。
仅在 ``useAnnotation=True`` 时,才不需要填写 **searchSpacePath**。
useAnnotation
^^^^^^^^^^^^^
可选。 布尔。 默认值:false。
使用 Annotation 分析 Trial 代码并生成搜索空间。
注意: 如果设置了 **useAnnotation=True**,searchSpacePath 字段必须被删除。
multiThread
^^^^^^^^^^^
可选。 布尔。 默认值:false。
为 Dispatcher 启用多线程模式。 如果启用了 multiThread,Dispatcher 将启动一个线程来处理来自 NNI 管理器的每个命令。
nniManagerIp
^^^^^^^^^^^^
可选。 字符串。 默认值:eth0 设备的 IP。
设置运行 NNI 管理器进程的计算机的 IP 地址。 此字段为可选项,如果没有设置,则会使用 eth0 的 IP 地址。
注意: 可在 NNI 管理器机器上运行 ifconfig 来检查 eth0 是否存在。 如果没有,建议显式设置 **nniManagerIp**。
logDir
^^^^^^
可选。 目录的路径。 默认:``<user home directory>/nni-experiments``。
配置目录以存储 Experiment 的日志和数据。
logLevel
^^^^^^^^
可选。 字符串。 默认值: ``info``。
设置 Experiment 的日志级别。 可设置的日志级别包括:``trace``\ , ``debug``\ , ``info``\ , ``warning``\ , ``error``\ , ``fatal``。
logCollection
^^^^^^^^^^^^^
可选。 ``http`` 或者 ``none``。 默认值:``none``。
设置在remote、pai、kubeflow、frameworkcontroller 平台中收集日志的方式。 日志支持两种设置,一种是通过 ``http``,让 Trial 将日志通过 POST 方法发回日志,这种方法会减慢 trialKeeper 的速度。 另一种方法是 ``none``,Trial 不将日志回传回来,仅仅回传 Job 的指标。 如果日志较大,可将此参数设置为 ``none``。
tuner
^^^^^
必填。
指定了 Experiment 的 Tuner 算法。有两种方法可设置 Tuner。 一种方法是使用 NNI SDK 提供的内置 Tuner,在这种情况下,需要设置 **builtinTunerName** 和 **classArgs**。 如果使用定制 Tuner,则为必需。 相对于 **codeDir** 的文件路径。 *必须选择其中的一种方式。*
builtinTunerName
^^^^^^^^^^^^^^^^
如果使用内置 Tuner,则为必需。 字符串。
指定系统 Tuner 的名称, NNI SDK 提供的各种 Tuner 的 `说明 <../Tuner/BuiltinTuner.rst>`__。
codeDir
^^^^^^^
如果使用定制 Tuner,则为必需。 相对于配置文件位置的路径。
指定 Tuner 代码的目录。
classFileName
^^^^^^^^^^^^^
如果使用定制 Tuner,则为必需。 相对于 **codeDir** 的文件路径。
指定 Tuner 文件的名称。
className
^^^^^^^^^
如果使用定制 Tuner,则为必需。 字符串。
指定 Tuner 的名称。
classArgs
^^^^^^^^^
可选。 键值对。 默认值:空。
指定 Tuner 算法的参数。 参考 `此文件 <../Tuner/BuiltinTuner.rst>`__ 来了解内置 Tuner 的配置参数。
gpuIndices
^^^^^^^^^^
可选。 字符串。 默认值:空。
指定 Tuner 进程可以使用的 GPU。 可以指定单个或多个 GPU 索引。 多个 GPU 索引用逗号 ``,`` 分隔。 例如, ``1``\ , 或 ``0,1,3``. 如果未设置该字段,则 Tuner 将找不到 GPU(设置 ``CUDA_VISIBLE_DEVICES`` 成空字符串)。
includeIntermediateResults
^^^^^^^^^^^^^^^^^^^^^^^^^^
可选。 布尔。 默认值:false。
如果 **includeIntermediateResults** 为 true,最后一个 Assessor 的中间结果会被发送给 Tuner 作为最终结果。
assessor
^^^^^^^^
指定 Assessor 算法以运行 Experiment。 与 Tuner 类似,有两种设置 Assessor 的方法。 一种方法是使用 NNI SDK 提供的 Assessor。 必填字段:builtinAssessorName 和 classArgs。 另一种方法,是使用用户自定义的 Assessor,需要设置 **codeDirectory**\ , **classFileName**\ , **className** 和 **classArgs**。 *必须选择其中的一种方式。*
默认情况下,未启用任何 Assessor。
builtinAssessorName
^^^^^^^^^^^^^^^^^^^
如果使用内置 Assessor,则为必需。 字符串。
指定内置 Assessor 的名称,NNI SDK 提供的 Assessor 可参考 `这里 <../Assessor/BuiltinAssessor.rst>`__。
codeDir
^^^^^^^
如果使用定制 Assessor,则为必需。 相对于配置文件位置的路径。
指定 Assessor 代码的目录。
classFileName
^^^^^^^^^^^^^
如果使用定制 Assessor,则为必需。 相对于 **codeDir** 的文件路径。
指定 Assessor 文件的名称。
className
^^^^^^^^^
如果使用定制 Assessor,则为必需。 字符串。
指定 Assessor 类的名称。
classArgs
^^^^^^^^^
可选。 键值对。 默认值:空。
指定 Assessor 算法的参数。
Advisor
^^^^^^^
可选。
指定 Experiment 中的 Advisor 算法。 与 Tuner 和 Assessor 类似,有两种指定 Advisor 的方法。 一种方法是使用 SDK 提供的 Advisor ,需要设置 **builtinAdvisorName** 和 **classArgs**。 另一种方法,是使用用户自定义的 Advisor ,需要设置 **codeDirectory**\ , **classFileName**\ , **className** 和 **classArgs**。
启用 Advisor 后,将忽略 Tuner 和 Advisor 的设置。
builtinAdvisorName
^^^^^^^^^^^^^^^^^^
指定内置 Advisor 的名称。 指定内置 Advisor 的名称。 NNI SDK 提供了 `BOHB <../Tuner/BohbAdvisor.rst>`__ 和 `Hyperband <../Tuner/HyperbandAdvisor.rst>`__ 。
codeDir
^^^^^^^
如果使用定制 Advisor,则为必需。 相对于配置文件位置的路径。
指定 Advisor 代码的目录。
classFileName
^^^^^^^^^^^^^
如果使用定制 Advisor,则为必需。 相对于 **codeDir** 的文件路径。
指定 Advisor 文件的名称。
className
^^^^^^^^^
如果使用定制 Advisor,则为必需。 字符串。
指定 Advisor 类的名称。
classArgs
^^^^^^^^^
可选。 键值对。 默认值:空。
指定 Advisor 的参数。
gpuIndices
^^^^^^^^^^
可选。 字符串。 默认值:空。
指定可以使用的 GPU。 可以指定单个或多个 GPU 索引。 多个 GPU 索引用逗号 ``,`` 分隔。 例如, ``1``\ , 或 ``0,1,3``. 如果未设置该字段,则 Tuner 将找不到 GPU(设置 ``CUDA_VISIBLE_DEVICES`` 成空字符串)。
trial
^^^^^
必填。 键值对。
在 local 和 remote 模式下,需要以下键。
*
**command**:必需字符串。 指定运行 Trial 的命令。
*
**codeDir**:必需字符串。 指定 Trial 文件的目录。 此目录将在 remote 模式下自动上传。
*
**gpuNum**:可选、整数。 指定了运行 Trial 进程的 GPU 数量。 默认值为 0。
在 PAI 模式下,需要以下键。
*
**command**:必需字符串。 指定运行 Trial 的命令。
*
**codeDir**:必需字符串。 指定 Trial 文件的目录。 目录中的文件将在 PAI 模式下上传。
*
**gpuNum**:必需、整数。 指定了运行 Trial 进程的 GPU 数量。 默认值为 0。
*
**cpuNum**:必需、整数。 指定要在 OpenPAI 容器中使用的 cpu 数。
*
**memoryMB**:必需、整数。 设置要在 OpenPAI 容器中使用的内存大小,以兆字节为单位。
*
**image**:必需字符串。 设置要在 OpenPAI 中使用的 Docker 映像。
*
**authFile**:可选、字符串。 用于提供 Docker 注册,用于为 OpenPAI 中的映像拉取请求进行身份验证。 `参考 <https://github.com/microsoft/pai/blob/2ea69b45faa018662bc164ed7733f6fdbb4c42b3/docs/faq.rst#q-how-to-use-private-docker-registry-job-image-when-submitting-an-openpai-job>`__.
*
**shmMB**:可选、整数。 容器的共享内存大小。
*
**portList**\ : ``label``\ , ``beginAt``\ , ``portNumber`` 的键值对 list。 参考 `OpenPAI 教程 <https://github.com/microsoft/pai/blob/master/docs/job_tutorial.rst>`__ 。
.. cannot find `Reference <https://github.com/microsoft/pai/blob/2ea69b45faa018662bc164ed7733f6fdbb4c42b3/docs/faq.rst#q-how-to-use-private-docker-registry-job-image-when-submitting-an-openpai-job>`__ and `job tutorial of PAI <https://github.com/microsoft/pai/blob/master/docs/job_tutorial.rst>`__
在 Kubeflow 模式下,需要以下键。
*
**codeDir**:指定了代码文件的本机路径。
*
**ps**:Kubeflow 的 tensorflow-operator 的可选配置,包括:
*
**replicas**:``ps`` 角色的副本数量。
*
**command**:**ps** 容器运行的脚本。
*
**gpuNum**:在 **ps** 容器中使用的 GPU 数量。
*
**cpuNum**:在 **ps** 容器中使用的 CPU 数量。
*
**memoryMB**:容器的内存大小。
*
**image**:在 **ps** 中使用的 Docker 映像。
*
**worker**:是 Kubeflow 的 tensorflow-operator 的可选配置。
*
**replicas**:**worker** 角色的副本数量。
*
**command**:**worker** 容器运行的脚本。
*
**gpuNum**:在 **worker** 容器中使用的 GPU 数量。
*
**cpuNum**:在 **worker** 容器中使用的 CPU 数量。
*
**memoryMB**:容器的内存大小。
*
**image**:在 **worker** 中使用的 Docker 映像。
localConfig
^^^^^^^^^^^
本机模式下可选。 键值对。
仅在 **trainingServicePlatform** 设为 **local** 时有效,否则,配置文件中不应该有 **localConfig** 部分。
gpuIndices
^^^^^^^^^^
可选。 字符串。 默认值:none。
用于指定特定的 GPU。设置此值后,只有指定的 GPU 会被用来运行 Trial 任务。 可以指定单个或多个 GPU 索引。 多个 GPU 索引,应用逗号(``,``)分隔,如 ``1`` 或 ``0,1,3``。 默认情况下,将使用所有可用的 GPU。
maxTrialNumPerGpu
^^^^^^^^^^^^^^^^^
可选。 整数。 默认值: 1。
用于指定 GPU 设备上的最大并发 Trial 的数量。
useActiveGpu
^^^^^^^^^^^^
可选。 布尔。 默认值:false。
用于指定 GPU 上存在其他进程时是否使用此 GPU。 默认情况下,NNI 仅在 GPU 中没有其他活动进程时才使用 GPU。 如果 **useActiveGpu** 设置为 true,则 NNI 无论某 GPU 是否有其它进程,都将使用它。 此字段不适用于 Windows 版的 NNI。
machineList
^^^^^^^^^^^
在 remote 模式下必需。 具有以下键的键值对的列表。
ip
^^
必填。 可从当前计算机访问的 IP 地址或主机名。
远程计算机的 IP 地址或主机名。
port
^^^^
可选。 整数。 有效端口。 默认值: 22。
用于连接计算机的 SSH 端口。
username
^^^^^^^^
使用用户名/密码进行身份验证时是必需的。 字符串。
远程计算机的帐户。
passwd
^^^^^^
使用用户名/密码进行身份验证时是必需的。 字符串。
指定帐户的密码。
sshKeyPath
^^^^^^^^^^
如果使用 SSH 密钥进行身份验证,则为必需。 私钥文件的路径。
如果用户使用 SSH 密钥登录远程计算机,**sshKeyPath** 应是有效的 SSH 密钥文件路径。
*注意:如果同时设置了 passwd 和 sshKeyPath,NNI 会首先使用 passwd。*
passphrase
^^^^^^^^^^
可选。 字符串。
用于保护 SSH 密钥,如果用户没有密码,可为空。
gpuIndices
^^^^^^^^^^
可选。 字符串。 默认值:none。
用于指定特定的 GPU。设置此值后,只有指定的 GPU 会被用来运行 Trial 任务。 可以指定单个或多个 GPU 索引。 多个 GPU 索引,应用逗号(``,``)分隔,如 ``1`` 或 ``0,1,3``。 默认情况下,将使用所有可用的 GPU。
maxTrialNumPerGpu
^^^^^^^^^^^^^^^^^
可选。 整数。 默认值: 1。
用于指定 GPU 设备上的最大并发 Trial 的数量。
useActiveGpu
^^^^^^^^^^^^
可选。 布尔。 默认值:false。
用于指定 GPU 上存在其他进程时是否使用此 GPU。 默认情况下,NNI 仅在 GPU 中没有其他活动进程时才使用 GPU。 如果 **useActiveGpu** 设置为 true,则 NNI 无论某 GPU 是否有其它进程,都将使用它。 此字段不适用于 Windows 版的 NNI。
pythonPath
^^^^^^^^^^
可选。 字符串。
用户可以通过设置 **pythonPath**,在远程机器上配置 Python 环境。
remoteConfig
^^^^^^^^^^^^
在 remote 模式下可选。 用户可以在 ``machineList`` 字段中设置每台机器的信息,并在该字段中设置远程模式的全局配置。
reuse
^^^^^
可选。 布尔。 默认值:``false``。 这是试用中的功能。
如果为 true,NNI 会重用远程作业,在其中运行尽可能多的 Trial。 这样可以节省创建新作业的时间。 用户需要确保同一作业中的每个 Trial 相互独立,例如,要避免从之前的 Trial 中读取检查点。
kubeflowConfig
^^^^^^^^^^^^^^
operator
^^^^^^^^
必填。 字符串。 必须是 ``tf-operator`` 或 ``pytorch-operator``。
指定要使用的 Kubeflow 运算符,当前版本中 NNI 支持 ``tf-operator``。
storage
^^^^^^^
可选。 字符串。 默认值 ``nfs``。
指定 Kubeflow 的存储类型,包括 ``nfs`` 和 ``azureStorage``。
nfs
^^^
如果使用 nfs,则必需。 键值对。
*
**server** 是 NFS 服务器的地址。
*
**path** 是 NFS 挂载的路径。
keyVault
^^^^^^^^
如果使用 Azure 存储,则必需。 键值对。
将 **keyVault** 设置为 Azure 存储帐户的私钥。 参考 `此文档 <https://docs.microsoft.com/zh-cn/azure/key-vault/key-vault-manage-with-cli2>`__ 。
*
**vaultName** 是 az 命令中 ``--vault-name`` 的值。
*
**name** 是 az 命令中 ``--name`` 的值。
azureStorage
^^^^^^^^^^^^
如果使用 Azure 存储,则必需。 键值对。
设置 Azure 存储帐户以存储代码文件。
*
**accountName** 是 Azure 存储账户的名称。
*
**azureShare** 是 Azure 文件存储的共享参数。
uploadRetryCount
^^^^^^^^^^^^^^^^
如果使用 Azure 存储,则必需。 1 到 99999 之间的整数。
如果上传文件至 Azure Storage 失败,NNI 会重试。此字段指定了重试的次数。
paiConfig
^^^^^^^^^
userName
^^^^^^^^
必填。 字符串。
OpenPAI 帐户的用户名。
password
^^^^^^^^
如果使用密码身份验证,则需要。 字符串。
OpenPAI 帐户的密码。
token
^^^^^
如果使用令牌(token)身份验证,则需要。 字符串。
可以从 OpenPAI 门户检索的个人访问令牌。
host
^^^^
必填。 字符串。
OpenPAI 的 IP 地址。
reuse
^^^^^
可选。 布尔。 默认值:``false``。 这是试用中的功能。
如果为 true,NNI 会重用 OpenPAI 作业,在其中运行尽可能多的 Trial。 这样可以节省创建新作业的时间。 用户需要确保同一作业中的每个 Trial 相互独立,例如,要避免从之前的 Trial 中读取检查点。
sharedStorage
^^^^^^^^^^^^^
storageType
^^^^^^^^^^^
必填。 字符串。
存储类型,支持 ``NFS`` 和 ``AzureBlob``。
localMountPoint
^^^^^^^^^^^^^^^
必填。 字符串。
已经或将要在本地挂载存储的绝对路径。 如果路径不存在,则会自动创建。 建议使用绝对路径。 =========================== ``/tmp/nni-shared-storage``
remoteMountPoint
^^^^^^^^^^^^^^^^
必填。 字符串。
远程挂载存储的绝对路径。 如果路径不存在,则会自动创建。 请注意,如果使用 AzureBlob,该目录必须为空。 建议使用相对路径。 即, ``./nni-shared-storage``
localMounted
^^^^^^^^^^^^
必填。 字符串。
``usermount``、``nnimount`` 和 ``nomount`` 其中之一。 ``usermount`` 表示已经在 localMountPoint 上挂载了此存储。 ``nnimount`` 表示 nni 将尝试在 localMountPoint 上挂载此存储。 ``nomount`` 表示存储不会挂载在本地机器上,将在未来支持部分存储。 ``usermount`` 表示已经在 localMountPoint 上挂载了此存储。 ``nnimount`` 表示 nni 将尝试在 localMountPoint 上挂载此存储。 ``nomount`` 表示存储不会挂载在本地机器上,将在未来支持部分存储。
nfsServer
^^^^^^^^^
可选。 字符串。
如果使用 NFS 存储,则必需。 NFS 服务器的 host。 如果使用 NFS 存储,则必需。 NFS 服务器的导出目录。
exportedDirectory
^^^^^^^^^^^^^^^^^
可选。 字符串。
如果使用 NFS 存储,则必需。 NFS 服务器的导出目录。
storageAccountName
^^^^^^^^^^^^^^^^^^
可选。 字符串。
如果使用 AzureBlob 存储,则必需。 AzureBlob 容器名。 如果使用 AzureBlob 存储且 ``resourceGroupName`` 未设置,则必需。 Azure 存储账户密钥。
storageAccountKey
^^^^^^^^^^^^^^^^^
可选。 字符串。
如果使用 AzureBlob 存储且 ``storageAccountKey`` 未设置,则必需。 AzureBlob 容器所属的资源组。 Azure 存储账户密钥。
resourceGroupName
^^^^^^^^^^^^^^^^^
可选。 字符串。
如果使用 Azure 存储,则必需。 1 到 99999 之间的整数。 AzureBlob 容器所属的资源组。
containerName
^^^^^^^^^^^^^
可选。 字符串。
如果使用 AzureBlob 存储,则必需。 AzureBlob 容器名。
示例
--------
本机模式
^^^^^^^^^^
如果要在本机运行 Trial 任务,并使用标记来生成搜索空间,可参考下列配置:
.. code-block:: yaml
authorName: test
experimentName: test_experiment
trialConcurrency: 3
maxExecDuration: 1h
maxTrialNum: 10
# 可选项:local, remote, pai, kubeflow
trainingServicePlatform: local
# 可选项:true, false
useAnnotation: true
tuner:
# 可选项:TPE, Random, Anneal, Evolution
builtinTunerName: TPE
classArgs:
# 可选项: maximize, minimize
optimize_mode: maximize
trial:
command: python3 mnist.py
codeDir: /nni/mnist
gpuNum: 0
增加 Assessor 配置
.. code-block:: yaml
authorName: test
experimentName: test_experiment
trialConcurrency: 3
maxExecDuration: 1h
maxTrialNum: 10
# 可选项:local, remote, pai, kubeflow
trainingServicePlatform: local
searchSpacePath: /nni/search_space.json
# 可选项:true, false
useAnnotation: false
tuner:
# 可选项:TPE, Random, Anneal, Evolution
builtinTunerName: TPE
classArgs:
# 可选项: maximize, minimize
optimize_mode: maximize
assessor:
# 可选项: Medianstop
builtinAssessorName: Medianstop
classArgs:
# 可选项: maximize, minimize
optimize_mode: maximize
trial:
command: python3 mnist.py
codeDir: /nni/mnist
gpuNum: 0
或者可以指定自定义的 Tuner 和 Assessor:
.. code-block:: yaml
authorName: test
experimentName: test_experiment
trialConcurrency: 3
maxExecDuration: 1h
maxTrialNum: 10
# 可选项:local, remote, pai, kubeflow
trainingServicePlatform: local
searchSpacePath: /nni/search_space.json
# 可选项:true, false
useAnnotation: false
tuner:
codeDir: /nni/tuner
classFileName: mytuner.py
className: MyTuner
classArgs:
# 可选项: maximize, minimize
optimize_mode: maximize
assessor:
codeDir: /nni/assessor
classFileName: myassessor.py
className: MyAssessor
classArgs:
# 可选项: maximize, minimize
optimize_mode: maximize
trial:
command: python3 mnist.py
codeDir: /nni/mnist
gpuNum: 0
远程模式
^^^^^^^^^^^
如果要在远程服务器上运行 Trial 任务,需要增加服务器信息:
.. code-block:: yaml
authorName: test
experimentName: test_experiment
trialConcurrency: 3
maxExecDuration: 1h
maxTrialNum: 10
# 可选项:local, remote, pai, kubeflow
trainingServicePlatform: remote
searchSpacePath: /nni/search_space.json
# 可选项:true, false
useAnnotation: false
tuner:
# 可选项:TPE, Random, Anneal, Evolution
builtinTunerName: TPE
classArgs:
# 可选项: maximize, minimize
optimize_mode: maximize
trial:
command: python3 mnist.py
codeDir: /nni/mnist
gpuNum: 0
# 如果是本地平台的话,machineList 可以为空
machineList:
- ip: 10.10.10.10
port: 22
username: test
passwd: test
- ip: 10.10.10.11
port: 22
username: test
passwd: test
- ip: 10.10.10.12
port: 22
username: test
sshKeyPath: /nni/sshkey
passphrase: qwert
# 以下是特定 python 环境的一个示例
pythonPath: ${replace_to_python_environment_path_in_your_remote_machine}
PAI 模式
^^^^^^^^
.. code-block:: yaml
authorName: test
experimentName: nni_test1
trialConcurrency: 1
maxExecDuration:500h
maxTrialNum: 1
# 可选项:local, remote, pai, kubeflow
trainingServicePlatform: pai
searchSpacePath: search_space.json
# 可选项:true, false
useAnnotation: false
tuner:
# 可选项: TPE, Random, Anneal, Evolution, BatchTuner
# SMAC (SMAC 应该通过 nnictl 安装)
builtinTunerName: TPE
classArgs:
# 可选项: maximize, minimize
optimize_mode: maximize
trial:
command: python3 main.py
codeDir: .
gpuNum: 4
cpuNum: 2
memoryMB: 10000
# 在 OpenPAI 上运行 NNI 的 Docker 映像
image: msranni/nni:latest
paiConfig:
# 登录 OpenPAI 的用户名
userName: test
# 登录 OpenPAI 的密码
passWord: test
# OpenPAI 的 RestFUL 服务器地址
host: 10.10.10.10
Kubeflow 模式
^^^^^^^^^^^^^^^^^^^^^^^^^^
使用 NFS 存储。
.. code-block:: yaml
authorName: default
experimentName: example_mni
trialConcurrency: 1
maxExecDuration: 1h
maxTrialNum: 1
# 可选项:local, remote, pai, kubeflow
trainingServicePlatform: kubeflow
searchSpacePath: search_space.json
# 可选项:true, false
useAnnotation: false
tuner:
# 可选项:TPE, Random, Anneal, Evolution
builtinTunerName: TPE
classArgs:
# 可选项: maximize, minimize
optimize_mode: maximize
trial:
codeDir: .
worker:
replicas: 1
command: python3 mnist.py
gpuNum: 0
cpuNum: 1
memoryMB: 8192
image: msranni/nni:latest
kubeflowConfig:
operator: tf-operator
nfs:
server: 10.10.10.10
path: /var/nfs/general
Kubeflow 中使用 Azure 存储
^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: yaml
authorName: default
experimentName: example_mni
trialConcurrency: 1
maxExecDuration: 1h
maxTrialNum: 1
# 可选项:local, remote, pai, kubeflow
trainingServicePlatform: kubeflow
searchSpacePath: search_space.json
# 可选项:true, false
useAnnotation: false
# nniManagerIp: 10.10.10.10
tuner:
# 可选项:TPE, Random, Anneal, Evolution
builtinTunerName: TPE
classArgs:
# 可选项: maximize, minimize
optimize_mode: maximize
assessor:
builtinAssessorName: Medianstop
classArgs:
optimize_mode: maximize
trial:
codeDir: .
worker:
replicas: 1
command: python3 mnist.py
gpuNum: 0
cpuNum: 1
memoryMB: 4096
image: msranni/nni:latest
kubeflowConfig:
operator: tf-operator
keyVault:
vaultName: Contoso-Vault
name: AzureStorageAccountKey
azureStorage:
accountName: storage
azureShare: share01
../../en_US/Tutorial/ExperimentConfig.rst
\ No newline at end of file
常见问答
=========
此页为常见问题
tmp 目录没空间了
^^^^^^^^^^^^^^^^^
nnictl 在执行时,使用 tmp 目录作为临时目录来复制 codeDir 下的文件。
当遇到下列错误时,先试试清空 **tmp** 目录。
..
OSError: [Errno 28] No space left on device
OpenPAI 模式下无法获得 Trial 的数据
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
在 OpenPAI 的训练模式下,NNI 管理器会在端口 51189 启动一个 RESTful 服务,来接收 OpenPAI 集群中 Trial 任务的指标数据。 如果在 OpenPAI 模式下的网页中不能看到任何指标,需要检查 51189 端口是否在防火墙规则中已打开。
安装时出现 Segmentation Fault (core dumped)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: text
make: *** [install-XXX] Segmentation fault (core dumped)
可依次试试以下方法:
* 更新或重新安装 Python 中的 pip:``python3 -m pip install -U pip``
* 在安装 NNI 时,添加 ``--no-cache-dir`` 参数:``python3 -m pip install nni --no-cache-dir``
Job management error: getIPV4Address() failed because os.networkInterfaces().eth0 is undefined.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
计算机没有 eth0 设备,需要在配置文件中手动设置 `nniManagerIp <ExperimentConfig.rst>`__ 。
运行时间超过了 MaxDuration ,但没有停止
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
当实验到达最长运行时间时,nniManager 不会创建新的尝试,但除非手动停止实验,运行中的尝试会继续。
使用 ``nnictl stop`` 无法停止 Experiment
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
如果在 Experiment 运行时,升级了 nni 或删除了一些配置文件,会因为丢失配置文件而出现这类错误。 可以使用 ``ps -ef | grep node`` 命令来找到 Experiment 的 PID,并用 ``kill -9 {pid}`` 命令来停止 Experiment 进程。
无法在虚拟机的 NNI 网页中看到 ``指标数据``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
将虚拟机的网络配置为桥接模式来让虚拟机能被网络访问,并确保虚拟机的防火墙没有禁止相关端口。
无法打开 Web 界面的链接
^^^^^^^^^^^^^^^^^^^^^^^^^
无法打开 Web 界面的链接可能有以下几个原因:
* ``http://127.0.0.1``\ , ``http://172.17.0.1`` 以及 ``http://10.0.0.15`` 都是 localhost。如果在服务器或远程计算机上启动 Experiment, 可将此 IP 替换为所连接的 IP 来查看 Web 界面,如 ``http://[远程连接的地址]:8080``
* 如果使用服务器 IP 后还是无法看到 Web 界面,可检查此服务器上是否有防火墙或需要代理。 或使用此运行 NNI Experiment 的服务器上的浏览器来查看 Web 界面。
* 另一个可能的原因是 Experiment 启动失败了,NNI 无法读取 Experiment 的信息。 可在如下目录中查看 NNIManager 的日志: ``~/nni/experiment/[your_experiment_id] /log/nnimanager.log``
RESTful 服务器启动失败
^^^^^^^^^^^^^^^^^^^^^^^^^^^
可能是网络配置有问题。可检查以下问题。
* 可能需要链接 ``127.0.0.1`` 与 ``localhost``。 在 ``/etc/hosts`` 中增加 ``127.0.0.1 localhost``。
* 也可能设置了一些代理。检查环境中是否有如 ``HTTP_PROXY`` 或 ``HTTPS_PROXY`` 的变量,如果有,则需要取消。
NNI 在 Windows 上的问题
^^^^^^^^^^^^^^^^^^^^^^^
参考 `在 Windows 上 安装 NNI <InstallationWin.rst>`__
更多常见问题解答
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
`标有常见问题标签的 Issue <https://github.com/microsoft/nni/labels/FAQ>`__
帮助改进
^^^^^^^^^^^^^^^
在创建新问题前,请在 https://github.com/Microsoft/nni/issues 查看是否有人已经报告了相似的问题。
../../en_US/Tutorial/FAQ.rst
\ No newline at end of file
在 NNI 中调试代码
===========================
概述
--------
NNI 中的日志分为三部分。 包括 NNI Manager, Dispatcher 以及 Trial。 这里会简单介绍这些组件。 更多信息可参考 `概述 <../Overview.rst>`__。
* **NNI controller**:NNI Controller (nnictl) 是命令行工具,用来管理 Experiments(如:启动 Experiment)。
* **nnimanager**:这是 NNI 的核心。当 Experiment 出现严重错误时,从它的日志中才能找到原因。(例如,Web 界面无法打开,或者训练平台失败)
* **Dispatcher**\ : NNI 调用 **Tuner** 和 **Assessor** 的方法。 它的日志与 Tuner 和 Assessor 代码有关。
* **Tuner**:Tuner 是一个自动机器学习算法,会为下一个 Trial 生成新的配置。 新的 Trial 会使用这组配置来运行。
* **Assessor**:Assessor 分析 Trial 的中间结果(例如,测试数据集上定期的精度),来确定 Trial 是否应该被提前终止。
* **Trial**:Trial 的代码是用户实现的代码,每次 Trial 运行时会尝试一组新的配置(例如,一组新的超参值,或者某个神经网络结构)。
日志的位置
----------------
NNI 中有三种日志。 在创建 Experiment 时,可增加命令行参数 ``--debug``,将日志级别设置为 debug 级别。 此外,还可以在配置文件中使用 ``logLevel`` 来
设置日志级别。 可设置的日志级别包括:``trace``\ , ``debug``\ , ``info``\ , ``warning``\ , ``error``\ , ``fatal``。
NNI Controller
^^^^^^^^^^^^^^
在启动 NNI Experiment 时发生的错误,都可以在这里找到。
通过 ``nnictl log stderr`` 命令来查看错误信息。 参考 `NNICTL <Nnictl.rst>`__ 了解更多信息。
Experiment 根目录
^^^^^^^^^^^^^^^^^^^^^^^^^
每个 Experiment 都有一个根目录,会显示在 Web 界面的右上角。 如果无法打开 Web 界面,可将 ``~/nni-experiments/experiment_id/`` 中的 ``experiment_id`` 替换为实际的 Experiment ID,来组合出根目录。 ``experiment_id`` 可以在运行 ``nnictl create ...`` 来创建新 Experiment 的输出中找到。
..
如有需要,可以在配置文件中修改 ``logDir``,来指定存储 Experiment 的目录。(默认为 ``~/nni-experiments``)。 请参考 `配置文档 <ExperimentConfig.rst>`__ 获取更多信息。
在此目录下,还会有另一个叫做 ``log`` 的目录,``nnimanager.log`` 和 ``dispatcher.log`` 都在此目录中。
Trial 根目录
^^^^^^^^^^^^^^^^^^^^
在 Web 界面中,可通过点击每个 Trial 左边的 ``+`` 来展开详情并看到它的日志路径。
在 Experiment 的根目录中,会有一个 ``trials`` 目录,这里存放了所有 Trial 的信息。
每个 Trial 都有一个用其 ID 命名的目录。 目录中会有一个 ``stderr`` 文件,是 Trial 的错误信息。另一个 ``trial.log`` 文件是 Trial 的日志。
不同类型的错误
-------------------------
NNI 中有不同的错误类型。 根据严重程度,可分为三类。 当 NNI 中发生错误时,需要按顺序检查以下三种错误。
一般情况下,打开 Web 界面,可以在 ``Overview`` 标签的 ``Status`` 上看到错误信息。 如果 Web 界面无法打开,可以通过命令行来检查。
**NNI** 失败
^^^^^^^^^^^^^^^^^
这是最严重的错误。 发生这种错误时,整个 Experiment 都会失败,Trial 也不会运行。 这通常是由安装问题导致的。
先检查 ``nnictl`` 的错误输出文件 ``stderr`` (运行 nnictl log stderr),然后检查 ``nnimanager`` 的日志来看看是否由任何错误。
Dispatcher 失败
^^^^^^^^^^^^^^^^^^^^^^^^
Dispatcher 失败, 这通常是 Tuner 失败的情况。 可检查 Dispatcher 的日志来分析出现了什么问题。 对于内置的 Tuner,常见的错误可能是无效的搜索空间(不支持的搜索空间类型,或配置文件中的 Tuner 参数与 ``__init__`` 函数所要求的不一致)。
以后一种情况为例。 某自定义的 Tuner, __init__ 函数有名为 ``optimize_mode`` 的参数,但配置文件中没有提供此参数。NNI 就会因为初始化 Tuner 失败而造成 Experiment 失败。 可在 Web 界面看到如下错误:
.. image:: ../../img/dispatcher_error.jpg
:target: ../../img/dispatcher_error.jpg
:alt:
可以看到这是一个 Dispatcher 的错误。 因此,检查 Dispatcher 的日志,可找到如下信息:
.. code-block:: bash
[2019-02-19 19:36:45] DEBUG (nni.main/MainThread) START
[2019-02-19 19:36:47] ERROR (nni.main/MainThread) __init__() missing 1 required positional arguments: 'optimize_mode'
Traceback (most recent call last):
File "/usr/lib/python3.7/site-packages/nni/__main__.py", line 202, in <module>
main()
File "/usr/lib/python3.7/site-packages/nni/__main__.py", line 164, in main
args.tuner_args)
File "/usr/lib/python3.7/site-packages/nni/__main__.py", line 81, in create_customized_class_instance
instance = class_constructor(**class_args)
TypeError: __init__() missing 1 required positional arguments: 'optimize_mode'.
**Trial** 失败
^^^^^^^^^^^^^^^^^^^
这种情况下,NNI 可以继续运行,并创建新的 Trial。
这表示 Trial 代码中出现了失败。 这种错误与 Trial 代码相关。 需检查 Trial 的日志来修复错误。
如,其中常见的一种错误是在运行 MNIST 示例时没有安装 TensorFlow。 因为导入模块的错误(没有安装 Tensorflow,但在 Trial 代码中有 import tensorflow 的语句),每次 Trial 都会运行失败。
.. image:: ../../img/trial_error.jpg
:target: ../../img/trial_error.jpg
:alt:
如图,每个 Trial 都有日志路径,可以从中找到 Trial 的日志和 stderr。
除了 Experiment 级调试之外,NNI 还提供调试单个 Trial 的功能,而无需启动整个 Experiment。 有关调试单个 Trial 代码的更多信息,请参考 `独立运行模式 <../TrialExample/Trials#standalone-mode-for-debugging>`__ 。
../../en_US/Tutorial/HowToDebug.rst
\ 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