使用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)