微软开源自动调参工具NNI实战教程(基于PyTorch 1.7.1)
简介:微软开源的NNI(Neural Network Intelligence)是一个强大的自动机器学习(AutoML)工具,专注于高效模型超参数调优。本教程基于NNI源码和PyTorch 1.7.1框架,系统讲解如何使用NNI进行自动化调参,涵盖实验配置、调优算法选择、训练代码编写及源码解析。通过本教程,用户将掌握NNI核心流程,提升深度学习模型性能,并具备二次开发与平台集成的能力。
1. NNI自动调参工具简介
NNI(Neural Network Intelligence)是由微软开发的开源自动化机器学习(AutoML)工具,专注于简化和优化深度学习模型的超参数调优过程。它通过集成多种主流调参算法(如网格搜索、贝叶斯优化、TPE等),为开发者提供统一、高效的实验管理平台。
NNI的核心优势在于其模块化设计与良好的框架兼容性,尤其在与PyTorch等主流深度学习框架结合使用时,能够显著提升模型调优效率。通过NNI,用户可以轻松定义参数搜索空间、配置训练任务,并实时监控调优进度,实现自动化、可扩展的模型优化流程。
2. NNI与PyTorch 1.7.1环境搭建
在进行NNI(Neural Network Intelligence)调参任务之前,搭建一个稳定、兼容的开发环境是至关重要的。本章将详细讲解如何构建一个支持NNI调参的PyTorch 1.7.1开发环境,涵盖Python虚拟环境配置、PyTorch安装与验证、NNI工具的安装步骤,以及开发工具链的整合与测试流程。通过本章内容,您将掌握完整的环境搭建流程,并能独立完成从零到一的NNI与PyTorch环境配置。
2.1 环境依赖与版本匹配
构建NNI与PyTorch集成环境的第一步是确保各个组件的版本兼容性。NNI支持多种深度学习框架,但在本章中我们聚焦于PyTorch 1.7.1,因为它是一个稳定且广泛使用的版本,适合大多数NNI实验任务。
2.1.1 Python版本与虚拟环境配置
NNI推荐使用Python 3.6到3.9之间的版本,而PyTorch 1.7.1对Python 3.6兼容性最佳。因此建议使用Python 3.6.15版本进行安装。为避免与其他Python项目产生冲突,使用虚拟环境(如 venv 或 conda )是明智的选择。
操作步骤:
- 创建虚拟环境:
python3.6 -m venv nni_env
- 激活虚拟环境:
# Linux/macOS
source nni_env/bin/activate
# Windows
nni_env\Scripts\activate
代码逻辑分析:
-
python3.6 -m venv nni_env:使用Python 3.6创建一个名为nni_env的虚拟环境。 -
source或activate命令激活虚拟环境,确保后续安装的包仅作用于该环境。
参数说明:
-
venv是 Python 自带的虚拟环境模块。 - 激活后,终端命令行前缀通常会显示环境名称,如
(nni_env)。
2.1.2 PyTorch 1.7.1的安装与验证
PyTorch 1.7.1的安装应通过官方推荐的方式进行,以确保兼容性和稳定性。
操作步骤:
- 使用pip安装PyTorch 1.7.1:
pip install torch==1.7.1+cu110 torchvision==0.8.2+cu110 torchaudio==0.7.2 -f https://download.pytorch.org/whl/torch_stable.html
注:以上命令适用于CUDA 11.0版本。若您使用的是CPU环境,请将
+cu110替换为+cpu。
- 验证安装是否成功:
import torch
print(torch.__version__)
print(torch.cuda.is_available()) # 如果安装了CUDA版本,应返回True
代码逻辑分析:
-
torch.__version__:输出PyTorch版本,确认是否为1.7.1。 -
torch.cuda.is_available():检测CUDA是否可用,适用于GPU版本安装验证。
参数说明:
-
torch:PyTorch核心模块。 -
torchvision:图像处理库,常用于图像分类任务。 -
torchaudio:音频处理模块。
2.1.3 NNI工具的安装与兼容性确认
NNI可通过pip直接安装,但需注意版本与PyTorch的兼容性。
操作步骤:
- 安装NNI:
pip install nni
- 验证NNI是否安装成功:
nnictl --version
输出示例:
INFO: NNI version: 2.10
代码逻辑分析:
-
pip install nni:安装最新稳定版NNI。 -
nnictl --version:检查NNI命令行工具版本。
参数说明:
-
nnictl是NNI的核心命令行工具,用于启动、监控和管理调参实验。
兼容性说明:
- NNI版本建议使用2.10或以上版本,以确保与PyTorch 1.7.1的兼容性。
- 若需特定版本,可使用
pip install nni==2.10指定安装。
2.2 开发工具链整合
在环境搭建完成后,还需配置开发工具链以提升编码效率与调试能力。
2.2.1 编辑器与调试工具的配置
推荐使用以下开发工具组合:
| 工具类型 | 推荐软件 | 特性说明 |
|---|---|---|
| 编辑器 | VSCode / PyCharm | 支持Python虚拟环境、调试、插件扩展 |
| 调试工具 | Python Debugger (vscode) / PyCharm Debugger | 支持断点、变量查看、控制流调试 |
| Jupyter Notebook | Jupyter Lab | 适合快速实验和调试脚本 |
配置步骤:
- 在VSCode中选择Python解释器为
nni_env环境。 - 安装Python插件,启用自动补全与语法检查。
- 配置Jupyter Notebook使用
nni_env环境:
pip install ipykernel
python -m ipykernel install --user --name=nni_env
代码逻辑分析:
-
ipykernel install:将当前虚拟环境注册为Jupyter的一个内核选项。
2.2.2 Git版本控制与项目结构初始化
为便于版本管理和协作开发,建议使用Git进行代码管理。
操作步骤:
- 初始化Git仓库:
git init
- 创建项目结构模板:
mkdir -p nni_pytorch_project/{data,models,scripts,config}
- 创建
.gitignore文件,排除虚拟环境和缓存文件:
nni_env/
__pycache__
*.pyc
*.pth
*.pt
项目结构说明:
| 文件夹 | 用途 |
|---|---|
data | 存放训练和测试数据集 |
models | 存放模型定义与预训练权重 |
scripts | 包含训练脚本和NNI调参脚本 |
config | NNI实验配置文件(如config.yml) |
2.2.3 常见安装问题及解决方法
问题1:pip安装慢或超时
解决方案:
使用国内镜像源加速安装:
pip install torch==1.7.1 -i https://pypi.tuna.tsinghua.edu.cn/simple
问题2:无法导入torch模块
解决方案:
检查当前Python环境是否与安装PyTorch的环境一致:
which python
python -c "import sys; print(sys.executable)"
问题3:NNI命令找不到
解决方案:
重新安装NNI并确认PATH环境变量包含pip安装路径:
pip install --force-reinstall nni
2.3 首个NNI+PyTorch集成测试
完成环境搭建后,进行一次集成测试以验证NNI与PyTorch是否能正常协作。
2.3.1 测试脚本的编写与执行
操作步骤:
- 创建训练脚本
scripts/train.py:
import torch
import torch.nn as nn
import torch.optim as optim
import nni
# 模拟训练参数
params = {
'lr': 0.01,
'momentum': 0.5
}
# 接收NNI传入的参数
optimized_params = nni.get_next_parameter()
params.update(optimized_params)
# 定义简单模型
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.fc = nn.Linear(10, 1)
def forward(self, x):
return self.fc(x)
model = Net()
optimizer = optim.SGD(model.parameters(), lr=params['lr'], momentum=params['momentum'])
loss_fn = nn.MSELoss()
# 模拟数据
inputs = torch.randn(100, 10)
targets = torch.randn(100, 1)
# 模拟训练过程
for epoch in range(5):
outputs = model(inputs)
loss = loss_fn(outputs, targets)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(f"Epoch {epoch+1}, Loss: {loss.item()}")
nni.report_intermediate_result(loss.item())
# 报告最终结果
nni.report_final_result(loss.item())
代码逻辑分析:
-
nni.get_next_parameter():获取NNI分配的超参数。 -
nni.report_intermediate_result():报告中间训练结果,供NNI评估。 -
nni.report_final_result():报告最终结果,用于收敛判断。
参数说明:
-
lr:学习率。 -
momentum:动量参数。 -
loss.item():每次迭代的损失值。
- 创建NNI实验配置文件
config.yml:
authorName: nni_user
experimentName: pytorch_test
trialConcurrency: 2
maxExecDuration: 1h
maxTrialNum: 5
trainingServicePlatform: local
searchSpacePath: search_space.json
useAnnotation: false
tuner:
builtinTunerName: TPE
assessor:
builtinAssessorName: Medianstop
trial:
command: python train.py
codeDir: .
gpuNum: 0
参数说明:
-
trialConcurrency:并发运行的调参实验数。 -
maxExecDuration:最大实验运行时间。 -
tuner:使用的调参算法,此处为TPE。 -
assessor:评估策略,使用Medianstop进行早停判断。 -
command:启动训练脚本的命令。
- 创建搜索空间文件
search_space.json:
{
"lr": {"_type": "loguniform", "_value": [0.001, 0.1]},
"momentum": {"_type": "uniform", "_value": [0.1, 0.9]}
}
参数说明:
-
loguniform:对数均匀分布,适用于学习率。 -
uniform:线性均匀分布,适用于动量参数。
- 启动NNI实验:
nnictl create --config config.yml
2.3.2 输出日志分析与环境验证指标
启动后,NNI会输出类似以下日志:
INFO: Starting experiment...
INFO: Web UI of NNI running at: http://localhost:8080
INFO: Experiment ID: ExperimentsId12345
INFO: Trial job ID: TrialJobId67890
验证指标:
- 能够成功访问NNI Web UI(默认端口8080)。
- 日志中显示多个trial任务成功运行。
- 最终输出的loss值逐步下降,表明模型训练正常。
流程图:
graph TD
A[启动NNI] --> B{加载配置文件}
B --> C[创建训练任务]
C --> D[调用train.py脚本]
D --> E[获取超参数]
E --> F[训练模型]
F --> G{是否收敛?}
G -->|是| H[报告最终结果]
G -->|否| I[继续迭代]
H --> J[实验结束]
总结:
通过本章节的完整流程,您已成功搭建了一个支持NNI与PyTorch 1.7.1的开发环境,并完成了首次调参实验的部署与测试。下一章将深入讲解NNI支持的调参算法原理与实践应用。
3. 超参数调优算法详解(随机搜索、网格搜索、贝叶斯优化、TPE等)
本章将深入解析NNI支持的主要调参算法,包括随机搜索(Random Search)、网格搜索(Grid Search)、贝叶斯优化(Bayesian Optimization)和TPE(Tree-structured Parzen Estimators)。通过理论分析与实际代码示例,我们将探讨这些算法在PyTorch模型训练中的实现方式、性能差异及其适用场景。
3.1 调参算法概述与适用场景
超参数调优是机器学习模型训练过程中至关重要的一环。不同的调参算法在效率、收敛速度和适应性方面各有千秋,适用于不同类型的模型和任务场景。
3.1.1 随机搜索与网格搜索原理
随机搜索(Random Search) 是一种简单而有效的调参方法。与网格搜索不同,它并不遍历所有可能的参数组合,而是从参数空间中随机采样若干组参数进行评估。这种方法在高维空间中往往表现更好,因为网格搜索在维度增加时会出现“维度灾难”。
网格搜索(Grid Search) 则是通过穷举的方式在预定义的参数空间中搜索最优参数组合。虽然搜索结果较为准确,但其计算成本随参数维度呈指数级增长。
示例代码:使用Scikit-learn实现网格搜索
from sklearn.model_selection import GridSearchCV
from sklearn.svm import SVC
from sklearn.datasets import load_iris
# 加载数据集
iris = load_iris()
X, y = iris.data, iris.target
# 定义模型与参数空间
param_grid = {
'C': [0.1, 1, 10, 100],
'gamma': [1, 0.1, 0.01, 0.001],
'kernel': ['rbf', 'linear']
}
# 初始化GridSearchCV
grid = GridSearchCV(SVC(), param_grid, refit=True, verbose=2)
grid.fit(X, y)
# 输出最佳参数
print("Best parameters found: ", grid.best_params_)
逐行解析与参数说明:
-
param_grid: 定义了待搜索的参数空间,包含正则化参数C、核函数参数gamma和核函数类型kernel。 -
GridSearchCV: 构造函数接收模型、参数空间、是否重训练(refit)和输出详细程度(verbose)。 -
fit(): 执行网格搜索过程。 -
best_params_: 返回最优参数组合。
适用场景:
- 网格搜索 适用于参数空间较小且维度较低的场景。
- 随机搜索 更适合高维参数空间,尤其是参数之间存在冗余或非线性关系的情况。
3.1.2 贝叶斯优化与TPE算法的核心思想
贝叶斯优化(Bayesian Optimization) 是一种基于概率模型的全局优化方法。其核心思想是通过构建一个代理模型(如高斯过程)来近似目标函数,并利用采集函数(Acquisition Function)来决定下一个采样点。
TPE(Tree-structured Parzen Estimators) 是一种基于概率密度估计的贝叶斯优化变体。它将参数空间划分为两个区域:目标函数表现良好和表现较差的区域,并通过Parzen窗估计这两类区域的概率密度函数,进而选择下一个采样点。
示例流程图(Mermaid格式)
graph TD
A[初始化参数空间] --> B[构建代理模型]
B --> C[计算采集函数]
C --> D[选择下一个采样点]
D --> E[评估目标函数]
E --> F[更新代理模型]
F --> G{是否达到最大迭代次数}
G -->|否| C
G -->|是| H[输出最优参数]
逻辑分析:
- 贝叶斯优化通过不断迭代更新代理模型来逼近真实目标函数,从而更高效地找到最优参数。
- TPE通过概率密度估计避免了高斯过程的高计算复杂度,适合大规模参数搜索。
适用场景:
- 贝叶斯优化适合计算代价较高的模型训练任务(如深度学习)。
- TPE在参数维度较高、计算资源有限时表现更佳。
3.1.3 不同算法在PyTorch模型训练中的性能对比
以下表格展示了在PyTorch图像分类任务中,不同调参算法的性能对比:
| 算法类型 | 搜索效率 | 收敛速度 | 参数敏感度 | 是否支持早停 | 适用场景建议 |
|---|---|---|---|---|---|
| 网格搜索 | 低 | 慢 | 高 | 否 | 参数空间小、维度低 |
| 随机搜索 | 中 | 中 | 中 | 否 | 参数空间大、维度高 |
| 贝叶斯优化 | 高 | 快 | 低 | 是 | 模型训练成本高 |
| TPE | 高 | 快 | 低 | 是 | 模型复杂、资源有限 |
性能说明:
- 搜索效率 :指在相同迭代次数下找到最优参数的概率。
- 参数敏感度 :参数对模型性能影响的敏感程度。
- 早停机制 :是否支持在训练中途停止低效实验。
3.2 算法在NNI中的实现机制
NNI框架提供了多种调参算法的集成支持,开发者只需通过配置文件指定算法类型即可自动应用。下面我们将解析NNI中调参算法的核心实现机制。
3.2.1 参数空间定义与采样策略
NNI使用YAML格式定义参数空间,支持连续型、离散型和条件型参数。例如:
search_space:
learning_rate:
_type: loguniform
_value: [0.0001, 0.1]
batch_size:
_type: choice
_value: [32, 64, 128]
optimizer:
_type: choice
_value: ['Adam', 'SGD']
参数说明:
-
_type: 表示参数类型,如loguniform表示对数均匀分布,choice表示枚举值。 -
_value: 表示参数取值范围或候选值列表。
采样策略:
- 随机搜索 :每次随机从参数空间中采样。
- 网格搜索 :遍历所有参数组合。
- 贝叶斯优化/TPE :基于历史实验结果动态选择下一个采样点。
3.2.2 实验评估与反馈机制
NNI通过Reporter机制将训练过程中的评估结果反馈给调优器。例如,在PyTorch脚本中使用如下方式上报中间结果:
import nni
def train():
for epoch in range(10):
loss = train_one_epoch()
nni.report_intermediate_result(loss)
nni.report_final_result(loss)
逻辑分析:
-
report_intermediate_result: 上报每个epoch的loss值。 -
report_final_result: 上报最终结果,用于调优器判断是否提前终止实验。
3.2.3 自适应调优与早停策略
NNI支持基于历史实验数据的自适应调优机制,并结合早停策略(Early Stopping)提升搜索效率。
示例:配置早停策略(在config.yml中)
assessor:
builtinAssessorName: Medianstop
classArgs:
start_step: 5
参数说明:
-
Medianstop: 使用中位数停止策略,若当前实验的性能低于历史实验的中位数,则提前终止。 -
start_step: 从第5个epoch开始评估是否停止。
3.3 实践案例分析
3.3.1 图像分类任务中的调参实践
以CIFAR-10图像分类任务为例,使用NNI进行学习率、批量大小和优化器类型的调优。
示例代码片段(train.py)
import torch
import torch.nn as nn
import torch.optim as optim
import nni
params = nni.get_next_parameter()
model = SimpleCNN()
optimizer = optim.__dict__[params['optimizer']](model.parameters(), lr=params['learning_rate'])
criterion = nn.CrossEntropyLoss()
for epoch in range(10):
train_loss = train_one_epoch(model, train_loader, optimizer, criterion)
nni.report_intermediate_result(train_loss)
nni.report_final_result(train_loss)
参数说明:
-
get_next_parameter: 获取NNI推荐的下一组超参数。 -
SimpleCNN: 自定义的卷积神经网络模型。 -
train_one_epoch: 单轮训练函数。
3.3.2 文本生成模型中的参数敏感度分析
在使用LSTM进行文本生成任务时,学习率和隐藏层大小对模型性能影响较大。
参数敏感度分析表:
| 参数 | 对训练损失影响 | 对生成质量影响 |
|---|---|---|
| 学习率 | 强 | 中 |
| 隐藏层大小 | 中 | 强 |
| 批量大小 | 弱 | 弱 |
结论:
- 学习率和隐藏层大小是文本生成任务中的关键超参数。
- NNI可以通过贝叶斯优化或TPE算法快速识别敏感参数并进行调优。
本章从算法原理、NNI实现机制到具体实践案例,系统地讲解了超参数调优的核心内容。下一章将聚焦NNI实验的核心配置文件 config.yml 的编写方法,帮助读者掌握如何高效配置调参任务。
4. NNI实验配置文件(config.yml)编写
NNI实验配置文件 config.yml 是整个自动调参流程的核心配置文件,它决定了实验的启动方式、参数搜索空间、使用的调参算法、资源分配策略等关键信息。理解并正确编写该配置文件,是成功运行NNI实验的前提。本章将从基础结构、高级配置到调试验证三个方面,全面讲解 config.yml 的编写技巧和注意事项。
4.1 配置文件结构与字段说明
NNI 的 config.yml 文件采用 YAML 格式,结构清晰、易于阅读。其主要组成部分包括实验基本信息、参数搜索空间定义、训练平台设置等。
4.1.1 基础实验信息设置
这部分定义实验的基本信息,包括实验名称、调参算法、最大尝试次数、最大并发任务数等。
authorName: John Doe
experimentName: CNN_Hyperparameter_Tuning
trialConcurrency: 3
maxExecDuration: 1h
maxTrialNum: 20
trainingServicePlatform: local
searchSpacePath: search_space.json
useAnnotation: false
tuner:
builtInTunerName: TPE
classArgs:
optimize_mode: maximize
参数说明:
| 参数名 | 说明 |
|---|---|
authorName | 实验作者 |
experimentName | 实验名称,显示在Web UI中 |
trialConcurrency | 同时运行的试验数量,控制并发度 |
maxExecDuration | 实验最大运行时间,单位为秒或小时 |
maxTrialNum | 最大试验次数 |
trainingServicePlatform | 训练平台类型,支持 local , remote , pai , k8s 等 |
searchSpacePath | 指定参数搜索空间文件路径 |
useAnnotation | 是否使用代码注解方式定义搜索空间,建议使用JSON文件方式 |
tuner.builtInTunerName | 使用的调参算法名称,如 TPE , Random , GridSearch 等 |
tuner.classArgs.optimize_mode | 优化目标是最大化还是最小化,如 maximize 或 minimize |
4.1.2 搜索空间定义与参数类型
搜索空间定义在 search_space.json 文件中,描述了哪些参数需要被调优及其取值范围。例如:
{
"learning_rate": {
"type": "logUniform",
"value": [0.0001, 0.1]
},
"batch_size": {
"type": "choice",
"value": [32, 64, 128]
},
"num_layers": {
"type": "intUniform",
"value": [2, 5]
}
}
参数类型说明:
| 类型 | 说明 |
|---|---|
choice | 从给定列表中选择一个值 |
intUniform | 整数范围内均匀分布 |
uniform | 浮点数范围内均匀分布 |
logUniform | 对数均匀分布,适合学习率等指数级变化参数 |
qUniform | 均匀分布,但按固定步长量化 |
normal | 正态分布 |
qNormal | 量化正态分布 |
4.1.3 训练平台与资源分配策略
训练平台决定了实验运行的位置,例如本地、远程服务器、Kubernetes集群等。资源分配策略影响实验的并行执行效率。
trial:
command: python train.py
codeDir: ./code
gpuNum: 1
cpuNum: 2
memoryMB: 4096
参数说明:
| 参数名 | 说明 |
|---|---|
command | 启动训练脚本的命令 |
codeDir | 训练脚本所在目录 |
gpuNum | 每个试验使用的GPU数量 |
cpuNum | CPU核心数 |
memoryMB | 内存限制(单位为MB) |
4.2 高级配置选项详解
4.2.1 并行实验设置与并发控制
NNI 支持多试验并发执行,合理设置并发数可以提升调参效率。
trialConcurrency: 4
maxExecDuration: 3600s
maxTrialNum: 100
- 并发控制机制图示 (Mermaid流程图):
graph TD
A[NNI主控] --> B[调度器]
B --> C[试验队列]
C --> D{资源可用?}
D -->|是| E[启动新试验]
D -->|否| F[等待资源释放]
分析:
-
trialConcurrency控制最大同时运行的试验数。 - 当资源不足时,新的试验会排队等待。
- 合理设置并发数可避免资源争用,提高实验效率。
4.2.2 调优算法与评估指标配置
NNI 支持多种调优算法,不同算法适用于不同场景。以下是一个使用 MedianStopping 评估器的示例:
assessor:
builtInAssessorName: MedianStopping
classArgs:
optimize_mode: maximize
start_step: 5
参数说明:
| 参数名 | 说明 |
|---|---|
builtInAssessorName | 使用的评估器名称,如 MedianStopping 、 CurveFitting 等 |
optimize_mode | 优化目标(最大化或最小化) |
start_step | 从第几步开始应用评估器 |
调优算法对比表格:
| 算法名称 | 特点 | 适用场景 |
|---|---|---|
| Random Search | 随机选择参数 | 参数空间小,简单快速 |
| Grid Search | 网格遍历 | 参数维度低,需穷举 |
| TPE | 基于概率模型 | 高维参数,收敛快 |
| SMAC | 基于贝叶斯优化 | 复杂模型调优 |
| Evolution | 遗传算法 | 多目标优化 |
4.2.3 日志输出与结果保存路径
日志和结果的保存路径决定了实验数据的可追溯性和后续分析的便利性。
logDir: /path/to/logs
resultDir: /path/to/results
参数说明:
| 参数名 | 说明 |
|---|---|
logDir | 日志文件保存路径 |
resultDir | 实验结果保存路径 |
4.3 配置文件的调试与验证
在正式运行实验前,验证配置文件的正确性至关重要。
4.3.1 格式检查与语法验证工具
NNI 提供了命令行工具来验证配置文件是否合法:
nnictl validate config.yml
输出示例:
INFO: Configuration file is valid.
如果配置文件有误,会输出错误信息,如字段缺失、类型错误等。
常见错误类型:
| 错误类型 | 原因 | 修复建议 |
|---|---|---|
| 缩进错误 | YAML格式错误 | 使用在线YAML校验器 |
| 字段缺失 | 必填字段未填写 | 参照NNI官方文档 |
| 类型不匹配 | 如字符串写成整数 | 检查JSON或YAML格式 |
| 文件路径错误 | 引用的文件不存在 | 使用绝对路径或相对路径检查 |
4.3.2 配置错误排查与常见问题解决方案
常见问题1:训练脚本无法启动
现象 :
ERROR: Failed to start trial.
可能原因 :
- command 命令拼写错误
- codeDir 路径不正确
- 缺少依赖库
解决方法 :
- 手动执行 python train.py 确保脚本可运行
- 检查 codeDir 下是否包含所需数据和依赖
- 在 train.py 中添加 print 输出,确认是否被调用
常见问题2:参数未正确传递
现象 :
TypeError: 'NoneType' object is not subscriptable
可能原因 :
- search_space.json 中参数名与 train.py 接收参数不一致
- 参数未通过 NNI 接口获取
解决方法 :
- 在 train.py 中使用如下方式获取参数:
import nni
params = nni.get_next_parameter()
learning_rate = params['learning_rate']
batch_size = params['batch_size']
4.3.3 示例:完整的 config.yml 文件
authorName: NNI User
experimentName: PyTorch_Tuning_Example
trialConcurrency: 2
maxExecDuration: 1h
maxTrialNum: 10
trainingServicePlatform: local
searchSpacePath: search_space.json
useAnnotation: false
tuner:
builtInTunerName: TPE
classArgs:
optimize_mode: maximize
assessor:
builtInAssessorName: MedianStopping
classArgs:
optimize_mode: maximize
start_step: 5
trial:
command: python train.py
codeDir: ./code
gpuNum: 1
本章系统讲解了 NNI 实验配置文件 config.yml 的结构、字段含义、高级配置选项以及调试验证方法。通过本章内容,读者应能够熟练编写和修改配置文件,为后续实验的顺利运行打下坚实基础。
5. NNI训练脚本(train.py)结构设计
在NNI调参系统中, train.py 脚本是整个调参流程的核心。它负责接收由NNI分配的超参数配置,执行模型训练过程,并将关键性能指标反馈给NNI系统,以指导后续的调参策略。设计一个结构清晰、兼容性强、可扩展的 train.py 脚本对于实现高效的超参数搜索至关重要。
本章将从设计规范、适配机制以及示例分析三个维度,深入探讨如何构建符合NNI调参要求的PyTorch训练脚本。通过本章内容,读者将掌握编写训练脚本的最佳实践,并具备根据实际需求进行扩展和优化的能力。
5.1 NNI训练脚本设计规范
为了与NNI调参系统无缝集成, train.py 脚本需要遵循一套规范化的结构和接口设计。这不仅有助于提升脚本的可维护性,也确保了在不同调参算法和平台之间的兼容性。
5.1.1 参数接收与解析机制
NNI通过环境变量或命令行参数将超参数传递给训练脚本。通常使用 NNI_PLATFORM 环境变量判断当前运行环境是否为NNI调参任务。在脚本中,推荐使用 nni 模块提供的 get_next_parameter() 函数来获取当前试验的超参数配置。
import nni
params = {
'learning_rate': 0.001,
'batch_size': 64,
'hidden_size': 128,
'dropout_rate': 0.5
}
# 如果运行在NNI平台中,会覆盖默认参数
received_params = nni.get_next_parameter()
if received_params:
params.update(received_params)
代码逻辑分析:
-
params定义了默认的超参数集合,用于本地调试。 -
nni.get_next_parameter()从NNI系统中获取当前实验的参数配置。 - 如果获取到参数,则用其覆盖默认值,实现动态调参。
| 参数字段 | 类型 | 描述 |
|---|---|---|
| learning_rate | float | 学习率 |
| batch_size | int | 每个训练批次的样本数量 |
| hidden_size | int | 神经网络隐藏层节点数 |
| dropout_rate | float | Dropout层的丢弃概率 |
5.1.2 模型定义与数据加载方式
在PyTorch中,模型定义和数据加载是训练流程的基础。为了支持NNI调参,建议将模型结构参数化,以便根据传入的参数动态调整网络结构。
import torch.nn as nn
class SimpleNet(nn.Module):
def __init__(self, input_size=784, hidden_size=128, num_classes=10, dropout_rate=0.5):
super(SimpleNet, self).__init__()
self.fc1 = nn.Linear(input_size, hidden_size)
self.relu = nn.ReLU()
self.dropout = nn.Dropout(dropout_rate)
self.fc2 = nn.Linear(hidden_size, num_classes)
def forward(self, x):
out = self.fc1(x)
out = self.relu(out)
out = self.dropout(out)
out = self.fc2(out)
return out
代码逻辑分析:
- 模型构造函数接受
hidden_size和dropout_rate作为可变参数,便于NNI调整。 -
forward函数定义了数据流动的顺序。 - 这种结构设计支持在不同实验中动态调整模型复杂度。
5.1.3 损失函数与优化器配置
损失函数和优化器的选择通常也会影响模型性能,因此也应支持参数化配置。
import torch.optim as optim
import torch.nn as nn
# 损失函数
criterion = nn.CrossEntropyLoss()
# 优化器选择(支持Adam或SGD)
optimizer_name = params.get('optimizer', 'Adam')
if optimizer_name == 'Adam':
optimizer = optim.Adam(model.parameters(), lr=params['learning_rate'])
elif optimizer_name == 'SGD':
optimizer = optim.SGD(model.parameters(), lr=params['learning_rate'], momentum=0.9)
else:
raise ValueError(f"Unsupported optimizer: {optimizer_name}")
代码逻辑分析:
- 使用
params.get()获取优化器类型,支持灵活配置。 - 根据不同优化器创建对应的实例。
- 支持未来扩展更多优化器类型。
| 配置项 | 可选值 | 描述 |
|---|---|---|
| optimizer | ‘Adam’, ‘SGD’ | 优化器类型 |
| learning_rate | float | 学习率 |
| momentum | float(仅SGD) | 动量值 |
5.2 模型训练流程的适配设计
训练流程需要适配NNI调参系统的核心功能,包括结果反馈、中间输出、多GPU支持等。以下内容将展示如何将这些功能集成到训练脚本中。
5.2.1 NNI Reporter接口集成
NNI通过Reporter接口接收训练过程中的评估结果。通常在每个训练周期结束后调用 nni.report_intermediate_result() 函数,将当前指标反馈给NNI。
for epoch in range(num_epochs):
model.train()
for images, labels in train_loader:
outputs = model(images)
loss = criterion(outputs, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 每个epoch结束后验证模型
model.eval()
with torch.no_grad():
correct = 0
total = 0
for images, labels in test_loader:
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
accuracy = correct / total
# 报告中间结果给NNI
nni.report_intermediate_result(accuracy)
代码逻辑分析:
- 每个训练周期结束后评估模型准确率。
- 调用
nni.report_intermediate_result()将评估结果反馈给NNI系统。 - NNI根据中间结果决定是否继续训练或提前终止。
流程图说明:
mermaid graph TD A[开始训练] --> B[读取超参数] B --> C[初始化模型] C --> D[训练一个Epoch] D --> E[验证模型] E --> F[报告中间结果] F --> G{是否达到收敛?} G -->|是| H[结束训练] G -->|否| D
5.2.2 中间结果输出与模型保存策略
为了便于调试和后续分析,建议在训练过程中将关键指标输出到日志文件,并在训练结束时保存模型。
import os
import torch
# 日志文件
log_file = open('training_log.txt', 'a')
# 模型保存路径
model_dir = 'models'
os.makedirs(model_dir, exist_ok=True)
# 在训练循环中记录日志
log_file.write(f"Epoch {epoch+1}: Accuracy = {accuracy:.4f}\n")
# 保存最终模型
torch.save(model.state_dict(), os.path.join(model_dir, 'best_model.pth'))
代码逻辑分析:
- 打开日志文件追加写入模式,记录每个epoch的准确率。
- 创建模型保存目录,防止路径错误。
- 使用
torch.save()保存模型权重,便于后续复用或部署。
5.2.3 多GPU训练与分布式训练支持
为了加速训练过程,可以利用多GPU或分布式训练技术。NNI支持在配置文件中指定资源类型(如GPU数量),脚本应根据配置选择合适的训练模式。
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
if torch.cuda.device_count() > 1 and params.get('use_multi_gpu', False):
model = nn.DataParallel(model)
model.to(device)
代码逻辑分析:
- 判断是否启用多GPU训练。
- 使用
DataParallel实现多GPU并行。 - 将模型移动到指定设备(CPU或GPU)。
| 参数字段 | 类型 | 描述 |
|---|---|---|
| use_multi_gpu | boolean | 是否启用多GPU训练 |
5.3 示例脚本分析与优化建议
5.3.1 完整图像分类训练脚本解析
以下是一个完整的图像分类任务训练脚本示例,整合了前面提到的所有关键点:
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
import nni
def main():
# 默认参数
params = {
'learning_rate': 0.001,
'batch_size': 64,
'hidden_size': 128,
'dropout_rate': 0.5,
'use_multi_gpu': False,
'optimizer': 'Adam'
}
# 接收NNI参数
received_params = nni.get_next_parameter()
if received_params:
params.update(received_params)
# 数据预处理
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))
])
train_dataset = datasets.MNIST(root='./data', train=True, transform=transform, download=True)
test_dataset = datasets.MNIST(root='./data', train=False, transform=transform)
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=params['batch_size'], shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=params['batch_size'], shuffle=False)
# 模型定义
model = SimpleNet(hidden_size=params['hidden_size'], dropout_rate=params['dropout_rate'])
# 多GPU支持
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
if torch.cuda.device_count() > 1 and params.get('use_multi_gpu', False):
model = nn.DataParallel(model)
model.to(device)
# 损失函数与优化器
criterion = nn.CrossEntropyLoss()
if params['optimizer'] == 'Adam':
optimizer = optim.Adam(model.parameters(), lr=params['learning_rate'])
elif params['optimizer'] == 'SGD':
optimizer = optim.SGD(model.parameters(), lr=params['learning_rate'], momentum=0.9)
# 训练循环
num_epochs = 10
log_file = open('training_log.txt', 'a')
for epoch in range(num_epochs):
model.train()
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images.view(images.size(0), -1))
loss = criterion(outputs, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 验证
model.eval()
with torch.no_grad():
correct = 0
total = 0
for images, labels in test_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images.view(images.size(0), -1))
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
accuracy = correct / total
log_file.write(f"Epoch {epoch+1}: Accuracy = {accuracy:.4f}\n")
nni.report_intermediate_result(accuracy)
# 保存模型
torch.save(model.state_dict(), 'best_model.pth')
class SimpleNet(nn.Module):
def __init__(self, input_size=784, hidden_size=128, num_classes=10, dropout_rate=0.5):
super(SimpleNet, self).__init__()
self.fc1 = nn.Linear(input_size, hidden_size)
self.relu = nn.ReLU()
self.dropout = nn.Dropout(dropout_rate)
self.fc2 = nn.Linear(hidden_size, num_classes)
def forward(self, x):
out = self.fc1(x)
out = self.relu(out)
out = self.dropout(out)
out = self.fc2(out)
return out
if __name__ == '__main__':
main()
5.3.2 性能瓶颈识别与调优建议
在训练脚本中,常见的性能瓶颈包括:
| 性能瓶颈 | 原因 | 建议解决方案 |
|---|---|---|
| GPU利用率低 | 数据加载速度慢 | 使用 num_workers 提升数据加载速度 |
| 模型训练慢 | 参数更新频繁 | 使用混合精度训练(AMP) |
| 内存占用高 | 模型过大或批量大 | 降低 batch_size 或使用梯度累积 |
| I/O阻塞 | 日志写入频繁 | 使用缓冲写入或异步写入方式 |
调优建议:
-
启用PyTorch的混合精度训练:
python from torch.cuda.amp import autocast, GradScaler scaler = GradScaler() with autocast(): outputs = model(inputs) loss = criterion(outputs, labels) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() -
使用
DataLoader的num_workers参数加速数据读取:
python train_loader = torch.utils.data.DataLoader( dataset=train_dataset, batch_size=params['batch_size'], shuffle=True, num_workers=4 ) -
启用梯度累积以减少GPU内存占用:
python accumulation_steps = 4 loss = criterion(outputs, labels) / accumulation_steps loss.backward() if (i + 1) % accumulation_steps == 0: optimizer.step() optimizer.zero_grad()
本章通过详细讲解 train.py 脚本的设计规范、训练流程适配与优化建议,帮助读者构建出高效、灵活且易于维护的NNI调参训练脚本。下一章将继续深入讲解如何使用 nnictl 命令行工具启动与管理调参任务。
6. 调参任务启动与管理命令(nnictl create)
NNI(Neural Network Intelligence)不仅提供了丰富的调参算法支持和灵活的配置机制,还通过命令行工具 nnictl 提供了强大的实验管理能力。本章将围绕 nnictl create 命令展开,详细介绍如何使用该命令启动调参任务,以及如何对实验进行监控、管理和终止。
本章内容将从命令行工具的基础结构讲起,逐步深入到实验的启动流程、状态查询、可视化监控、暂停恢复机制,以及多实验并行管理策略,帮助读者全面掌握 NNI 实验的生命周期管理。
6.1 NNI命令行工具基础
NNI 提供了命令行工具 nnictl 来管理调参实验的整个生命周期。 nnictl 是一个功能强大的命令行接口,支持创建、启动、停止、查看实验状态等功能。
6.1.1 nnictl命令结构与常用参数
nnictl 的基本命令结构如下:
nnictl [command] [experiment_id] [options]
常用命令包括:
| 命令 | 功能说明 |
|---|---|
create | 创建并启动新的调参实验 |
stop | 停止指定实验 |
resume | 恢复已暂停的实验 |
list | 查看当前所有实验 |
show | 显示实验的详细信息 |
log | 查看实验日志 |
例如,启动一个新实验的命令如下:
nnictl create --config config.yml
该命令将根据 config.yml 文件中的配置启动一个调参实验。
常用参数说明:
-
--config:指定实验配置文件路径,通常是config.yml。 -
--port:指定实验 Web UI 的端口号(默认为 8080)。 -
--debug:启用调试模式,输出更详细的日志。 -
--experiment_name:自定义实验名称。
代码块:使用 nnictl create 启动实验
nnictl create --config config.yml --port 8888 --experiment_name my_nni_exp
逻辑分析:
-
--config config.yml:加载实验配置文件,定义调参参数、算法、训练脚本路径等。 -
--port 8888:设置 Web UI 访问端口为 8888,避免与默认端口冲突。 -
--experiment_name my_nni_exp:为实验指定一个易于识别的名称。
该命令执行后,NNI 会自动启动训练脚本,并通过 nnictl 提供的命令和 Web UI 对实验进行监控。
6.1.2 实验启动流程与配置加载机制
当执行 nnictl create 时,NNI 会按照以下流程启动实验:
graph TD
A[用户执行 nnictl create] --> B[加载 config.yml 配置文件]
B --> C[解析训练脚本路径]
C --> D[启动训练进程]
D --> E[初始化调优器与评估器]
E --> F[开始搜索参数空间]
F --> G[生成参数组合并启动训练任务]
配置文件加载机制详解:
NNI 通过 config.yml 文件控制实验的各个方面,包括:
- 训练脚本路径 (
trainingService中的command) - 调参算法类型 (
tuner) - 参数搜索空间 (
searchSpace) - 并行任务数量 (
trialConcurrency) - 日志与结果保存路径
例如:
experimentName: my_nni_exp
trialConcurrency: 2
maxExecDuration: 1h
trainingService:
platform: local
command: python train.py
tuner:
name: TPE
classArgs:
optimize_mode: maximize
searchSpace:
learning_rate:
_type: loguniform
_value: [0.0001, 0.1]
batch_size:
_type: choice
_value: [32, 64, 128]
参数说明:
-
trialConcurrency: 同时运行的训练任务数。 -
maxExecDuration: 实验最大执行时间。 -
trainingService.command: 指定训练脚本的启动命令。 -
tuner.name: 指定使用的调优算法,如TPE、Random、GridSearch等。 -
searchSpace: 定义超参数搜索空间,支持多种采样方式。
6.2 实验的监控与交互
实验启动后,NNI 提供了多种方式对其实时监控和交互。
6.2.1 实验状态查询与日志查看
可以使用以下命令查看实验状态:
nnictl list
输出示例:
ID NAME STATUS PORT
0 my_nni_exp RUNNING 8888
查看某个实验的详细信息:
nnictl show --id 0
查看实验日志:
nnictl log stdout --id 0
或查看训练脚本的输出日志:
nnictl log trial --id 0
6.2.2 中间结果可视化与调优进度跟踪
NNI 提供了 Web UI 来可视化调参过程,访问方式如下:
http://localhost:8888
在 Web 界面中,你可以查看以下内容:
- 参数空间搜索轨迹
- 每个试验的评估指标(如准确率、损失值)
- 当前最优参数组合
- 实验的运行时间与资源使用情况
此外,NNI 还支持将实验结果导出为 CSV 或 JSON 文件,便于后续分析。
示例:导出实验结果
nnictl experiment export --id 0 --type json --path results.json
6.3 实验管理与终止控制
NNI 支持灵活的实验管理功能,包括暂停、恢复、终止等操作,便于在训练过程中进行动态控制。
6.3.1 实验暂停与恢复机制
暂停实验:
nnictl stop --id 0
恢复实验:
nnictl resume --id 0
注意:暂停实验不会清除已有的试验结果,恢复后将继续搜索参数空间。
6.3.2 异常中断处理与资源回收
如果实验因系统崩溃或手动中断而终止,可以使用以下命令清理资源:
nnictl clean --id 0
此命令会终止所有与该实验相关的训练进程,并释放占用的端口和内存资源。
6.3.3 多实验并行管理策略
NNI 支持同时运行多个实验,使用不同的端口即可:
nnictl create --config config1.yml --port 8888
nnictl create --config config2.yml --port 8889
查看所有实验状态:
nnictl list
你还可以通过 --id 参数对特定实验进行操作,例如:
nnictl stop --id 0
小结
本章深入讲解了如何使用 nnictl create 启动调参任务,并通过命令行工具对实验进行全生命周期管理。从实验启动、状态监控、日志查看、可视化分析,到暂停恢复、资源回收和多实验并行管理, nnictl 提供了完整且高效的调参实验管理方案。
通过本章的学习,读者应能熟练使用 nnictl 工具,高效地进行模型调参实验,提升模型性能优化的效率与自动化水平。
7. NNI核心组件源码解析(调优器、控制器、通信模块)
本章从源码层面深入分析NNI的核心模块,帮助开发者理解其内部工作机制。
7.1 调优器(Tuner)模块源码分析
调优器是NNI中负责生成超参数组合的核心组件。其主要职责是根据当前实验策略(如随机搜索、贝叶斯优化等)生成一组新的超参数配置,供训练脚本使用。
7.1.1 参数生成算法的实现逻辑
NNI的调优器实现位于 nni/tuner 目录下,其中每个调参算法都有一个对应的子类,例如 RandomTuner 和 TPE_Tuner 。
以 RandomTuner 为例,其核心逻辑如下:
from nni.tuner import Tuner
import random
class RandomTuner(Tuner):
def __init__(self, search_space):
self.search_space = search_space
self.params_list = []
def update_search_space(self, search_space):
self.search_space = search_space
def generate_parameters(self, parameter_id):
# 随机采样参数
params = {}
for key, value in self.search_space.items():
if value['_type'] == 'choice':
params[key] = random.choice(value['_value'])
elif value['_type'] == 'uniform':
low, high = value['_value']
params[key] = random.uniform(low, high)
self.params_list.append(params)
return params
参数说明:
-
search_space: 定义了参数搜索空间,结构为字典,如:
python { "learning_rate": {"_type": "uniform", "_value": [0.001, 0.1]}, "num_layers": {"_type": "choice", "_value": [2, 3, 4]} } -
generate_parameters: 每次被调用时,根据参数空间随机生成一组参数。
7.1.2 支持的调优算法插件结构
NNI的调优器设计为插件式结构,允许开发者通过继承 Tuner 类并实现相应方法,来添加新的调参算法。
主要接口如下:
| 方法名 | 功能描述 |
|---|---|
update_search_space() | 更新搜索空间 |
generate_parameters() | 生成一组超参数 |
receive_trial_result() | 接收训练脚本返回的评估结果 |
7.1.3 与PyTorch训练脚本的交互机制
调优器与训练脚本之间通过标准输入输出进行通信。训练脚本在运行时,会读取NNI提供的环境变量,获取当前参数配置:
import os
import json
import nni
params = nni.get_next_parameter() # 获取当前调参器生成的参数
print("Current parameters:", params)
NNI框架内部会将参数通过 nni.get_next_parameter() 接口传入训练脚本,并等待训练脚本通过 nni.report_final_result() 返回评估结果。
7.2 控制器(Assessor)与评估模块
控制器负责评估当前训练任务的表现,并决定是否继续训练、提前终止或调整资源分配。
7.2.1 模型评估指标的定义与处理
控制器通过接收训练脚本提交的中间结果来评估模型性能。例如,以下是一个训练脚本中提交中间结果的代码:
for epoch in range(10):
loss = train_one_epoch(model, data_loader)
nni.report_intermediate_result(loss)
控制器会接收这些中间结果并进行处理。
7.2.2 早停策略与资源调度机制
早停策略通常由 assessor 实现,如 MedianStopAssessor 。其核心逻辑如下:
from nni.assessor import Assessor
class MedianStopAssessor(Assessor):
def __init__(self, start_step=5):
self.start_step = start_step
self.trial_history = {}
def assess_trial(self, trial_job_id, trial_history):
current_step = len(trial_history)
if current_step < self.start_step:
return True # 继续训练
# 计算当前历史平均值
mean_value = sum(trial_history) / len(trial_history)
current_value = trial_history[-1]
# 如果当前值高于平均值,则继续训练
return current_value > mean_value * 0.9
7.2.3 实验结果反馈与收敛判断逻辑
NNI通过监听 nni.report_final_result() 的调用,将最终结果反馈给调优器,调优器根据结果更新其内部模型(如贝叶斯优化中的高斯过程模型)。
示例调用:
nni.report_final_result(final_score)
该函数调用后,调优器会记录该参数集对应的最终性能,并用于下一轮参数选择。
7.3 通信模块与数据流分析
NNI框架内部通过通信模块协调调优器、控制器与训练脚本之间的数据交互。
7.3.1 训练脚本与NNI框架的通信协议
NNI使用基于标准输入输出(stdin/stdout)的轻量级通信协议。训练脚本通过以下方式与NNI交互:
# 启动命令示例
nnictl create --config config.yml
NNI启动后,会在本地创建一个HTTP服务(默认端口8080),并通过环境变量将参数传递给训练脚本。
7.3.2 数据传输格式与序列化机制
NNI使用JSON作为参数与结果的序列化格式。例如,参数传递格式如下:
{
"learning_rate": 0.01,
"batch_size": 64,
"num_layers": 3
}
训练脚本提交的结果也以JSON格式返回:
{
"default": 0.85
}
7.3.3 异常通信处理与容错机制
NNI内置了通信异常处理机制,主要包括:
- 超时重试 :若训练脚本未在规定时间内返回结果,NNI会尝试重新启动该实验。
- 断点续训 :支持训练中断后从上次状态恢复。
- 日志记录 :所有通信过程均被记录在
trial.log中,便于排查问题。
流程图如下:
graph TD
A[调优器生成参数] --> B[传递参数给训练脚本]
B --> C[训练脚本开始执行]
C --> D{是否完成训练?}
D -- 是 --> E[提交最终结果]
D -- 否 --> F[提交中间结果]
F --> G[控制器评估是否早停]
G -- 继续训练 --> C
G -- 停止训练 --> E
E --> H[调优器更新模型]
H --> A
通过上述机制,NNI实现了调参过程的闭环控制,支持动态调整参数空间与训练策略。
简介:微软开源的NNI(Neural Network Intelligence)是一个强大的自动机器学习(AutoML)工具,专注于高效模型超参数调优。本教程基于NNI源码和PyTorch 1.7.1框架,系统讲解如何使用NNI进行自动化调参,涵盖实验配置、调优算法选择、训练代码编写及源码解析。通过本教程,用户将掌握NNI核心流程,提升深度学习模型性能,并具备二次开发与平台集成的能力。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)