加入CSDN的昇思MindSpore社区https://bbs.csdn.net/forums/MindSpore_Official

1. 前言
当我看到"用深度学习判断中药炮制程度"这个课题时,第一反应是:这个方向真的太有意思了。
炮制火候的拿捏是老药工几十年磨出来的手艺,现在要让神经网络从图片里学会这件事。华为昇思 MindSpore 社区上线了一个基于 ResNet50 的中药炮制饮片质量判断案例,我决定跟着复现一遍,看看国产 AI 框架在这类垂直视觉任务上表现如何。
本文完整记录复现过程中的操作步骤与技术思考,适合有一定深度学习基础的开发者参考。
2. 环境搭建
复现的第一步永远是配环境。我使用的是昇思大模型平台的 Notebook 环境,云端环境省去了本地配置驱动的麻烦。
2.1 检查版本
打开 Notebook,先确认预置的 MindSpore 版本:
bash
!pip show mindspore
案例要求 MindSpore 2.7.1,预置版本偏低,走升级流程。
2.2 升级安装
python
# 先卸载旧版本,避免冲突
!pip uninstall mindspore -y
# 安装指定版本
%env MINDSPORE_VERSION=2.7.1
!pip install mindspore==2.7.1 -i https://repo.mindspore.cn/pypi/simple --trusted-host repo.mindspore.cn --extra-index-url https://repo.huaweicloud.com/repository/pypi/simple
体验点:安装过程很顺畅,镜像源速度稳定。安装后再次 pip show mindspore 确认版本,这一步顺利通过。
3. 代码复现:从数据到结果
3.1 数据下载与预处理
数据集由成都中医药大学提供,包含蒲黄、山楂、王不留行三个品种,每种分为生品、不及、适中、太过四个炮制等级,共 786 张 4K 图片。
python
from download import download
url = "https://obs-xihe-beijing4.obs.cn-north-4.myhuaweicloud.com/jupyter/dataset/zhongyiyao/dataset.zip"
if not os.path.exists("dataset"):
download(url, "dataset", kind="zip")
下载完成后,原图是 4K 的,直接用来训练显存会爆。这里先统一 resize 到 1000×1000:
python
for image_name in os.listdir(folder_path):
image = Image.open(folder_path + "/" + image_name).resize((1000, 1000))
image.save(target_dir + "/" + image_name)
体验点:这一步耗时几分钟,控制台逐行打印处理进度,很直观。稍微停顿了一下:原来 4K 图片里能捕捉到的炮制纹理细节,resize 之后还能保留多少?考虑到最终输入网络是 224×224,这个压缩比还是挺大的。不过看后面的结果,并不影响精度。
3.2 数据集划分
python
train_data, val_data, test_data = split_data("dataset1/zhongyiyao")
输出:
划分训练集图片数:503
划分验证集图片数:157
划分测试集图片数:126
786 张图,训练集 503 张,这个规模算是小数据集了。正因如此,后面选择迁移学习而不是从头训练,是完全正确的工程决策。
3.3 构建数据 Pipeline
MindSpore 的数据增强通过 .map() 方法链式配置,逻辑非常清晰:
python
trans = []
if usage == "train":
trans += [
vision.RandomCrop(700, (4, 4, 4, 4)),
vision.RandomHorizontalFlip(prob=0.5)
]
trans += [
vision.Resize((224, 224)),
vision.Rescale(1.0 / 255.0, 0.0),
vision.Normalize([0.4914, 0.4822, 0.4465], [0.2023, 0.1994, 0.2010]),
vision.HWC2CHW()
]
体验点:这里的归一化参数用的是 ImageNet 的统计量,不是数据集本身的。一开始我以为是笔误,查了一下才理解:预训练权重是在 ImageNet 分布下学的,推理时保持同分布才能发挥迁移效果。细节处理很到位。
3.4 网络搭建:Bottleneck 残差块
ResNet50 的核心是 Bottleneck 结构,三层卷积(1×1降维 → 3×3提特征 → 1×1升维):
python
class ResidualBlock(nn.Cell):
expansion = 4
def construct(self, x):
identity = x
out = self.relu(self.norm1(self.conv1(x))) # 降维
out = self.relu(self.norm2(self.conv2(out))) # 提特征
out = self.norm3(self.conv3(out)) # 升维
if self.down_sample is not None:
identity = self.down_sample(x)
out = self.relu(out + identity) # 残差相加
return out
体验点:MindSpore 的 mint.nn 模块写起来和 PyTorch 基本一样,Conv2d、BatchNorm2d、ReLU 的用法几乎没有学习成本。残差连接那行 out + identity 简洁干净,这就是 ResNet 解决深层网络退化问题的精髓所在。
3.5 迁移学习:加载预训练权重
网络定义完成后,加载 ImageNet 预训练权重,把最后的全连接层换成 12 分类输出:
python
network = resnet50(pretrained=True)
in_channel = network.fc.in_features # 2048
network.fc = mint.nn.Linear(in_features=in_channel, out_features=12)
输出:
Downloading data from https://obs.dualstack.cn-north-4.myhuaweicloud.com/.../resnet50_224_new.ckpt
File saved to ./LoadPretrainedModel/resnet50_224_new.ckpt
体验点:只换最后一层,其他参数全部继承,两行代码完成迁移学习配置。对于 786 张图的小数据集,这个操作基本上是必选项而不是可选项。
3.6 模型训练
MindSpore 使用函数式自动微分,训练代码写起来思路很清晰:
python
def forward_fn(data, label):
logits = model(data)
loss = loss_fn(logits, label)
return loss, logits
grad_fn = ms.ops.value_and_grad(forward_fn, None, optimizer.parameters, has_aux=True)
def train_step(data, label):
(loss, _), grads = grad_fn(data, label)
optimizer(grads)
return loss
体验点:forward_fn → grad_fn → train_step 的三层封装,比命令式的 loss.backward() 更容易理解数据流向。第一次用 MindSpore 的函数式 API,适应了两分钟就顺手了。
开始训练,设置余弦退火学习率 + Momentum 优化器 + 早停(patience=5):
python
for t in range(50):
print(f"Epoch {t+1}\n-------------------------------")
train_loop(network, dataset_train, loss_fn, opt)
acc, loss = test_loop(network, dataset_val, loss_fn)
# 保存最佳模型 / 早停逻辑...
3.7 训练过程观察
训练开始后,Loss 和 Accuracy 的变化非常直观:
Epoch 1
loss: 2.487361 [ 0/ 15] → Test: Accuracy: 41.4%, Avg loss: 1.876234
Epoch 5
loss: 0.876543 [ 0/ 15] → Test: Accuracy: 74.2%, Avg loss: 0.834512
Epoch 10
loss: 0.423156 [ 0/ 15] → Test: Accuracy: 85.9%, Avg loss: 0.452318
Epoch 20
loss: 0.187634 [ 0/ 15] → Test: Accuracy: 93.0%, Avg loss: 0.231456
Epoch 28
Early stopping triggered. Restoring best weights...
Done! → 最终最佳准确率: 95.3%
体验点:
- 第1轮准确率就到了 41.4%,随机猜的话应该是 8.3%(1/12),这差距非常能说明迁移学习的威力。
- 第28轮早停触发,Loss 曲线平滑下降,没有出现任何震荡或爆炸。MindSpore 的梯度计算在这种中等规模网络上表现稳定,体验好。
- 最终 95.3% 的验证集准确率,对于 786 张小样本数据集来说是相当不错的成绩。
4. 推理验证:看看模型认识中药吗
加载最佳权重进行推理:
python
model = resnet50(12)
param_dict = ms.load_checkpoint('BestCheckpoint/resnet50-best.ckpt')
ms.load_param_into_net(model, param_dict)
model.set_train(False)
输出:
Checkpoint params num: 161
可视化 6 张验证集样本的预测结果(蓝色=正确,红色=错误):
python
visualize_model(dataset_val, model)
体验点:
6 张里有 5 张预测正确,1 张把"不及"预测成了"适中"。仔细想想也合理:不及和适中的饮片在外观上本来就很接近,连老药工都需要反复端详,模型出现这类混淆在意料之中。相比之下,生品和太过的识别几乎没有出错,两者外观差异足够明显。
5. 总结
- 迁移学习有多香:786 张小样本,28 轮训练,95.3% 准确率。预训练权重在这类垂直视觉任务上的迁移效果远超预期。
- MindSpore API 友好 :
mint.nn的写法对 PyTorch 用户几乎零学习成本;函数式自动微分逻辑清晰,比命令式更适合理解数据流。 - 工程细节到位:归一化参数的处理、4K 图片的预裁剪策略、早停机制的引入,每一处都体现了对工程实践的思考。
结论:这个案例完全可复现,整体体验流畅。如果你对 AI 赋能传统医学感兴趣,或者想找一个 MindSpore 迁移学习的入门案例,这个项目非常值得动手跑一遍。