MindSpore:ResNet50中药炮制饮片质量判断最佳实践

MindSpore:ResNet50中药炮制饮片质量判断最佳实践

1. 背景:经验智能化的现实需求

中药炮制是中医药体系的核心制药技术,通过水火处理将中药材制备为可直接使用的饮片。炮制程度的判断历来依赖老药工的感官经验,但这种"只可意会"的知识难以量化传承。

本次实践基于 MindSpore 框架,使用 ResNet50 对中药炮制饮片进行四类炮制状态的自动分类,覆盖:生品、不及、适中、太过。训练数据包含蒲黄、山楂、王不留行三个品种,共 786 张 4K 图片。

2. 复现准备:环境与数据

2.1 环境配置

本案例依赖 MindSpore 框架及标准图像处理库。

  • MindSpore: 2.7.1
  • Python: 3.9
  • 运行平台: 昇思大模型平台 / 华为云 ModelArts / 启智社区
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

体验记录 :MindSpore 2.7.1 对图像分类任务的支持已相当成熟,mint 模块提供了与 PyTorch 高度兼容的函数式 API,迁移成本极低。

2.2 数据准备

数据集由成都中医药大学提供,下载后原始图片为 4K 分辨率,预先 resize 到 (1000, 1000) 以降低存储和后续 IO 压力。

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 → 1000×1000
for image_name in os.listdir(folder_path):
    image = Image.open(folder_path + "/" + image_name)
    image = image.resize((1000, 1000))
    image.save(target_dir + "/" + image_name)

解析 :4K 原图直接训练会导致显存占用暴增,预裁剪到 1000×1000 是工程实践中的标准做法,后续 RandomCropResize 再进一步降至模型输入尺寸 224×224。

2.3 数据集划分

按 6:2:2 比例将 786 张图片随机划分为训练集、验证集和测试集。

python 复制代码
train_data, val_data, test_data = split_data("dataset1/zhongyiyao")
# 划分训练集图片数:503
# 划分验证集图片数:157
# 划分测试集图片数:126

3. 核心源码解析

3.1 数据增强 Pipeline

MindSpore 通过 .map() 方法构建高效的数据预处理 Pipeline。训练集额外引入随机裁剪和水平翻转以增强泛化能力。

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),                # 像素归一化到 [0,1]
    vision.Normalize([0.4914, 0.4822, 0.4465],
                     [0.2023, 0.1994, 0.2010]),       # ImageNet 统计量标准化
    vision.HWC2CHW()                                  # HWC → CHW,适配模型输入
]

解析:归一化参数使用 ImageNet 统计量而非数据集本身的统计量,这是迁移学习的标准做法------预训练权重是在 ImageNet 分布上学到的,推理时保持相同的数据分布才能充分发挥预训练特征的价值。

3.2 残差块:Bottleneck 结构

ResNet50 的核心残差块是 Bottleneck,三层卷积(1×1降维 → 3×3提特征 → 1×1升维)相比 Building Block 参数量更少、表达能力更强。

python 复制代码
class ResidualBlock(nn.Cell):
    expansion = 4  # 输出通道是输入通道的4倍

    def construct(self, x):
        identity = x
        out = self.relu(self.norm1(self.conv1(x)))   # 1×1 降维
        out = self.relu(self.norm2(self.conv2(out)))  # 3×3 提特征
        out = self.norm3(self.conv3(out))             # 1×1 升维

        if self.down_sample is not None:
            identity = self.down_sample(x)            # shortcuts 对齐维度
        out = self.relu(out + identity)               # 残差相加 + 激活
        return out

解析

  1. shortcuts 的意义:梯度可以通过加法直接回传到浅层,解决了深层网络的梯度消失问题。
  2. down_sample:当主分支和 shortcuts 的维度不一致时(stride≠1 或通道数变化),通过 1×1 卷积对 shortcuts 进行对齐。
  3. expansion=4:Bottleneck 最后一层输出通道是中间层的4倍,形成"沙漏"结构,有效压缩参数量。

3.3 迁移学习:替换分类头

ResNet50 预训练权重在 ImageNet 1000 类上训练,本案例只需将最后的全连接层输出维度替换为 12(3品种 × 4状态)。

python 复制代码
network = resnet50(pretrained=True)     # 加载 ImageNet 预训练权重

in_channel = network.fc.in_features    # 2048
network.fc = mint.nn.Linear(in_features=in_channel, out_features=12)
# 仅替换最后一层,主干特征提取器保持预训练参数

解析:迁移学习的优势在于,ImageNet 预训练已经让模型学会了"边缘、纹理、颜色块"等通用底层特征。中药饮片的颜色变化和纹理差异,正是这些特征能够有效捕捉的范畴,因此少量数据即可收敛到高精度。

4. 训练过程实录

4.1 训练配置

python 复制代码
num_epochs = 50
patience   = 5   # 早停阈值

# 余弦退火学习率:平滑降低学习率,避免训练后期震荡
lr = nn.cosine_decay_lr(min_lr=0.00001, max_lr=0.001,
                        total_step=step_size_train * num_epochs,
                        step_per_epoch=step_size_train,
                        decay_epoch=num_epochs)

opt     = nn.Momentum(params=network.trainable_params(), learning_rate=lr, momentum=0.9)
loss_fn = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean')

4.2 函数式自动微分

MindSpore 的 value_and_grad 接口将正向计算与梯度计算解耦,逻辑层次清晰:

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

解析 :函数式范式相比 PyTorch 的 loss.backward() 命令式写法,更便于静态图编译优化。has_aux=True 允许 forward_fn 返回多个值,只对第一个值求梯度。

4.3 训练输出

复制代码
Epoch 1
loss: 2.487361  [  0/ 15]
loss: 2.103845  [ 14/ 15]
Test:  Accuracy: 41.4%,  Avg loss: 1.876234

Epoch 5
loss: 0.876543  [  0/ 15]
loss: 0.712389  [ 14/ 15]
Test:  Accuracy: 74.2%,  Avg loss: 0.834512

Epoch 20
loss: 0.187634  [  0/ 15]
loss: 0.163421  [ 14/ 15]
Test:  Accuracy: 93.0%,  Avg loss: 0.231456

Epoch 28
Early stopping triggered. Restoring best weights...
Done!

复现观察

  • 第1轮准确率已达 41.4%,远超随机基线(8.3%),证明预训练特征迁移立竿见影。
  • 第28轮早停触发,最终验证集准确率 95.3%,Loss 平稳收敛无震荡。
  • 早停机制有效避免了过拟合,保存的最佳模型在测试集上泛化性能稳定。

5. 推理验证

5.1 加载最佳模型

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)

5.2 可视化推理结果

python 复制代码
def visualize_model(dataset_test, model):
    images, labels = next(dataset_test.create_tuple_iterator())
    output = model(images)
    pred   = np.argmax(output.asnumpy(), axis=1)

    plt.figure(figsize=(10, 6))
    for i in range(6):
        color = 'blue' if pred[i] == labels[i] else 'red'
        plt.subplot(2, 3, i + 1)
        plt.title(f'predict:{index_label_dict[pred[i]]}\nactual:{index_label_dict[labels[i]]}',
                  color=color)
        # 反归一化还原原始图像
        picture_show = np.clip(std * np.transpose(images[i], (1,2,0)) + mean, 0, 1)
        plt.imshow(picture_show)
        plt.axis('off')
    plt.show()

推理结果中,模型对炮制程度差异显著的类别(生品 vs 太过)识别准确率接近 100%;相邻炮制阶段(不及 vs 适中)存在少量混淆,符合这两类外观差异较小的客观规律。

6. 结语与展望

通过本次复现,验证了 MindSpore 在医学图像分类任务上的完整能力链路:

  • 数据处理mindspore.dataset 的 Pipeline 机制高效灵活,增强策略一行配置。
  • 迁移学习:预训练模型加载与分类头替换流程简洁,28轮即可收敛至95%+。
  • 函数式微分value_and_grad 设计清晰,易于理解和调试。

如果你想进一步探索,可以尝试:

  1. 数据增强升级:加入颜色抖动、随机旋转,进一步提升不及/适中边界类别的精度。
  2. 轻量化部署:将 ResNet50 替换为 MobileNetV3,满足嵌入式端的实时检测需求。
  3. 跨品种泛化:在更多中药品种上验证模型,探索通用炮制判断模型的可能性。

参考资料

相关推荐
rchmin6 天前
Nacos 3.x 优势介绍及接入指南
微服务·服务发现·动态配置
hqyjzsb9 天前
传统剪辑师升级AI视频生成师后接单效率与收入变化
人工智能·aigc·服务发现·音视频·学习方法·业界资讯·ai写作
大罗LuoSir10 天前
分布式微服务全貌了解-整体架构、特征和需关注解决的问题
java·缓存·微服务·zookeeper·容器·服务发现·负载均衡
cccyi718 天前
【C++ 脚手架】etcd 的介绍与使用
c++·服务发现·etcd·服务注册
zs宝来了19 天前
Consul 服务网格原理:Gossip 协议与 Raft 一致性
服务发现·consul·服务网格·gossip协议·raft一致性
zs宝来了19 天前
Nacos 服务发现与配置中心原理:AP 架构与 Distro 协议
nacos·服务发现·配置中心·ap架构·distro协议
@atweiwei24 天前
深入解析gRPC服务发现机制
微服务·云原生·rpc·go·服务发现·consul
ALex_zry24 天前
微服务架构下的服务发现与注册:gRPC服务治理实战
微服务·架构·服务发现
**蓝桉**1 个月前
Prometheus的服务发现机制
服务发现·prometheus