使用MindSpore构建LeNet-5模型实现分类任务体验

举报
Tianyi_Li 发表于 2020/11/22 14:49:18 2020/11/22
【摘要】 图像信息中存在中大量的信息,所谓一图胜千言,就是在表达这个意思。 在众多处理图像中,将图像进行分类将是最基本的任务。实验中使用的深度学习框架Mindspore构建是卷积神经网络模型解决图像分类问题。

使用MindSpore构建LeNet-5模型实现分类任务

实验介绍

图像信息中存在中大量的信息,所谓一图盛千言,就是在表达这个意思。 在众多处理图像中,将图像进行分类将是最基本的任务。本实验将利用卷积神经网络进行手写体识别,实验中使用的深度学习框架Mindspore构建是卷积神经网络模型解决图像分类问题。
实验使用的数据集是Cifar-10,CIFAR-10数据集由10个类的60000个32x32彩**像组成,每个类有6000个图像。有50000个训练图像和10000个测试图像,学员们将通过本实验基本理解物理识别的基础理解。

【实验环境要求】:

  1. Python 3.7.5

  2. Mindspore 0.5

实验总体设计:

  1. 环境导入

  • 数据集展示

  • 数据集载入预处理

  • 构建LeNet5模型

  • 训练模型

    • 设置mindspore环境

    • 设计损失函数与优化器

    • 设置callback函数

    • 模型训练

  • 测试网络模型

  • 模型优化

    • 改善网络

  • 重新训练与测试

  • 预测效果可视化

实验目的:

  1. 加强对基于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()

image.png

定义数据预处理的步骤

定义了两个函数,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,原本是一种用于手写体字符识别的非常高效的卷积神经网络,包含了深度学习的基本模块:卷积层,池化层,全链接层。

其网络结构包含如下:

  1. INPUT(输入层) 32∗32的图片。

  2. C1(卷积层)选取6个5∗5卷积核(不包含偏置),得到6个特征图,每个特征图的的一个边为32−5+1=28,也就是神经元的个数由1024减小到了28∗28=784。

  3. S2(池化层)池化层是一个下采样层,输出14∗14∗6的特征图。

  4. C3(卷积层)选取16个卷积核大小为5∗5,得到特征图大小为10∗10∗16。

  5. S4(池化层)窗口大小为2∗2,输出5∗5∗16的特征图。

  6. F5(全连接层)120个神经元。

  7. F6(全连接层)84个神经元。

  8. 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的网络结构,增加卷积的个数与卷积核的大小,同时略微增加了网路的深度。

  1. 所有的卷积核从5∗5变成3∗3.

  2. 增加了一层网路的深度,提升模型的非线性映射能力

  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()

image.png


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。