☰
华为云ModelArts实操:从模型训练到在线部署的完整路径
2026/10/15 9:39:49 网站建设 项目流程

今年年初我给自己定了一个技术目标:完整地在云上把"训练一个模型、再把模型部署上线"这条路走通一遍。之前我在本地机器上也折腾过不少模型,但显存紧张、环境配置混乱、结果不好复现,折腾久了很容易泄气。后来我系统地把流程迁移到华为云ModelArts上,从数据准备、模型训练到模型部署全部跑了一遍,这篇文章就是我的学习笔记,把它整理成一份可以参考的实操记录。

这篇笔记不是官方文档的复述,而是我以个人身份从零开始跑通的真实过程。文章里会重点讲几个我踩得比较深的坑:训练Loss突然变成NaN、OBS数据读取慢到让人怀疑人生、部署时模型目录结构不对反复创建失败,以及云资源账单悄悄烧钱的盲区。如果你也打算用ModelArts做模型训练和模型部署,希望这份记录能帮你少走几个来回。

1. 从本地训练转云端:我为什么最后留在了ModelArts

1.1 我那台老显卡和跑不完的ResNet

先说背景。我自己有一台装着老显卡的机器,显存捉襟见肘,跑个ResNet50做图像分类,batch size稍微大一点就报CUDA out of memory。为了把batch size压到能跑的程度,我还得手动调学习率,明明是同样的代码,换个环境又要重新折腾CUDA版本、cuDNN、PyTorch编译链,经常一晚上就耗在修环境上。

更要命的是结果复现困难。本地训练一半断电、硬盘空间不够、conda环境被其他项目搞乱,训练日志也不知道丢到哪里去了。一次两次还能忍,次数多了我就意识到:个人玩深度学习的瓶颈其实不在算法,而在环境管理和算力调度。

1.2 ModelArts到底帮我省了哪些事

我选择华为云ModelArts,准确说是被它的"一站式"特性吸引的。所谓一站式,就是数据处理、算法开发、训练、模型管理、部署上线都在同一个平台上形成闭环,不需要自己拼装一堆工具。

用了一段时间,我总结出它真正省事的地方:

  • 预置镜像帮你把环境问题解决了大半。创建训练作业时直接选PyTorch、TensorFlow或MindSpore的版本,驱动和CUDA层云平台已经处理好,不用自己装。
  • 训练作业自带日志和资源监控。控制台能看到实时日志,训练完日志也可以落盘到OBS(华为云对象存储服务),排查问题比本地翻终端舒服。
  • 模型管理有版本概念。同一个模型的多个版本可以分别部署,回滚也方便。
  • 部署上线简单直接。训练产物可以直接发布成在线服务,得到一个可供外部调用的API地址,不用自己搭Web服务。

1.3 先摸清平台的家底:训练、部署、AI Gallery

在动手之前,我建议先花半小时把ModelArts的界面浏览一遍。平台大致由几个模块组成:

模块主要作用我的使用场景
数据管理数据集创建、标注、版本管理小数据集自己传,标注量大的走标注任务
AI Gallery算法、模型、数据集的共享市场直接订阅预训练模型和示例代码
开发环境(ModelArts Studio)在线Notebook调试写脚本前的快速验证
训练管理创建训练作业、查看日志和资源正式跑训练都在这里
模型管理模型导入、版本管理把训练输出转成可部署的元模型
部署上线在线服务、批量服务、边缘服务最终对外提供推理接口

如果你是零基础,还可以先玩自动学习(AutoML),上传数据、标几个标签就能训练,代码都不用写。但我的建议是:学习阶段别只依赖自动学习,还是得自己写一遍训练脚本,才能真正理解整个流程。后面几章内容,都是基于我自己写脚本跑训练的方式。

2. 环境准备的第一道坎:账号、区域和OBS数据上云

2.1 区域选错,后面全是泪

第一次使用华为云的时候,我对"区域(Region)"这个概念没太当回事,默认选了一个离自己感觉最近的区域。结果后面创建训练作业时发现找不到自己上传的OBS桶,查了半天才知道,ModelArts训练作业默认只能访问同一区域的OBS数据,跨区域访问需要额外配置而且速度没有保证。

我的建议很直接:如果你主要用ModelArts,直接选"华为-北京四"这个区域。ModelArts的很多新功能、免费规格和AI Gallery资源都优先在该区域上线,而且云社区的教程大多也默认用这个区域。确定区域后,后续创建OBS桶、创建训练作业、部署在线服务都固定在这个区域,不要随意切换。

2.2 OBS桶目录怎么规划才不返工

OBS是华为云的对象存储服务,ModelArts的数据和模型都放在OBS里,所以桶和目录结构很关键。我第一次用的时候把数据集、代码、输出全部堆在一个目录下,训练作业跑了几次之后目录乱成一锅粥。

现在我的目录结构是这样规划的:

obs://my-modelarts-learn/ ├── datasets/ │ └── flowers/ │ ├── train/ │ │ ├── daisy/ │ │ ├── dandelion/ │ │ └── ... │ └── val/ ├── codes/ │ └── train_image_classify/ │ ├── train.py │ └── requirements.txt └── outputs/ └── experiment-001/

三个顶层目录各管一件事:datasets存数据、codes存代码、outputs存训练输出。训练输出一定要按实验命名,每次训练写到不同的子目录,不然旧模型容易被覆盖。我还吃过一个亏:把训练输出目录放在数据集目录里面,结果训练任务清洗数据时差点把上一轮模型当数据读进去。

2.3 三种往OBS传数据的方式,按场景选

数据上云有三种常见方式,按数据量大小选择:

第一种是控制台网页直接上传。适合几十MB的小文件,鼠标拖拽就行,但大文件网页上传容易断。

第二种是用obsutil命令行工具。从华为云官网下载对应平台的obsutil,解压后先初始化:

./obsutil config -i=AK -k=SK -e=obs.cn-north-4.myhuaweicloud.com

AK/SK在华为云的"我的凭证"里创建,建议单独创建一个只用于OBS的AK/SK,别把管理员密钥到处用。初始化后上传目录:

./obsutil cp ./train_data obs://my-modelarts-learn/datasets/flowers/train -f -r

-f是强制覆盖,-r是递归上传目录。obsutil支持断点续传,几百GB的数据量也能稳住,我传花卉数据集时就用它。

第三种是写代码用OBS SDK或ModelArts自带的MoXing SDK上传。这种适合数据集已经在云服务器上,或者需要定时同步的场景。学习阶段用前两种就够了。

再多说一句:如果你的数据集是几万个几十KB的小图片,直接这样散着传上去也能用,但训练时逐个读取会慢,这点后面第4节会专门讲。更优的做法是先打包成tar或者zip再传上去,脚本里解压使用。

2.4 MoXing在训练脚本里的正确姿势

MoXing是ModelArts提供的文件访问SDK,简单说就是让你像操作本地文件一样操作OBS。这个工具在我整个训练流程里出现的频率非常高,典型用法就两个:判断文件是否存在、目录拷贝。

import moxing as mox if mox.file.exists('obs://my-modelarts-learn/datasets/flowers'): mox.file.copy_parallel( 'obs://my-modelarts-learn/datasets/flowers', '/cache/flowers' )

第一次看到这代码时我有个疑问:为什么不直接让训练脚本去读OBS路径,非要拷贝到本地?后面踩了坑才明白,对象存储适合顺序读大文件,不适合训练场景下海量的小文件随机读取,图片数据集直接读OBS会慢到怀疑人生。正确的做法就是先通过MoXing把数据集拉取到训练作业的本地缓存目录,比如/cache。

还有一个技巧:训练脚本里可以把当前所有环境变量先打印出来。ModelArts训练作业会把数据路径、输出路径等信息通过环境变量注入,不同版本的变量名可能略有差异,先跑一行print(os.environ)看清楚再写代码,比自己凭记忆写保险得多。

3. 实战跑通:基于ResNet预训练模型的图像分类训练

3.1 为什么第一个训练任务选ResNet

选ResNet作为第一个练手项目,我的理由很简单:ResNet是图像分类领域的"基本功",结构清晰,预训练权重到处都是,而且概念上一个"残差连接"就能讲明白深层网络为什么能训练起来。ResNet通过让网络学习残差映射,解决了深层网络的梯度消失问题,这种设计思想后来在很多模型里都能看到。

更实际的原因是,用ImageNet预训练的ResNet做迁移学习,即使是几百张图片的小数据集也能取得不错的效果。这让我可以专注于ModelArts平台本身的使用流程,而不是跟收敛问题死磕。对第一次上云训练的人来说,目标应该是"把流程跑通",不是"从零训练一个刷新精度的模型"。

预训练模型可以从几个渠道获取:torchvision里带的ResNet50权重,AI Gallery上别人分享的模型,以及HuggingFace上的pytorch版本权重。我用的是torchvision的ImageNet预训练权重。

3.2 训练脚本里的几个关键点

我的训练脚本整体不长,但有几个关键点值得展开讲。以PyTorch为例:

import argparse import os import torch import torch.nn as nn import torchvision from torchvision import transforms, datasets import moxing as mox def parse_args(): parser = argparse.ArgumentParser() parser.add_argument('--data_url', type=str, required=True) parser.add_argument('--train_url', type=str, required=True) parser.add_argument('--epochs', type=int, default=10) parser.add_argument('--batch_size', type=int, default=64) parser.add_argument('--learning_rate', type=float, default=0.001) return parser.parse_args() def main(): args = parse_args() local_data = '/cache/flowers' mox.file.copy_parallel(args.data_url, local_data) train_transforms = transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) train_dataset = datasets.ImageFolder( os.path.join(local_data, 'train'), transform=train_transforms) train_loader = torch.utils.data.DataLoader( train_dataset, batch_size=args.batch_size, shuffle=True, num_workers=4) model = torchvision.models.resnet50( weights=torchvision.models.ResNet50_Weights.IMAGENET1K_V1) model.fc = nn.Linear(model.fc.in_features, 5) for param in model.parameters(): param.requires_grad = False for param in model.fc.parameters(): param.requires_grad = True model = model.cuda() criterion = nn.CrossEntropyLoss() optimizer = torch.optim.SGD( model.fc.parameters(), lr=args.learning_rate, momentum=0.9) model.train() global_step = 0 for epoch in range(args.epochs): for images, labels in train_loader: images, labels = images.cuda(), labels.cuda() optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() global_step += 1 if global_step % 50 == 0: print(f'[step {global_step}] loss: {loss.item():.4f}') os.makedirs('/cache/model', exist_ok=True) torch.save(model.state_dict(), '/cache/model/model.pth') mox.file.copy('/cache/model/model.pth', os.path.join(args.train_url, 'model.pth')) if __name__ == '__main__': main()

这里有三个关键点值得解释。

第一,为什么先把数据copy到/cache。前面说了,OBS是对象存储,逐个读小图片性能很差;/cache是训练作业的本地磁盘,虽然训练结束会清空,但训练期间读写速度是本地SSD级别。训练数据进缓存、产出模型出缓存,这是ModelArts训练的标准节奏。

第二,为什么固定主干只训练全连接层。迁移学习的核心思路是,ImageNet上学到的特征提取能力可以直接复用,所以把前面所有层的参数冻结,只让最后一个全连接层学习新分类任务。这样参数量小、收敛快,几百张图片也能训起来。数据量大的时候再解冻更多层做微调。

第三,为什么最后要mox.file.copy把模型传回OBS。因为/cache目录是临时的,训练作业一结束就没了。模型必须显式拷贝到train_url指向的OBS路径,才能后续用于部署。训练输出路径里找不到模型文件,十有八九是漏了这一行。

3.3 训练作业配置:算法、数据源、规格怎么填

脚本准备好后,我把它上传到codes/train_image_classify目录,然后在ModelArts控制台创建训练作业。页面上有四个核心配置项:

算法来源选"我的算法",代码目录指向OBS里的codes/train_image_classify,启动文件填train.py。数据来源选择"数据存储位置",输入路径填obs://my-modelarts-learn/datasets/flowers,输出路径填obs://my-modelarts-learn/outputs/experiment-001。创建作业时还可以通过参数设置显式传入--epochs 20 --batch_size 64 --learning_rate 0.001。

资源池选择上,新手阶段用公共资源池就行,按需付费,不用预留。规格方面预算有限就选GPU,V100 16GB足够这个任务;如果想体验昇腾芯片,就选Ascend 910的规格,但脚本可能需要适配MindSpore或对应的PyTorch版本,训练PyTorch模型时优先还是选GPU。

这里有个容易忽略的点:数据来源路径和输出路径必须是OBS路径,而数据来源路径在脚本里对应的是--data_url这个参数。ModelArts训练任务会把数据来源路径和输出路径作为参数传给启动命令,所以脚本里argparse的定义名字要跟页面上配置的对应起来。

3.4 学习率与迭代次数:我实测的一组调参记录

我在这个数据集上做了三组实验,结果很有代表性:

实验配置结果观察
exp1ResNet50, lr=0.01, batch=64, epochs=10收敛很慢,loss震荡学习率对迁移学习偏高
exp2ResNet50, lr=0.001, batch=64, epochs=10稳定收敛,准确率明显提升预训练模型适合小学习率
exp3ResNet50, lr=0.001, batch=64, epochs=20 + 学习率衰减准确率进一步上升多训练几轮+衰减更稳

经验法则:使用预训练模型做迁移学习时,学习率一般从0.001开始,不要照搬从头训练时的0.1这种量级。预训练权重已经处于一个较好的局部最优附近,学习率太大一步就把权重踢出去了。另外,如果用更大的batch size,学习率通常也要相应放大,这个比例关系没有万能公式,但至少要知道它们相互关联。

如果追求更稳的训练曲线,可以在每若干个epoch把学习率乘以0.1,或者用余弦退火。ModelArts训练作业支持在脚本里直接写PyTorch的torch.optim.lr_scheduler,该怎么做跟本地训练完全一致。

4. 训练事故现场:Loss变NaN、数据读取慢、显存爆掉

训练不可能一帆风顺,我这两周里遇到的三个问题,几乎每个做深度学习的人都逃不掉。

4.1 Loss=NaN的完整排查链路

现象很经典:训练到某个step,loss打印变成了nan,随后准确率直接崩掉。第一次遇到时我以为是平台问题,后来查了一圈发现还是自己代码的问题。

我的排查链路按顺序是这样走的:

第一步,查数据。先确认输入张量和标签里没有NaN。图像数据一般不会有问题,但如果数据是从CSV、JSON读的数值特征,这一步必须做。

for images, labels in train_loader: print(torch.isnan(images).sum(), torch.isnan(labels).sum()) break

第二步,降学习率。Loss变成NaN最常见的原因是学习率过大导致梯度爆炸。把学习率直接除以10,重新跑几个step试试,能稳定就不再往上调。

第三步,查损失函数和标签范围。交叉熵损失对标签取值有要求,如果标签从1开始而模型输出是5类,CrossEntropyLoss会算出一个奇怪的损失甚至NaN。用print(labels.unique())看一眼标签集合是否在[0, num_classes-1]区间。

第四步,检查归一化。使用ImageNet预训练模型时,输入必须做标准化,用ImageNet的mean和std。如果忘了做Normalize,输入值范围不对,激活值可能饱和,梯度也会异常。

第五步,查混合精度。如果用了torch.cuda.amp,先关掉自动混合精度试试。FP16的梯度溢出也是NaN的常见来源,特别是某些算子在半精度下不稳定。

排查这种事情,最有效的方法是"二分法":先用一个batch、小学习率把训练脚本跑通,确认没问题后再逐步放开到完整数据和完整参数。直接在完整训练里大海捞针,效率太低了。

4.2 OBS直读拖垮训练:换缓存目录前后对比

我刚开始写训练脚本时偷了个懒,没有把数据copy到/cache,而是写了一个自定义Dataset,每次__getitem__的时候用mox.file.read去OBS读图片。结果第一个epoch跑了30多分钟还没结束,CPU一直在等待网络IO,GPU利用率不到10%。

这个问题的本质是:对象存储擅长顺序访问大对象,不擅长海量小文件的随机访问。训练时每个step要随机读取几十张图片,每张图片都是一次独立的HTTP请求,延迟被无限放大。

解决办法就是前面第2.4节说的,先把整个数据集拷贝到本地缓存,再走PyTorch正常的ImageFolder读取。改完之后同样的epoch只花了3分钟左右,GPU利用率也上来了。

对比效果很明显:

读取方式单个epoch耗时GPU利用率
训练时逐张读OBS大于30分钟低于10%
先拷贝到/cache再读约3分钟80%以上

如果你的数据集大到/cache放不下,另一个思路是打包成tar或TFRecord,训练时流式读取大文件。ModelArts的/cache本身有容量上限,关注训练日志里的磁盘告警。

4.3 显存不够时先改哪个参数

CUDA out of memory是最不让人意外的报错。我的经验是,遇到显存不够,按以下顺序处理:

第一,调小batch size。这是最直接的,显存占用跟batch size基本线性相关。但注意batch size变了,学习率也要跟着调整,否则收敛行为会变。

第二,做梯度累积。如果batch size已经很小还是溢出,用梯度累积模拟大batch:

accumulation_steps = 4 optimizer.zero_grad() loss = loss / accumulation_steps loss.backward() if (step + 1) % accumulation_steps == 0: optimizer.step() optimizer.zero_grad()

第三,降低输入分辨率。图像任务可以先把图片resize到更小的尺寸,比如从256x256降到192x192,显存占用会明显下降。

第四,换更高规格的资源。如果在公共资源池训练,直接在创建作业时选16GB以上显存的规格即可,这是云上训练的优势。

还有一个细节:如果你在ModelArts上使用PyTorch,训练结束记得torch.cuda.empty_cache(),或者让作业正常退出,避免显存不释放影响后续同一资源池上的作业调度。

4.4 日志与监控:怎么看训练日志定位问题

ModelArts训练作业页面有"日志"入口,能看到实时的标准输出。我自己踩过的坑是日志太多刷屏,关键信息被淹没。后来我在脚本里统一了日志格式,每个step打印loss和当前学习率,隔一段时间打印一次GPU利用率,排查问题就有了锚点。

if global_step % 50 == 0: current_lr = optimizer.param_groups[0]['lr'] print(f'[{epoch}/{args.epochs}] step {global_step} ' f'loss {loss.item():.4f} lr {current_lr:.6f}')

训练日志还可以在作业配置里指定一个OBS路径,训练结束后日志会保存到那里,方便回看。作业异常退出时,优先看训练日志的最后500行,一般报错堆栈就在那个位置。如果日志里只有"进程被终止"之类的话,再去看系统日志,很可能是资源规格不够或者代码里出现了死循环。

5. 最后一公里:把训练产物变成在线部署服务

训练跑通只是前半程。我真正觉得ModelArts有价值的地方,是把模型转成在线服务这个流程足够顺滑。

5.1 从训练输出到模型导入

训练作业结束之后,模型已经保存在obs://my-modelarts-learn/outputs/experiment-001/model.pth。下一步要在"模型管理"里导入模型。

ModelArts的模型导入,本质上是让平台认识你的模型文件、知道怎么加载它、怎么处理请求。所以模型目录必须是一个标准结构,我一开始直接选OBS里的单个.pth文件路径,创建模型就失败,换成目录结构后一次通过。标准的模型目录是这样:

model/ ├── model.pth # 模型权重文件 ├── config.json # 模型配置文件 └── custom_service.py # 自定义推理脚本

在"模型管理"页面选择"从OBS导入",指向这个目录,然后填写模型名称和版本。如果训练作业是直接从ModelArts创建的,也可以直接在训练作业页面点"发布"到模型管理,平台会自动帮你整理模型目录,省去手动操作。推荐后一种方式,不容易出错。

5.2 config.json和自定义推理脚本

如果说模型权重是"心脏",那config.json和custom_service.py就是"神经系统"。config.json告诉ModelArts这个模型是什么类型、用什么运行时、暴露什么API,我第一次手动写的时候少写了runtime字段,部署时一直报错。

一个可以用的config.json示例:

{ "model_algorithm": "image_classification", "model_type": "PyTorch", "runtime": "python3.9", "apis": [ { "url": "/", "method": "post", "request": { "Content-Type": "application/json" } } ] }

custom_service.py里定义推理逻辑。ModelArts针对PyTorch提供了一个基类PTServingService,我们只需要实现预处理和后处理:

import io import torch from PIL import Image from torchvision import transforms from model_service.pytorch_model_service import PTServingService class ResnetService(PTServingService): def _load_model(self, model_path): import torchvision model = torchvision.models.resnet50(weights=None) model.fc = torch.nn.Linear(model.fc.in_features, 5) state = torch.load(model_path, map_location='cpu') model.load_state_dict(state) model.eval() return model def _preprocess(self, data): image = data['image'] img = Image.open(io.BytesIO(image.content)).convert('RGB') transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) return transform(img).unsqueeze(0) def _postprocess(self, preds): prob = torch.softmax(preds, dim=1)[0] idx = int(torch.argmax(prob)) return {'label': idx, 'prob': float(prob[idx])}

这里最容易忽略的地方是:加载模型的代码里,网络结构必须和训练时完全一致,包括最后一层全连接的输出维度。我训练时把model.fc改成5分类,部署脚本里如果忘了改,加载权重就会报state_dict大小不匹配。另外,不同版本的ModelArts里基类方法名可能略有差别,拿不准就去AI Gallery找一个官方PyTorch推理样例参考。

5.3 创建在线服务并完成API测试

模型导入成功后,在"部署上线"里选择"在线服务",创建时选择刚才导入的模型版本,再选一个推理资源规格。规格影响响应速度和费用,学习阶段选最低配的就行,但注意至少要选带一点CPU或GPU资源的规格,纯CPU跑ResNet50会慢。

创建后会有一个等待过程,服务状态从"创建中"变成"运行中"才算就绪。就绪后控制台会显示一个API地址,把这个地址填到请求工具里,再按服务要求的请求体格式传参。

最简单的测试方式是使用控制台自带的"预测"功能,上传一张图片直接看返回结果。如果想模拟真实调用,就需要走鉴权。在线服务一般要求请求头携带X-Auth-Token(IAM Token)或者使用App认证的AK/SK签名。

我的建议是:第一次不用急着写代码调用,先在控制台的"预测"页里把服务跑通,确认模型行为正常后,再研究API鉴权和批量调用。

5.4 自动缩放和资源释放的配置

在线服务创建成功后,默认是固定1个实例在跑,每小时都在计费。如果你只是学习验证,用完一定要回到在线服务列表把服务删除掉,我就是因为忘记删,一个周末白白扣了几十块钱。

如果服务确实需要长期在线,可以配置自动缩放策略。ModelArts支持按实例数设置最小和最大值,并根据流量指标自动扩容缩容。学习阶段我的建议是:min和max都设为1,先观察实际流量再说,不要一上来就开高并发配置。自动缩放并不是越多越好,因为扩容出来的实例全部计费。

另外,ModelArts还有"批量服务"模式,适合离线批量推理。如果只是把一批图片跑一遍出结果,用批量服务比长期挂在线服务省钱得多。这个模式是我后来才发现的,早知道能省不少费用。

6. 云资源的真实成本账:怎么花得明白又省心

6.1 ModelArts到底怎么计费

用云平台训练,成本一定要心里有数。ModelArts的计费项我整理了一下:

计费项计费方式我的实际经验
训练作业计算资源按实际运行时长x规格单价作业结束即停止计费
开发环境Notebook按实例运行时长计费忘记关就持续计费
在线服务推理实例按实例数x时长计费挂着就计费,还好单价有梯度
OBS存储按存储容量和流量计费数据删了才不花钱
模型管理借助OBS存储计费模型文件体积不大时可忽略

训练作业的费用关键是规格档位。同样训练一小时,低配CPU和高配GPU的价格可能相差一个数量级。公共资源池按需计费,用完即停,适合实验;专属资源池需要预留,适合生产环境,学习阶段没必要。

6.2 我踩过的几个扣费盲区

第一个盲区是Notebook实例不关。我在ModelArts Studio里调试脚本,开了个GPU实例,调试完直接关了浏览器,以为就停了。第二天看账单,才发现Notebook实例一直运行到欠费提醒。解决办法很简单:在Notebook控制台页面显式点"停止",或者配置自动停止策略,比如无操作30分钟后自动停止。

第二个盲区是在线服务删不掉。我把一个测试服务挂了两天,每天固定的实例费用叠加起来比训练费用还高。这个坑提醒我:验证完推理效果后,第一件事就是删除服务。

第三个盲区是OBS的跨区域数据传输。如果你开了多个区域的桶,把数据从一个区域传到另一个区域,会产生额外的流量费用。所以前面才反复强调,账号、桶、训练、部署全部固定在同一区域。

第四个盲区是日志存储。训练作业日志会写到OBS,很多次训练累积下来日志体积也不小,这部分虽然单价低,但确实是一个容易被忽视的持续成本。

6.3 学习阶段怎么用免费额度和社群资源

华为云对ModelArts有不少免费额度和低价规格,实际以控制台页面和官方活动为准。我的建议是:学习阶段优先找带"免费"或"试用"标记的规格,先用最小资源把全流程跑通,再考虑升级规格。AI Gallery里也有很多现成的算法和模型可以直接订阅,用来熟悉平台操作非常合适。

ModelArts的官方文档里有一个"最佳实践"栏目,里面有不少场景化的案例,比如图像分类、目标检测、文本分类。这些案例相当于官方带路,跟一遍比自己瞎试高效得多。云社区和开发者论坛里的提问也值得搜,很多问题别人已经踩过一遍。

7. 进阶方向:从图像分类到目标检测和中文NLP

图像分类这个闭环跑通之后,我发现ModelArts的这套流程是可复用的。换模型、换任务,本质上都是"数据进OBS、脚本里处理数据、训练、导出、部署"这个套路。所以最后聊两个我接下来打算尝试的方向,也是大家问得比较多的方向。

7.1 YOLO系目标检测在ModelArts上的落地

图像分类跑通后,很自然的下一步就是目标检测。YOLO系列,比如YOLOv5和更新的YOLOv11,是目前实践中最常用的检测模型。在ModelArts上训练YOLO,数据格式一般是VOC或COCO格式,图片路径和标签文件要组织好。

我建议的方式依然是自定义算法:把YOLO的训练代码传到OBS,创建训练作业时数据路径指向标注好的数据集,训练参数里指定类别数量和anchor配置。需要注意的坑包括:自己数据集的类别顺序和预训练权重不完全一致时,最后一层要改;图片尺寸和模型输入尺寸不匹配会影响训练效率;以及YOLO的loss对学习率也比较敏感,照样会出现NaN问题。

如果你的最终目标是部署到K210这种边缘AI芯片上,那ModelArts负责的是云端训练,训练完导出模型后,还要在本地工具链里做模型转换才能烧录到芯片。训练和部署分离的思路一定要提前想清楚,别在云端花大力气训练完,结果发现转换环节过不去。

7.2 中文RoBERTa文本分类的注意点

除了视觉任务,NLP方向也很常用。中文RoBERTa预训练模型现在很容易获取,HuggingFace上有不少中文版本,比如哈工大开源的中文RoBERTa模型。文本分类的流程跟图像分类大同小异:数据是文本和标签,先用Tokenizer把文本转成input_ids和attention_mask,然后微调预训练模型的分类头。

在ModelArts上做NLP要注意几个细节。第一,预训练模型的下载需要网络,训练作业环境能不能访问外网要提前确认,最好的做法是提前把模型文件下载好放到OBS,训练时再copy到本地。第二,transformers库版本要和模型兼容,版本不匹配经常会加载失败。第三,NLP数据通常也是海量小文件,训练时同样建议先拉到本地缓存。

推理部署阶段,要记得在custom_service.py里把Tokenizer一起加载,因为推理时文本也必须经过完全相同的分词和处理逻辑,tokenizer不一致会直接导致推理结果异常。

7.3 我个人对"学习笔记"这件事的建议

最后说点题外话。很多人学习深度学习,喜欢囤教程和模型,但真正上手的时间很少。我的体会是:第一次跑通比跑得完美重要得多。哪怕只是用一个很小的数据集,在ModelArts上把训练、部署、调用完整走一遍,你对整套体系的认知都会完全不一样。

具体的习惯方面,我现在做每个实验都会记一个简单的表格,包含实验编号、参数配置、数据集版本、最终指标、备注。后面回看的时候,这些记录比模型文件本身更有价值。写博客或者记笔记的时候,不要只记录成功路径,也要把踩过的坑和排查过程写下来,那部分才是真正能帮到后来人的内容。至少在处理NaN、OBS慢加载、部署目录结构这些问题上,我自己就是靠别人的踩坑笔记省下了大把时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询