NNI 与 Scikit-learn 集成实战:用 nnictl 自动搜索模型与超参数
【免费下载链接】nniAn open source AutoML toolkit for automate machine learning lifecycle, including feature engineering, neural architecture search, model compression and hyper-parameter tuning.项目地址: https://gitcode.com/gh_mirrors/nn/nni
Scikit-learn 是数据挖掘与数据分析领域最流行的机器学习工具库之一,覆盖 LinearRegression、LogisticRegression、DecisionTree、SVM 等大量经典模型。本指南以 NNI(Neural Network Intelligence)仓库中 examples/trials/sklearn 目录下的完整示例为主线,讲解如何用 NNI 的调优算法自动搜索 scikit-learn 的最佳模型与超参数,并支持本地机器、远程服务器与云端等多种运行环境。读完本文,你将掌握从search_space.json定义搜索空间、到nni.get_next_parameter()获取参数、再到nni.report_final_result()回报指标的三步集成方法,并能够直接复制运行仓库中的分类与回归示例。
示例概览:仓库结构与你将运行的内容
本主题对应的完整示例位于仓库的 examples/trials/sklearn 目录,共包含两个任务:
examples/trials/sklearn/ ├── classification/ # 手写数字分类(SVC 支持向量机) │ ├── config.yml # 实验配置 │ ├── main.py # trial 训练脚本 │ ├── search_space.json # 搜索空间定义 │ ├── python_api_connect.ipynb │ └── python_api_start.ipynb ├── regression/ # 回归任务(多种线性回归模型) │ ├── config.yml │ ├── main.py │ └── search_space.json └── requirements.txt # 系统级依赖安装说明两个任务的代码骨架完全一致:加载数据 → 从 NNI 获取一组参数 → 构建模型 → 训练并评测 → 把得分回报给 NNI。后续第 3 节会以代码为准逐行剖析这一流程。
一、如何运行示例
1.1 环境准备
运行前需要先安装 NNI 包,并准备好 Python 环境(numpy、scipy、sklearn等科学计算依赖)。仓库的 examples/trials/sklearn/requirements.txt 给出了 Ubuntu 系统下的依赖安装参考:
sudo apt-get install libblas-dev liblapack-dev libatlas-base-dev gfortran python3 -m pip install --user numpy scipy sklearnNNI 的完整安装与运行环境准备方法可参考 docs/source/installation.rst。启动实验统一使用命令行工具nnictl。
1.2 启动实验
安装完成后,进入对应的示例目录并执行:
nnictl create --config ./config.ymlNNI 会根据config.yml中声明的trialCommand启动若干个 trial 进程,每个 trial 从搜索空间中采样一组超参数独立训练、评测,并把得分回报给调优器以生成下一组参数。下面以分类示例的 examples/trials/sklearn/classification/config.yml 为例,说明各字段含义:
searchSpaceFile: search_space.json # 搜索空间文件(JSON) trialCommand: python3 main.py # 每个 trial 要执行的命令 trialConcurrency: 1 # 同时并发运行的 trial 数量 maxTrialNumber: 100 # 最多运行的 trial 总数 maxExperimentDuration: 1h # 实验最长运行时间 tuner: name: TPE # 调优算法:Tree-structured Parzen Estimator classArgs: optimize_mode: maximize # 优化方向:最大化指标(如准确率/R²) trainingService: # For other platforms, check mnist-pytorch example platform: local # 本地运行平台关键字段说明:
trialCommand:每个 trial 实际执行的命令,此处为python3 main.py;tuner.name:指定调优算法,示例使用TPE。NNI 还内置了 Random、Anneal、GridSearch、Hyperband 等大量算法(可参考 nni/algorithms/hpo 目录下的实现与 docs/source/hpo/tuners.rst);optimize_mode:必须与指标含义一致——本例中分类用准确率、回归用 R²,均为“越大越好”,故设为maximize;若指标是损失(loss)则应设为minimize;- 回归示例 examples/trials/sklearn/regression/config.yml 的配置结构完全相同,区别仅在于
maxTrialNumber: 30。
实验运行期间可用nnictl提供的 Web 界面实时查看 trial 状态、中间指标与最优参数,也可以使用nnictl stop停止实验。
二、示例详解:分类与回归
2.1 分类示例(手写数字识别 + SVC)
分类示例使用 sklearn 内置的 digits 数据集:由 1797 张 8×8 的手写数字灰度图组成,每张图对应一个 0~9 的数字,目标是将图像正确分类到 10 个类别。
模型选用支持向量机SVC,并对影响其性能的核心超参数进行搜索,包括C(正则化强度)、kernel(核函数类型)、degree(多项式核的阶数)、gamma(核系数)和coef0(核函数中的常数项)。这些参数的详细说明可参考 sklearn 官方文档sklearn.svm.SVC一节。
数据加载与预处理逻辑见 examples/trials/sklearn/classification/main.py:
def load_data(): digits = load_digits() X_train, X_test, y_train, y_test = train_test_split( digits.data, digits.target, random_state=99, test_size=0.25) ss = StandardScaler() X_train = ss.fit_transform(X_train) X_test = ss.transform(X_test) return X_train, X_test, y_train, y_test注意代码在划分训练/测试集后对特征做了StandardScaler标准化——SVM 这类基于距离/核的模型对特征尺度敏感,标准化能显著提升搜索到的超参数质量。训练与评测使用model.score(X_test, y_test)(即准确率)作为最终指标。
2.2 回归示例(多种回归模型自动选择)
回归示例的目标是根据房屋特征预测房价。仓库当前实现使用 sklearn 内置的 diabetes 数据集(load_diabetes),训练目标与分类示例一致。
与分类示例“固定模型、只调参数”不同,回归示例把模型本身也放进搜索空间:在LinearRegression、Ridge、Lars、ARDRegression四种模型之间自动选择,同时对normalize参数(是否对数据进行归一化)进行搜索。模型映射逻辑见 examples/trials/sklearn/regression/main.py:
def get_model(PARAMS): model_dict = { 'LinearRegression': LinearRegression(), 'Ridge': Ridge(), 'Lars': Lars(), 'ARDRegression': ARDRegression() } if not model_dict.get(PARAMS['model_name']): LOG.exception('Not supported model!') exit(1) model = model_dict[PARAMS['model_name']] model.normalize = bool(PARAMS['normalize']) return model评测指标使用回归问题常用的决定系数 R²(r2_score):
def run(X_train, X_test, y_train, y_test, model): model.fit(X_train, y_train) predict_y = model.predict(X_test) score = r2_score(y_test, predict_y) LOG.debug('r2 score: %s', score) nni.report_final_result(score)通过把model_name放进_type: choice,NNI 的调优器会结合历史 trial 的 R² 表现,在四种模型之间做出有依据的取舍——这正是“模型选择 + 超参数调优”一体化的典型用法。
三、三步编写 NNI + scikit-learn 代码
在 scikit-learn 代码中使用 NNI 非常简单,只需三步。
第一步:准备 search_space.json 定义搜索空间
搜索空间文件用 JSON 描述每个待搜索变量的取值范围。如果只想在多个模型间选择,可以这样写:
{ "model_name":{"_type":"choice","_value":["LinearRegression", "SVR", "KNeighborsRegressor", "DecisionTreeRegressor"]} }如果想同时搜索模型和参数,把变量放进同一个文件即可:
{ "model_name":{"_type":"choice","_value":["LinearRegression", "SVR", "KNeighborsRegressor", "DecisionTreeRegressor"]}, "svr_kernel": {"_type":"choice","_value":["linear", "poly", "rbf"]}, "knr_weights": {"_type":"choice","_value":["uniform", "distance"]} }搜索空间支持丰富的采样类型(详见 docs/source/hpo/search_space.rst)。分类示例 examples/trials/sklearn/classification/search_space.json 就综合使用了uniform与choice:
{ "C": {"_type":"uniform","_value":[0.1, 1]}, "kernel": {"_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]} }常见类型的语义与适用场景:
| 类型 | 写法 | 语义 |
|---|---|---|
choice | {"_type":"choice","_value":[opt1, opt2, ...]} | 从选项列表中离散选取 |
uniform | {"_type":"uniform","_value":[low, high]} | 在[low, high]区间内均匀采样 |
quniform | {"_type":"quniform","_value":[low, high, q]} | 离散化的均匀采样,值被约束到q的倍数,例如[0, 10, 2.5]只会产生[0, 2.5, 5.0, 7.5, 10.0] |
loguniform | {"_type":"loguniform","_value":[low, high]} | 对数均匀采样,适合数量级跨度大的连续量(如学习率) |
qloguniform | {"_type":"qloguniform","_value":[low, high, q]} | loguniform的离散化版本 |
选择类型时有一条实用原则:如果参数是"越平滑越好"的连续量(如C、gamma)用uniform;如果是有明确候选集合的离散项(如核函数名、模型名)用choice。
第二步:用 nni.get_next_parameter() 获取参数
在 Python 代码开头import nni,然后调用nni.get_next_parameter()获取 NNI 调优器为当前 trial 生成的一组参数:
import nni对应第一步中那个搜索空间,某次 trial 可能收到这样一组参数:
params = { 'C': 1.0, 'kernel': 'linear', 'degree': 3, 'gamma': 0.01, 'coef0': 0.01 }拿到 dict 后即可用它构造 scikit-learn 模型。参考仓库代码,推荐配合"默认参数 + 增量更新"的模式,保证即使调优器给出的参数不完整也能正常运行:
def get_default_parameters(): params = { 'C': 1.0, 'kernel': 'linear', 'degree': 3, 'gamma': 0.01, 'coef0': 0.01 } return params def get_model(PARAMS): model = SVC() model.C = PARAMS.get('C') model.kernel = PARAMS.get('kernel') model.degree = PARAMS.get('degree') model.gamma = PARAMS.get('gamma') model.coef0 = PARAMS.get('coef0') return model if __name__ == '__main__': ... RECEIVED_PARAMS = nni.get_next_parameter() PARAMS = get_default_parameters() PARAMS.update(RECEIVED_PARAMS) model = get_model(PARAMS)从源码看,get_next_parameter定义于 nni/trial.py,它通过默认的 trial 命令通道接收调优器下发的参数记录,并返回其中的parameters字段。需要注意:每个 trial 应当且只应当调用一次该函数,重复调用属于未定义行为。
第三步:用 nni.report_final_result() 回报分数
训练完成后,把模型的评测得分回报给 NNI。NNI 的调优器正是依据这些得分来生成下一组参数的,因此这一步是闭环的关键:
nni.report_final_result(score)如果训练分多个阶段、希望展示中间结果(例如每个 epoch 的指标),可以使用nni.report_intermediate_result(score)回报中间指标,这样 Web 界面可以绘制学习曲线,early-stopping 类算法(如 Assessor)也能据此提前终止劣质 trial。中间指标是可选的,但最终结果report_final_result必须回报,否则该 trial 无法被正确评估。
关于指标格式的约定,源码 nni/trial.py 中的文档说明如下:
metric可以是浮点数,也可以是 dict;若是 dict,metric['default']必须是浮点数,调优器以该值为准,其余键值可用于 Web 界面可视化;report_final_result发送类型为FINAL的指标记录,report_intermediate_result发送类型为PERIODICAL的记录并附带自增的序号。
四、底层原理:trial 与调优器如何通信
整条链路可以概括为:调优器(Tuner)生成参数 → 命令通道下发 → trial 进程接收参数 → 训练评测 → 指标回报 → 调优器更新策略 → 生成下一组参数。
在 nni/trial.py 中,参数获取与指标回报统一通过get_default_trial_command_channel()完成,通道实现在 nni/runtime/trial_command_channel 目录下。report_final_result内部会断言“必须先调用过get_next_parameter()才能回报结果”,以保证参数与结果能够正确配对:
assert _params or trial_env_vars.NNI_PLATFORM is None, \ 'nni.get_next_parameter() needs to be called before report_intermediate_result'而调优器一侧(如示例使用的 TPE)会根据收到的历史 trial 得分,构建参数与指标之间的概率模型,进而决定下一次采样偏向哪些区域——这正是“自动搜索”相较于手工网格搜索(grid search)效率更高的原因。NNI 内置的各类调优算法实现位于 nni/algorithms/hpo(含 tuner 与 advisor 两个子包)。
此外,示例代码将import nni、nni.get_next_parameter()、nni.report_final_result()包裹在try/except中并打印异常日志(见main.py末尾),这是一种值得沿用的防御性写法:trial 失败时把完整异常栈留在日志里,便于在 Web 界面或nnictl的日志中排查问题。
五、扩展:更多模型与更大规模
本文的两组示例展示了两类最典型的模式:
- 固定模型、搜索超参数(分类示例):适合你已经选定模型、只需微调参数的情况;
- 同时搜索模型与参数(回归示例):适合建模初期在多个候选模型间做选择的情况。
将search_space.json中的变量与main.py中的model_dict扩展即可覆盖更多 sklearn 模型(如KNeighborsRegressor、DecisionTreeRegressor、集成模型等),代码骨架无需改动。同时,NNI 的搜索空间语法还支持loguniform、qloguniform、嵌套/条件搜索空间等高级特性,配合maxTrialNumber、trialConcurrency、maxExperimentDuration等实验约束,即可在本地或分布式环境下对 sklearn 任务进行系统化、可复现的超参数调优。
完成上述三步改造后,你的 scikit-learn 代码就具备了"自动选模型、自动调参数"的能力,剩下的探索交给 NNI 的调优算法去完成。
【免费下载链接】nniAn open source AutoML toolkit for automate machine learning lifecycle, including feature engineering, neural architecture search, model compression and hyper-parameter tuning.项目地址: https://gitcode.com/gh_mirrors/nn/nni
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考