☰
ModelArts 图像分类训练与在线部署实践:从数据到服务全流程解析
2026/10/15 7:47:25 网站建设 项目流程

1. 整体思路:为什么选 ModelArts 而不是自己搭训练环境

先说结论:如果你手上有一个明确的模型训练需求(无论是图像分类、目标检测还是 NLP 任务),但又不希望从买显卡、装驱动、配 CUDA、写训练脚本开始一步步折腾,那华为云 ModelArts 就是那个帮你把“训练到部署”整条链路都打通的地方。

我当时的需求很简单:用 ResNet 预训练模型做图像分类,把模型跑通、部署成一个 HTTP 接口,让业务方调用。如果走传统路线,我需要准备一台带 GPU 的服务器,装好驱动和深度学习框架,然后手动写训练脚本,训练完成后再单独找地方部署推理服务。这套流程对个人开发者来说并不轻松——光是环境兼容问题就能耗掉几天时间。

ModelArts 解决的核心痛点,是把“数据集管理 -> 算法选择 -> 训练任务 -> 模型注册 -> 在线部署”这些环节在一个平台内串起来。你在页面上点几下,它会在后台帮你拉起训练资源,跑完把模型存好,再一键发布成在线服务。这背后的实质是它替你管理了底层基础设施,包括 GPU 资源调度、训练镜像、环境变量、推理容器的生命周期等等。

适合谁来参考这篇笔记?首先是有模型训练需求、但不想深究服务器运维的算法工程师;其次是想了解 ModelArts 全流程操作、但从没在华为云上跑过训练任务的同学;最后是那些已经在本地用 PyTorch / TensorFlow 训练过模型、只是对平台化操作不熟悉的人。这篇笔记不会按官方文档逐条抄,我只写自己在实际操作中真正跑通的流程,以及踩过的坑。如果你是零基础,完全没关系,我会把关键概念用最直白的方式解释一遍。

2. 核心概念解构:训练任务、算法、工作环境之间的关系

在 ModelArts 上动手之前,有几个概念必须搞明白,否则页面上的选项会把你看晕,而且选错了后面很难排查。

2.1 “训练任务”到底是什么

ModelArts 的“训练任务”并不是一个抽象的概念,它在底层就是一个容器实例——这个容器里有固定的镜像(比如 PyTorch 1.8 或 TensorFlow 2.6),你指定代码目录、数据路径、启动命令,平台把这个容器调度到 GPU 节点上运行。从用户角度,你在界面里填写的参数,最终都会转化为容器的环境变量和启动指令。

我一开始犯的错误是把“训练任务”理解成“一个脚本”,以为只要上传了 .py 文件就能跑。实际上你上传的是一个代码目录,里面包含训练脚本、依赖配置文件、甚至自定义的模型结构定义文件。平台会把你的代码目录挂载到容器的工作区,然后执行你在“启动命令”里填的那条指令。举个例子,如果我的代码目录叫train_src/,里面有个main.py,那启动命令就应该写python main.py或者带参数python main.py --epochs 50。

2.2 算法、预训练模型与训练脚本的关系

ModelArts 页面里有一个“算法”的概念,它和“训练脚本”经常被混为一谈。我个人的理解是:算法是一个更高层的封装,它可以包含训练脚本、推理脚本、模型评估逻辑,甚至附带一组默认超参数。华为云官方提供了一批现成算法,比如用于图像分类的 ResNet、用于目标检测的 YOLO,你可以直接创建训练任务时选用这些算法,而不用自己写脚本。

但如果你想用自己的脚本,那“训练脚本”就是你自己的代码目录,平台只负责把它跑起来。这里有一个关键点:如果选择官方算法,代码路径、输出路径这些一般都被提前处理好了,你只需要指定数据集路径和超参数;如果用自定义脚本,那你得自己处理数据读取、模型保存路径、日志输出路径——因为这些路径在容器里是有约定的,写错了模型就存不到指定位置。

2.3 工作环境(Notebook)和训练任务的区别

在 ModelArts 里你还会看到一个叫“开发环境”的功能,它可以创建一个 Jupyter Notebook 实例,给你一个在线写代码、调参的环境。很多人刚上手时搞不清:我到底是在 Notebook 里训练,还是创建训练任务?

我的做法是:在 Notebook 里做小规模验证和调试,在训练任务里跑正式的大规模训练。为什么这样分?因为 Notebook 实例的资源配置比较灵活,你可以启动一个小规格(比如 1 张 V100 GPU),快速跑几个 epoch,验证数据加载逻辑是否正确、模型能否正常收敛;而正式训练任务可以申请更大的资源池,并且支持分布式训练,训练过程中平台会自动监控资源利用率和日志,任务结束后自动释放资源——这对长时间训练来说既方便又省钱。

2.4 OBS 存储:数据的“中转站”

几乎每一步操作都会碰到 OBS(对象存储服务),它是华为云的对象存储系统。ModelArts 训练的数据集、代码包、输出的模型文件,都要放到 OBS 里。

一开始我有点抗拒用 OBS,因为多了一步上传下载的操作,觉得有点绕。但实际用起来后发现,它确实是最合理的方案:训练节点可能是临时创建的,容器重启后本地文件就没了,所以必须把数据放在持久化的对象存储里,训练时再把数据从 OBS 拉取到容器。这在分布式训练场景下尤其重要——多个计算节点不可能各自维护一份数据,统一从 OBS 读取才能保证一致性。

你在创建训练任务时,填的“数据来源”路径基本都长这样:s3://bucket-name/dataset/。这里的s3://其实是兼容 S3 协议的标识,华为云 OBS 的路径也沿用这个格式。如果你在代码里硬编码了本地路径,训练一开始就会报文件找不到的错误——这是所有新手必踩的坑。

3. 实操过程:从数据集准备到在线服务发布

这一部分我按自己完整跑通的项目为例,一步步拆解操作流程。项目背景是:要对一批工业零件图片做缺陷分类,类别有“正常”“划痕”“裂纹”“脏污”四类,数据量大约 8000 张图片,每类 2000 张。模型使用 ResNet50 预训练权重,在 ModelArts 上完成迁移学习训练,最后部署成在线服务。

3.1 数据准备:上传到 OBS 并做目录规划

动手第一步不是写代码,而是把数据规整好。我在本地把数据集按类别分好了目录,确保目录结构是:

dataset/ ├── train/ │ ├── normal/ # 2000张 │ ├── scratch/ # 2000张 │ ├── crack/ # 2000张 │ └── stain/ # 2000张 └── val/ ├── normal/ # 200张 ├── scratch/ # 200张 ├── crack/ # 200张 └── stain/ # 200张

然后通过 OBS 控制台创建了一个桶(bucket),名字叫modelarts-learning,在里面建了dataset/目录,把训练集和验证集传上去。

这里有一个细节值得特别提醒:图片数据集的目录结构最好保持“一个类别一个文件夹”,因为像 ImageFolder 这样的 PyTorch 数据加载类,默认就是按子目录名作为类别标签的。如果你把所有图片堆在一个文件夹里,然后靠一个 CSV 文件标注类别,也不是不行,但你在代码里要额外写数据读取逻辑,而且 ModelArts 的很多内置算法规范也是按目录结构来的。所以,多花五分钟整理目录,后面会省很多事。

3.2 编写训练脚本:处理路径映射和模型保存

这是整个过程中最容易出错的地方。我用的算法是自定义脚本,代码结构如下:

train_src/ ├── main.py ├── config.py └── requirements.txt

main.py的核心逻辑大概是:

import argparse import os import torch import torch.nn as nn from torch.utils.data import DataLoader from torchvision import datasets, transforms, models def parse_args(): parser = argparse.ArgumentParser() parser.add_argument('--data_url', type=str, required=True, help='训练数据路径') parser.add_argument('--train_url', type=str, required=True, help='模型输出路径') parser.add_argument('--epochs', type=int, default=50) parser.add_argument('--batch_size', type=int, default=32) parser.add_argument('--learning_rate', type=float, default=0.001) return parser.parse_args() def main(): args = parse_args() # 注意:ModelArts 会把远程 OBS 路径映射到本地 /home/work/user-job-dir/... local_data_path = '/home/work/user-job-dir/' transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) train_dataset = datasets.ImageFolder(os.path.join(args.data_url, 'train'), transform=transform) val_dataset = datasets.ImageFolder(os.path.join(args.data_url, 'val'), transform=transform) train_loader = DataLoader(train_dataset, batch_size=args.batch_size, shuffle=True, num_workers=4) val_loader = DataLoader(val_dataset, batch_size=args.batch_size, shuffle=False, num_workers=4) model = models.resnet50(pretrained=True) num_ftrs = model.fc.in_features model.fc = nn.Linear(num_ftrs, 4) # 4类 criterion = nn.CrossEntropyLoss() optimizer = torch.optim.SGD(model.parameters(), lr=args.learning_rate, momentum=0.9) device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') model = model.to(device) for epoch in range(args.epochs): model.train() running_loss = 0.0 for inputs, labels in train_loader: inputs, labels = inputs.to(device), labels.to(device) optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() * inputs.size(0) epoch_loss = running_loss / len(train_dataset) print(f'Epoch [{epoch+1}/{args.epochs}] Loss: {epoch_loss:.4f}') # 验证 model.eval() correct = 0 total = 0 with torch.no_grad(): for inputs, labels in val_loader: inputs, labels = inputs.to(device), labels.to(device) outputs = model(inputs) _, predicted = torch.max(outputs, 1) total += labels.size(0) correct += (predicted == labels).sum().item() val_acc = correct / total print(f'Validation Accuracy: {val_acc:.4f}') # 保存模型 os.makedirs(args.train_url, exist_ok=True) torch.save(model.state_dict(), os.path.join(args.train_url, 'model.pth')) print('模型已保存') if __name__ == '__main__': main()

这段代码有几个关键点值得仔细讲。

参数解析与路径映射。args.data_url和args.train_url是 ModelArts 平台在创建训练任务时,通过启动命令传进来的参数。你在页面上填的“训练数据路径”最终会变成--data_url s3://modelarts-learning/dataset/这样的启动参数,而“模型输出路径”会变成--train_url s3://modelarts-learning/output/。但这里有个坑:你的代码在容器里运行时,不能直接用s3://路径去读取文件,因为本地文件系统根本不认识这个协议。实际上 ModelArts 平台会把这些 OBS 路径自动映射(或者说挂载/下载)到容器本地,所以你代码里读到的args.data_url其实是一个本地路径,比如/home/work/user-job-dir/或/root/dataset/。具体映射到哪,不同版本可能有差异,最保险的方式就是在训练脚本第一行打印出来看实际值。

我当时第一次跑的时候,直接用了s3://路径去调用datasets.ImageFolder,结果报错说找不到文件。后来在代码里加了print(args.data_url),才发现平台已经把路径转成了本地地址,直接用就行。这个排查过程虽然耗时,但搞清楚之后后面就很顺了。

模型保存路径的处理。同理,torch.save()也不能直接把模型写到s3://下,必须写到本地映射路径。我代码里os.makedirs(args.train_url, exist_ok=True)的作用就是确保这个目录存在,然后torch.save(model.state_dict(), os.path.join(args.train_url, 'model.pth'))会把模型保存到本地挂载路径,训练任务结束后平台会自动把整个目录同步回 OBS 指定的位置。如果你在脚本里忘了加os.makedirs,并且那个目录刚好不存在,torch.save就会报错——另外如果你写的路径不是平台约定的输出目录,模型文件根本不会被同步回去,等于白训练。

数据集加载方式。datasets.ImageFolder这个类要求数据目录组织形式为“父目录/类别子目录/图片文件”。前面我说过我们上传的数据结构是dataset/train/normal/、dataset/train/scratch/这样的,所以这里os.path.join(args.data_url, 'train')就是正确的训练目录。它的返回对象会按子目录名称自动生成索引,class_to_idx属性可以看到真实对应关系。如果训练过程中发现类别顺序和预期不符,一定要检查这个映射。

预训练模型的使用。models.resnet50(pretrained=True)会从 torchvision 缓存中加载预训练权重。这个预训练权重在哪里、是否能下载成功,取决于镜像是否预装以及网络情况。ModelArts 的官方 PyTorch 镜像里通常会预置好这些常用权重,所以一般不用自己下载。但如果这个环节报网络错误或者找不到权重,可以在本地先运行一次同样的代码把权重缓存准备好,然后上传到 OBS,再在脚本里指定权重文件路径。我和不少朋友交流时发现,这种做法在网络受限或者镜像不完整的情况下相当实用。

3.3 创建训练任务:关键配置逐项说明

脚本准备好后,把它和数据集都上传到 OBS,然后进入 ModelArts 控制台,创建训练任务。这部分的配置项较多,我按实际操作中填写的思路逐项说明。

训练名称:建议命名规范一些,比如industrial-defect-resnet50-v1,这样后续模型版本多了之后不容易混乱。我见过有人用“test1”“final2”这样命名的,两周后自己都分不清哪个是哪个。

算法来源:选择“自定义”。这里要解释一下,如果选了“算法管理”里的官方算法,那你一般不需要准备代码目录,只需要指定数据路径和超参数;如果像我一样用自己的脚本,就一定要选“自定义”,然后在“代码目录”里填入包含main.py的 OBS 路径,比如s3://modelarts-learning/train_src/,在“启动文件”里填main.py。

训练输入:这里配置data_url参数对应的值,我填的是s3://modelarts-learning/dataset/,并把参数名设置为data_url。如果你的脚本里有多个输入(比如训练集和验证集分开),可以添加多组输入,但要注意与代码参数一一对应。这里有个小技巧:即使你需要多个输入路径,也可以在代码里统一从一个路径读取下面不同的子目录,这样界面配置会更简洁。我这次就是统一用data_url指向dataset/,然后在脚本里拼train和val。

训练输出:配置train_url参数对应的值,填s3://modelarts-learning/output/,参数名train_url。平台会在任务结束后,把容器中该路径下的内容自动回传到 OBS。值得注意的是,这个路径不一定需要提前在 OBS 上建好,平台会自动创建。

资源池:ModelArts 里最常用的有两种选择——按需计费的公共资源池和专属资源池。按需计费简单灵活,但高峰期可能排队;如果有稳定的训练需求,专属资源池相当于你预定了固定规格的资源,训练任务基本能做到即开即跑。我这次因为只是学习和验证,选择了按需计费的公共资源池中的GPU: 1*V100(16GB) | CPU: 8核 | 内存: 32GB。这个规格对 ResNet50 跑 8000 张图片的数据集绰绰有余。

启动命令:这段是核心。我填的是:

python main.py --data_url ${data_url} --train_url ${train_url} --epochs 50 --batch_size 32 --learning_rate 0.001

注意--data_url和--train_url后面的值,不是直接写 OBS 路径,而是用${data_url}、${train_url}这种占位符。平台在启动任务时会自动把这两个变量替换成你在“训练输入/训练输出/”里配置的实际路径值。如果你写成固定路径,虽然也能跑,但一旦换个数据集就要改脚本,很不灵活。

超参数:有些开发者在界面上配置超参数,而不是写在启动命令里。这种做法的意思是,在“超参数”区域里填写的键值对会自动以--key value的形式附加到启动命令后面。我试过两种方式,都能正常工作,但建议选其中一种保持一致——如果你在启动命令里写了--epochs 50,又在超参区域里写了一个epochs: 60,那么最终生效的是哪一个取决于平台的拼接逻辑,这种不一致很容易造成困惑。我在实践中统一把所有超参放在启动命令里,这样在训练日志中一眼就能看到完整命令,排查问题更方便。

3.4 任务运行与日志观察

创建任务后,点击“提交”,训练任务就进入排队状态,然后变为运行中。这个环节的关键是学会看日志。

训练日志页面会实时打印main.py里print出来的内容。我最常在日志里确认几件事:args.data_url实际解析到的本地路径对不对;每个 epoch 的 loss 是否在下降;验证集准确率是否合理;最后模型是否打印了“模型已保存”。

有一个现实情况需要提醒:公共资源池在高峰时段排队时间可能比较长,从几分钟到半小时不等。如果此时你等了很久任务仍是“创建中”,不用着急,这是正常现象。假如一小时后还是创建中,可以考虑换成其他可用区或者不同规格的资源池再试试。

我这次训练过程比较顺利,50 个 epoch 大约跑了 40 分钟,最终验证准确率 96.5%。日志中每个 epoch 的 loss 从最初的 1.2 左右逐步降到 0.1 以下,准确率稳定提升,说明模型在学习。这个表现对四分类工业缺陷图像来说已经够用了——如果数据量更大、类别更复杂,可以进一步进行超参数调优或者改用更深的网络结构。

3.5 模型注册与版本管理

训练完成后,在“训练任务”列表里点击任务名称,可以看到输出文件列表,model.pth应该在那里。下一步是把这个模型注册到 ModelArts 的“模型管理”中,注册完后才能用于部署。

进入“AI 应用管理 -> 模型管理”页面,点击“创建模型”。这里有一个比较关键的选择:“元模型来源”选择“从训练任务中选择”。然后选择刚才的训练任务,平台会自动读取输出目录下的模型文件。如果你训练脚本里还额外生成了推理代码或配置文件,可以一并指定。

创建模型时要填写“推理代码”和“配置文件”,也就是所谓的模型包。ModelArts 默认会生成一套模板,其中customize_service.py负责定义推理逻辑,config.json定义模型文件路径和推理环境。继承父类后,你需要实现_preprocess和_postprocess方法,把外部请求的图片数据转换成模型输入,再把模型输出转换成可返回的结果。

我用的模型包结构大致是:

model_repo/ ├── customize_service.py ├── config.json └── model.pth

customize_service.py的核心代码如下:

import torch import torch.nn as nn import torchvision.transforms as transforms from PIL import Image import io from models import resnet50 # 注意这里要能 import 到模型定义 from model_service.model_service import ModelService class MyService(ModelService): def __init__(self, model_path, model_name): super().__init__(model_path, model_name) self.model = resnet50(num_classes=4) self.model.load_state_dict(torch.load(model_path, map_location='cpu')) self.model.eval() self.transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) def _preprocess(self, data): # data 是平台解析后的请求体,通常是 {'images': [base64字符串]} images = [] for k, v in data.items(): for img_str in v: img_data = base64.b64decode(img_str) img = Image.open(io.BytesIO(img_data)).convert('RGB') img = self.transform(img) images.append(img) return torch.stack(images) def _postprocess(self, data): # data 是模型输出张量 prob = torch.nn.functional.softmax(data, dim=1) _, pred = torch.max(data, 1) pred = pred.tolist() prob = prob.tolist() results = [] for i in range(len(pred)): results.append({'pred_label': pred[i], 'prob': prob[i]}) return results

这段代码里最需要注意的坑是:加载模型时的类定义要与训练时一致。我训练脚本里用的是models.resnet50,推理脚本里也要能用同样的方式构建模型结构。如果customize_service.py里没有正确导入模型定义,load_state_dict就会报参数不匹配的错误。常见的解决办法有两类:要么在推理代码里重建一个和训练时完全一样的模型类,要么把训练好的完整模型(包括网络结构定义)打包上传。我个人经验是,单独上传一个模型文件到 OBS 再在推理代码里构建模型结构会更灵活,因为推理代码和模型文件分离,更新环境时不会互相干扰。

3.6 创建在线服务并测试

模型注册完成后,在“在线服务”页面点击“部署”,选择你刚注册的模型版本。这一步有几个配置:

服务名称:建议和模型名称关联,比如industrial-defect-service。

资源规格:这里要注意,如果模型推理是 CPU 就够用,可以选择 CPU 规格省钱;如果需要 GPU 加速推理(比如模型推理很慢),才选择 GPU 规格。我这次因为是四分类 ResNet50,图片 224x224,CPU 推理单张大约 100 毫秒,所以直接用CPU: 2核 | 4GB的规格就够了。如果用了 GPU,费用会明显上升,所以不需要时就别选。

实例数量:默认 1 即可。如果想提高并发能力,再增加实例数量,平台会自动做负载均衡。这里我提醒一句,创建在线服务后,平台会自动分配一个公网访问地址,并且提供 API 调试页面。你可以直接上传一张测试图片,调一个接口,看返回结果是否正确。我当时传了几张不同类别的图片,响应体返回了预测类别索引和概率,验证逻辑完全正确。

4. 常见问题与排查技巧实录

这一节我把实际操作中遇到过的、以及在社区里经常看到的问题做个汇总,每一条都是真实踩坑后的经验总结。

4.1 训练任务报“找不到数据文件”

症状:日志里报FileNotFoundError,说某个路径下找不到图片。

排查思路:

  • 首先确认你填写的训练输入路径是否正确,有没有包含完整的目录层级。我遇到过有人把路径写到dataset/train/而不是dataset/,导致脚本里os.path.join(args.data_url, 'train')拼出来的路径变成了dataset/train/train/。
  • 然后在脚本里打印args.data_url,看平台映射后到底指向本地哪个目录,再用os.listdir()列出里面有哪些文件。
  • 最后确认数据集目录结构是否和代码中期望的一致——最常见的错误是漏了一层子目录。

4.2 日志中损失值一直是 NaN

症状:第一个 epoch loss 就是nan,后面一直nan。

排查思路:

  • 最常见原因是学习率设置太高。
  • 其次是数据中没有做归一化,或者数据本身包含异常值(比如像素值为 0 的空白图片)。
  • 还有一种情况是模型输出维度与标签类别数不一致,比如标签有 5 类但模型最后全连接层输出是 4。

我自己的经验是,如果数据预处理用了transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),那一般不会有数值稳定问题。如果你做的是自定义数据增强,强烈建议在训练前先单独跑一次数据加载流程,检查有没有 NaN 数据。

4.3 部署成功后请求接口返回 500

症状:在线服务状态显示“运行中”,但调用接口返回 500。

排查思路:

  • 首先看“日志”页面,找到具体的报错信息。常见的是推理代码中_preprocess解析请求数据时出了问题。
  • 其次确认请求体格式。ModelArts 在线服务的默认输入格式是 JSON,图片字段一般是 base64 编码的字符串。如果你的请求体直接给了图片二进制,而代码里按 base64 解码,那肯定报错。
  • 还有一个隐蔽问题是map_location。如果模型是在 GPU 上训练的,加载到 CPU 推理时务必加map_location='cpu',否则 PyTorch 会尝试在 CPU 环境加载 CUDA 张量,直接报错。我在测试部署时踩过这个坑,第一次部署的实例就是 GPU 训练、CPU 推理,日志里报了 CUDA driver 相关的错误,非常容易绕晕。

4.4 训练任务提示“超时”或“资源不足”

这个现象通常出现在公共资源池高峰期。其实不只是训练任务,在线服务的启动也可能遇到类似情况。解决方法是错峰运行,或者换一个资源规格。如果项目周期长,建议创建专属资源池,虽然费用高一些但稳定性和效率明显更好。

4.5 模型输出结果完全错误

这个问题的根本原因通常是类别顺序不一致。在ImageFolder中,类别的索引是基于子目录名称排序后生成的,比如['crack', 'normal', 'scratch', 'stain']。如果你的推理代码里期望的分类顺序是['normal', 'scratch', 'crack', 'stain'],那么预测结果全部错位。我的建议是训练时把dataset.class_to_idx打印出来保存好,部署时参照它来确定输出类别名称,这样两个环节就对齐了。

5. 模型优化路径与后续扩展思路

训练跑通只是第一步,后面还有大量可以优化的空间,这里分享几个我觉得值得深入的方向。

超参数调优。这次实验里我直接使用了 0.001 的学习率,验证准确率已经不错了。但如果你发现模型欠拟合或过拟合,可以尝试学习率衰减策略,或者用余弦退火调度器。ModelArts 支持在训练任务里直接加载多个超参数组合,不过对大多数场景来说,先手动调几组就够用了。

数据增强与迁移学习策略。我们这次用的 ResNet50 预训练权重,训练时整体微调了所有层。如果数据量很小(比如每类只有几百张),更稳妥的做法是冻结前面若干层的参数,只训练最后一两层。这样能防止过拟合,也能显著降低训练时间。在requires_grad = False和requires_grad = True之间切换,是 PyTorch 里很常规的操作。

自动学习功能。如果你完全不想写代码,ModelArts 还提供了一个叫“自动学习”的功能,你只需要上传标注好的数据,平台自动帮你做模型选择、训练、调参和部署。它的本质是 AutoML 技术,对入门用户非常友好。但如果你希望精细化控制模型结构、超参数、训练逻辑,那还是走自定义脚本这条路,自由度大得多。

监控与告警。ModelArts 任务页面会显示资源使用率、GPU 利用率、内存占用等监控指标。训练跑得很快但不收敛,可以去看 GPU 利用率是不是一直很低——这种现象可能意味着数据加载速度跟不上训练速度,也就是 CPU 成了瓶颈。解决办法是增大num_workers,或者把数据预处理在 GPU 空闲时用进程池并行处理。

镜像与依赖管理。如果你的训练脚本用到了第三方库,比如albumentations或timm,可以在代码目录中放一个requirements.txt,平台会在启动训练任务时自动安装这些依赖。我一开始不知道这个机制,把库的安装命令写在了main.py里面,导致每次训练都重复安装,白白增加启动时间。后来改成requirements.txt,平台在容器创建阶段就会处理依赖,启动速度快了很多。

6. 个人经验总结:最值得记住的几条建议

最后再写几条从实际项目中沉淀下来的经验,与其说它们是技巧,不如说是一些避坑原则。

第一,先小规模验证,再全量训练。我第一次训练时直接上了全部 8000 张图片,结果脚本在第一个 epoch 就报错了,白白等了十几分钟排队和启动时间。后来我改成先创建一个小数据集(每类 20 张),用同样的代码验证流程跑通,再去跑全量。这个习惯后来帮我节约了大量时间和精力——不只是 ModelArts,任何平台训练任务都适用这条铁律。

第二,训练脚本里多打印、多记录。平台日志就是你唯一的诊断工具,所以print语句不要省。我当时在每个关键步骤都打了日志:路径解析结果、数据集类别映射、每轮 loss、验证准确率、保存成功的提示。这些日志在后续排查问题时提供了极大帮助。甚至可以顺手把每个 epoch 的指标写到本地文件里,训练结束后从 OBS 下载分析曲线。

第三,模型注册与部署时,推理代码的模型结构一定要和训练时完全一致。这个坑最隐蔽,也最让人头疼。如果你训练时用了自定义网络结构,最好把它单独保存为.py文件一起打包到模型目录中;推理代码里直接从这个文件中导入模型类。这样就不会出现版本不一致的问题。如果你训练时用的是官方预训练模型,也建议在推理代码里写清楚版本号(比如resnet50是 torchvision 哪个版本的实现),避免不同机器上的模型定义有细微差异。

第四,理解计费逻辑。ModelArts 的公共资源池按实际使用时间计费,从任务提交到结束都算时间。如果你的训练任务排队了 10 分钟,那这 10 分钟也要计费。所以提交任务前确认代码正确、数据路径正确,能有效节省费用。在线服务只要处于“运行中”状态就持续计费,测试完毕后记得立即停止服务,否则一晚上几十块钱就出去了。我身边确实有人因为忘记关服务,月底账单突然翻倍,这个教训可以说相当深刻。

第五,平台版本和文档更新频繁,操作要以当前控制台为准。华为云很多功能入口的位置、名称在不同时期可能有调整,搜索结果里的截图教程可能会过期。在动手前,先花两分钟看看当前页面上各类按钮的分布,比对着老教程反复摸索要高效得多。

ModelArts 给我的整体感受是:它在平衡“易用性”和“灵活性”这方面做得比较到位。对小白来说,自动学习功能可以帮你快速上手;对专业开发者来说,自定义脚本模式又保留了足够的自由空间。通过这一整套流程,我把一个本地需要大量环境配置的项目,顺利迁移到了云端,让模型训练和部署变得更规范化、可复现。希望这篇笔记能帮你减少一些踩坑时间,更快地跑通自己的第一个 ModelArts 训练任务。如果你在实操中遇到其他问题,也可以参考我之前分享过的排查思路——本质上都是先缩小范围、再看日志、再验证,这九个字能解决大多数平台使用中的疑难杂症。

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

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

立即咨询