From c89d511a59527f6a7c18f3efdb2a95e0c0c8ae50 Mon Sep 17 00:00:00 2001 From: Guangran Zhang Date: Mon, 26 Jan 2026 10:31:16 +0800 Subject: [PATCH 1/2] Delete ai_x/train_resnet_classification.ipynb --- ai_x/train_resnet_classification.ipynb | 1119 ------------------------ 1 file changed, 1119 deletions(-) delete mode 100644 ai_x/train_resnet_classification.ipynb diff --git a/ai_x/train_resnet_classification.ipynb b/ai_x/train_resnet_classification.ipynb deleted file mode 100644 index 8de3a50..0000000 --- a/ai_x/train_resnet_classification.ipynb +++ /dev/null @@ -1,1119 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "530389db", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "# 基于MindSpore的ResNet50模型中药炮制饮片质量判断任务\n", - "\n", - "## 案例介绍\n", - "\n", - "中药炮制是根据中医药理论,依照临床辨证施治用药的需要和药物自身性质,以及调剂、制剂的不同要求,**将中药材制备成中药饮片所采取的一项制药技术**。平时我们老百姓能接触到的中药,主要指自己回家煎煮或者请医院代煎煮的中药,都是中药饮片,也就是中药炮制这个技术的结果。而中药炮制饮片,大部分涉及到水火的处理,一定需要讲究“程度适中”,炮制火候不够达不到最好药效,炮制火候过度也会丧失药效。\n", - "- “生品”一般是指仅仅采用简单净选得到的饮片,通常没有经过火的处理,也是后续用火加工的原料。\n", - "- “不及”就是“炮制不到位”,没有达到规定的程度,饮片不能发挥最好的效果。\n", - "- “适中”是指炮制程度刚刚好,正是一个最佳的炮制点位,也是通常炮制结束的终点。\n", - "- “太过”是指炮制程度过度了,超过了“适中”的最佳状态,这时候的饮片也会丧失药效,不能再使用了。\n", - "\n", - "过去的炮制饮片程度的判断,都是采用的老药工经验判断,但随着老药工人数越来越少,这种经验判断可能存在“失传”的风险。而随着人工智能的发展,使用深度神经网络模型对饮片状态进行判断能达到很好的效果,可以很好的实现经验的“智能化”和经验的传承。" - ] - }, - { - "attachments": { - "7e54cf16-e21b-4975-aa94-381d656950b2.png": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAmIAAAFPCAYAAADujKKTAAAAAXNSR0IArs4c6QAAAARnQU1BAACx\njwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAGHaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8\nP3hwYWNrZXQgYmVnaW49J++7vycgaWQ9J1c1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCc/Pg0KPHg6\neG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyI+PHJkZjpSREYgeG1sbnM6cmRmPSJodHRw\nOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj48cmRmOkRlc2NyaXB0aW9u\nIHJkZjphYm91dD0idXVpZDpmYWY1YmRkNS1iYTNkLTExZGEtYWQzMS1kMzNkNzUxODJmMWIiIHht\nbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIj48dGlmZjpPcmllbnRhdGlv\nbj4xPC90aWZmOk9yaWVudGF0aW9uPjwvcmRmOkRlc2NyaXB0aW9uPjwvcmRmOlJERj48L3g6eG1w\nbWV0YT4NCjw/eHBhY2tldCBlbmQ9J3cnPz4slJgLAABo1klEQVR4Xu3dd3xT1fvA8U+S7lJK2YWW\nVfZUQQWZCt+fiAqoTFkCsocMGYU6oVAUZYgs2SBLQDYUkL33nm1p6YRO6G4zfn900F5ahkTSNs+b\nV16v5jwnt+E2efLcc0/uURkMBgNCCCGEEOKVUysbhBBCCCHEqyGFmBBCCCGEiUghJoQQQghhIlKI\nCSGEEEKYiBRiQgghhBAmIoWYEEIIIYSJSCEmhBBCCGEiUogJIYQQQpiIFGJCCCGEECYihZgQQggh\nhIlIISaEEEIIYSJSiAkhhBBCmIgUYkIIIYQQJiKFmBBCCCGEiUghJoQQQghhIlKICSGEEEKYiBRi\nQgghhBAmIoWYEEIIIYSJSCEmhBBCCGEiUogJIYQQQpiIFGJCCCGEECYihZgQQgghhIlIISaEEEII\nYSJSiAkhhBBCmIgUYkIIIYQQJiKFmBBCCCGEiagMBoNB2fgq+Pn5cffuXWWzEGbL2dmZmjVrKpuF\nyFVAQAA+Pj7KZiHMVsmSJalTp46yOU8zWSHm4eGBp6cnFhYWypAQZker1dKzZ0+WL1+uDAmRKy8v\n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\n" - } - }, - "cell_type": "markdown", - "id": "68406d8c-b9df-449e-9c12-bb89143c9c46", - "metadata": { - "tags": [] - }, - "source": [ - "## 模型简介\n", - "\n", - "ResNet50网络是2015年由微软实验室的何恺明提出,获得ILSVRC2015图像分类竞赛第一名。在ResNet网络提出之前,传统的卷积神经网络都是将一系列的卷积层和池化层堆叠得到的,但当网络堆叠到一定深度时,就会出现退化问题。在CIFAR-10数据集上使用56层网络与20层网络训练,56层网络比20层网络训练误差和测试误差更大,随着网络的加深,其误差并没有如预想的一样减小。\n", - "\n", - "ResNet网络提出了残差网络结构(Residual Network)来减轻退化问题,使用ResNet网络可以实现搭建较深的网络结构(突破1000层)。\n", - "![1.png](attachment:7e54cf16-e21b-4975-aa94-381d656950b2.png)" - ] - }, - { - "cell_type": "markdown", - "id": "137be2b4", - "metadata": {}, - "source": [ - "## 环境准备\n", - "\n", - "本案例的运行环境为:\n", - "\n", - "| Python | MindSpore |\n", - "| :----- | :-------- |\n", - "| 3.9 | 2.7.1 |" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f557f629-ec73-47be-884f-58346fd4bf12", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "# 检查mindspore版本是否为2.7.1,如果不是则取消下个单元格注释进行安装\n", - "!pip show mindspore" - ] - }, - { - "cell_type": "markdown", - "id": "731bb59a-52ff-463a-af06-9ada7f4ded31", - "metadata": {}, - "source": [ - "如果你在如[昇思大模型平台](https://xihe.mindspore.cn/training-projects)、[华为云ModelArts](https://www.huaweicloud.com/product/modelarts.html)、[启智社区](https://openi.pcl.ac.cn/)等算力平台的Jupyter在线编程环境中运行本案例,可取消如下代码的注释,进行依赖库安装:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3eab2c4a", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "# 安装mindspore==2.7.1版本,如需更换mindspore版本,可更改下面 MINDSPORE_VERSION 变量\n", - "# !pip uninstall mindspore -y\n", - "# %env MINDSPORE_VERSION=2.7.1\n", - "# !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" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a87f1302-0261-4d93-a716-6579ee09473f", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "import os\n", - "import random\n", - "import shutil\n", - "import numpy as np\n", - "import mindspore as ms\n", - "import matplotlib.pyplot as plt\n", - "import mindspore.dataset.vision as vision\n", - "import mindspore.dataset.transforms as transforms\n", - "\n", - "from PIL import Image\n", - "from download import download\n", - "from typing import Type, Union, List, Optional\n", - "from mindspore.common.initializer import Normal\n", - "from mindspore.dataset import ImageFolderDataset\n", - "from mindspore import (Tensor, nn, train, mint, context, load_checkpoint, load_param_into_net, ops,)" - ] - }, - { - "cell_type": "markdown", - "id": "13b77923", - "metadata": {}, - "source": [ - "其他场景可参考[MindSpore安装指南](https://www.mindspore.cn/install)进行环境搭建。" - ] - }, - { - "cell_type": "markdown", - "id": "707edc02-9007-45de-8a50-1c02672a6506", - "metadata": {}, - "source": [ - "## 数据加载与预处理\n", - "\n", - "我们使用“中药炮制饮片”数据集,该数据集由成都中医药大学提供,共包含中药炮制饮片的 3 个品种,分别为:蒲黄、山楂、王不留行,每个品种又有着4种炮制状态:生品、不及适中、太过,图片尺寸为4K,图片格式为jpg,共786张图片。" - ] - }, - { - "cell_type": "markdown", - "id": "f3b5736c-9d1e-4b14-a5ef-180ae665279e", - "metadata": {}, - "source": [ - "#### **数据下载**" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fd60d946-63e4-4364-82f6-cfaca6e17ad3", - "metadata": { - "pycharm": { - "name": "#%%\n" - }, - "tags": [] - }, - "outputs": [], - "source": [ - "# 数据集下载链接\n", - "url = \"https://obs-xihe-beijing4.obs.cn-north-4.myhuaweicloud.com/jupyter/dataset/zhongyiyao/dataset.zip\"\n", - "if not os.path.exists(\"dataset\"):\n", - " download(url, \"dataset\", kind=\"zip\")" - ] - }, - { - "cell_type": "markdown", - "id": "bd962595-1e6d-4b85-a79a-2a356788de42", - "metadata": {}, - "source": [ - "#### **数据裁剪**\n", - "原图片尺寸为4k比较大,我们预处理将图片resize到指定尺寸(1000,1000)。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "231fad30-b2d5-4541-8015-1f482fa014e0", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "data_dir = \"dataset/zhongyiyao/zhongyiyao\"\n", - "new_data_path = \"dataset1/zhongyiyao\"\n", - "if not os.path.exists(new_data_path):\n", - " for path in ['train','test']:\n", - " data_path = data_dir + \"/\" + path\n", - " classes = os.listdir(data_path)\n", - " for (i,class_name) in enumerate(classes):\n", - " floder_path = data_path+\"/\"+class_name\n", - " print(f\"正在处理{floder_path}...\")\n", - " for image_name in os.listdir(floder_path):\n", - " try:\n", - " image = Image.open(floder_path + \"/\" + image_name)\n", - " image = image.resize((1000,1000))\n", - " target_dir = new_data_path+\"/\"+path+\"/\"+class_name\n", - " if not os.path.exists(target_dir):\n", - " os.makedirs(target_dir)\n", - " if not os.path.exists(target_dir+\"/\"+image_name):\n", - " image.save(target_dir+\"/\"+image_name)\n", - " except:\n", - " pass" - ] - }, - { - "cell_type": "markdown", - "id": "e3cd1102-4242-4203-9099-99e1fcfb0b5c", - "metadata": {}, - "source": [ - "#### **数据集划分**\n", - "我们将中药炮制饮片数据集划分为训练集、验证集和测试集,并将数据按类别整理到不同的目录中,以便后续模型训练和评估。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "68211057-19b4-4ac4-a38f-ba685acf4414", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "def split_data(data_dir, test_size=0.2, val_size=0.2, random_seed=42):\n", - " random.seed(random_seed)\n", - " folders = ['train', 'test']\n", - " imgs = []\n", - " labels = []\n", - "\n", - " for path in folders:\n", - " data_path = os.path.join(data_dir, path)\n", - " classes = os.listdir(data_path)\n", - " for class_name in classes:\n", - " class_dir = os.path.join(data_path, class_name)\n", - " if not os.path.isdir(class_dir):\n", - " continue\n", - " for img_name in os.listdir(class_dir):\n", - " img_path = os.path.join(class_dir, img_name)\n", - " if os.path.isfile(img_path):\n", - " imgs.append(img_path)\n", - " labels.append(class_name)\n", - "\n", - " data = list(zip(imgs, labels))\n", - " random.shuffle(data)\n", - "\n", - " total = len(data)\n", - " test_size = int(total * test_size)\n", - " val_size = int(total * val_size)\n", - " train_size = total - test_size - val_size\n", - "\n", - " train_data = data[:train_size]\n", - " val_data = data[train_size:train_size+val_size]\n", - " test_data = data[train_size+val_size:]\n", - "\n", - " print(f\"划分训练集图片数:{len(train_data)}\")\n", - " print(f\"划分验证集图片数:{len(val_data)}\")\n", - " print(f\"划分测试集图片数:{len(test_data)}\")\n", - "\n", - " # 创建train, valid, test文件夹并根据划分移动数据\n", - " for split, data_split in zip(['train', 'valid', 'test'], [train_data, val_data, test_data]):\n", - " target_data_dir = os.path.join(data_dir, split)\n", - " if not os.path.exists(target_data_dir):\n", - " os.makedirs(target_data_dir)\n", - "\n", - " for img_path, label in data_split:\n", - " target_label_dir = os.path.join(target_data_dir, label)\n", - " if not os.path.exists(target_label_dir):\n", - " os.makedirs(target_label_dir)\n", - " # 移动图片文件到目标目录\n", - " target_img_path = os.path.join(target_label_dir, os.path.basename(img_path))\n", - " shutil.move(img_path, target_img_path)\n", - " return train_data, val_data, test_data\n", - "\n", - "def create_data_splits(data_dir):\n", - " train_data, val_data, test_data = split_data(data_dir)\n", - " return train_data, val_data, test_data\n", - "\n", - "data_dir = \"dataset1/zhongyiyao\"\n", - "train_data, val_data, test_data = create_data_splits(data_dir)" - ] - }, - { - "cell_type": "markdown", - "id": "68c55815-d08a-4b24-a26b-8deb75f01bea", - "metadata": {}, - "source": [ - "#### **定义数据加载方式**\n", - "在我们将数据喂进模型之前,可以通过MindSpore提供的多种数据变换(Transforms)方法来增强训练数据的多样性、统一数据尺寸,并进行必要的归一化和标准化操作,确保数据在喂入模型前符合要求,提高模型的训练效果。所有这些变换都通过 .map(...)方法在数据加载时被应用,从而实现了整个数据预处理的Pipeline。\n", - "在本案例中我们对数据进行了随机裁剪(RandomCrop)、随机水平翻转(RandomHorizontalFlip)、调整图像尺寸(Resize)、像素值归一化(Rescale)、图像标准化(Normalize)、格式转换(HWC2CHW)等的处理。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "72837cc1-602a-4c7c-8fe3-7a39d6abf952", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "# 如果创建.ipynb_checkpoints文件夹,则删除\n", - "def remove_ipynb_checkpoints(data_dir):\n", - " ipynb_checkpoints_dir = os.path.join(data_dir, '.ipynb_checkpoints')\n", - " if os.path.exists(ipynb_checkpoints_dir):\n", - " print(f\"删除目录: {ipynb_checkpoints_dir}\")\n", - " shutil.rmtree(ipynb_checkpoints_dir)\n", - "\n", - "def create_dataset_zhongyao(dataset_dir, usage, resize, batch_size, workers):\n", - " remove_ipynb_checkpoints(dataset_dir)\n", - " # 使用 ImageFolder 加载数据集\n", - " dataset = ImageFolderDataset(dataset_dir, decode=True)\n", - " trans = []\n", - " if usage == \"train\":\n", - " trans += [\n", - " vision.RandomCrop(700, (4, 4, 4, 4)),\n", - " vision.RandomHorizontalFlip(prob=0.5)\n", - " ]\n", - "\n", - " trans += [\n", - " vision.Resize((resize, resize)),\n", - " vision.Rescale(1.0 / 255.0, 0.0),\n", - " vision.Normalize([0.4914, 0.4822, 0.4465], [0.2023, 0.1994, 0.2010]),\n", - " vision.HWC2CHW()\n", - " ]\n", - "\n", - " target_trans = transforms.TypeCast(ms.int32)\n", - " dataset = dataset.map(\n", - " operations=trans,\n", - " input_columns='image',\n", - " num_parallel_workers=workers\n", - " )\n", - "\n", - " dataset = dataset.map(\n", - " operations=target_trans,\n", - " input_columns='label',\n", - " num_parallel_workers=workers\n", - " )\n", - "\n", - " dataset = dataset.batch(batch_size, drop_remainder=True)\n", - " return dataset" - ] - }, - { - "cell_type": "markdown", - "id": "35c20baa-42cf-41e3-a220-2381fbe1c27f", - "metadata": {}, - "source": [ - "#### **加载数据**\n", - "我们接下来为模型训练准备数据,并设置了一些相关的超参数,同时也确保实验的可复现性。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f75b6284-80d4-4c74-981c-c07641786906", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "data_dir = \"dataset1/zhongyiyao\"\n", - "train_dir = data_dir+\"/\"+\"train\"\n", - "valid_dir = data_dir+\"/\"+\"valid\"\n", - "test_dir = data_dir+\"/\"+\"test\"\n", - "batch_size = 32 # 批量大小\n", - "image_size = 224 # 训练图像空间大小\n", - "workers = 4 # 并行线程个数\n", - "num_classes = 12 # 分类数量\n", - "\n", - "seed = 42\n", - "ms.set_seed(seed)\n", - "np.random.seed(seed)\n", - "random.seed(seed)\n", - "\n", - "dataset_train = create_dataset_zhongyao(dataset_dir=train_dir,\n", - " usage=\"train\",\n", - " resize=image_size,\n", - " batch_size=batch_size,\n", - " workers=workers)\n", - "step_size_train = dataset_train.get_dataset_size()\n", - "\n", - "dataset_val = create_dataset_zhongyao(dataset_dir=valid_dir,\n", - " usage=\"valid\",\n", - " resize=image_size,\n", - " batch_size=batch_size,\n", - " workers=workers)\n", - "dataset_test = create_dataset_zhongyao(dataset_dir=test_dir,\n", - " usage=\"test\",\n", - " resize=image_size,\n", - " batch_size=batch_size,\n", - " workers=workers)\n", - "step_size_val = dataset_val.get_dataset_size()\n", - "\n", - "print(f'训练集数据:{dataset_train.get_dataset_size()*batch_size}\\n')\n", - "print(f'验证集数据:{dataset_val.get_dataset_size()*batch_size}\\n')\n", - "print(f'测试集数据:{dataset_test.get_dataset_size()*batch_size}\\n')" - ] - }, - { - "cell_type": "markdown", - "id": "6c979ccd-477b-491a-8b4c-39bee552ca70", - "metadata": {}, - "source": [ - "#### **类别标签说明**\n", - "由于平台字体问题,无法正确显示中文,这里给出英文标签对应的类别:\n", - "- ph-sp:蒲黄-生品\n", - "- ph_bj:蒲黄-不及\n", - "- ph_sz:蒲黄-适中\n", - "- ph_tg:蒲黄-太过\n", - "- sz_sp:山楂-生品\n", - "- sz_bj:山楂-不及\n", - "- sz_sz:山楂-适中\n", - "- sz_tg:山楂-太过\n", - "- wblx_sp:王不留行-生品\n", - "- wblx_bj:王不留行-不及\n", - "- wblx_sz:王不留行-适中\n", - "- wblx_tg:王不留行-太过" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9e3afe8f-8f23-42f4-8536-94e1c5893c05", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "index_label_dict = {}\n", - "classes = os.listdir(train_dir)\n", - "if '.ipynb_checkpoints' in classes:\n", - " classes.remove('.ipynb_checkpoints')\n", - "for i,label in enumerate(classes):\n", - " index_label_dict[i] = label\n", - "label2chin = {'ph_sp':'蒲黄-生品', 'ph_bj':'蒲黄-不及', 'ph_sz':'蒲黄-适中', 'ph_tg':'蒲黄-太过', 'sz_sp':'山楂-生品',\n", - " 'sz_bj':'山楂-不及', 'sz_sz':'山楂-适中', 'sz_tg':'山楂-太过', 'wblx_sp':'王不留行-生品', 'wblx_bj':'王不留行-不及',\n", - " 'wblx_sz':'王不留行-适中', 'wblx_tg':'王不留行-太过'}\n", - "index_label_dict" - ] - }, - { - "cell_type": "markdown", - "id": "c48cf89d", - "metadata": {}, - "source": [ - "#### **数据可视化**" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "baf82d45-7453-4214-b710-6b6b0643202f", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "data_iter = next(dataset_val.create_dict_iterator())\n", - "\n", - "images = data_iter[\"image\"].asnumpy()\n", - "labels = data_iter[\"label\"].asnumpy()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b3cc1899-836f-43f7-9ac2-156eea0e8f41", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "plt.figure(figsize=(12, 5))\n", - "for i in range(24):\n", - " plt.subplot(3, 8, i+1)\n", - " image_trans = np.transpose(images[i], (1, 2, 0))\n", - " mean = np.array([0.4914, 0.4822, 0.4465])\n", - " std = np.array([0.2023, 0.1994, 0.2010])\n", - " image_trans = std * image_trans + mean\n", - " image_trans = np.clip(image_trans, 0, 1)\n", - " plt.title(index_label_dict[labels[i]])\n", - " plt.imshow(image_trans)\n", - " plt.axis(\"off\")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "id": "740dbcee", - "metadata": {}, - "source": [ - "## 模型构建\n", - "\n", - "#### **创建分类网络**\n", - "处理完数据后,就可以来进行网络的搭建了。我们将最后一层线性映射的输出改为类别数12,以适应数据集。" - ] - }, - { - "cell_type": "markdown", - "id": "c146acee-2cb8-4120-bc18-21b8f250123d", - "metadata": {}, - "source": [ - "残差结构是ResNet网络中最重要的结构,由两个分支构成:一个主分支,一个shortcuts。主分支通过堆叠一系列的卷积操作得到,shortcuts从输入直接到输出,主分支的输出与shortcuts的输出相加后通过Relu激活函数后即为残差网络最后的输出。\n", - "\n", - "残差网络结构主要由两种,一种是Building Block,适用于较浅的ResNet网络,如ResNet18和ResNet34;另一种是Bottleneck,适用于层数较深的ResNet网络,如ResNet50、ResNet101和ResNet152。" - ] - }, - { - "cell_type": "markdown", - "id": "11d4ec89-b80b-47ad-b993-58481fe173c5", - "metadata": {}, - "source": [ - "#### **定义 Building Block**\n", - "Building Block结构的主分支有两层卷积网络结构:\n", - "\n", - "- 主分支第一层网络以输入channel为64为例,首先通过一个3×3的卷积层,然后通过Batch Normalization层,最后通过Relu激活函数层,输出channel为64;\n", - "\n", - "- 主分支第二层网络的输入channel为64,首先通过一个3×3的卷积层,然后通过Batch Normalization层,输出channel为64。\n", - "\n", - "最后将主分支输出的特征矩阵与shortcuts输出的特征矩阵相加,通过Relu激活函数即为Building Block最后的输出。
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1a99595c-3dc3-4733-8bd1-377edfada070", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "class ResidualBlockBase(nn.Cell):\n", - " expansion: int = 1 # 最后一个卷积核数量与第一个卷积核数量相等\n", - " def __init__(self, in_channel: int, out_channel: int,\n", - " stride: int = 1, norm: Optional[nn.Cell] = None,\n", - " down_sample: Optional[nn.Cell] = None) -> None:\n", - " super(ResidualBlockBase, self).__init__()\n", - " if not norm:\n", - " self.norm = mint.nn.BatchNorm2d(out_channel, momentum=0.9)\n", - " else:\n", - " self.norm = norm\n", - " # 一层卷积\n", - " self.conv1 = mint.nn.Conv2d(in_channels=in_channel, out_channels=out_channel,\n", - " kernel_size=3, stride=stride, padding=1, bias=False)\n", - " # 二层卷积\n", - " self.conv2 = mint.nn.Conv2d(in_channels=out_channel, out_channels=out_channel,\n", - " kernel_size=3, padding=1, bias=False)\n", - " # 接ReLU激活函数\n", - " self.relu = mint.nn.ReLU()\n", - " self.down_sample = down_sample\n", - "\n", - " # 定义前向传播\n", - " def construct(self, x):\n", - " \"\"\"ResidualBlockBase construct.\"\"\"\n", - " identity = x\n", - "\n", - " out = self.conv1(x) # 主分支第一层:3*3卷积层\n", - " out = self.norm(out)\n", - " out = self.relu(out)\n", - " out = self.conv2(out) # 主分支第二层:3*3卷积层\n", - " out = self.norm(out)\n", - "\n", - " if self.down_sample is not None:\n", - " identity = self.down_sample(x)\n", - " sum1 = out + identity\n", - " out = sum1 # 输出为主分支与shortcuts之和\n", - " out = self.relu(out)\n", - "\n", - " return out" - ] - }, - { - "cell_type": "markdown", - "id": "41bec72d-540e-4e17-bcfb-d482ae5d1b3a", - "metadata": {}, - "source": [ - "#### **定义 Bottleneck**\n", - "Bottleneck在输入相同的情况下Bottleneck结构相对Building Block结构的参数数量更少,更适合层数较深的网络,ResNet50使用的残差结构就是Bottleneck。该结构的主分支有三层卷积结构,分别为1×1的卷积层、3×3卷积层和1×1的卷积层,其中1×1的卷积层分别起降维和升维的作用。\n", - "\n", - "- 主分支第一层网络以输入channel为256为例,首先通过数量为64,大小为的卷积核进行降维,然后通过Batch Normalization层,最后通过Relu激活函数层,其输出channel为64;\n", - "\n", - "- 主分支第二层网络通过数量为64,大小为的卷积核提取特征,然后通过Batch Normalization层,最后通过Relu激活函数层,其输出channel为64;\n", - "\n", - "- 主分支第三层通过数量为256,大小的卷积核进行升维,然后通过Batch Normalization层,其输出channel为256。\n", - "\n", - "最后将主分支输出的特征矩阵与shortcuts输出的特征矩阵相加,通过Relu激活函数即为Bottleneck最后的输出。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a767e952", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "class ResidualBlock(nn.Cell):\n", - " expansion = 4 # 最后一个卷积核的数量是第一个卷积核数量的4倍\n", - " def __init__(self, in_channel: int, out_channel: int,\n", - " stride: int = 1, down_sample: Optional[nn.Cell] = None) -> None:\n", - " super(ResidualBlock, self).__init__()\n", - "\n", - " self.conv1 = mint.nn.Conv2d(in_channels=in_channel, out_channels=out_channel,\n", - " kernel_size=1, padding=0, bias=False)\n", - " self.norm1 = mint.nn.BatchNorm2d(out_channel, momentum=0.9)\n", - " self.conv2 = mint.nn.Conv2d(in_channels=out_channel, out_channels=out_channel,\n", - " kernel_size=3, stride=stride, padding=1, bias=False)\n", - " self.norm2 = mint.nn.BatchNorm2d(out_channel, momentum=0.9)\n", - " self.conv3 = mint.nn.Conv2d(in_channels=out_channel, out_channels=out_channel * self.expansion,\n", - " kernel_size=1, padding=0, bias=False)\n", - " self.norm3 = mint.nn.BatchNorm2d(out_channel * self.expansion, momentum=0.9)\n", - "\n", - " self.relu = mint.nn.ReLU()\n", - " self.down_sample = down_sample\n", - "\n", - " def construct(self, x):\n", - " identity = x # shortscuts分支\n", - "\n", - " out = self.conv1(x) # 主分支第一层:1*1卷积层\n", - " out = self.norm1(out)\n", - " out = self.relu(out)\n", - " out = self.conv2(out) # 主分支第二层:3*3卷积层\n", - " out = self.norm2(out)\n", - " out = self.relu(out)\n", - " out = self.conv3(out) # 主分支第三层:1*1卷积层\n", - " out = self.norm3(out)\n", - "\n", - " if self.down_sample is not None:\n", - " identity = self.down_sample(x)\n", - " sum2 = out + identity\n", - " out = sum2 # 输出为主分支与shortcuts之和\n", - " out = self.relu(out)\n", - "\n", - " return out" - ] - }, - { - "cell_type": "markdown", - "id": "b45270a9-e34f-4a1e-909f-d2d332a5dab9", - "metadata": {}, - "source": [ - "#### **构建ResNet网络**\n", - "定义make_layer函数,用于构建一组残差块(ResidualBlock 或 ResidualBlockBase),并且根据给定的参数堆叠多个残差块,还可以将多个残差块组合成一个更大的网络模块。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "40671719-6a91-4a60-af68-79e53ca770f9", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "def make_layer(last_out_channel, block: Type[Union[ResidualBlockBase, ResidualBlock]],\n", - " channel: int, block_nums: int, stride: int = 1):\n", - " down_sample = None\n", - " if stride != 1 or last_out_channel != channel * block.expansion:\n", - " down_sample = nn.SequentialCell([\n", - " mint.nn.Conv2d(in_channels=last_out_channel, out_channels=channel * block.expansion,\n", - " kernel_size=1, stride=stride, padding=0, bias=False),\n", - " mint.nn.BatchNorm2d(channel * block.expansion, momentum=0.9)\n", - " ])\n", - "\n", - " layers = []\n", - " layers.append(block(last_out_channel, channel, stride=stride, down_sample=down_sample))\n", - " in_channel = channel * block.expansion\n", - " # 堆叠残差网络\n", - " for _ in range(1, block_nums):\n", - " layers.append(block(in_channel, channel))\n", - " return nn.SequentialCell(layers)" - ] - }, - { - "cell_type": "markdown", - "id": "4f301fd5-d351-4d73-9e2f-30a964aca681", - "metadata": {}, - "source": [ - "实现典型的ResNet架构,包括多个残差层、卷积层、池化层、全连接层等。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "77f3f87a-72c9-4d05-b91c-664983a1da07", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "class ResNet(nn.Cell):\n", - " def __init__(self, block: Type[Union[ResidualBlockBase, ResidualBlock]],\n", - " layer_nums: List[int], num_classes: int, input_channel: int) -> None:\n", - " super(ResNet, self).__init__()\n", - "\n", - " self.relu = mint.nn.ReLU()\n", - " # 第一个卷积层,输入channel为3(彩色图像),输出channel为64\n", - " self.conv1 = mint.nn.Conv2d(in_channels=3, out_channels=64, kernel_size=7, stride=2, padding=3, bias=False)\n", - " self.norm = mint.nn.BatchNorm2d(64, momentum=0.9, track_running_stats=True)\n", - " # 最大池化层,缩小图片的尺寸\n", - " self.max_pool = nn.MaxPool2d(kernel_size=3, stride=2, pad_mode='same')\n", - " # 各个残差网络结构块定义\n", - " self.layer1 = make_layer(64, block, 64, layer_nums[0])\n", - " self.layer2 = make_layer(64 * block.expansion, block, 128, layer_nums[1], stride=2)\n", - " self.layer3 = make_layer(128 * block.expansion, block, 256, layer_nums[2], stride=2)\n", - " self.layer4 = make_layer(256 * block.expansion, block, 512, layer_nums[3], stride=2)\n", - " # 全连接层\n", - " self.fc = mint.nn.Linear(in_features=input_channel, out_features=num_classes)\n", - "\n", - " def construct(self, x):\n", - " x = self.conv1(x)\n", - " x = self.norm(x)\n", - " x = self.relu(x)\n", - " x = self.max_pool(x)\n", - "\n", - " x = self.layer1(x)\n", - " x = self.layer2(x)\n", - " x = self.layer3(x)\n", - " x = self.layer4(x)\n", - "\n", - " x = mint.mean(x, (2, 3), True)\n", - " x = mint.flatten(x, start_dim=1)\n", - " x = self.fc(x)\n", - " return x" - ] - }, - { - "cell_type": "markdown", - "id": "b1a57ff0-09f9-48cf-8025-61220b306616", - "metadata": {}, - "source": [ - "使用函数resnet50和辅助函数_resnet,来加载一个预训练的ResNet-50模型,或者返回一个未预训练的ResNet-50模型。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "261e7463-1e4c-4d09-939a-f9520749d559", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "def _resnet(model_url: str, block: Type[Union[ResidualBlockBase, ResidualBlock]],\n", - " layers: List[int], num_classes: int, pretrained: bool, pretrained_ckpt: str,\n", - " input_channel: int):\n", - " model = ResNet(block, layers, num_classes, input_channel)\n", - "\n", - " if pretrained:\n", - " # 加载预训练模型\n", - " download(url=model_url, path=pretrained_ckpt)\n", - " param_dict = load_checkpoint(pretrained_ckpt)\n", - " load_param_into_net(model, param_dict)\n", - " return model\n", - "\n", - "def resnet50(num_classes: int = 1000, pretrained: bool = False):\n", - " resnet50_url = \"https://obs.dualstack.cn-north-4.myhuaweicloud.com/mindspore-website/notebook/models/application/resnet50_224_new.ckpt\"\n", - " resnet50_ckpt = \"./LoadPretrainedModel/resnet50_224_new.ckpt\"\n", - " return _resnet(resnet50_url, ResidualBlock, [3, 4, 6, 3], num_classes,\n", - " pretrained, resnet50_ckpt, 2048)" - ] - }, - { - "cell_type": "markdown", - "id": "716057a3-f5aa-4d39-9cd1-1b582c281c85", - "metadata": {}, - "source": [ - "#### **ResNet分类模型初始化**\n", - "模型定义完成后,实例化ResNet分类模型,并设置网络参数的梯度更新。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1ee91e4d-0d21-43f4-a72d-7ebbd76188a7", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "network = resnet50(pretrained=True)\n", - "num_class = 12\n", - "in_channel = network.fc.in_features\n", - "fc = mint.nn.Linear(in_features=in_channel, out_features=num_class)\n", - "network.fc = fc" - ] - }, - { - "cell_type": "markdown", - "id": "5d80b693", - "metadata": {}, - "source": [ - "## 模型训练\n", - "\n", - "MindSpore使用函数式自动微分的设计理念,提供更接近于数学语义的自动微分接口`mindspore.value_and_grad`。通过如下步骤实现模型训练:\n", - "\n", - "1. 定义超参、损失函数和优化器\n", - "2. 定义正向函数\n", - "3. 使用`mindspore.value_and_grad`获取微分函数\n", - "4. 将微分函数和优化器执行封装为单步训练函数\n", - "5. 循环迭代数据集进行训练\n", - "\n", - "首先,我们设置epoch为50,Momentum作为优化器,其中参数momentum设为0.9,而损失函数则采用SoftmaxCrossEntropyWithLogits。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9794104f", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "num_epochs = 50\n", - "patience = 5\n", - "lr = nn.cosine_decay_lr(min_lr=0.00001, max_lr=0.001, total_step=step_size_train * num_epochs,\n", - " step_per_epoch=step_size_train, decay_epoch=num_epochs)\n", - "opt = nn.Momentum(params=network.trainable_params(), learning_rate=lr, momentum=0.9)\n", - "loss_fn = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean')\n", - "model = network\n", - "\n", - "best_acc = 0\n", - "best_ckpt_dir = \"./BestCheckpoint\"\n", - "best_ckpt_path = \"./BestCheckpoint/resnet50-best.ckpt\"" - ] - }, - { - "cell_type": "markdown", - "id": "54792cd0", - "metadata": {}, - "source": [ - "#### **定义训练推理函数**\n", - "定义正向函数`forward_fn`。使用`mindspore.value_and_grad`获取微分函数`grad_fn`。将微分函数`grad_fn`和优化器执行封装为单步训练函数`train_step`。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2a228b1a", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "def train_loop(model, dataset, loss_fn, optimizer):\n", - " def forward_fn(data, label):\n", - " logits = model(data)\n", - " loss = loss_fn(logits, label)\n", - " return loss, logits\n", - "\n", - " grad_fn = ms.ops.value_and_grad(forward_fn, None, optimizer.parameters, has_aux=True)\n", - "\n", - " def train_step(data, label):\n", - " (loss, _), grads = grad_fn(data, label)\n", - " optimizer(grads)\n", - " return loss\n", - " size = dataset.get_dataset_size()\n", - " model.set_train()\n", - " for batch, (data, label) in enumerate(dataset.create_tuple_iterator()):\n", - " loss = train_step(data, label)\n", - " if batch % 100 == 0 or batch == step_size_train - 1:\n", - " loss, current = loss.asnumpy(), batch\n", - " print(f\"loss: {loss:>7f} [{current:>3d}/{size:>3d}]\")\n", - "\n", - "def test_loop(model, dataset, loss_fn):\n", - " num_batches = dataset.get_dataset_size()\n", - " model.set_train(False)\n", - " total, test_loss, correct = 0, 0, 0\n", - " y_true = []\n", - " y_pred = []\n", - " for data, label in dataset.create_tuple_iterator():\n", - " y_true.extend(label.asnumpy().tolist())\n", - " pred = model(data)\n", - " total = total + len(data)\n", - " test_loss = test_loss + loss_fn(pred, label).asnumpy()\n", - " y_pred.extend(pred.argmax(1).asnumpy().tolist())\n", - " correct = correct + (pred.argmax(1) == label).asnumpy().sum()\n", - " test_loss = test_loss / num_batches\n", - " correct = correct / total\n", - " print(f\"Test: \\n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \\n\")\n", - "\n", - " return correct, test_loss" - ] - }, - { - "cell_type": "markdown", - "id": "7cb94f32", - "metadata": {}, - "source": [ - "#### **开始训练**\n", - "\n", - "在每个训练轮次中,使用训练集进行模型训练,并计算交叉熵损失以更新参数。随后在验证集上对模型进行测试,并以\"accuracy\"作为评价指标来评估模型的性能。为了防止过拟合,我们引入了早停机制,通过监控验证集上的指标,及时停止训练以避免过拟合,并保存具有最佳性能的模型参数。通过这样的训练过程,我们期望模型能够逐渐优化,并达到更好的性能水平。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2378a76f", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "no_improvement_count = 0\n", - "acc_list = []\n", - "loss_list = []\n", - "stop_epoch = num_epochs\n", - "for t in range(num_epochs):\n", - " print(f\"Epoch {t+1}\\n-------------------------------\")\n", - " train_loop(network, dataset_train, loss_fn, opt)\n", - " acc,loss = test_loop(network, dataset_val, loss_fn)\n", - " acc_list.append(acc)\n", - " loss_list.append(loss)\n", - " if acc > best_acc:\n", - " best_acc = acc\n", - " if not os.path.exists(best_ckpt_dir):\n", - " os.mkdir(best_ckpt_dir)\n", - " ms.save_checkpoint(network, best_ckpt_path)\n", - " no_improvement_count = 0\n", - " else:\n", - " no_improvement_count = no_improvement_count + 1\n", - " if no_improvement_count > patience:\n", - " print('Early stopping triggered. Restoring best weights...')\n", - " stop_epoch = t\n", - " break\n", - "\n", - "print(\"Done!\")" - ] - }, - { - "cell_type": "markdown", - "id": "07c4a56f-638c-48e6-a515-aa31a492224f", - "metadata": {}, - "source": [ - "#### **结果可视化展示**\n", - "绘制loss和accuracy曲线,将训练过程可视化。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8defcb02", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "def plot_training_process(acc_list, loss_list):\n", - " epochs = range(1, len(acc_list) + 1)\n", - " plt.figure(figsize=(10, 7))\n", - "\n", - " # 绘制准确率曲线\n", - " plt.subplot(121)\n", - " plt.plot(epochs, acc_list, 'b-', label='Training Accuracy')\n", - " plt.title('Training Accuracy')\n", - " plt.xlabel('Epochs')\n", - " plt.ylabel('Accuracy')\n", - " plt.legend()\n", - "\n", - " # 绘制损失函数曲线\n", - " plt.subplot(122)\n", - " plt.plot(epochs, loss_list, 'r-', label='Training Loss')\n", - " plt.title('Training Loss')\n", - " plt.xlabel('Epochs')\n", - " plt.ylabel('Loss')\n", - " plt.legend()\n", - " plt.subplots_adjust(wspace=0.4)\n", - " plt.show()\n", - "\n", - "plot_training_process(acc_list, loss_list)" - ] - }, - { - "cell_type": "markdown", - "id": "fbf2898d", - "metadata": {}, - "source": [ - "## 模型推理\n", - "\n", - "在模型推理阶段,我们提供了两种预测推理方式:单张图片推理和数据集推理方式" - ] - }, - { - "cell_type": "markdown", - "id": "86dce5f6-0709-4196-b8a5-d31c860d0ea3", - "metadata": {}, - "source": [ - "#### **加载模型**\n", - "加载训练好了的最佳模型权重。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a5b7a0f2", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "num_class = 12\n", - "model = resnet50(num_class)\n", - "best_ckpt_path = 'BestCheckpoint/resnet50-best.ckpt'\n", - "# 加载模型参数\n", - "param_dict = ms.load_checkpoint(best_ckpt_path)\n", - "ms.load_param_into_net(model, param_dict)\n", - "image_size = 224\n", - "workers = 1" - ] - }, - { - "cell_type": "markdown", - "id": "f43d219a-0ecb-4fcd-9d51-62bdad33e48d", - "metadata": {}, - "source": [ - "#### **通过传入测试数据集进行推理**\n", - "直接给定测试数据集,模型将对数据集中的样本进行预测,并生成可视化展示来呈现推理结果。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "196160df-c480-4722-859f-578cb7bf422c", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "def visualize_model(dataset_test, model):\n", - " images, labels = next(dataset_test.create_tuple_iterator())\n", - " output = model(images)\n", - " pred = np.argmax(output.asnumpy(), axis=1)\n", - " images = images.asnumpy()\n", - " labels = labels.asnumpy()\n", - "\n", - " # 显示图像及图像的预测值\n", - " plt.figure(figsize=(10, 6))\n", - " for i in range(6):\n", - " plt.subplot(2, 3, i + 1)\n", - " color = 'blue' if pred[i] == labels[i] else 'red'\n", - " plt.title(\n", - " 'predict:{} actual:{}'.format(\n", - " index_label_dict[pred[i]],\n", - " index_label_dict[labels[i]]\n", - " ),\n", - " color=color\n", - " )\n", - "\n", - " picture_show = np.transpose(images[i], (1, 2, 0)) # CHW -> HWC\n", - " mean = np.array([0.4914, 0.4822, 0.4465])\n", - " std = np.array([0.2023, 0.1994, 0.2010])\n", - " picture_show = std * picture_show + mean\n", - " picture_show = np.clip(picture_show, 0, 1)\n", - "\n", - " plt.imshow(picture_show)\n", - " plt.axis('off')\n", - " plt.show()" - ] - }, - { - "cell_type": "markdown", - "id": "49db7c27-794a-4478-8615-63315a0210ea", - "metadata": {}, - "source": [ - "展示模型的预测结果与真实标签的对比" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "54e90bbd-cc6f-4776-b1ea-810fe9724947", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "visualize_model(dataset_val, model)" - ] - }, - { - "cell_type": "markdown", - "id": "cb0416a0-b6ce-44c7-b7d2-06b3319223cc", - "metadata": {}, - "source": [ - "### **参考文献**\n", - "[1] He, K., Zhang, X., Ren, S., & Sun, J. (2016). Deep residual learning for image recognition. In Proceedings of the IEEE conference on computer vision and pattern recognition (pp. 770-778)." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.23" - }, - "vscode": { - "interpreter": { - "hash": "b3fbd24d2d81707c4b561437a4228ef79a00e041a3a9b4f7e2930dcc6bd46aa3" - } - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} From 0e1391b3feb51bf5778d74d0024c1e9089c4f22f Mon Sep 17 00:00:00 2001 From: Guangran Zhang Date: Mon, 26 Jan 2026 10:33:02 +0800 Subject: [PATCH 2/2] Add files via upload --- cv/resnet/train_resnet_classification.ipynb | 1119 +++++++++++++++++++ 1 file changed, 1119 insertions(+) create mode 100644 cv/resnet/train_resnet_classification.ipynb diff --git a/cv/resnet/train_resnet_classification.ipynb b/cv/resnet/train_resnet_classification.ipynb new file mode 100644 index 0000000..8de3a50 --- /dev/null +++ b/cv/resnet/train_resnet_classification.ipynb @@ -0,0 +1,1119 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "530389db", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "# 基于MindSpore的ResNet50模型中药炮制饮片质量判断任务\n", + "\n", + "## 案例介绍\n", + "\n", + "中药炮制是根据中医药理论,依照临床辨证施治用药的需要和药物自身性质,以及调剂、制剂的不同要求,**将中药材制备成中药饮片所采取的一项制药技术**。平时我们老百姓能接触到的中药,主要指自己回家煎煮或者请医院代煎煮的中药,都是中药饮片,也就是中药炮制这个技术的结果。而中药炮制饮片,大部分涉及到水火的处理,一定需要讲究“程度适中”,炮制火候不够达不到最好药效,炮制火候过度也会丧失药效。\n", + "- “生品”一般是指仅仅采用简单净选得到的饮片,通常没有经过火的处理,也是后续用火加工的原料。\n", + "- “不及”就是“炮制不到位”,没有达到规定的程度,饮片不能发挥最好的效果。\n", + "- “适中”是指炮制程度刚刚好,正是一个最佳的炮制点位,也是通常炮制结束的终点。\n", + "- “太过”是指炮制程度过度了,超过了“适中”的最佳状态,这时候的饮片也会丧失药效,不能再使用了。\n", + "\n", + "过去的炮制饮片程度的判断,都是采用的老药工经验判断,但随着老药工人数越来越少,这种经验判断可能存在“失传”的风险。而随着人工智能的发展,使用深度神经网络模型对饮片状态进行判断能达到很好的效果,可以很好的实现经验的“智能化”和经验的传承。" + ] + }, + { + "attachments": { + "7e54cf16-e21b-4975-aa94-381d656950b2.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAmIAAAFPCAYAAADujKKTAAAAAXNSR0IArs4c6QAAAARnQU1BAACx\njwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAGHaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8\nP3hwYWNrZXQgYmVnaW49J++7vycgaWQ9J1c1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCc/Pg0KPHg6\neG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyI+PHJkZjpSREYgeG1sbnM6cmRmPSJodHRw\nOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj48cmRmOkRlc2NyaXB0aW9u\nIHJkZjphYm91dD0idXVpZDpmYWY1YmRkNS1iYTNkLTExZGEtYWQzMS1kMzNkNzUxODJmMWIiIHht\nbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIj48dGlmZjpPcmllbnRhdGlv\nbj4xPC90aWZmOk9yaWVudGF0aW9uPjwvcmRmOkRlc2NyaXB0aW9uPjwvcmRmOlJERj48L3g6eG1w\nbWV0YT4NCjw/eHBhY2tldCBlbmQ9J3cnPz4slJgLAABo1klEQVR4Xu3dd3xT1fvA8U+S7lJK2YWW\nVfZUQQWZCt+fiAqoTFkCsocMGYU6oVAUZYgs2SBLQDYUkL33nm1p6YRO6G4zfn900F5ahkTSNs+b\nV16v5jwnt+E2efLcc0/uURkMBgNCCCGEEOKVUysbhBBCCCHEqyGFmBBCCCGEiUghJoQQQghhIlKI\nCSGEEEKYiBRiQgghhBAmIoWYEEIIIYSJSCEmhBBCCGEiUogJIYQQQpiIFGJCCCGEECYihZgQQggh\nhIlIISaEEEIIYSJSiAkhhBBCmIgUYkIIIYQQJiKFmBBCCCGEiUghJoQQQghhIlKICSGEEEKYiBRi\nQgghhBAmIoWYEEIIIYSJSCEmhBBCCGEiUogJIYQQQpiIFGJCCCGEECYihZgQQgghhIlIISaEEEII\nYSJSiAkhhBBCmIgUYkIIIYQQJiKFmBBCCCGEiagMBoNB2fgq+Pn5cffuXWWzEGbL2dmZmjVrKpuF\nyFVAQAA+Pj7KZiHMVsmSJalTp46yOU8zWSHm4eGBp6cnFhYWypAQZker1dKzZ0+WL1+uDAmRKy8v\n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\n" + } + }, + "cell_type": "markdown", + "id": "68406d8c-b9df-449e-9c12-bb89143c9c46", + "metadata": { + "tags": [] + }, + "source": [ + "## 模型简介\n", + "\n", + "ResNet50网络是2015年由微软实验室的何恺明提出,获得ILSVRC2015图像分类竞赛第一名。在ResNet网络提出之前,传统的卷积神经网络都是将一系列的卷积层和池化层堆叠得到的,但当网络堆叠到一定深度时,就会出现退化问题。在CIFAR-10数据集上使用56层网络与20层网络训练,56层网络比20层网络训练误差和测试误差更大,随着网络的加深,其误差并没有如预想的一样减小。\n", + "\n", + "ResNet网络提出了残差网络结构(Residual Network)来减轻退化问题,使用ResNet网络可以实现搭建较深的网络结构(突破1000层)。\n", + "![1.png](attachment:7e54cf16-e21b-4975-aa94-381d656950b2.png)" + ] + }, + { + "cell_type": "markdown", + "id": "137be2b4", + "metadata": {}, + "source": [ + "## 环境准备\n", + "\n", + "本案例的运行环境为:\n", + "\n", + "| Python | MindSpore |\n", + "| :----- | :-------- |\n", + "| 3.9 | 2.7.1 |" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f557f629-ec73-47be-884f-58346fd4bf12", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# 检查mindspore版本是否为2.7.1,如果不是则取消下个单元格注释进行安装\n", + "!pip show mindspore" + ] + }, + { + "cell_type": "markdown", + "id": "731bb59a-52ff-463a-af06-9ada7f4ded31", + "metadata": {}, + "source": [ + "如果你在如[昇思大模型平台](https://xihe.mindspore.cn/training-projects)、[华为云ModelArts](https://www.huaweicloud.com/product/modelarts.html)、[启智社区](https://openi.pcl.ac.cn/)等算力平台的Jupyter在线编程环境中运行本案例,可取消如下代码的注释,进行依赖库安装:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3eab2c4a", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# 安装mindspore==2.7.1版本,如需更换mindspore版本,可更改下面 MINDSPORE_VERSION 变量\n", + "# !pip uninstall mindspore -y\n", + "# %env MINDSPORE_VERSION=2.7.1\n", + "# !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" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a87f1302-0261-4d93-a716-6579ee09473f", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import os\n", + "import random\n", + "import shutil\n", + "import numpy as np\n", + "import mindspore as ms\n", + "import matplotlib.pyplot as plt\n", + "import mindspore.dataset.vision as vision\n", + "import mindspore.dataset.transforms as transforms\n", + "\n", + "from PIL import Image\n", + "from download import download\n", + "from typing import Type, Union, List, Optional\n", + "from mindspore.common.initializer import Normal\n", + "from mindspore.dataset import ImageFolderDataset\n", + "from mindspore import (Tensor, nn, train, mint, context, load_checkpoint, load_param_into_net, ops,)" + ] + }, + { + "cell_type": "markdown", + "id": "13b77923", + "metadata": {}, + "source": [ + "其他场景可参考[MindSpore安装指南](https://www.mindspore.cn/install)进行环境搭建。" + ] + }, + { + "cell_type": "markdown", + "id": "707edc02-9007-45de-8a50-1c02672a6506", + "metadata": {}, + "source": [ + "## 数据加载与预处理\n", + "\n", + "我们使用“中药炮制饮片”数据集,该数据集由成都中医药大学提供,共包含中药炮制饮片的 3 个品种,分别为:蒲黄、山楂、王不留行,每个品种又有着4种炮制状态:生品、不及适中、太过,图片尺寸为4K,图片格式为jpg,共786张图片。" + ] + }, + { + "cell_type": "markdown", + "id": "f3b5736c-9d1e-4b14-a5ef-180ae665279e", + "metadata": {}, + "source": [ + "#### **数据下载**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fd60d946-63e4-4364-82f6-cfaca6e17ad3", + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# 数据集下载链接\n", + "url = \"https://obs-xihe-beijing4.obs.cn-north-4.myhuaweicloud.com/jupyter/dataset/zhongyiyao/dataset.zip\"\n", + "if not os.path.exists(\"dataset\"):\n", + " download(url, \"dataset\", kind=\"zip\")" + ] + }, + { + "cell_type": "markdown", + "id": "bd962595-1e6d-4b85-a79a-2a356788de42", + "metadata": {}, + "source": [ + "#### **数据裁剪**\n", + "原图片尺寸为4k比较大,我们预处理将图片resize到指定尺寸(1000,1000)。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "231fad30-b2d5-4541-8015-1f482fa014e0", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "data_dir = \"dataset/zhongyiyao/zhongyiyao\"\n", + "new_data_path = \"dataset1/zhongyiyao\"\n", + "if not os.path.exists(new_data_path):\n", + " for path in ['train','test']:\n", + " data_path = data_dir + \"/\" + path\n", + " classes = os.listdir(data_path)\n", + " for (i,class_name) in enumerate(classes):\n", + " floder_path = data_path+\"/\"+class_name\n", + " print(f\"正在处理{floder_path}...\")\n", + " for image_name in os.listdir(floder_path):\n", + " try:\n", + " image = Image.open(floder_path + \"/\" + image_name)\n", + " image = image.resize((1000,1000))\n", + " target_dir = new_data_path+\"/\"+path+\"/\"+class_name\n", + " if not os.path.exists(target_dir):\n", + " os.makedirs(target_dir)\n", + " if not os.path.exists(target_dir+\"/\"+image_name):\n", + " image.save(target_dir+\"/\"+image_name)\n", + " except:\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "id": "e3cd1102-4242-4203-9099-99e1fcfb0b5c", + "metadata": {}, + "source": [ + "#### **数据集划分**\n", + "我们将中药炮制饮片数据集划分为训练集、验证集和测试集,并将数据按类别整理到不同的目录中,以便后续模型训练和评估。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "68211057-19b4-4ac4-a38f-ba685acf4414", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "def split_data(data_dir, test_size=0.2, val_size=0.2, random_seed=42):\n", + " random.seed(random_seed)\n", + " folders = ['train', 'test']\n", + " imgs = []\n", + " labels = []\n", + "\n", + " for path in folders:\n", + " data_path = os.path.join(data_dir, path)\n", + " classes = os.listdir(data_path)\n", + " for class_name in classes:\n", + " class_dir = os.path.join(data_path, class_name)\n", + " if not os.path.isdir(class_dir):\n", + " continue\n", + " for img_name in os.listdir(class_dir):\n", + " img_path = os.path.join(class_dir, img_name)\n", + " if os.path.isfile(img_path):\n", + " imgs.append(img_path)\n", + " labels.append(class_name)\n", + "\n", + " data = list(zip(imgs, labels))\n", + " random.shuffle(data)\n", + "\n", + " total = len(data)\n", + " test_size = int(total * test_size)\n", + " val_size = int(total * val_size)\n", + " train_size = total - test_size - val_size\n", + "\n", + " train_data = data[:train_size]\n", + " val_data = data[train_size:train_size+val_size]\n", + " test_data = data[train_size+val_size:]\n", + "\n", + " print(f\"划分训练集图片数:{len(train_data)}\")\n", + " print(f\"划分验证集图片数:{len(val_data)}\")\n", + " print(f\"划分测试集图片数:{len(test_data)}\")\n", + "\n", + " # 创建train, valid, test文件夹并根据划分移动数据\n", + " for split, data_split in zip(['train', 'valid', 'test'], [train_data, val_data, test_data]):\n", + " target_data_dir = os.path.join(data_dir, split)\n", + " if not os.path.exists(target_data_dir):\n", + " os.makedirs(target_data_dir)\n", + "\n", + " for img_path, label in data_split:\n", + " target_label_dir = os.path.join(target_data_dir, label)\n", + " if not os.path.exists(target_label_dir):\n", + " os.makedirs(target_label_dir)\n", + " # 移动图片文件到目标目录\n", + " target_img_path = os.path.join(target_label_dir, os.path.basename(img_path))\n", + " shutil.move(img_path, target_img_path)\n", + " return train_data, val_data, test_data\n", + "\n", + "def create_data_splits(data_dir):\n", + " train_data, val_data, test_data = split_data(data_dir)\n", + " return train_data, val_data, test_data\n", + "\n", + "data_dir = \"dataset1/zhongyiyao\"\n", + "train_data, val_data, test_data = create_data_splits(data_dir)" + ] + }, + { + "cell_type": "markdown", + "id": "68c55815-d08a-4b24-a26b-8deb75f01bea", + "metadata": {}, + "source": [ + "#### **定义数据加载方式**\n", + "在我们将数据喂进模型之前,可以通过MindSpore提供的多种数据变换(Transforms)方法来增强训练数据的多样性、统一数据尺寸,并进行必要的归一化和标准化操作,确保数据在喂入模型前符合要求,提高模型的训练效果。所有这些变换都通过 .map(...)方法在数据加载时被应用,从而实现了整个数据预处理的Pipeline。\n", + "在本案例中我们对数据进行了随机裁剪(RandomCrop)、随机水平翻转(RandomHorizontalFlip)、调整图像尺寸(Resize)、像素值归一化(Rescale)、图像标准化(Normalize)、格式转换(HWC2CHW)等的处理。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "72837cc1-602a-4c7c-8fe3-7a39d6abf952", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# 如果创建.ipynb_checkpoints文件夹,则删除\n", + "def remove_ipynb_checkpoints(data_dir):\n", + " ipynb_checkpoints_dir = os.path.join(data_dir, '.ipynb_checkpoints')\n", + " if os.path.exists(ipynb_checkpoints_dir):\n", + " print(f\"删除目录: {ipynb_checkpoints_dir}\")\n", + " shutil.rmtree(ipynb_checkpoints_dir)\n", + "\n", + "def create_dataset_zhongyao(dataset_dir, usage, resize, batch_size, workers):\n", + " remove_ipynb_checkpoints(dataset_dir)\n", + " # 使用 ImageFolder 加载数据集\n", + " dataset = ImageFolderDataset(dataset_dir, decode=True)\n", + " trans = []\n", + " if usage == \"train\":\n", + " trans += [\n", + " vision.RandomCrop(700, (4, 4, 4, 4)),\n", + " vision.RandomHorizontalFlip(prob=0.5)\n", + " ]\n", + "\n", + " trans += [\n", + " vision.Resize((resize, resize)),\n", + " vision.Rescale(1.0 / 255.0, 0.0),\n", + " vision.Normalize([0.4914, 0.4822, 0.4465], [0.2023, 0.1994, 0.2010]),\n", + " vision.HWC2CHW()\n", + " ]\n", + "\n", + " target_trans = transforms.TypeCast(ms.int32)\n", + " dataset = dataset.map(\n", + " operations=trans,\n", + " input_columns='image',\n", + " num_parallel_workers=workers\n", + " )\n", + "\n", + " dataset = dataset.map(\n", + " operations=target_trans,\n", + " input_columns='label',\n", + " num_parallel_workers=workers\n", + " )\n", + "\n", + " dataset = dataset.batch(batch_size, drop_remainder=True)\n", + " return dataset" + ] + }, + { + "cell_type": "markdown", + "id": "35c20baa-42cf-41e3-a220-2381fbe1c27f", + "metadata": {}, + "source": [ + "#### **加载数据**\n", + "我们接下来为模型训练准备数据,并设置了一些相关的超参数,同时也确保实验的可复现性。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f75b6284-80d4-4c74-981c-c07641786906", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "data_dir = \"dataset1/zhongyiyao\"\n", + "train_dir = data_dir+\"/\"+\"train\"\n", + "valid_dir = data_dir+\"/\"+\"valid\"\n", + "test_dir = data_dir+\"/\"+\"test\"\n", + "batch_size = 32 # 批量大小\n", + "image_size = 224 # 训练图像空间大小\n", + "workers = 4 # 并行线程个数\n", + "num_classes = 12 # 分类数量\n", + "\n", + "seed = 42\n", + "ms.set_seed(seed)\n", + "np.random.seed(seed)\n", + "random.seed(seed)\n", + "\n", + "dataset_train = create_dataset_zhongyao(dataset_dir=train_dir,\n", + " usage=\"train\",\n", + " resize=image_size,\n", + " batch_size=batch_size,\n", + " workers=workers)\n", + "step_size_train = dataset_train.get_dataset_size()\n", + "\n", + "dataset_val = create_dataset_zhongyao(dataset_dir=valid_dir,\n", + " usage=\"valid\",\n", + " resize=image_size,\n", + " batch_size=batch_size,\n", + " workers=workers)\n", + "dataset_test = create_dataset_zhongyao(dataset_dir=test_dir,\n", + " usage=\"test\",\n", + " resize=image_size,\n", + " batch_size=batch_size,\n", + " workers=workers)\n", + "step_size_val = dataset_val.get_dataset_size()\n", + "\n", + "print(f'训练集数据:{dataset_train.get_dataset_size()*batch_size}\\n')\n", + "print(f'验证集数据:{dataset_val.get_dataset_size()*batch_size}\\n')\n", + "print(f'测试集数据:{dataset_test.get_dataset_size()*batch_size}\\n')" + ] + }, + { + "cell_type": "markdown", + "id": "6c979ccd-477b-491a-8b4c-39bee552ca70", + "metadata": {}, + "source": [ + "#### **类别标签说明**\n", + "由于平台字体问题,无法正确显示中文,这里给出英文标签对应的类别:\n", + "- ph-sp:蒲黄-生品\n", + "- ph_bj:蒲黄-不及\n", + "- ph_sz:蒲黄-适中\n", + "- ph_tg:蒲黄-太过\n", + "- sz_sp:山楂-生品\n", + "- sz_bj:山楂-不及\n", + "- sz_sz:山楂-适中\n", + "- sz_tg:山楂-太过\n", + "- wblx_sp:王不留行-生品\n", + "- wblx_bj:王不留行-不及\n", + "- wblx_sz:王不留行-适中\n", + "- wblx_tg:王不留行-太过" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9e3afe8f-8f23-42f4-8536-94e1c5893c05", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "index_label_dict = {}\n", + "classes = os.listdir(train_dir)\n", + "if '.ipynb_checkpoints' in classes:\n", + " classes.remove('.ipynb_checkpoints')\n", + "for i,label in enumerate(classes):\n", + " index_label_dict[i] = label\n", + "label2chin = {'ph_sp':'蒲黄-生品', 'ph_bj':'蒲黄-不及', 'ph_sz':'蒲黄-适中', 'ph_tg':'蒲黄-太过', 'sz_sp':'山楂-生品',\n", + " 'sz_bj':'山楂-不及', 'sz_sz':'山楂-适中', 'sz_tg':'山楂-太过', 'wblx_sp':'王不留行-生品', 'wblx_bj':'王不留行-不及',\n", + " 'wblx_sz':'王不留行-适中', 'wblx_tg':'王不留行-太过'}\n", + "index_label_dict" + ] + }, + { + "cell_type": "markdown", + "id": "c48cf89d", + "metadata": {}, + "source": [ + "#### **数据可视化**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "baf82d45-7453-4214-b710-6b6b0643202f", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "data_iter = next(dataset_val.create_dict_iterator())\n", + "\n", + "images = data_iter[\"image\"].asnumpy()\n", + "labels = data_iter[\"label\"].asnumpy()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b3cc1899-836f-43f7-9ac2-156eea0e8f41", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "plt.figure(figsize=(12, 5))\n", + "for i in range(24):\n", + " plt.subplot(3, 8, i+1)\n", + " image_trans = np.transpose(images[i], (1, 2, 0))\n", + " mean = np.array([0.4914, 0.4822, 0.4465])\n", + " std = np.array([0.2023, 0.1994, 0.2010])\n", + " image_trans = std * image_trans + mean\n", + " image_trans = np.clip(image_trans, 0, 1)\n", + " plt.title(index_label_dict[labels[i]])\n", + " plt.imshow(image_trans)\n", + " plt.axis(\"off\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "740dbcee", + "metadata": {}, + "source": [ + "## 模型构建\n", + "\n", + "#### **创建分类网络**\n", + "处理完数据后,就可以来进行网络的搭建了。我们将最后一层线性映射的输出改为类别数12,以适应数据集。" + ] + }, + { + "cell_type": "markdown", + "id": "c146acee-2cb8-4120-bc18-21b8f250123d", + "metadata": {}, + "source": [ + "残差结构是ResNet网络中最重要的结构,由两个分支构成:一个主分支,一个shortcuts。主分支通过堆叠一系列的卷积操作得到,shortcuts从输入直接到输出,主分支的输出与shortcuts的输出相加后通过Relu激活函数后即为残差网络最后的输出。\n", + "\n", + "残差网络结构主要由两种,一种是Building Block,适用于较浅的ResNet网络,如ResNet18和ResNet34;另一种是Bottleneck,适用于层数较深的ResNet网络,如ResNet50、ResNet101和ResNet152。" + ] + }, + { + "cell_type": "markdown", + "id": "11d4ec89-b80b-47ad-b993-58481fe173c5", + "metadata": {}, + "source": [ + "#### **定义 Building Block**\n", + "Building Block结构的主分支有两层卷积网络结构:\n", + "\n", + "- 主分支第一层网络以输入channel为64为例,首先通过一个3×3的卷积层,然后通过Batch Normalization层,最后通过Relu激活函数层,输出channel为64;\n", + "\n", + "- 主分支第二层网络的输入channel为64,首先通过一个3×3的卷积层,然后通过Batch Normalization层,输出channel为64。\n", + "\n", + "最后将主分支输出的特征矩阵与shortcuts输出的特征矩阵相加,通过Relu激活函数即为Building Block最后的输出。
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1a99595c-3dc3-4733-8bd1-377edfada070", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "class ResidualBlockBase(nn.Cell):\n", + " expansion: int = 1 # 最后一个卷积核数量与第一个卷积核数量相等\n", + " def __init__(self, in_channel: int, out_channel: int,\n", + " stride: int = 1, norm: Optional[nn.Cell] = None,\n", + " down_sample: Optional[nn.Cell] = None) -> None:\n", + " super(ResidualBlockBase, self).__init__()\n", + " if not norm:\n", + " self.norm = mint.nn.BatchNorm2d(out_channel, momentum=0.9)\n", + " else:\n", + " self.norm = norm\n", + " # 一层卷积\n", + " self.conv1 = mint.nn.Conv2d(in_channels=in_channel, out_channels=out_channel,\n", + " kernel_size=3, stride=stride, padding=1, bias=False)\n", + " # 二层卷积\n", + " self.conv2 = mint.nn.Conv2d(in_channels=out_channel, out_channels=out_channel,\n", + " kernel_size=3, padding=1, bias=False)\n", + " # 接ReLU激活函数\n", + " self.relu = mint.nn.ReLU()\n", + " self.down_sample = down_sample\n", + "\n", + " # 定义前向传播\n", + " def construct(self, x):\n", + " \"\"\"ResidualBlockBase construct.\"\"\"\n", + " identity = x\n", + "\n", + " out = self.conv1(x) # 主分支第一层:3*3卷积层\n", + " out = self.norm(out)\n", + " out = self.relu(out)\n", + " out = self.conv2(out) # 主分支第二层:3*3卷积层\n", + " out = self.norm(out)\n", + "\n", + " if self.down_sample is not None:\n", + " identity = self.down_sample(x)\n", + " sum1 = out + identity\n", + " out = sum1 # 输出为主分支与shortcuts之和\n", + " out = self.relu(out)\n", + "\n", + " return out" + ] + }, + { + "cell_type": "markdown", + "id": "41bec72d-540e-4e17-bcfb-d482ae5d1b3a", + "metadata": {}, + "source": [ + "#### **定义 Bottleneck**\n", + "Bottleneck在输入相同的情况下Bottleneck结构相对Building Block结构的参数数量更少,更适合层数较深的网络,ResNet50使用的残差结构就是Bottleneck。该结构的主分支有三层卷积结构,分别为1×1的卷积层、3×3卷积层和1×1的卷积层,其中1×1的卷积层分别起降维和升维的作用。\n", + "\n", + "- 主分支第一层网络以输入channel为256为例,首先通过数量为64,大小为的卷积核进行降维,然后通过Batch Normalization层,最后通过Relu激活函数层,其输出channel为64;\n", + "\n", + "- 主分支第二层网络通过数量为64,大小为的卷积核提取特征,然后通过Batch Normalization层,最后通过Relu激活函数层,其输出channel为64;\n", + "\n", + "- 主分支第三层通过数量为256,大小的卷积核进行升维,然后通过Batch Normalization层,其输出channel为256。\n", + "\n", + "最后将主分支输出的特征矩阵与shortcuts输出的特征矩阵相加,通过Relu激活函数即为Bottleneck最后的输出。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a767e952", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "class ResidualBlock(nn.Cell):\n", + " expansion = 4 # 最后一个卷积核的数量是第一个卷积核数量的4倍\n", + " def __init__(self, in_channel: int, out_channel: int,\n", + " stride: int = 1, down_sample: Optional[nn.Cell] = None) -> None:\n", + " super(ResidualBlock, self).__init__()\n", + "\n", + " self.conv1 = mint.nn.Conv2d(in_channels=in_channel, out_channels=out_channel,\n", + " kernel_size=1, padding=0, bias=False)\n", + " self.norm1 = mint.nn.BatchNorm2d(out_channel, momentum=0.9)\n", + " self.conv2 = mint.nn.Conv2d(in_channels=out_channel, out_channels=out_channel,\n", + " kernel_size=3, stride=stride, padding=1, bias=False)\n", + " self.norm2 = mint.nn.BatchNorm2d(out_channel, momentum=0.9)\n", + " self.conv3 = mint.nn.Conv2d(in_channels=out_channel, out_channels=out_channel * self.expansion,\n", + " kernel_size=1, padding=0, bias=False)\n", + " self.norm3 = mint.nn.BatchNorm2d(out_channel * self.expansion, momentum=0.9)\n", + "\n", + " self.relu = mint.nn.ReLU()\n", + " self.down_sample = down_sample\n", + "\n", + " def construct(self, x):\n", + " identity = x # shortscuts分支\n", + "\n", + " out = self.conv1(x) # 主分支第一层:1*1卷积层\n", + " out = self.norm1(out)\n", + " out = self.relu(out)\n", + " out = self.conv2(out) # 主分支第二层:3*3卷积层\n", + " out = self.norm2(out)\n", + " out = self.relu(out)\n", + " out = self.conv3(out) # 主分支第三层:1*1卷积层\n", + " out = self.norm3(out)\n", + "\n", + " if self.down_sample is not None:\n", + " identity = self.down_sample(x)\n", + " sum2 = out + identity\n", + " out = sum2 # 输出为主分支与shortcuts之和\n", + " out = self.relu(out)\n", + "\n", + " return out" + ] + }, + { + "cell_type": "markdown", + "id": "b45270a9-e34f-4a1e-909f-d2d332a5dab9", + "metadata": {}, + "source": [ + "#### **构建ResNet网络**\n", + "定义make_layer函数,用于构建一组残差块(ResidualBlock 或 ResidualBlockBase),并且根据给定的参数堆叠多个残差块,还可以将多个残差块组合成一个更大的网络模块。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40671719-6a91-4a60-af68-79e53ca770f9", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "def make_layer(last_out_channel, block: Type[Union[ResidualBlockBase, ResidualBlock]],\n", + " channel: int, block_nums: int, stride: int = 1):\n", + " down_sample = None\n", + " if stride != 1 or last_out_channel != channel * block.expansion:\n", + " down_sample = nn.SequentialCell([\n", + " mint.nn.Conv2d(in_channels=last_out_channel, out_channels=channel * block.expansion,\n", + " kernel_size=1, stride=stride, padding=0, bias=False),\n", + " mint.nn.BatchNorm2d(channel * block.expansion, momentum=0.9)\n", + " ])\n", + "\n", + " layers = []\n", + " layers.append(block(last_out_channel, channel, stride=stride, down_sample=down_sample))\n", + " in_channel = channel * block.expansion\n", + " # 堆叠残差网络\n", + " for _ in range(1, block_nums):\n", + " layers.append(block(in_channel, channel))\n", + " return nn.SequentialCell(layers)" + ] + }, + { + "cell_type": "markdown", + "id": "4f301fd5-d351-4d73-9e2f-30a964aca681", + "metadata": {}, + "source": [ + "实现典型的ResNet架构,包括多个残差层、卷积层、池化层、全连接层等。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "77f3f87a-72c9-4d05-b91c-664983a1da07", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "class ResNet(nn.Cell):\n", + " def __init__(self, block: Type[Union[ResidualBlockBase, ResidualBlock]],\n", + " layer_nums: List[int], num_classes: int, input_channel: int) -> None:\n", + " super(ResNet, self).__init__()\n", + "\n", + " self.relu = mint.nn.ReLU()\n", + " # 第一个卷积层,输入channel为3(彩色图像),输出channel为64\n", + " self.conv1 = mint.nn.Conv2d(in_channels=3, out_channels=64, kernel_size=7, stride=2, padding=3, bias=False)\n", + " self.norm = mint.nn.BatchNorm2d(64, momentum=0.9, track_running_stats=True)\n", + " # 最大池化层,缩小图片的尺寸\n", + " self.max_pool = nn.MaxPool2d(kernel_size=3, stride=2, pad_mode='same')\n", + " # 各个残差网络结构块定义\n", + " self.layer1 = make_layer(64, block, 64, layer_nums[0])\n", + " self.layer2 = make_layer(64 * block.expansion, block, 128, layer_nums[1], stride=2)\n", + " self.layer3 = make_layer(128 * block.expansion, block, 256, layer_nums[2], stride=2)\n", + " self.layer4 = make_layer(256 * block.expansion, block, 512, layer_nums[3], stride=2)\n", + " # 全连接层\n", + " self.fc = mint.nn.Linear(in_features=input_channel, out_features=num_classes)\n", + "\n", + " def construct(self, x):\n", + " x = self.conv1(x)\n", + " x = self.norm(x)\n", + " x = self.relu(x)\n", + " x = self.max_pool(x)\n", + "\n", + " x = self.layer1(x)\n", + " x = self.layer2(x)\n", + " x = self.layer3(x)\n", + " x = self.layer4(x)\n", + "\n", + " x = mint.mean(x, (2, 3), True)\n", + " x = mint.flatten(x, start_dim=1)\n", + " x = self.fc(x)\n", + " return x" + ] + }, + { + "cell_type": "markdown", + "id": "b1a57ff0-09f9-48cf-8025-61220b306616", + "metadata": {}, + "source": [ + "使用函数resnet50和辅助函数_resnet,来加载一个预训练的ResNet-50模型,或者返回一个未预训练的ResNet-50模型。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "261e7463-1e4c-4d09-939a-f9520749d559", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "def _resnet(model_url: str, block: Type[Union[ResidualBlockBase, ResidualBlock]],\n", + " layers: List[int], num_classes: int, pretrained: bool, pretrained_ckpt: str,\n", + " input_channel: int):\n", + " model = ResNet(block, layers, num_classes, input_channel)\n", + "\n", + " if pretrained:\n", + " # 加载预训练模型\n", + " download(url=model_url, path=pretrained_ckpt)\n", + " param_dict = load_checkpoint(pretrained_ckpt)\n", + " load_param_into_net(model, param_dict)\n", + " return model\n", + "\n", + "def resnet50(num_classes: int = 1000, pretrained: bool = False):\n", + " resnet50_url = \"https://obs.dualstack.cn-north-4.myhuaweicloud.com/mindspore-website/notebook/models/application/resnet50_224_new.ckpt\"\n", + " resnet50_ckpt = \"./LoadPretrainedModel/resnet50_224_new.ckpt\"\n", + " return _resnet(resnet50_url, ResidualBlock, [3, 4, 6, 3], num_classes,\n", + " pretrained, resnet50_ckpt, 2048)" + ] + }, + { + "cell_type": "markdown", + "id": "716057a3-f5aa-4d39-9cd1-1b582c281c85", + "metadata": {}, + "source": [ + "#### **ResNet分类模型初始化**\n", + "模型定义完成后,实例化ResNet分类模型,并设置网络参数的梯度更新。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1ee91e4d-0d21-43f4-a72d-7ebbd76188a7", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "network = resnet50(pretrained=True)\n", + "num_class = 12\n", + "in_channel = network.fc.in_features\n", + "fc = mint.nn.Linear(in_features=in_channel, out_features=num_class)\n", + "network.fc = fc" + ] + }, + { + "cell_type": "markdown", + "id": "5d80b693", + "metadata": {}, + "source": [ + "## 模型训练\n", + "\n", + "MindSpore使用函数式自动微分的设计理念,提供更接近于数学语义的自动微分接口`mindspore.value_and_grad`。通过如下步骤实现模型训练:\n", + "\n", + "1. 定义超参、损失函数和优化器\n", + "2. 定义正向函数\n", + "3. 使用`mindspore.value_and_grad`获取微分函数\n", + "4. 将微分函数和优化器执行封装为单步训练函数\n", + "5. 循环迭代数据集进行训练\n", + "\n", + "首先,我们设置epoch为50,Momentum作为优化器,其中参数momentum设为0.9,而损失函数则采用SoftmaxCrossEntropyWithLogits。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9794104f", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "num_epochs = 50\n", + "patience = 5\n", + "lr = nn.cosine_decay_lr(min_lr=0.00001, max_lr=0.001, total_step=step_size_train * num_epochs,\n", + " step_per_epoch=step_size_train, decay_epoch=num_epochs)\n", + "opt = nn.Momentum(params=network.trainable_params(), learning_rate=lr, momentum=0.9)\n", + "loss_fn = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean')\n", + "model = network\n", + "\n", + "best_acc = 0\n", + "best_ckpt_dir = \"./BestCheckpoint\"\n", + "best_ckpt_path = \"./BestCheckpoint/resnet50-best.ckpt\"" + ] + }, + { + "cell_type": "markdown", + "id": "54792cd0", + "metadata": {}, + "source": [ + "#### **定义训练推理函数**\n", + "定义正向函数`forward_fn`。使用`mindspore.value_and_grad`获取微分函数`grad_fn`。将微分函数`grad_fn`和优化器执行封装为单步训练函数`train_step`。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2a228b1a", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "def train_loop(model, dataset, loss_fn, optimizer):\n", + " def forward_fn(data, label):\n", + " logits = model(data)\n", + " loss = loss_fn(logits, label)\n", + " return loss, logits\n", + "\n", + " grad_fn = ms.ops.value_and_grad(forward_fn, None, optimizer.parameters, has_aux=True)\n", + "\n", + " def train_step(data, label):\n", + " (loss, _), grads = grad_fn(data, label)\n", + " optimizer(grads)\n", + " return loss\n", + " size = dataset.get_dataset_size()\n", + " model.set_train()\n", + " for batch, (data, label) in enumerate(dataset.create_tuple_iterator()):\n", + " loss = train_step(data, label)\n", + " if batch % 100 == 0 or batch == step_size_train - 1:\n", + " loss, current = loss.asnumpy(), batch\n", + " print(f\"loss: {loss:>7f} [{current:>3d}/{size:>3d}]\")\n", + "\n", + "def test_loop(model, dataset, loss_fn):\n", + " num_batches = dataset.get_dataset_size()\n", + " model.set_train(False)\n", + " total, test_loss, correct = 0, 0, 0\n", + " y_true = []\n", + " y_pred = []\n", + " for data, label in dataset.create_tuple_iterator():\n", + " y_true.extend(label.asnumpy().tolist())\n", + " pred = model(data)\n", + " total = total + len(data)\n", + " test_loss = test_loss + loss_fn(pred, label).asnumpy()\n", + " y_pred.extend(pred.argmax(1).asnumpy().tolist())\n", + " correct = correct + (pred.argmax(1) == label).asnumpy().sum()\n", + " test_loss = test_loss / num_batches\n", + " correct = correct / total\n", + " print(f\"Test: \\n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \\n\")\n", + "\n", + " return correct, test_loss" + ] + }, + { + "cell_type": "markdown", + "id": "7cb94f32", + "metadata": {}, + "source": [ + "#### **开始训练**\n", + "\n", + "在每个训练轮次中,使用训练集进行模型训练,并计算交叉熵损失以更新参数。随后在验证集上对模型进行测试,并以\"accuracy\"作为评价指标来评估模型的性能。为了防止过拟合,我们引入了早停机制,通过监控验证集上的指标,及时停止训练以避免过拟合,并保存具有最佳性能的模型参数。通过这样的训练过程,我们期望模型能够逐渐优化,并达到更好的性能水平。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2378a76f", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "no_improvement_count = 0\n", + "acc_list = []\n", + "loss_list = []\n", + "stop_epoch = num_epochs\n", + "for t in range(num_epochs):\n", + " print(f\"Epoch {t+1}\\n-------------------------------\")\n", + " train_loop(network, dataset_train, loss_fn, opt)\n", + " acc,loss = test_loop(network, dataset_val, loss_fn)\n", + " acc_list.append(acc)\n", + " loss_list.append(loss)\n", + " if acc > best_acc:\n", + " best_acc = acc\n", + " if not os.path.exists(best_ckpt_dir):\n", + " os.mkdir(best_ckpt_dir)\n", + " ms.save_checkpoint(network, best_ckpt_path)\n", + " no_improvement_count = 0\n", + " else:\n", + " no_improvement_count = no_improvement_count + 1\n", + " if no_improvement_count > patience:\n", + " print('Early stopping triggered. Restoring best weights...')\n", + " stop_epoch = t\n", + " break\n", + "\n", + "print(\"Done!\")" + ] + }, + { + "cell_type": "markdown", + "id": "07c4a56f-638c-48e6-a515-aa31a492224f", + "metadata": {}, + "source": [ + "#### **结果可视化展示**\n", + "绘制loss和accuracy曲线,将训练过程可视化。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8defcb02", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "def plot_training_process(acc_list, loss_list):\n", + " epochs = range(1, len(acc_list) + 1)\n", + " plt.figure(figsize=(10, 7))\n", + "\n", + " # 绘制准确率曲线\n", + " plt.subplot(121)\n", + " plt.plot(epochs, acc_list, 'b-', label='Training Accuracy')\n", + " plt.title('Training Accuracy')\n", + " plt.xlabel('Epochs')\n", + " plt.ylabel('Accuracy')\n", + " plt.legend()\n", + "\n", + " # 绘制损失函数曲线\n", + " plt.subplot(122)\n", + " plt.plot(epochs, loss_list, 'r-', label='Training Loss')\n", + " plt.title('Training Loss')\n", + " plt.xlabel('Epochs')\n", + " plt.ylabel('Loss')\n", + " plt.legend()\n", + " plt.subplots_adjust(wspace=0.4)\n", + " plt.show()\n", + "\n", + "plot_training_process(acc_list, loss_list)" + ] + }, + { + "cell_type": "markdown", + "id": "fbf2898d", + "metadata": {}, + "source": [ + "## 模型推理\n", + "\n", + "在模型推理阶段,我们提供了两种预测推理方式:单张图片推理和数据集推理方式" + ] + }, + { + "cell_type": "markdown", + "id": "86dce5f6-0709-4196-b8a5-d31c860d0ea3", + "metadata": {}, + "source": [ + "#### **加载模型**\n", + "加载训练好了的最佳模型权重。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a5b7a0f2", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "num_class = 12\n", + "model = resnet50(num_class)\n", + "best_ckpt_path = 'BestCheckpoint/resnet50-best.ckpt'\n", + "# 加载模型参数\n", + "param_dict = ms.load_checkpoint(best_ckpt_path)\n", + "ms.load_param_into_net(model, param_dict)\n", + "image_size = 224\n", + "workers = 1" + ] + }, + { + "cell_type": "markdown", + "id": "f43d219a-0ecb-4fcd-9d51-62bdad33e48d", + "metadata": {}, + "source": [ + "#### **通过传入测试数据集进行推理**\n", + "直接给定测试数据集,模型将对数据集中的样本进行预测,并生成可视化展示来呈现推理结果。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "196160df-c480-4722-859f-578cb7bf422c", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "def visualize_model(dataset_test, model):\n", + " images, labels = next(dataset_test.create_tuple_iterator())\n", + " output = model(images)\n", + " pred = np.argmax(output.asnumpy(), axis=1)\n", + " images = images.asnumpy()\n", + " labels = labels.asnumpy()\n", + "\n", + " # 显示图像及图像的预测值\n", + " plt.figure(figsize=(10, 6))\n", + " for i in range(6):\n", + " plt.subplot(2, 3, i + 1)\n", + " color = 'blue' if pred[i] == labels[i] else 'red'\n", + " plt.title(\n", + " 'predict:{} actual:{}'.format(\n", + " index_label_dict[pred[i]],\n", + " index_label_dict[labels[i]]\n", + " ),\n", + " color=color\n", + " )\n", + "\n", + " picture_show = np.transpose(images[i], (1, 2, 0)) # CHW -> HWC\n", + " mean = np.array([0.4914, 0.4822, 0.4465])\n", + " std = np.array([0.2023, 0.1994, 0.2010])\n", + " picture_show = std * picture_show + mean\n", + " picture_show = np.clip(picture_show, 0, 1)\n", + "\n", + " plt.imshow(picture_show)\n", + " plt.axis('off')\n", + " plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "49db7c27-794a-4478-8615-63315a0210ea", + "metadata": {}, + "source": [ + "展示模型的预测结果与真实标签的对比" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54e90bbd-cc6f-4776-b1ea-810fe9724947", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "visualize_model(dataset_val, model)" + ] + }, + { + "cell_type": "markdown", + "id": "cb0416a0-b6ce-44c7-b7d2-06b3319223cc", + "metadata": {}, + "source": [ + "### **参考文献**\n", + "[1] He, K., Zhang, X., Ren, S., & Sun, J. (2016). Deep residual learning for image recognition. In Proceedings of the IEEE conference on computer vision and pattern recognition (pp. 770-778)." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.23" + }, + "vscode": { + "interpreter": { + "hash": "b3fbd24d2d81707c4b561437a4228ef79a00e041a3a9b4f7e2930dcc6bd46aa3" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}