赞
踩
该实验包含了2部分内容:
1.基于Mindspore框架的模型本地训练及预测
2.基于Modelarts平台和Tensorflow框架的模型训练及部署
要求实现一个简单的图片分类的功能,整体流程如下:
1、处理需要的数据集,这里使用了MNIST数据集。
2、定义一个网络,这里我们使用LeNet网络。
3、定义损失函数和优化器。
4、加载数据集并进行训练,训练完成后,查看结果及保存模型文件。
5、加载保存的模型,进行推理。
验证模型,加载测试数据集和训练后的模型,验证结果精度。
要求将本地的自定义算法通过简单的代码适配,实现在ModelArts上进行模型训练与部署的全流程。
要求使用PyTorch1.8实现手写数字图像识别,示例采用的数据集为MNIST官方数据集。
通过案例,要求了解在ModelArts平台上训练作业、部署推理模型并预测的完整流程。
操作样例前流程如下:
在动手进行实践之前安装相应版本的MindSpore到电脑,并进行好环境配置。
根据电脑配置,我选择安装的是1.10.0的windows的python3.9的CPU版本。
另外,还需要在运行时进行相应缺失的库的安装。由于我是在anaconda环境下安装的,该有的库基本都装过都有,所以这里省去了一些安装库的步骤;但我第一次尝试的时候用的是本地的python环境,运行时就出现了很多报错,因为很多库没有装。
同时,安装mindstore的时候还应该注意安装到了哪个环境里,比如我曾经装过anaconda和anaconda3两个环境,在直接用pip指令装的时候由于路径优先级,优先装在anaconda的环境而非其他,因此在使用anaconda3时应该将anaconda prompt的目标路径设置成anaconda3之后再使用conda指令进行安装。
示例中用到的MNIST数据集是由10类28*28的灰度图片组成,训练数据集包含60000张图片,测试数据集包含10000张图片。
MNIST数据集下载页面如下:http://yann.lecun.com/exdb/mnist/。页面提供4个数据集下载链接,其中前2个文件是训练数据需要,后2个文件是测试结果需要。
将数据集下载并解压到本地路径下,这里将数据集解压分别存放到工作区的./MNIST_Data/train、./MNIST_Data/test路径下。
目录结构如下:
└─MNIST_Data
├─ test
│ t10k-images.idx3-ubyte
│ t10k-labels.idx1-ubyte
│
└─ train
train-images.idx3-ubyte
train-labels.idx1-ubyte
为了方便样例使用,我们在样例脚本中添加了自动下载数据集的功能。
注意,此处必须是把文件直接存在test或train文件夹下,而非文件夹。
此次实验的本地阶段主要分为以下几个步骤:导入Python库和模块并配置运行信息,数据处理,网络定义,损失函数及优化器定义,以及训练和验证。
在使用前,先提前导入一些明面上需要的Python库,例如目前使用到os库等。
我们是通过context.set_context来配置运行需要的信息的,譬如运行模式、后端信息、硬件等信息。因此导入context模块来配置运行需要的信息,其代码如下。
parser.add_argument('--device_target', type=str, default=" Ascend", choices=['Ascend', 'GPU', 'CPU'], help='device where the code will be implemented (default: Ascend)')
在样例中我们配置样例运行使用图模式。再train.py和eval.py根据实际情况配置硬件信息,譬如代码运行在Ascend AI处理器上,则device_target选择Ascend,代码运行在CPU、GPU同理。
在本地实验中,应当按自己的电脑配置进行选择,比如我就选的是CPU模式,要将代码中一部分改为:...default="CPU"...。
此处的代码都已经融入在算法实现处的代码之中,因此仅介绍数据处理包含的一些功能。数据集对于训练非常重要,好的数据集可以有效提高训练精度和效率。在加载数据集前,我们通常会对数据集进行一些处理。
首先我们要定义一个函数create_dataset来创建数据集。
在这个函数中,我们定义好需要进行的数据增强和处理操作:
1. 定义数据集
2. 定义进行数据增强和处理所需要的一些参数
3. 根据参数,生成对应的数据增强操作
4. 使用map映射函数,将数据操作应用到数据集
5. 对生成的数据集进行处理
其次我们根据数据集存储地址,生成数据集:
def create_dataset(data_dir, training=True, batch_size=32, resize=(32, 32), rescale=1/(255*0.3081), shift=-0.1307/0.3081, buffer_size=64):
之后生成训练集和测试集的路径:
- data_train = os.path.join(data_dir, 'train') # train set
-
- data_test = os.path.join(data_dir, 'test') # test set
然后利用MnistDataset方法读取mnist数据集,如果training是True则读取训练集,否则读取测试集:
ds = ms.dataset.MnistDataset(data_train if training else data_test)
下面是一些对数据集的简单处理操作,此处介绍map和shuffle两种:
map方法是非常有效的方法,可以整体对数据集进行处理,resize改变数据形状,rescale进行归一化,HWC2CHW改变图像通道:
ds = ds.map(input_columns=["image"], operations=[CV.Resize(resize), CV.Rescale(rescale, shift), CV.HWC2CHW()])
我们可以利用map方法改变数据集标签的数据类型,方法如下:
ds = ds.map(input_columns=["label"], operations=C.TypeCast(ms.int32))
而shuffle是打乱操作,同时设定了batchsize的大小,并将最后不足一个batch的数据抛弃掉:
- ds.shuffle(buffer_size=buffer_size).batch(batch_size, drop_remainder=True)
-
- return ds
其中,batch_size:每组包含的数据个数,现设置每组包含32个数据。
在进行数据集处理时,一般先进行修改图片尺寸,归一化,修改图像频道数等工作,再修改标签的数据类型。最后进行shuffle操作,同时设定batch_size,设置drop_remainder为True,则数据集中不足最后一个batch的数据会被抛弃。
MindSpore支持进行多种数据处理和增强的操作,各种操作往往组合使用。所有的默认的数据集创建和处理都在dataset.py中,不需自己进行修正。
当然,最后我们还需要根据数据集存储地址,生成数据集,构建训练和验证函数:
- ds_train = create_dataset(os.path.join(args.data_path, "train"), cfg.batch_size)
-
- ds_eval = create_dataset(os.path.join(args.data_path, "test"), cfg.batch_size,1)
我们选择相对简单的LeNet网络。LeNet网络不包括输入层的情况下,共有7层:2个卷积层、2个下采样层(池化层)、3个全连接层。每层都包含不同数量的训练参数,如下图所示:
LeNet-5
使用MindSpore定义神经网络需要继承mindspore.nn.cell.Cell。Cell是所有神经网络(Conv2d等)的基类。
神经网络的各层需要预先在__init__方法中定义,然后通过定义construct方法来完成神经网络的前向构造。按照LeNet的网络结构,定义网络各层如下:
首先定义模型结构,MindSpore中的模型时通过construct定义模型结构,在__init__中初始化各层的对象:
- class LeNet5(nn.Cell):
-
- def __init__(self, num_class=10, num_channel=1):
-
- super(LeNet5, self).__init__()
其次定义卷积层,ReLU激活函数,平坦层和全连接层
其中conv2d的输入通道为1维,输出为6维,卷积核尺寸为5*5,步长为1,不适用padding:
- self.conv1 = nn.Conv2d(num_channel, 6, 5, pad_mode='valid')
-
- self.conv2 = nn.Conv2d(6, 16, 5, pad_mode='valid')
-
- self.fc1 = nn.Dense(16 * 5 * 5, 120, weight_init=Normal(0.02))
-
- self.fc2 = nn.Dense(120, 84, weight_init=Normal(0.02))
-
- self.fc3 = nn.Dense(84, num_class, weight_init=Normal(0.02))
-
- self.relu = nn.ReLU()
-
- self.max_pool2d = nn.MaxPool2d(kernel_size=2, stride=2)
-
- self.flatten = nn.Flatten()
最后构建Lenet5架构,x代表网络的输入:
- def construct(self, x):
-
- x = self.max_pool2d(self.relu(self.conv1(x)))
-
- x = self.max_pool2d(self.relu(self.conv2(x)))
-
- x = self.flatten(x)
-
- x = self.relu(self.fc1(x))
-
- x = self.relu(self.fc2(x))
-
- x = self.fc3(x)
-
- return x
网络的构建也是已经给出的,我们要做的就是把lenet/src/lenet.py中的代码改成这个网络架构的代码即可。
在进行定义之前,先简单介绍损失函数及优化器的概念。
损失函数:又叫目标函数,用于衡量预测值与实际值差异的程度。深度学习通过不停地迭代来缩小损失函数的值。定义一个好的损失函数,可以有效提高模型的性能。
优化器:用于最小化损失函数,从而在训练过程中改进模型。
定义了损失函数后,可以得到损失函数关于权重的梯度。梯度用于指示优化器优化权重的方向,以提高模型性能。
MindSpore支持的损失函数有SoftmaxCrossEntropyWithLogits、L1Loss、MSELoss等。这里使用SoftmaxCrossEntropyWithLogits损失函数。
MindSpore提供了callback机制,可以在训练过程中执行自定义逻辑,这里使用框架提供的ModelCheckpoint为例。 ModelCheckpoint可以保存网络模型和参数,以便进行后续的fine-tuning(微调)操作。
因此我们这次实验就使用这个框架进行优化和损失函数定义即可,即,将这里的代码相应的复制到train函数和eval函数相应的部分,
- #设定loss函数
-
- net_loss = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction="mean")
-
- #设定优化器
-
- net_opt = nn.Momentum(network.trainable_params(), cfg.lr, cfg.momentum)
-
- #编译形成模型
-
- model = Model(network, net_loss, net_opt, metrics={"Accuracy": Accuracy()})
-
- # 训练网络 train.py
-
- model.train(cfg['epoch_size'], ds_train, callbacks=[time_cb, ckpoint_cb, LossMonitor()], dataset_sink_mode=args.dataset_sink_mode)
在train.py中,如果是如数据准备中路径设置,则和函数中默认值一样,不需修改。在eval.py中,需要写清一个具体的.ckpt文件路径和文件名,例如下列代码中标红部分:
parser.add_argument('--ckpt_path', type=str, default="./ckpt/checkpoint_lenet-1_1875.ckpt", help='if mode is test, must be the path where the trained ckpt file')
编译运行train.py和eval.py完成模型的训练及验证过程。
训练过程中会打印loss值。loss值会波动,但总体来说train.py在epoch变多时打印的loss值会逐步减小,eval.py根据其结果的.ckpt文件打印出的精度逐步提高。
已注册华为云帐号,且在使用ModelArts前检查帐号状态,帐号不能处于欠费或冻结状态。
本案例使用的数据是MNIST数据集,从MNIST官网下载四个数据集至本地。四个数据集具体情况如下:
“train-images-idx3-ubyte.gz”:训练集的压缩包文件。训练集,共包含60000个样本。
“train-labels-idx1-ubyte.gz”:训练集标签的压缩包文件。训练集标签,共包含60000个样本的类别标签。
“t10k-images-idx3-ubyte.gz”:验证集的压缩包文件。验证集,共包含10000个样本。
“t10k-labels-idx1-ubyte.gz”:验证集标签的压缩包文件。验证集标签,共包含10000个样本的类别标签。
注意,这里准备的训练数据都是压缩包形式,而不是解压缩后的文件!
需要准备train.py,customize_service.py以及config.json三个文件,第一个为训练文件,后两个为推理文件。
此三个文件样例中都有提供,只需创建相应的空白文件并将代码复制进去保存上传即可。同时如果有需要还可以按需进行修改。
将训练使用的数据和代码文件、推理代码文件与推理配置文件,上传到OBS桶中。在 ModelArts 上运行训练作业时,需要从OBS桶中读取数据和代码文件。
创建OBS桶并上传数据的步骤如下:
上传Step1 准备训练数据下载的MNIST数据集压缩包文件到OBS中。上传数据集到“mnist-data”文件夹中。
注意:
创建的OBS桶所在区域和后续使用ModelArts必须在同一个区域Region,否则会导致训练时找不到OBS桶。创建OBS桶时,桶的存储类别请勿选择“归档存储”,归档存储的OBS桶会导致模型训练失败。
同时,由于要免费使用,要选择华北-北京四通道,且选择单用户访问,低频存储。
登录ModelArts管理控制台,选择和OBS桶相同的区域。
在“全局配置”中检查当前帐号是否已完成访问授权的配置。如未完成,请参考使用委托授权。针对之前使用访问密钥授权的用户,建议清空授权,然后使用委托进行授权。
在左侧导航栏的“训练管理”-> “训练作业”中,单击“创建训练作业”。填写创建训练作业相关信息。
“创建方式”:选择“自定义算法”。
“启动方式”:选择“预置框架”,下拉框中选择PyTorch,pytorch_1.8.0-cuda_10.2-py_3.7-ubuntu_18.04-x86_64。
“代码目录”:选择已创建的代码目录路径“/test-modelarts-xx/pytorch/mnist-code/”。
“启动文件”:选择代码目录下上传的训练脚本“train.py”。
“输入”:单击“添加”,设置训练输入的“参数名称”为“data_url”。设置数据存储位置为 “/test-modelarts-xx/pytorch/mnist-data/”。
“输出”:单击“添加”,设置训练输出的“参数名称”为“train_url”。设置数据存储位置为 “/test-modelarts-xx/pytorch/mnist-output/”
“资源类型”:选择 GPU 单卡的规格,如“GPU: 1*NVIDIA-V100(16GB) | CPU: 8 核 64GB 780GB”。
其他参数保持默认即可。同时本样例代码为单机单卡场景,选择GPU多卡规格会导致训练失败。
之后就可以开始训练了。
模型训练完成后,可以创建AI应用,将AI应用部署为在线服务。
在ModelArts管理控制台,单击左侧导航栏中的“AI应用管理>AI应用”,进入“我的AI应用”页面,单击“创建”。
在“创建AI应用”页面,填写相关参数,然后单击“立即创建”。
在“元模型来源”中,选择“从训练中选择>训练作业(New)”页签,选择Step4 创建训练作业中完成的训练作业,勾选“动态加载”。AI引擎的值是系统自动写入的,无需设置。
在AI应用列表页面,当AI应用状态变为“正常”时,表示AI应用创建成功。
单击AI应用名称左侧的小三角,打开此AI应用下的所有版本。在对应版本所在行,单击操作列“部署>在线服务”,将AI应用部署为在线服务,填写参数并根据界面提示完成在线服务创建,节点规格需选择CPU。
完成服务部署后,返回在线服务页面列表页,等待服务部署完成,当服务状态显示为“运行中”,表示服务已部署成功。
在“在线服务”页面,单击在线服务名称,进入服务详情页面。
单击“预测”页签,请求类型选择“multipart/form-data”,请求参数填写“image”,单击“上传”按钮上传示例图片,然后单击“预测”。
预测完成后,预测结果显示区域将展示预测结果,根据预测结果内容,可识别出此图片的数字具体为多少。
在运行结束后,如不再需要资源,则要手动进行删除,释放资源。
环境为python3.9, pycharm 2022.2.1, cuda 11.6, anaconda环境编译 。
代码思路见上,具体主要代码如下:(其他需要的话可私信发压缩包)
- # Copyright 2020 Huawei Technologies Co., Ltd
- #
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- # ============================================================================
- """
- ######################## train lenet example ########################
- train lenet and get network model files(.ckpt) :
- python train.py --data_path /YourDataPath
- """
-
- import os
- import ast
- import argparse
- from src.config import mnist_cfg as cfg
- from src.dataset import create_dataset
- from src.lenet import LeNet5
- import mindspore.nn as nn
- from mindspore import context
- from mindspore.train.callback import ModelCheckpoint, CheckpointConfig, LossMonitor, TimeMonitor
- from mindspore.train import Model
- from mindspore.nn.metrics import Accuracy
- from mindspore.common import set_seed
-
- set_seed(1)
-
- if __name__ == "__main__":
- parser = argparse.ArgumentParser(description='MindSpore Lenet Example')
- # 设备设置
- parser.add_argument('--device_target', type=str, default="CPU", choices=['Ascend', 'GPU', 'CPU'],
- help='default: CPU')
- parser.add_argument('--data_path', type=str, default="./MNIST_Data",
- help='./MNIST_Data') #path where the dataset is saved
- parser.add_argument('--ckpt_path', type=str, default="./ckpt", help='./MNIST_Data/train') #if is test, must provide\path where the trained ckpt file
- parser.add_argument('--dataset_sink_mode', type=ast.literal_eval, default=True,
- help='dataset_sink_mode is False or True') #dataset_sink_mode is False or True
-
- args = parser.parse_args()
-
-
- context.set_context(mode=context.GRAPH_MODE, device_target=args.device_target)
- ds_train = create_dataset(os.path.join(args.data_path, "train"), cfg.batch_size)
- network = LeNet5(cfg.num_classes)
- # 设定loss函数
- net_loss = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction="mean")
- # 设定优化器
- net_opt = nn.Momentum(network.trainable_params(), cfg.lr, cfg.momentum)
-
- time_cb = TimeMonitor(data_size=ds_train.get_dataset_size())
- config_ck = CheckpointConfig(save_checkpoint_steps=cfg.save_checkpoint_steps,
- keep_checkpoint_max=cfg.keep_checkpoint_max)
- ckpoint_cb = ModelCheckpoint(prefix="checkpoint_lenet", directory=args.ckpt_path, config=config_ck)
- #编译形成模型
- model = Model(network, net_loss, net_opt, metrics={"Accuracy": Accuracy()})
-
- print("============== Starting Training ==============")
- # 训练网络 train.py
- model.train(cfg['epoch_size'], ds_train, callbacks=[time_cb, ckpoint_cb, LossMonitor()],
- dataset_sink_mode=args.dataset_sink_mode)
- # Copyright 2020 Huawei Technologies Co., Ltd
- #
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- # ============================================================================
- """
- ######################## eval lenet example ########################
- eval lenet according to model file:
- python eval.py --data_path /YourDataPath --ckpt_path Your.ckpt
- """
-
- import os
- import ast
- import argparse
- import mindspore.nn as nn
- from mindspore import context
- from mindspore.train.serialization import load_checkpoint, load_param_into_net
- from mindspore.train import Model
- from mindspore.nn.metrics import Accuracy
- from src.dataset import create_dataset
- from src.config import mnist_cfg as cfg
- from src.lenet import LeNet5
-
- if __name__ == "__main__":
- parser = argparse.ArgumentParser(description='MindSpore Lenet Example')
- # 设备设置
- parser.add_argument('--device_target', type=str, default="CPU", choices=['Ascend', 'GPU', 'CPU'],
- help='device where the code will be implemented (default: CPU)')
- parser.add_argument('--data_path', type=str, default="./MNIST_Data",
- help='path where the dataset is saved')
- parser.add_argument('--ckpt_path', type=str, default="./ckpt/checkpoint_lenet-10_1875.ckpt", help='if mode is test, must provide\
- path where the trained ckpt file')
- parser.add_argument('--dataset_sink_mode', type=ast.literal_eval,
- default=False, help='dataset_sink_mode is False or True')
-
- args = parser.parse_args()
-
- context.set_context(mode=context.GRAPH_MODE, device_target=args.device_target)
-
- network = LeNet5(cfg.num_classes)
- #设定loss函数
- net_loss = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction="mean")
- repeat_size = cfg.epoch_size
- #设定优化器
- net_opt = nn.Momentum(network.trainable_params(), cfg.lr, cfg.momentum)
- #编译形成模型
- model = Model(network, net_loss, net_opt, metrics={"Accuracy": Accuracy()})
-
- print("============== Starting Testing ==============")
- param_dict = load_checkpoint(args.ckpt_path)
- load_param_into_net(network, param_dict)
- ds_eval = create_dataset(os.path.join(args.data_path, "test"),
- cfg.batch_size,
- 1)
- acc = model.eval(ds_eval, dataset_sink_mode=args.dataset_sink_mode)
- print("============== {} ==============".format(acc))
- # Copyright 2020 Huawei Technologies Co., Ltd
- #
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- # ============================================================================
- """LeNet."""
- import mindspore.nn as nn
- from mindspore.common.initializer import Normal
-
-
- class LeNet5(nn.Cell):
- """
- Lenet network
- Args:
- num_class (int): Number of classes. Default: 10.
- num_channel (int): Number of channels. Default: 1.
- Returns:
- Tensor, output tensor
- Examples:
- >>> LeNet(num_class=10)
- """
-
-
-
- def __init__(self, num_class=10, num_channel=1):
- super(LeNet5, self).__init__()
- #定义卷积层,ReLU激活函数,平坦层和全连接层
- #conv2d的输入通道为1维,输出为6维,卷积核尺寸为5*5,步长为1,不适用padding
- self.conv1 = nn.Conv2d(num_channel, 6, 5, pad_mode='valid')
- self.conv2 = nn.Conv2d(6, 16, 5, pad_mode='valid')
- self.fc1 = nn.Dense(16 * 5 * 5, 120, weight_init=Normal(0.02))
- self.fc2 = nn.Dense(120, 84, weight_init=Normal(0.02))
- self.fc3 = nn.Dense(84, num_class, weight_init=Normal(0.02))
- self.relu = nn.ReLU()
- self.max_pool2d = nn.MaxPool2d(kernel_size=2, stride=2)
- self.flatten = nn.Flatten()
-
- def construct(self, x):
- #构建Lenet5架构,x代表网络的输入
- x = self.max_pool2d(self.relu(self.conv1(x)))
- x = self.max_pool2d(self.relu(self.conv2(x)))
- x = self.flatten(x)
- x = self.relu(self.fc1(x))
- x = self.relu(self.fc2(x))
- x = self.fc3(x)
- return x
上图为train.py成功运行的截图,其会训练生成11个ckpt文件存在.ckpt文件夹里。
下图为文件夹中文件展示。
以下分别为取其中1~10编号的文件进行评估(eval)得到的accuracy值:(此处仅取第一个文件的运行结果截图,其余见下一部分)
- # coding=gbk
- # base on https://github.com/pytorch/examples/blob/main/mnist/main.py
-
- from __future__ import print_function
-
- import os
- import gzip
- import codecs
- import argparse
- from typing import IO, Union
-
- import numpy as np
-
- import torch
- import torch.nn as nn
- import torch.nn.functional as F
- import torch.optim as optim
- from torchvision import datasets, transforms
- from torch.optim.lr_scheduler import StepLR
-
- import shutil
-
-
- class Net(nn.Module):
- def __init__(self):
- super(Net, self).__init__()
- self.conv1 = nn.Conv2d(1, 32, 3, 1)
- self.conv2 = nn.Conv2d(32, 64, 3, 1)
- self.dropout1 = nn.Dropout(0.25)
- self.dropout2 = nn.Dropout(0.5)
- self.fc1 = nn.Linear(9216, 128)
- self.fc2 = nn.Linear(128, 10)
-
- def forward(self, x):
- x = self.conv1(x)
- x = F.relu(x)
- x = self.conv2(x)
- x = F.relu(x)
- x = F.max_pool2d(x, 2)
- x = self.dropout1(x)
- x = torch.flatten(x, 1)
- x = self.fc1(x)
- x = F.relu(x)
- x = self.dropout2(x)
- x = self.fc2(x)
- output = F.log_softmax(x, dim=1)
- return output
-
-
- # 模型训练,设置模型为训练模式,加载训练数据,计算损失函数,执行梯度下降
- def train(args, model, device, train_loader, optimizer, epoch):
- model.train()
- for batch_idx, (data, target) in enumerate(train_loader):
- data, target = data.to(device), target.to(device)
- optimizer.zero_grad()
- output = model(data)
- loss = F.nll_loss(output, target)
- loss.backward()
- optimizer.step()
- if batch_idx % args.log_interval == 0:
- print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
- epoch, batch_idx * len(data), len(train_loader.dataset),
- 100. * batch_idx / len(train_loader), loss.item()))
- if args.dry_run:
- break
-
-
- # 模型验证,设置模型为验证模式,加载验证数据,计算损失函数和准确率
- def test(model, device, test_loader):
- model.eval()
- test_loss = 0
- correct = 0
- with torch.no_grad():
- for data, target in test_loader:
- data, target = data.to(device), target.to(device)
- output = model(data)
- test_loss += F.nll_loss(output, target, reduction='sum').item()
- pred = output.argmax(dim=1, keepdim=True)
- correct += pred.eq(target.view_as(pred)).sum().item()
-
- test_loss /= len(test_loader.dataset)
-
- print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
- test_loss, correct, len(test_loader.dataset),
- 100. * correct / len(test_loader.dataset)))
-
-
- # pytorch mnist
- # https://github.com/pytorch/vision/blob/v0.9.0/torchvision/datasets/mnist.py
- def get_int(b: bytes) -> int:
- return int(codecs.encode(b, 'hex'), 16)
-
-
- def open_maybe_compressed_file(path: Union[str, IO]) -> Union[IO, gzip.GzipFile]:
- """Return a file object that possibly decompresses 'path' on the fly.
- Decompression occurs when argument `path` is a string and ends with '.gz' or '.xz'.
- """
- if not isinstance(path, torch._six.string_classes):
- return path
- if path.endswith('.gz'):
- return gzip.open(path, 'rb')
- if path.endswith('.xz'):
- return lzma.open(path, 'rb')
- return open(path, 'rb')
-
-
- SN3_PASCALVINCENT_TYPEMAP = {
- 8: (torch.uint8, np.uint8, np.uint8),
- 9: (torch.int8, np.int8, np.int8),
- 11: (torch.int16, np.dtype('>i2'), 'i2'),
- 12: (torch.int32, np.dtype('>i4'), 'i4'),
- 13: (torch.float32, np.dtype('>f4'), 'f4'),
- 14: (torch.float64, np.dtype('>f8'), 'f8')
- }
-
-
- def read_sn3_pascalvincent_tensor(path: Union[str, IO], strict: bool = True) -> torch.Tensor:
- """Read a SN3 file in "Pascal Vincent" format (Lush file 'libidx/idx-io.lsh').
- Argument may be a filename, compressed filename, or file object.
- """
- # read
- with open_maybe_compressed_file(path) as f:
- data = f.read()
- # parse
- magic = get_int(data[0:4])
- nd = magic % 256
- ty = magic // 256
- assert 1 <= nd <= 3
- assert 8 <= ty <= 14
- m = SN3_PASCALVINCENT_TYPEMAP[ty]
- s = [get_int(data[4 * (i + 1): 4 * (i + 2)]) for i in range(nd)]
- parsed = np.frombuffer(data, dtype=m[1], offset=(4 * (nd + 1)))
- assert parsed.shape[0] == np.prod(s) or not strict
- return torch.from_numpy(parsed.astype(m[2], copy=False)).view(*s)
-
-
- def read_label_file(path: str) -> torch.Tensor:
- with open(path, 'rb') as f:
- x = read_sn3_pascalvincent_tensor(f, strict=False)
- assert(x.dtype == torch.uint8)
- assert(x.ndimension() == 1)
- return x.long()
-
-
- def read_image_file(path: str) -> torch.Tensor:
- with open(path, 'rb') as f:
- x = read_sn3_pascalvincent_tensor(f, strict=False)
- assert(x.dtype == torch.uint8)
- assert(x.ndimension() == 3)
- return x
-
-
- def extract_archive(from_path, to_path):
- to_path = os.path.join(to_path, os.path.splitext(os.path.basename(from_path))[0])
- with open(to_path, "wb") as out_f, gzip.GzipFile(from_path) as zip_f:
- out_f.write(zip_f.read())
- # --- pytorch mnist
- # --- end
-
-
- # raw mnist 数据处理
- def convert_raw_mnist_dataset_to_pytorch_mnist_dataset(data_url):
- """
- raw
- {data_url}/
- train-images-idx3-ubyte.gz
- train-labels-idx1-ubyte.gz
- t10k-images-idx3-ubyte.gz
- t10k-labels-idx1-ubyte.gz
- processed
- {data_url}/
- train-images-idx3-ubyte.gz
- train-labels-idx1-ubyte.gz
- t10k-images-idx3-ubyte.gz
- t10k-labels-idx1-ubyte.gz
- MNIST/raw
- train-images-idx3-ubyte
- train-labels-idx1-ubyte
- t10k-images-idx3-ubyte
- t10k-labels-idx1-ubyte
- MNIST/processed
- training.pt
- test.pt
- """
- resources = [
- "train-images-idx3-ubyte.gz",
- "train-labels-idx1-ubyte.gz",
- "t10k-images-idx3-ubyte.gz",
- "t10k-labels-idx1-ubyte.gz"
- ]
-
- pytorch_mnist_dataset = os.path.join(data_url, 'MNIST')
-
- raw_folder = os.path.join(pytorch_mnist_dataset, 'raw')
- processed_folder = os.path.join(pytorch_mnist_dataset, 'processed')
-
- os.makedirs(raw_folder, exist_ok=True)
- os.makedirs(processed_folder, exist_ok=True)
-
- print('Processing...')
-
- for f in resources:
- extract_archive(os.path.join(data_url, f), raw_folder)
-
- training_set = (
- read_image_file(os.path.join(raw_folder, 'train-images-idx3-ubyte')),
- read_label_file(os.path.join(raw_folder, 'train-labels-idx1-ubyte'))
- )
- test_set = (
- read_image_file(os.path.join(raw_folder, 't10k-images-idx3-ubyte')),
- read_label_file(os.path.join(raw_folder, 't10k-labels-idx1-ubyte'))
- )
- with open(os.path.join(processed_folder, 'training.pt'), 'wb') as f:
- torch.save(training_set, f)
- with open(os.path.join(processed_folder, 'test.pt'), 'wb') as f:
- torch.save(test_set, f)
-
- print('Done!')
-
-
- def main():
- # 定义可以接收的训练作业运行参数
- parser = argparse.ArgumentParser(description='PyTorch MNIST Example')
-
- parser.add_argument('--data_url', type=str, default=False,
- help='mnist dataset path')
- parser.add_argument('--train_url', type=str, default=False,
- help='mnist model path')
-
- parser.add_argument('--batch-size', type=int, default=64, metavar='N',
- help='input batch size for training (default: 64)')
- parser.add_argument('--test-batch-size', type=int, default=1000, metavar='N',
- help='input batch size for testing (default: 1000)')
- parser.add_argument('--epochs', type=int, default=14, metavar='N',
- help='number of epochs to train (default: 14)')
- parser.add_argument('--lr', type=float, default=1.0, metavar='LR',
- help='learning rate (default: 1.0)')
- parser.add_argument('--gamma', type=float, default=0.7, metavar='M',
- help='Learning rate step gamma (default: 0.7)')
- parser.add_argument('--no-cuda', action='store_true', default=False,
- help='disables CUDA training')
- parser.add_argument('--dry-run', action='store_true', default=False,
- help='quickly check a single pass')
- parser.add_argument('--seed', type=int, default=1, metavar='S',
- help='random seed (default: 1)')
- parser.add_argument('--log-interval', type=int, default=10, metavar='N',
- help='how many batches to wait before logging training status')
- parser.add_argument('--save-model', action='store_true', default=True,
- help='For Saving the current Model')
- args = parser.parse_args()
-
- use_cuda = not args.no_cuda and torch.cuda.is_available()
-
- torch.manual_seed(args.seed)
-
- # 设置使用 GPU 还是 CPU 来运行算法
- device = torch.device("cuda" if use_cuda else "cpu")
-
- train_kwargs = {'batch_size': args.batch_size}
- test_kwargs = {'batch_size': args.test_batch_size}
- if use_cuda:
- cuda_kwargs = {'num_workers': 1,
- 'pin_memory': True,
- 'shuffle': True}
- train_kwargs.update(cuda_kwargs)
- test_kwargs.update(cuda_kwargs)
-
- # 定义数据预处理方法
- transform=transforms.Compose([
- transforms.ToTensor(),
- transforms.Normalize((0.1307,), (0.3081,))
- ])
-
- # 将 raw mnist 数据集转换为 pytorch mnist 数据集
- convert_raw_mnist_dataset_to_pytorch_mnist_dataset(args.data_url)
-
- # 分别创建训练和验证数据集
- dataset1 = datasets.MNIST(args.data_url, train=True, download=False,
- transform=transform)
- dataset2 = datasets.MNIST(args.data_url, train=False, download=False,
- transform=transform)
-
- # 分别构建训练和验证数据迭代器
- train_loader = torch.utils.data.DataLoader(dataset1, **train_kwargs)
- test_loader = torch.utils.data.DataLoader(dataset2, **test_kwargs)
-
- # 初始化神经网络模型并拷贝模型到计算设备上
- model = Net().to(device)
- # 定义训练优化器和学习率策略,用于梯度下降计算
- optimizer = optim.Adadelta(model.parameters(), lr=args.lr)
- scheduler = StepLR(optimizer, step_size=1, gamma=args.gamma)
-
- # 训练神经网络,每一轮进行一次验证
- for epoch in range(1, args.epochs + 1):
- train(args, model, device, train_loader, optimizer, epoch)
- test(model, device, test_loader)
- scheduler.step()
-
- # 保存模型与适配 ModelArts 推理模型包规范
- if args.save_model:
-
- # 在 train_url 训练参数对应的路径内创建 model 目录
- model_path = os.path.join(args.train_url, 'model')
- os.makedirs(model_path, exist_ok = True)
-
- # 按 ModelArts 推理模型包规范,保存模型到 model 目录内
- torch.save(model.state_dict(), os.path.join(model_path, 'mnist_cnn.pt'))
-
- # 拷贝推理代码与配置文件到 model 目录内
- the_path_of_current_file = os.path.dirname(__file__)
- shutil.copyfile(os.path.join(the_path_of_current_file, 'infer/customize_service.py'), os.path.join(model_path, 'customize_service.py'))
- shutil.copyfile(os.path.join(the_path_of_current_file, 'infer/config.json'), os.path.join(model_path, 'config.json'))
-
- if __name__ == '__main__':
- main()
- # coding=gbk
-
- import os
- import log
- import json
-
- import torch.nn.functional as F
- import torch.nn as nn
- import torch
- import torchvision.transforms as transforms
-
- import numpy as np
- from PIL import Image
-
- from model_service.pytorch_model_service import PTServingBaseService
-
- logger = log.getLogger(__name__)
-
- # 定义模型预处理
- infer_transformation = transforms.Compose([
- transforms.Resize(28),
- transforms.CenterCrop(28),
- transforms.ToTensor(),
- transforms.Normalize((0.1307,), (0.3081,))
- ])
-
-
- class PTVisionService(PTServingBaseService):
-
- def __init__(self, model_name, model_path):
- # 调用父类构造方法
- super(PTVisionService, self).__init__(model_name, model_path)
-
- # 调用自定义函数加载模型
- self.model = Mnist(model_path)
-
- # 加载标签
- self.label = [0,1,2,3,4,5,6,7,8,9]
-
- def _preprocess(self, data):
- preprocessed_data = {}
- for k, v in data.items():
- input_batch = []
- for file_name, file_content in v.items():
- with Image.open(file_content) as image1:
- # 灰度处理
- image1 = image1.convert("L")
- if torch.cuda.is_available():
- input_batch.append(infer_transformation(image1).cuda())
- else:
- input_batch.append(infer_transformation(image1))
- input_batch_var = torch.autograd.Variable(torch.stack(input_batch, dim=0), volatile=True)
- print(input_batch_var.shape)
- preprocessed_data[k] = input_batch_var
-
- return preprocessed_data
-
- def _postprocess(self, data):
- results = []
- for k, v in data.items():
- result = torch.argmax(v[0])
- result = {k: self.label[result]}
- results.append(result)
- return results
-
- def _inference(self, data):
-
- result = {}
- for k, v in data.items():
- result[k] = self.model(v)
-
- return result
-
-
- class Net(nn.Module):
- def __init__(self):
- super(Net, self).__init__()
- self.conv1 = nn.Conv2d(1, 32, 3, 1)
- self.conv2 = nn.Conv2d(32, 64, 3, 1)
- self.dropout1 = nn.Dropout(0.25)
- self.dropout2 = nn.Dropout(0.5)
- self.fc1 = nn.Linear(9216, 128)
- self.fc2 = nn.Linear(128, 10)
-
- def forward(self, x):
- x = self.conv1(x)
- x = F.relu(x)
- x = self.conv2(x)
- x = F.relu(x)
- x = F.max_pool2d(x, 2)
- x = self.dropout1(x)
- x = torch.flatten(x, 1)
- x = self.fc1(x)
- x = F.relu(x)
- x = self.dropout2(x)
- x = self.fc2(x)
- output = F.log_softmax(x, dim=1)
- return output
-
-
- def Mnist(model_path, **kwargs):
- # 生成网络
- model = Net()
-
- # 加载模型
- if torch.cuda.is_available():
- device = torch.device('cuda')
- model.load_state_dict(torch.load(model_path, map_location="cuda:0"))
- else:
- device = torch.device('cpu')
- model.load_state_dict(torch.load(model_path, map_location=device))
-
- # CPU 或者 GPU 映射
- model.to(device)
-
- # 声明为推理模式
- model.eval()
-
- return model
- {
- "model_algorithm": "image_classification",
- "model_type": "PyTorch",
- "runtime": "pytorch_1.8.0-cuda_10.2-py_3.7-ubuntu_18.04-x86_64"
- }
本地运行train.py后生成ckpt文件夹,其中包含各个epoch生成的.ckpt文件。之后运行eval.py即可输出accuracy。运行截图如下:
在线服务部署完成后,便可以在“在线服务”页面,单击在线服务名称,进入服务详情页面,单击“预测”页签,请求类型选择“multipart/form-data”,请求参数填写“image”,单击“上传”按钮上传示例图片如下,然后单击“预测”。
预测完成后,预测结果显示区域将展示预测结果如下图。(左下区域应该显示输入的图片,这里我没加载出来)
根据预测结果内容,可识别出此图片的数字是“2”。
[1] 华为云.使用自定义算法构建模型.2023-05-11.[DB/OL].使用自定义算法构建模型(手写数字识别)_AI开发平台ModelArts_最佳实践_模型训练(自定义算法-新版训练)_华为云 (huaweicloud.com)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。