ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

MXNet Gluon 迁移学习实战:从实验训练到模型部署的完整流程

MXNet Gluon 迁移学习实战:从实验训练到模型部署的完整流程 MXNet Gluon 迁移学习实战从实验训练到模型部署的完整流程【免费下载链接】mxnetLightweight, Portable, Flexible Distributed/Mobile Deep Learning with Dynamic, Mutation-aware Dataflow Dep Scheduler; for Python, R, Julia, Scala, Go, Javascript and more项目地址: https://gitcode.com/gh_mirrors/mxnet1/mxnet导读本文以「花种识别服务」这一典型业务场景为线索完整讲解如何在 MXNet 中使用 Gluon API 走通「数据准备 → 迁移学习微调 → 模型序列化 → 部署推理」的全链路。你将掌握基于 ImageNet 预训练 ResNet50 V2 进行小数据集微调的方法、Gluon 数据增强与hybridize()的实战用法以及如何把微调后的模型导出为.json.params文件并用 Module API 完成在线推理为后续接入 C、Java、Scala 或 MXNet Model Server 部署打下基础。本文基于 docs/python_docs/python/tutorials/getting-started/gluon_from_experiment_to_deployment.md 展开并补充了对应源码实现细节。场景与思路为什么用迁移学习假设你要构建一个提供「花种识别」能力的服务。一个常见困境是业务方往往没有足够的数据去从零训练一个高质量模型。此时可以借助迁移学习Transfer Learning利用一个在大规模标准数据集如约 130 万张图像的 ImageNet上预训练好的、解决相近任务的模型把其中学到的通用视觉特征迁移到新任务上从而用少量数据训练出更稳健的模型。Gluon 模型动物园Model Zoo为分类、目标检测、语义分割等标准任务提供了众多预训练模型。本教程选用在 ImageNet 上预训练的ResNet50 V2出自论文Identity Mappings in Deep Residual Networks其 ImageNet top-1 精度为 77.11%。我们的目标是从中迁移尽可能多的知识用于识别 Oxford 102 花卉数据集的 102 个花种。说明本教程的训练与推理部分使用 Python完成本教程后可继续阅读仓库中的 C 推理示例 了解如何在 C 侧加载同一套模型文件。前置条件使用带 PythonGluon与 C 包的 MXNet 构建环境具备 Gluon 基础知识可先学习官方 Gluon 速成课所覆盖的 Block、Trainer、Dataset/DataLoader 等概念。数据准备Oxford 102 花卉数据集教程采用 Oxford 102 Category Flower Dataset102 类花卉共 8189 张图像作为示例。仓库在 docs/tutorial_utils/data/oxford_102_flower_dataset.py 中提供了配套工具脚本负责自动下载并整理数据。从源码看该脚本主要完成三件事download_data()从牛津官网下载102flowers.tgz图像、imagelabels.mat标签与setid.mat训练/测试/验证集合划分解压出jpg/目录prepare_data()用scipy.io.loadmat读取三个.mat文件将官方划分转换为 0-based 标签并把每个样本映射为花名如lotus、rose随后把jpg/中的图像按类别复制进train/、test/、valid/三个子目录generate_synset()按字母序把 102 个花名写入synset.txt供推理阶段映射标签序号到名称使用。在仓库中运行如下代码即可下载并组织数据工具脚本位于上述路径可先将其复制到当前工作目录再导入import mxnet as mx data_util_file oxford_102_flower_dataset.py mx.test_utils.download(base_url.format(data_util_file), fnamedata_util_file) import oxford_102_flower_dataset # 下载并将数据整理到 train/test/valid 目录 path ./data oxford_102_flower_dataset.get_data(path)整理完成后同一类别的图片会归入同一文件夹目录结构与gluon.data.vision.ImageFolderDataset期望的root/类别/图片组织方式完全一致参见 python/mxnet/gluon/data/vision/datasets.py 的文档字符串与_list_images实现它会按文件夹枚举类别、生成synsets属性并建立(filename, label)列表。使用 Gluon 进行训练定义超参数先导入必要依赖import math import os import time from mxnet import autograd from mxnet import gluon, init from mxnet.gluon import nn from mxnet.gluon.data.vision import transforms from mxnet.gluon.model_zoo.vision import resnet50_v2然后定义微调所需的超参数。教程采用 MXNet 学习率调度器在训练过程中动态调整学习率详细的调度器用法可参考仓库教程 learning_rate_schedules.md。示例中epochs设为 1 仅为快速演示正式训练请改为 40。classes 102 epochs 1 lr 0.001 per_device_batch_size 32 momentum 0.9 wd 0.0001 lr_factor 0.75 # 学习率在这些 epoch 处发生衰减 lr_epochs [10, 20, 30] num_gpus mx.context.num_gpus() # 可将 num_workers 替换为设备上的 CPU 核数 num_workers 8 ctx [mx.gpu(i) for i in range(num_gpus)] if num_gpus 0 else [mx.cpu()] batch_size per_device_batch_size * max(num_gpus, 1)要点说明batch_size按 GPU 数量翻倍保证多卡训练时每个设备仍有per_device_batch_size大小的批次num_workers控制 DataLoader 的并行读取进程数建议与 CPU 核数相当后续所有训练与验证代码均可同时运行在 CPUmx.cpu()或 GPU 列表ctx上。数据增强与 Transform 流水线训练集较小是微调场景的普遍痛点数据增强通过对训练图像做轻微改动模型会视其为不同图像来扩充有效样本量有助于提升最终精度。这里结合 Gluon 的 Dataset、DataLoader 与 Transform API对训练图像依次执行随机裁剪并缩放到 224×224随机水平翻转随机抖动颜色并添加光照扰动将数据从[height, width, num_channels]转置为[num_channels, height, width]并把像素值从[0, 255]映射到[0, 1]用 ImageNet 数据集的均值与标准差做归一化。验证与推理阶段只需执行第 1、4、5 步。同时要把均值/标准差保存为 NDArray 文件供后续 C 推理复用。jitter_param 0.4 lighting_param 0.1 # 归一化图像值域 0~1所用的 mean 与 std mean [0.485, 0.456, 0.406] std [0.229, 0.224, 0.225] training_transformer transforms.Compose([ transforms.RandomResizedCrop(224), transforms.RandomFlipLeftRight(), transforms.RandomColorJitter(brightnessjitter_param, contrastjitter_param, saturationjitter_param), transforms.RandomLighting(lighting_param), transforms.ToTensor(), transforms.Normalize(mean, std) ]) validation_transformer transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean, std) ]) # 保存 mean/std 的 NDArray 值供推理阶段使用 mean_img mx.nd.stack(*[mx.nd.full((224, 224), m) for m in mean]) std_img mx.nd.stack(*[mx.nd.full((224, 224), s) for s in std]) mx.nd.save(mean_std_224.nd, {mean_img: mean_img, std_img: std_img}) train_path os.path.join(path, train) val_path os.path.join(path, valid) test_path os.path.join(path, test) # 加载数据并应用预处理transforms train_data gluon.data.DataLoader( gluon.data.vision.ImageFolderDataset(train_path).transform_first(training_transformer), batch_sizebatch_size, shuffleTrue, num_workersnum_workers) val_data gluon.data.DataLoader( gluon.data.vision.ImageFolderDataset(val_path).transform_first(validation_transformer), batch_sizebatch_size, shuffleFalse, num_workersnum_workers) test_data gluon.data.DataLoader( gluon.data.vision.ImageFolderDataset(test_path).transform_first(validation_transformer), batch_sizebatch_size, shuffleFalse, num_workersnum_workers)这里transform_first只对数据图像做变换而保持标签不变是图像分类数据加载的推荐写法RandomColorJitter的brightness/contrast/saturation抖动幅度统一取jitter_param 0.4RandomLighting扰动系数取0.1。加载预训练模型与 Hybridization我们使用在 ImageNet1000 类上预训练的resnet50_v2。由于花卉数据只有 102 类必须把最后一层 softmax输出层重新定义为 102 维并重新初始化该层参数。在训练之前还要了解 Gluon 的一个独特特性Hybridization混合化。调用net.hybridize()即可把命令式imperative代码转换为静态符号图执行带来两大收益——更优的执行性能、以及更容易序列化以便部署。更深入的解释可参考仓库教程 hybridize.md。# 从模型动物园加载预训练 resnet50_v2 finetune_net resnet50_v2(pretrainedTrue, ctxctx) # 类别数不同替换最后一层 softmax with finetune_net.name_scope(): finetune_net.output nn.Dense(classes) finetune_net.output.initialize(init.Xavier(), ctxctx) # hybridize 以获得更好性能 finetune_net.hybridize() num_batch len(train_data) # 配置学习率调度器 iterations_per_epoch math.ceil(num_batch) # 学习率在以下 step 处衰减 lr_steps [epoch * iterations_per_epoch for epoch in lr_epochs] schedule mx.lr_scheduler.MultiFactorScheduler(steplr_steps, factorlr_factor, base_lrlr) # 配置带学习率调度器的优化器、评估指标与损失函数 sgd_optimizer mx.optimizer.SGD(learning_ratelr, lr_schedulerschedule, momentummomentum, wdwd) metric mx.metric.Accuracy() softmax_cross_entropy gluon.loss.SoftmaxCrossEntropyLoss()从源码结构看resnet50_v2python/mxnet/gluon/model_zoo/vision/resnet.py实际是get_resnet(2, 50)的封装对应ResNetV2类resnet.py。ResNetV2继承自HybridBlock网络主体由features含BatchNorm、Conv2D、残差 stage、GlobalAvgPool2D、Flatten与outputnn.Dense(classes)构成且hybrid_forward只接受符号接口F这正是它可以被hybridize()编译为静态图、并被export()导出为符号文件的前提。几个配置要点MultiFactorScheduler在第 10、20、30 个 epoch 处将学习率乘以factor0.75lr_steps把「按 epoch 衰减」换算成「按 iteration 衰减」step epoch * iterations_per_epochSGD使用 momentum0.9、weight decay0.0001损失为SoftmaxCrossEntropyLoss指标为Accuracy。在自定义数据集上微调下面定义验证函数并启动微调循环。gluon.utils.split_and_load实现见 python/mxnet/gluon/utils.py会把一个批次按ctx列表切分到多张卡上even_splitFalse表示最后一个设备可少分数据。def test(net, val_data, ctx): metric mx.metric.Accuracy() for i, (data, label) in enumerate(val_data): data gluon.utils.split_and_load(data, ctx_listctx, even_splitFalse) label gluon.utils.split_and_load(label, ctx_listctx, even_splitFalse) outputs [net(x) for x in data] metric.update(label, outputs) return metric.get() trainer gluon.Trainer(finetune_net.collect_params(), optimizersgd_optimizer) # 从 epoch 1 开始便于学习率计算 for epoch in range(1, epochs 1): tic time.time() train_loss 0 metric.reset() for i, (data, label) in enumerate(train_data): # 取图像与标签 data gluon.utils.split_and_load(data, ctx_listctx, even_splitFalse) label gluon.utils.split_and_load(label, ctx_listctx, even_splitFalse) with autograd.record(): outputs [finetune_net(x) for x in data] loss [softmax_cross_entropy(yhat, y) for yhat, y in zip(outputs, label)] for l in loss: l.backward() trainer.step(batch_size) train_loss sum([l.mean().asscalar() for l in loss]) / len(loss) metric.update(label, outputs) _, train_acc metric.get() train_loss / num_batch _, val_acc test(finetune_net, val_data, ctx) print([Epoch %d] Train-acc: %.3f, loss: %.3f | Val-acc: %.3f | learning-rate: %.3E | time: %.1f % (epoch, train_acc, train_loss, val_acc, trainer.learning_rate, time.time() - tic)) _, test_acc test(finetune_net, test_data, ctx) print([Finished] Test-acc: %.3f % (test_acc))训练循环的关键点autograd.record()记录前向计算图l.backward()之后调用trainer.step(batch_size)更新参数注意trainer.step传入的是全局batch_size含多卡而不是单卡批次大小每个 epoch 结束后在验证集上评估一次精度全部完成后在测试集上给出最终指标。以下为 40 个 epoch 的示例训练输出教程原文记录[Epoch 40] Train-acc: 0.945, loss: 0.354 | Val-acc: 0.955 | learning-rate: 4.219E-04 | time: 17.8 [Finished] Test-acc: 0.952该结果来自一台配备 4 块 Tesla V100 GPU 的实例40 个 epoch 约 12 分钟即达到约 95.5% 的测试精度。之所以如此高效正是因为模型已在约 130 万张图像的 ImageNet 上预训练对小数据集的特征提取非常有效——这正是迁移学习的核心价值。保存微调后的模型训练完成后用export把模型序列化为模型文件finetune_net.export(flower-recognition, epochepochs)export会在当前目录生成两个文件模型结构文件flower-recognition-symbol.json与参数文件flower-recognition-0040.params0040对应训练的 40 个 epoch若epochs1则生成flower-recognition-0001.params二者即为下一节部署推理的输入。从源码看HybridBlock.exportpython/mxnet/gluon/block.py有以下行为值得注意必须先调用net.hybridize()并至少前向执行一次否则会抛出RuntimeError(Please first call block.hybridize() and then run forward with this block at least once before calling export.)——因为导出需要_cached_graph中缓存的符号图单输入模型的输入节点名固定为data多输入时为data0、data1…这正是后续推理时data_shapes[(data, (1, 3, 224, 224))]的来源参数按arg:/aux:前缀分别写入.params文件供load_checkpoint与set_params还原。用 MXNet Module API 加载模型并推理MXNet 为部署推理提供了多种接口可以使用 MXNet Model Server 直接托管模型并对外提供服务也可以借助 Python、Java、Scala、C 等多种语言 API 把模型集成进既有服务。本节演示 Python 侧使用 Module API 完成一次预测。推理整体分为五步加载模型结构symbol 文件与训练好的参数params 文件加载 synset 文件获取类别名称加载图片并应用与训练时验证集相同的变换对图片数据执行一次前向计算把输出概率转换为预测的类别名。import numpy as np from collections import namedtuple ctx mx.cpu() # 加载模型 symbol 与 params sym, arg_params, aux_params mx.model.load_checkpoint(flower-recognition, epochs) mod mx.mod.Module(symbolsym, contextctx, label_namesNone) mod.bind(for_trainingFalse, data_shapes[(data, (1, 3, 224, 224))], label_shapesmod._label_shapes) mod.set_params(arg_params, aux_params, allow_missingTrue) # 加载 synset 以获取类别名 with open(synset.txt, r) as f: labels [l.rstrip() for l in f] # 加载一张待预测图片 img mx.image.imread(./data/test/lotus/image_01832.jpg) # 应用训练时相同的变换 img validation_transformer(img) # batchify扩展为 batch 维度 img img.expand_dims(axis0) Batch namedtuple(Batch, [data]) mod.forward(Batch([img])) prob mod.get_outputs()[0].asnumpy() prob np.squeeze(prob) idx np.argmax(prob) print(probability%f, class%s % (prob[idx], labels[idx]))执行结果如下可见图片被正确分类为 lotusprobability9.798435, classlotus几个易错点需要留意输入形状必须与训练一致data_shapes[(data, (1, 3, 224, 224))]对应 224×224、RGB 三通道、单样本 batch若训练时用了其他尺寸这里需同步修改变换必须与验证一致推理仍使用validation_transformerResize 256 → CenterCrop 224 → ToTensor → Normalize否则归一化域不一致会导致精度骤降synset 顺序必须与训练一致synset.txt由工具脚本按类别名排序生成而ImageFolderDataset内部也按排序枚举类别二者对齐才能正确解析预测序号。部署路径与后续方向模型导出为-symbol.json与-params后就可以脱离训练代码独立部署C 部署继续阅读仓库 cpp-package/example/inference 下的推理示例了解如何使用 C API 加载同一套模型文件、复用mean_std_224.nd完成预处理并执行前向计算Java / Scala 部署仓库 scala-package 提供了 Java 与 Scala 的推理示例Java 示例位于scala-package/examples/src/main/java/org/apache/mxnetexamples/javaapi/infer服务化部署可以使用 MXNet Model Server 启动推理服务把训练好的模型托管为 HTTP 接口供上层业务调用。参考资源docs/tutorial_utils/data/oxford_102_flower_dataset.pyOxford 102 数据下载与整理脚本含 102 个花名清单python/mxnet/gluon/model_zoo/vision/resnet.pyResNetV2/resnet50_v2实现python/mxnet/gluon/block.pyHybridBlock.export序列化实现learning_rate_schedules.md学习率调度器详解hybridize.mdHybridization 原理与用法Gluon 微调相关实践可参考公开的《动手学深度学习》(d2l) 微调章节与 GluonCV 迁移学习教程【免费下载链接】mxnetLightweight, Portable, Flexible Distributed/Mobile Deep Learning with Dynamic, Mutation-aware Dataflow Dep Scheduler; for Python, R, Julia, Scala, Go, Javascript and more项目地址: https://gitcode.com/gh_mirrors/mxnet1/mxnet创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表