使用MindSpore构建LeNet-5模型实现分类任务体验
使用MindSpore构建LeNet-5模型实现分类任务
实验介绍
图像信息中存在中大量的信息,所谓一图盛千言,就是在表达这个意思。 在众多处理图像中,将图像进行分类将是最基本的任务。本实验将利用卷积神经网络进行手写体识别,实验中使用的深度学习框架Mindspore构建是卷积神经网络模型解决图像分类问题。
实验使用的数据集是Cifar-10,CIFAR-10数据集由10个类的60000个32x32彩**像组成,每个类有6000个图像。有50000个训练图像和10000个测试图像,学员们将通过本实验基本理解物理识别的基础理解。
【实验环境要求】:
Python 3.7.5
Mindspore 0.5
实验总体设计:
环境导入
数据集展示
数据集载入预处理
构建LeNet5模型
训练模型
设置mindspore环境
设计损失函数与优化器
设置callback函数
模型训练
测试网络模型
模型优化
改善网络
重新训练与测试
预测效果可视化
实验目的:
加强对基于Mindspore的神经网络模型构建流程的理解
掌握如何用Mindspore实现卷积神经网络的构建
学会利用checkpoint函数保存模型参数
掌握如何利用模型预测单张图像的分类结果
实验详细设计与实现
本节将详细介绍实验的设计与实现。
4.1节 导入实验环境;
4.2节 数据集展示与数据初始化;
4.3节 构建网络模型;
4.4节 模型训练与测试;
4.5节 模型优化与重新训练;
4.6节 模型测试与可视化。
说明:如果运行结果中出现WARNING或者UserWarning,无需担心;不会影响实验结果。
导入实验环境
导入相关实验模块
mindspore包主要用于本次实验卷积神经网络的构建,包括很多子模块,在该实验当中,mindspore.dataset主要模块cifar-10数据集的载入与处理,也可以自定义数据集。mindspore.common包中会有诸如type形态转变、权重初始化等的常规工具;mindspore.Tensor提供了mindspore网络可用的张量, context用于设定,mindspore的运行环境与运行设备,Model用来承载网络结构,并能够调用优化器,损失函数,评价指标,mindspord.nn当中主要会包括网络可能涉及到的各类网络层,诸如卷积层,池化层,全连接层;也包括损失函数,激活函数等。mindspore.train.callback下面会涉及到各类回调函数,如checkpoint,lossMonitor等,主要用于在每个epoch训练完的时候自动执行。其他numpy用来处理数组问题,matplotlib用于画图。
import mindspore # 载入mindspore的默认数据集 import mindspore.dataset as ds # 常用转化用算子 import mindspore.dataset.transforms.c_transforms as C # 图像转化用算子 import mindspore.dataset.transforms.vision.c_transforms as CV from mindspore.common import dtype as mstype # mindspore的tensor from mindspore import Tensor # 各类网络层都在nn里面 import mindspore.nn as nn # 参数初始化的方式 from mindspore.common.initializer import TruncatedNormal # 设置mindspore运行的环境 from mindspore import context # 引入训练时候会使用到回调函数,如checkpoint, lossMoniter from mindspore.train.callback import ModelCheckpoint, CheckpointConfig, LossMonitor, TimeMonitor # 引入模型 from mindspore.train import Model # 引入评估模型的包 from mindspore.nn.metrics import Accuracy # numpy import numpy as np # 画图用 import matplotlib.pyplot as plt
数据集展示与数据初始化
CIFAR-10数据集由10个类的60000个32x32彩**像组成,每个类有6000个图像。有50000个训练图像和10000个测试图像。数据集分为五个训练批次和一个测试批次,每个批次有10000个图像。测试批次包含来自每个类别的恰好1000个随机选择的图像。训练批次以随机顺序包含剩余图像,但一些训练批次可能包含来自一个类别的图像比另一个更多。总体来说,五个训练集之和包含来自每个类的正好5000张图像。
10个类完全相互排斥。且类之间没有重叠,汽车和卡车之间没有重叠。“汽车”包括轿车,SUV,这类东西。“卡车”只包括大卡车,都不包括皮卡车。
以下是类的名字:airplane/automobile/bird/cat/deer/dog/frog/horse/ship/truck
数据集下载
import os import requests import zipfile current_path = os.getcwd() filename = 'cifar10_mindspore.zip' url = 'https://professional-construction.obs.cn-north-4.myhuaweicloud.com/ComputerVision/cifar10_mindspore.zip' # 使用request下载 print('downloading with requests') r = requests.get(url) with open(filename, 'wb') as code: code.write(r.content) print('download finished') #将打包的文件解压 with zipfile.ZipFile(filename, 'r') as f: for file in f.namelist(): f.extract(file, current_path) # 删除文件 os.remove(os.path.join(current_path,filename)) print('data prepared')
查看数据集
注意:这里每次提取出来的数据是随机的。
#创建图像标签列表 category_dict = {0:'airplane',1:'automobile',2:'bird',3:'cat',4:'deer',5:'dog', 6:'frog',7:'horse',8:'ship',9:'truck'} cifar_ds = ds.Cifar10Dataset('./data/10-verify-bin') # 设置图像大小 plt.figure(figsize=(8,8)) i = 1 # 打印9张子图 for dic in cifar_ds.create_dict_iterator(): plt.subplot(3,3,i) plt.imshow(dic['image']) plt.xticks([]) plt.yticks([]) plt.axis('off') plt.title(category_dict[dic['label'].sum()]) i +=1 if i > 9 : break plt.show()
定义数据预处理的步骤
定义了两个函数,get_batch用于数据集的读取,使用dataset.Cifar10Dataset()来完成(数据需要预下载);第二个函数process_dataset是对于图像数据特征处理,其中主要包括尺寸大小变更,平移,归一化与标准化,训练时候的随机裁剪,随机翻转。并且内部对于数据集进行了shuffle,变更了一个批量输出的generator.
def get_data(datapath): cifar_ds = ds.Cifar10Dataset(datapath) return cifar_ds def process_dataset(cifar_ds,batch_size =32,status="train"): ''' ---- 定义算子 ---- ''' # 归一化 rescale = 1.0 / 255.0 # 平移 shift = 0.0 resize_op = CV.Resize((32, 32)) rescale_op = CV.Rescale(rescale, shift) # 对于RGB三通道分别设定mean和std normalize_op = CV.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)) if status == "train": # 随机裁剪 random_crop_op = CV.RandomCrop([32, 32], [4, 4, 4, 4]) # 随机翻转 random_horizontal_op = CV.RandomHorizontalFlip() # 通道变化 channel_swap_op = CV.HWC2CHW() # 类型变化 typecast_op = C.TypeCast(mstype.int32) ''' ---- 算子运算 ---- ''' cifar_ds = cifar_ds.map(input_columns="label", operations=typecast_op) if status == "train": cifar_ds = cifar_ds.map(input_columns="image", operations=random_crop_op) cifar_ds = cifar_ds.map(input_columns="image", operations=random_horizontal_op) cifar_ds = cifar_ds.map(input_columns="image", operations=resize_op) cifar_ds = cifar_ds.map(input_columns="image", operations=rescale_op) cifar_ds = cifar_ds.map(input_columns="image", operations=normalize_op) cifar_ds = cifar_ds.map(input_columns="image", operations=channel_swap_op) # shuffle cifar_ds = cifar_ds.shuffle(buffer_size=1000) # 切分数据集到batch_size cifar_ds = cifar_ds.batch(batch_size, drop_remainder=True) return cifar_ds
生成训练数据集
引用了上述的函数,训练集的位置在'./data/10-batches-bin'中,我们设置的batch_size=32
data_path='./data/10-batches-bin' batch_size=32 status="train" # 生成训练数据集 cifar_ds = get_data(data_path) ds_train = process_dataset(cifar_ds,batch_size =batch_size, status=status)
构建网络模型
定义Lenet网络结构,构建网络
LeNet-5出自论文Gradient-Based Learning Applied to Document Recognition,原本是一种用于手写体字符识别的非常高效的卷积神经网络,包含了深度学习的基本模块:卷积层,池化层,全链接层。
其网络结构包含如下:
INPUT(输入层) 32∗32的图片。
C1(卷积层)选取6个5∗5卷积核(不包含偏置),得到6个特征图,每个特征图的的一个边为32−5+1=28,也就是神经元的个数由1024减小到了28∗28=784。
S2(池化层)池化层是一个下采样层,输出14∗14∗6的特征图。
C3(卷积层)选取16个卷积核大小为5∗5,得到特征图大小为10∗10∗16。
S4(池化层)窗口大小为2∗2,输出5∗5∗16的特征图。
F5(全连接层)120个神经元。
F6(全连接层)84个神经元。
OUTPUT(全连接层)10个神经元,10分类问题。
"""LeNet.""" def conv(in_channels, out_channels, kernel_size, stride=1, padding=0): """weight initial for conv layer""" weight = weight_variable() return nn.Conv2d(in_channels, out_channels, kernel_size=kernel_size, stride=stride, padding=padding, weight_init=weight, has_bias=False, pad_mode="valid") def fc_with_initialize(input_channels, out_channels): """weight initial for fc layer""" weight = weight_variable() bias = weight_variable() return nn.Dense(input_channels, out_channels, weight, bias) def weight_variable(): """weight initial""" return TruncatedNormal(0.02) class LeNet5(nn.Cell): """ Lenet network Args: num_class (int): Num classes. Default: 10. Returns: Tensor, output tensor Examples: >>> LeNet(num_class=10) """ def __init__(self, num_class=10, channel=3): super(LeNet5, self).__init__() self.num_class = num_class self.conv1 = conv(channel, 6, 5) self.conv2 = conv(6, 16, 5) self.fc1 = fc_with_initialize(16 * 5 * 5, 120) self.fc2 = fc_with_initialize(120, 84) self.fc3 = fc_with_initialize(84, self.num_class) self.relu = nn.ReLU() self.max_pool2d = nn.MaxPool2d(kernel_size=2, stride=2) self.flatten = nn.Flatten() def construct(self, x): x = self.conv1(x) x = self.relu(x) x = self.max_pool2d(x) x = self.conv2(x) x = self.relu(x) x = self.max_pool2d(x) x = self.flatten(x) x = self.fc1(x) x = self.relu(x) x = self.fc2(x) x = self.relu(x) x = self.fc3(x) return x # 构建网络 network = LeNet5(10)
模型训练与测试
定义损失函数与优化器
这一部分主要给出了用于训练网路的损失函数优化器,本次使用的损失函数为 nn.SoftmaxCrossEntropyWithLogits损失函数,即把网络输出层的值经过softmax函数之后计算真实值与预测值之间的交叉熵损失。优化器使用了Momentum,即动量优化器。
另外我们同时设置了mindspore网路的设备与图的模型,context.GRAPH_MODE指向静态图模型,即在运行之前会把全部图建立编译完毕。设备指定为CPU或Ascend.
# 设置模型的设备与图的模式 context.set_context(mode=context.GRAPH_MODE, device_target='Ascend') # 使用交叉熵函数作为损失函数 net_loss = nn.SoftmaxCrossEntropyWithLogits(is_grad=False, sparse=True, reduction="mean") # 优化器为momentum net_opt = nn.Momentum(params=network.trainable_params(), learning_rate=0.01, momentum=0.9) # 监控每个epoch训练的时间 time_cb = TimeMonitor(data_size=ds_train.get_dataset_size())
定义保存路径与训练
这一部分主要函数训练时候的callback函数CheckpointConfig,ModelCheckpoint。Model函数中,确定网路模型,损失函数,优化器,评估指标。
# 设置CheckpointConfig,callback函数。save_checkpoint_steps=训练总数/batch_size config_ck = CheckpointConfig(save_checkpoint_steps=1562, keep_checkpoint_max=10) ckpoint_cb = ModelCheckpoint(prefix="checkpoint_lenet_original", directory='./results',config=config_ck) # 建立可训练模型 model = Model(network = network, loss_fn=net_loss,optimizer=net_opt, metrics={"Accuracy": Accuracy()}) print("============== Starting Training ==============") model.train(10, ds_train,callbacks=[time_cb, ckpoint_cb, LossMonitor(per_print_times=200)],dataset_sink_mode=False) model.eval(ds_train, dataset_sink_mode=False)
设置测试集参数并测试
注意:测试集不会进行随机裁剪与翻转
data_path='./data/10-verify-bin' batch_size=32 status="test" # 生成测试数据集 cifar_ds = ds.Cifar10Dataset(data_path) ds_eval = process_dataset(cifar_ds,batch_size=batch_size,status=status) res = model.eval(ds_eval, dataset_sink_mode=False) print(res)
模型优化与重新训练
重新定义网络
lenet网络本身并不足以对于cifar-10的图像分类产生出足够的效果,因此需要做进一步改进。总的来说,网络基本维持了lenet的网络结构,增加卷积的个数与卷积核的大小,同时略微增加了网路的深度。
所有的卷积核从5∗5变成3∗3.
增加了一层网路的深度,提升模型的非线性映射能力
提升了卷积核数量,是模型可以提取更多的特征,如64核,128核,512核。
class LeNet5_2(nn.Cell): """ Lenet network Args: num_class (int): Num classes. Default: 10. Returns: Tensor, output tensor Examples: >>> LeNet(num_class=10) """ def __init__(self, num_class=10, channel=3): super(LeNet5_2, self).__init__() self.num_class = num_class self.conv1 = conv(channel, 64, 3) self.conv2 = conv(64, 128, 3) self.conv3 = conv(128, 128, 3) self.fc1 = fc_with_initialize(512, 120) self.fc2 = fc_with_initialize(120, 84) self.fc3 = fc_with_initialize(84, self.num_class) self.relu = nn.ReLU() self.max_pool2d = nn.MaxPool2d(kernel_size=2, stride=2) self.flatten = nn.Flatten() def construct(self, x): x = self.conv1(x) x = self.relu(x) x = self.max_pool2d(x) x = self.conv2(x) x = self.relu(x) x = self.max_pool2d(x) x = self.conv3(x) x = self.relu(x) x = self.max_pool2d(x) x = self.flatten(x) x = self.fc1(x) x = self.relu(x) x = self.fc2(x) x = self.relu(x) x = self.fc3(x) return x
用新网络进行训练
其余损失函数与,优化器等保持不变,以及模型训练的参数保持不变,仅仅将 ModelCheckpoint中保存模型的前缀prefix改为"checkpoint_lenet_verified".
network = LeNet5_2(10) # 设置模型的设备 context.set_context(mode=context.GRAPH_MODE, device_target='Ascend') # 使用交叉熵函数作为损失函数 net_loss = nn.SoftmaxCrossEntropyWithLogits(is_grad=False, sparse=True, reduction="mean") # 优化器为momentum net_opt = nn.Momentum(params=network.trainable_params(), learning_rate=0.01, momentum=0.9) # 时间监控,反馈每个epoch的运行时间 time_cb = TimeMonitor(data_size=ds_train.get_dataset_size()) # 设置callback函数。 config_ck = CheckpointConfig(save_checkpoint_steps=1562, keep_checkpoint_max=10) ckpoint_cb = ModelCheckpoint(prefix="checkpoint_lenet_verified",directory='./results', config=config_ck) # 建立可训练模型 model = Model(network = network, loss_fn=net_loss,optimizer=net_opt, metrics={"Accuracy": Accuracy()}) print("============== Starting Training ==============") model.train(10, ds_train,callbacks=[time_cb, ckpoint_cb, LossMonitor(200)],dataset_sink_mode=False)
模型测试与可视化
评估模型的有效性
data_path='./data/10-verify-bin' batch_size=32 status="test" ds_eval = get_data(data_path) ds_eval = process_dataset(cifar_ds=ds_eval,batch_size=batch_size,status=status) res_train = model.eval(ds_train, dataset_sink_mode=False) res_test = model.eval(ds_eval, dataset_sink_mode=False) # 评估训练集 print('train results:',res_train) # 评估测试集 print('test results:',res_test)
图片类别预测与可视化
#创建图像标签列表 category_dict = {0:'airplane',1:'automobile',2:'bird',3:'cat',4:'deer',5:'dog', 6:'frog',7:'horse',8:'ship',9:'truck'} cifar_ds = get_data('./data/10-verify-bin') df_test = process_dataset(cifar_ds,batch_size=1,status='test') def normalization(data): _range = np.max(data) - np.min(data) return (data - np.min(data)) / _range # 设置图像大小 plt.figure(figsize=(10,10)) i = 1 # 打印9张子图 for dic in df_test: # 预测单张图片 input_img = dic[0] output = model.predict(Tensor(input_img)) output = nn.Softmax()(output) # 反馈可能性最大的类别 predicted = np.argmax(output.asnumpy(),axis=1)[0] # 可视化 plt.subplot(3,3,i) # 删除batch维度 input_image = np.squeeze(input_img,axis=0).transpose(1,2,0) # 重新归一化,方便可视化 input_image = normalization(input_image) plt.imshow(input_image) plt.xticks([]) plt.yticks([]) plt.axis('off') plt.title('True label:%s,\n Predicted:%s'%(category_dict[dic[1].sum()],category_dict[predicted])) i +=1 if i > 9 : break plt.show()
- 点赞
- 收藏
- 关注作者
评论(0)