lenet+resnet可视化探讨
一、实验目的
- 学会使用MindSpore进行简单卷积神经网络的开发。
- 学会使用MindSpore进行CIFAR-10数据集分类任务的训练和测试。
- 可视化学习到的特征表达器,和手工定义的特征进行分析和比较
- 实验框架
mindspore + pytorch(特征可视化)
- 实验数据集CIFAR10
3.1 CIFAR10简介
CIFAR-10数据集由10个类的60000个32x32彩色图像组成,每个类有6000个图像。
10个类完全相互排斥,且类之间没有重叠,汽车和卡车之间没有重叠。“汽车”包括轿车,SUV等。“卡车”只包括大卡车,不包括皮卡车。
以下是10个类别类的名字: airplane/automobile/bird/cat/deer/dog/frog/horse/ship/truck。
3.2 数据集下载
根据mindspore官网提示使用命令下载并解压数据集到相应位置:
wget -N https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/cifar-10-binary.tar.gz --no-check-certificate
tar -zxvf cifar-10-binary.tar.gz -C ./datasets
下载好的cifar-10-batches-bin文件包含一个操作说明readme.html,一个写有类别名称的batches.meta.txt,以及5个训练集和1个测试集bin文件
本次实验不单独划分验证集而直接使用测试集进行val操作。在cifar-10-batches-bin文件目录下创建两个文件夹分别命名为train和test,将训练集和测试机分别放入相应文件夹下,方便后续数据集加载与处理。
3.3 数据集预处理与增强
将数据预处理与增强操作写在函数def create_dataset(data_path, batch_size=32, repeat_size=1,num_parallel_workers=1, training=True)中
调用相应数据集时将数据集目录传入函数data_path中,例如:
# 加载训练数据集
data_path = "./datasets/cifar-10-batches-bin"
ds_train = create_dataset(os.path.join(data_path, "train"), 32, repeat_size, training=True)
ds_test = create_dataset(os.path.join(data_path, "test"), 32, training=False)
在函数create_dataset中首先定义数据集及数据增强参数大小。resize_height, resize_width分别为图像放大后的长和宽。在使用Lent训练时采用长和宽为32,其原因是将图片放大到224,224后,并不能提升Lenet的训练准确率,还使得训练时间增加。rescale操作是将原本像素值为0-255映射到0-1之间,符合网络训练对数据集的要求。
# 定义数据集
cifar10_ds = ds.Cifar10Dataset(data_path)
resize_height, resize_width = 32, 32
#resize_height, resize_width = 224, 224
rescale = 1.0 / 255.0
shift = 0.0
rescale_nml = 1 / 0.3081
shift_nml = -1 * 0.1307 / 0.3081
进行数据增强操作需要引用两个头文件,具体的操作在注释中给出:
import mindspore.dataset.transforms.c_transforms as C
import mindspore.dataset.vision.c_transforms as CV
#图像大小缩放
resize_op = CV.Resize((resize_height, resize_width), interpolation=Inter.LINEAR)
#将给定图像随机裁剪为不同的大小和宽高比,然后缩放所裁剪得到的图像为制定的大小
crop_op = c_vision.RandomCrop((32, 32), (4, 4, 4, 4))
#像素值调整
rescale_nml_op = CV.Rescale(rescale_nml, shift_nml)
rescale_op = CV.Rescale(rescale, shift)
#归一化处理
normalize_op = CV.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
#图片HWC格式转CHW格式
hwc2chw_op = CV.HWC2CHW()
#标签数据类型由int8转换为int32
type_cast_op = C.TypeCast(mstype.int32)
随后使用map映射函数,将数据操作应用到数据集。这里使用list的方式映射,使得代码更简洁。需要注意crop操作只需要对训练集进行,对传入的training进行判断:
# 使用map映射函数,将数据操作应用到数据集
transforms_list=[]
if training:
transforms_list+=[crop_op]
transforms_list+=[resize_op,rescale_nml_op,rescale_op,normalize_op,hwc2chw_op]
cifar10_ds = cifar10_ds.map(operations=type_cast_op, input_columns="label", num_parallel_workers=num_parallel_workers)
cifar10_ds = cifar10_ds.map(operations=transforms_list, input_columns="image", num_parallel_workers=num_parallel_workers)
最后一部,对数据集打乱和分批,训练集/测试集数据就处理好了。
ds.config.set_seed(58)
# 进行shuffle、batch操作
buffer_size = 10000
# 随机打乱数据顺序
cifar10_ds = cifar10_ds.shuffle(buffer_size=buffer_size)
# 对数据集进行分批
cifar10_ds = cifar10_ds.batch(batch_size, drop_remainder=True)
return cifar10_ds
- 模型简述
4.1 Lenet5
LeNet-5出自论文Gradient-Based Learning Applied to Document Recognition,是一种用于手写体字符识别的卷积神经网络。LeNet5 的网络就今天而言虽然很小,但是包含了深度学习的基本模块有:卷积层,池化层,全链接层,是其他深度学习模型的基础。
LeNet-5共有7层,其中有2个卷积层、2个下抽样层(池化层)、3个全连接层,每层都包含可训练参数;每个层有多个Feature Map,每个Feature Map有多个神经元并通过一种卷积滤波器提取输入的一种特征,结构如下
LeNet-5第一层:卷积层C1
C1层是卷积层,形成6个特征图谱。卷积的输入区域大小是5x5,每个特征图谱内参数共享,即每个特征图谱内只使用一个共同卷积核,卷积核有5x5个连接参数加上1个偏置共26个参数。卷积区域每次滑动一个像素,这样卷积层形成的每个特征图谱大小是(32-5)/1+1=28x28。
LeNet-5第二层:池化层S2
S2层是一个下采样层,利用图像局部相关性的原理,对图像进行子抽样,减少数据处理量同时保留有用信息。C1层的6个28x28的特征图谱分别进行以2x2为单位的下抽样得到6个14x14的图。每个特征图谱使用一个下抽样核。
LeNet-5第三层:卷积层C3
C3层是一个卷积层,卷积核和C1相同,不同的是C3的每个节点与S2中的多个图相连。C3层有16个10x10的图,他采取的不对称的组合连接方式有利于提取多种组合特征。
LeNet-5第四层:池化层S4
S4是一个下采样层。C3层的16个10x10的图分别进行以2x2为单位的下抽样得到16个5x5的图。5x5x5x16=2000个连接。连接的方式与S2层类似。
LeNet-5第五层:全连接层C5
C5层是一个全连接层。由于S4层的16个图的大小为5x5,与卷积核的大小相同,所以卷积后形成的图的大小为1x1。这里形成120个卷积结果。每个都与上一层的16个图相连。
LeNet-5第六层:全连接层F6
F6层是全连接层。F6层有84个节点,对应于一个7x12的比特图。该层的训练参数和连接数是(120 + 1)x84=10164。
LeNet-5第七层:全连接层Output
Output层也是全连接层,共有10个节点,分别代表数字0到9。该层有84x10=840个参数和连接。
4.2 resnet50
resnet可以说是卷积神经网络结构的一次飞跃。其使用了Residual net(残差网络)。残差网络将靠前若干层的某一层数据输出直接跳过多层引入到后面数据层的输入部分。这意味着后面的特征层的内容会有一部分由其前面的某一层线性贡献。残差网络很好的解决了深度神经网络的退化问题,其结构如下:
resnet使用的block结构使用多个小的卷积核代替大的卷积核的方法,使得输出尺寸不变的同时训练的时间大大缩短,这也是resenet的优势。
resnet50有两个基本的块,分别名为Conv Block和Identity Block,其中Conv Block输入和输出的维度是不一样的,所以不能连续串联,它的作用是改变网络的维度;Identity Block输入维度和输出维度相同,可以串联,用于加深网络的。他们的结构如下:
- 训练过程与代码
5.1 Lenet5模型代码
根据mindspore官网的提示,我们可以较为容易地构建出Lenet5 网络结构模型代码:
class LeNet5(nn.Cell):
def __init__(self, num_class=10, num_channel=3):
super(LeNet5, self).__init__()
# 定义所需要的运算
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)
self.fc2 = nn.Dense(120, 84)
self.fc3 = nn.Dense(84, 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
5.2 训练代码
在开始训练时,我们首先定义全局变量train_epoch为我们要训练的epoch,并将使用显卡训练的信息引入mindspore:
#运行信息
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
context.set_context(mode=context.GRAPH_MODE, device_target="GPU")
context.set_context(device_id=0)
train_epoch = 50
随后在main函数中将Lenet5网络实例化,并定义算损失函数与优化器。在定义好数据地址与模型保存地址等信息后,调用train_net函数进行训练。学习率定为0.01,优化器选择了Momentum即动量优化器。
if __name__ == '__main__':
#网络实例化
net = LeNet5()
# 定义损失函数
net_loss = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean')
#定义优化器
lr = 0.01
momentum = 0.9
net_opt = nn.Momentum(net.trainable_params(), learning_rate=lr, momentum=momentum)
dataset_size = 1
model = Model(net, net_loss, net_opt, metrics={"Accuracy": Accuracy()})
#数据处理
cifar10_path = "./datasets/cifar-10-batches-bin"
model_path = "./models"
# 设置模型保存参数
config_ck = CheckpointConfig(save_checkpoint_steps=1875, keep_checkpoint_max=10)
# 应用模型保存参数
ckpoint = ModelCheckpoint(prefix="checkpoint_lenet",directory=model_path, config=config_ck)
train_net(model, train_epoch, cifar10_path, dataset_size, ckpoint, False)
在train_net函数中调用create_dataset函数创建数据集,随后将需要打印的信息传入继承了Callback类的EvalCallBack后,使用model.train语句开始训练:
def train_net(model, epoch_size, data_path, repeat_size, ckpoint_cb, sink_mode):
"""定义训练的方法"""
# 加载训练数据集
ds_train = create_dataset(os.path.join(data_path, "train"), 32, repeat_size, training=True)
ds_test = create_dataset(os.path.join(data_path, "test"), 32, training=False)
#打印信息
steps_loss = {"step": [], "loss_value": []}
steps_eval = {"step": [], "acc": []}
eval_param_dict = {"model":model,"dataset":ds_test,"metrics_name":"Accuracy"}
step_loss_acc_info = EvalCallBack(apply_eval, eval_param_dict,model , ds_test, steps_loss, steps_eval)
model.train(epoch_size, ds_train, callbacks=[ckpoint_cb, LossMonitor(125),step_loss_acc_info], dataset_sink_mode=sink_mode)
EvalCallBack类继承了Callback类的__init__、step_end、epoch_end、end类函数,其作用分别是:__init__根据传入的参数初始化类;step_end在每一step完成后进行操作;epoch_end在每一epoch完成后进行操作;end在训练结束时进行操作。
class EvalCallBack(Callback):
def __init__(self, eval_function, eval_param_dict,model, eval_dataset, steps_loss, steps_eval,interval=1,eval_start_epoch=1,save_best_ckpt=True,ckpt_directory="./model/", besk_ckpt_name="best.ckpt", metrics_name="acc"):
self.eval_param_dict = eval_param_dict
self.eval_function = eval_function
self.model = model
self.eval_dataset = eval_dataset
self.steps_loss = steps_loss
self.steps_eval = steps_eval
self.eval_start_epoch = eval_start_epoch
self.interval = interval
self.save_best_ckpt = save_best_ckpt
self.best_res = 0
self.best_epoch = 0
if not os.path.isdir(ckpt_directory):
os.makedirs(ckpt_directory)
self.best_ckpt_path = os.path.join(ckpt_directory, besk_ckpt_name)
self.metrics_name = metrics_name
self.loss_d=[]
self.res_d=[]
def step_end(self, run_context):
cb_params = run_context.original_args()
cur_epoch = cb_params.cur_epoch_num
cur_step = (cur_epoch-1)*1562 + cb_params.cur_step_num
self.steps_loss["loss_value"].append(str(cb_params.net_outputs))
self.steps_loss["step"].append(str(cur_step))
# 删除ckpt文件
def remove_ckpoint_file(self, file_name):
os.chmod(file_name, stat.S_IWRITE)
os.remove(file_name)
# 每一个epoch后,打印训练集的损失值和验证集的模型精度,并保存精度最好的ckpt文件
def epoch_end(self, run_context):
cb_params = run_context.original_args()
cur_epoch = cb_params.cur_epoch_num
loss_epoch = cb_params.net_outputs
if cur_epoch >= self.eval_start_epoch and (cur_epoch - self.eval_start_epoch) % self.interval == 0:
res = self.eval_function(self.eval_param_dict)
print('Epoch {}/{}'.format(cur_epoch, train_epoch))
print('-' * 10)
print('train Loss: {}'.format(loss_epoch))
print('val Acc: {}'.format(res))
if res >= self.best_res:
self.best_res = res
self.best_epoch = cur_epoch
if self.save_best_ckpt:
if os.path.exists(self.best_ckpt_path):
self.remove_ckpoint_file(self.best_ckpt_path)
save_checkpoint(cb_params.train_network, self.best_ckpt_path)
self.loss_d.append(loss_epoch.asnumpy())
self.res_d.append(res)
# 训练结束后,打印最好的精度和对应的epoch
def end(self, run_context):
print("End training, the best {0} is: {1}, the best {0} epoch is{2}".format(self.metrics_name,self.best_res,self.best_epoch), flush=True)
x=np.arange(1,train_epoch+1)
_,ax1=plt.subplots()
ax2=ax1.twinx()
ax1.plot(x,self.loss_d, color="red",label='train loss')
ax2.plot(x,self.res_d, color="blue",label='val Acc')
ax1.set_xlabel('epoch')
ax1.set_ylabel('train loss')
ax2.set_ylabel('val Acc')
ax1.legend(loc='best')
ax2.legend(loc='best')
plt.savefig(fname="lenet1_trainning.png")
我们在train_net函数中向EvalCallBack传入的eval_function为:
# 模型验证
def apply_eval(eval_param):
eval_model = eval_param['model']
eval_ds = eval_param['dataset']
metrics_name = eval_param['metrics_name']
res = eval_model.eval(eval_ds)
return res[metrics_name]
开始训练后,在执行一定的step后会返回实时的loss值;在每一个epoch完成后,会返回实时的训练loss值与验证集准确率;训练结束后会将训练过程中loss的变化曲线与准确率的变化曲线画成曲线图并保存。
训练过程中的输出信息示例如下:
5.3 模型改进
步骤一
在原有Lenet的结构中将第一层的输入通道数改为3后直接训练,在训练到第10个epoch时准确率达到55%,随后继续训练即使loss一直在降低,准确率并没有上升甚至有所下滑。初步判定为Lenet网络结构较浅,对于三通道输入的图片并不能很好地提取特征。
步骤二
在原有Lenet的结构基础上进行模型的微调,依旧只使用卷积层、池化层、全连接层。将卷积层的网络结构加深,在第一个卷积层中将三通道的输入图片使用大小(32,3,3,3)的卷积核,输出32通道的特征,之后进一步加深网络。最终第一个全连接层的输出特征大小为(32*8*8)。卷积层个数在原有2个的基础上增加一个,各网络层的参数也相应变化。
此方法由于加深了卷积层网络通道数,loss降低速度较步骤一Lenet5原有结构而言稍微缓慢,但由于深度的增加、卷积核参数增加,准确率在训练到30个
epoch时达到了70%。
但随之而来的问题是训练的继续进行,loss的进一步下降带来的却是准确率的飞速下降,并在训练到40个epoch时变为10%,而对于十分类而言10%的准确率说明出现了过拟合的现象,模型已经失效。
class LeNet5(nn.Cell):
def __init__(self, num_class=10, num_channel=3):
super(LeNet5, self).__init__()
# 定义所需要的运算
self.conv1 = nn.Conv2d(num_channel, 32, kernel_size=3, stride=1, pad_mode="same", weight_init='XavierUniform')
self.conv2 = nn.Conv2d(32, 32, 3, stride=1, pad_mode="same")
self.conv3 = nn.Conv2d(32, 32, 3, stride=1, pad_mode="same")
self.fc1 = nn.Dense(32*8*8, 400)
self.fc2 = nn.Dense(400, 120)
self.fc3 = nn.Dense(120, 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.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
步骤三
随后由于出现过拟合现象,我们进一步增加卷积层与网络深度,但在增加了一个卷积层并将最终输出特征通道数定为64时,loss从训练开始遍无法下降,准确率也相应没有提升。
为此我们初步采取使用不同的初始化手段如正态分布、Xavier对卷积层参数初始化,调整学习率和优化器参数等方法改变训练超参数,但都没有效果,loss依旧无法下降。
于是我们打算从网络结构入手进行调整,由于考虑到网络无法有效训练可能是网络参数过多导致的,我们在引入BN(batch_norm)的基础上增加dropout层。
这里需要注意的是mindspore的dropout层参数与pytorch不同的是,mindspore定义的dropout层参数是留下来的参数比例,这和pytorch是相反的,及mindspore中nn.Dropout(0.75)代表的是留下75%的参数。
于是乎我们的网络结构也是在对比了其他信息最终我们采用的网络结构有4个卷积层,每个卷积层之间由BN层与Relu层连接,每两个卷积层后跟随着一个kernel_size=2的池化层使特征的size变为原本的四分之一,每个池化层后跟随着一个dropout层。
BN层的加入可以使训练过程更加的稳定,对权重的初始化、梯度不会过于敏感,同时在不依赖dropout层的情况下减轻了过拟合的风险。
准确率在网络训练的第一个epoch就达到了45%,在50个epoch训练结束后,准确率达到了80.3%
网络训练过程的loss与准确率变化如下图所示。
class LeNet5(nn.Cell):
def __init__(self, num_class=10, num_channel=3):
super(LeNet5, self).__init__()
# 定义所需要的运算
self.conv1 = nn.Conv2d(num_channel, 32, kernel_size=3, stride=1, pad_mode="same", weight_init='XavierUniform')
self.conv2 = nn.Conv2d(32, 32, 3, stride=1, pad_mode="same", weight_init='XavierUniform')
self.conv3 = nn.Conv2d(32, 64, 3, stride=1, pad_mode="same", weight_init='XavierUniform')
self.conv4 = nn.Conv2d(64, 64, 3, stride=1, pad_mode="same", weight_init='XavierUniform')
self.conv5 = nn.Conv2d(64, 128, 3, stride=1, pad_mode="same", weight_init='XavierUniform')
self.conv6 = nn.Conv2d(128, 128, 3, stride=1, pad_mode="same", weight_init='XavierUniform')
self.fc1 = nn.Dense(128*28*28, 4096, weight_init='XavierUniform', bias_init='Uniform')
self.fc2 = nn.Dense(4096, 400, weight_init='XavierUniform', bias_init='Uniform')
self.fc3 = nn.Dense(400, num_class, weight_init='XavierUniform', bias_init='Uniform')
self.relu = nn.ReLU()
self.max_pool2d = nn.MaxPool2d(kernel_size=2, stride=2)
self.flatten = nn.Flatten()
self.bn1 = nn.BatchNorm2d(32, momentum=0.99, eps=0.00001, gamma_init='Uniform',
beta_init=Tensor(np.zeros(32).astype(np.float32)), moving_mean_init=Tensor(np.zeros(32).astype(np.float32)), moving_var_init=Tensor(np.ones(32).astype(np.float32)))
self.bn2 = nn.BatchNorm2d(32, momentum=0.99, eps=0.00001, gamma_init='Uniform',
beta_init=Tensor(np.zeros(32).astype(np.float32)), moving_mean_init=Tensor(np.zeros(32).astype(np.float32)), moving_var_init=Tensor(np.ones(32).astype(np.float32)))
self.bn3 = nn.BatchNorm2d(64, momentum=0.99, eps=0.00001, gamma_init='Uniform',
beta_init=Tensor(np.zeros(64).astype(np.float32)), moving_mean_init=Tensor(np.zeros(64).astype(np.float32)), moving_var_init=Tensor(np.ones(64).astype(np.float32)))
self.bn4 = nn.BatchNorm2d(64, momentum=0.99, eps=0.00001, gamma_init='Uniform',
beta_init=Tensor(np.zeros(64).astype(np.float32)), moving_mean_init=Tensor(np.zeros(64).astype(np.float32)), moving_var_init=Tensor(np.ones(64).astype(np.float32)))
self.bn5 = nn.BatchNorm2d(128, momentum=0.99, eps=0.00001, gamma_init='Uniform',
beta_init=Tensor(np.zeros(128).astype(np.float32)), moving_mean_init=Tensor(np.zeros(128).astype(np.float32)), moving_var_init=Tensor(np.ones(128).astype(np.float32)))
self.bn6 = nn.BatchNorm2d(128, momentum=0.99, eps=0.00001, gamma_init='Uniform',
beta_init=Tensor(np.zeros(128).astype(np.float32)), moving_mean_init=Tensor(np.zeros(128).astype(np.float32)), moving_var_init=Tensor(np.ones(128).astype(np.float32)))
self.dropout=nn.Dropout(0.75)
def construct(self, x):
# 使用定义好的运算构建前向网络
x = self.conv1(x)
x=self.bn1(x)
x = self.relu(x)
x=self.conv2(x)
x=self.bn2(x)
x = self.relu(x)
x = self.max_pool2d(x)
x=self.dropout(x)
x = self.conv3(x)
x=self.bn3(x)
x = self.relu(x)
x = self.conv4(x)
x=self.bn4(x)
x = self.relu(x)
x = self.max_pool2d(x)
x=self.dropout(x)
x = self.conv5(x)
x=self.bn5(x)
x = self.relu(x)
x = self.conv6(x)
x=self.bn6(x)
x = self.relu(x)
x = self.max_pool2d(x)
x=self.dropout(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
步骤四
在尝到了一点甜头后,我们希望在加入了BN和dropout层的基础上进一步增加卷积层数量与网络深度。
参照resnet对CIFAR10的做法,我们在数据预处理时将图像由原本的(32x32x3) resize为(224x224x3)。增加卷积层个数到6个并相应增加其他层的个数。加深网络深度,在最后一个卷积层输出通道数为256的特征图。
然而此举并没有明显的准确率增加,带来的反而是准确率上升速率变慢,模型训练速率变慢。步骤三训练一个epoch的时间约为1秒而到了这里训练一个epoch需要2分钟。此过程可能还伴随着梯度等问题得不偿失,于是我们决定采用步骤三的网络结构进行之后的可视化操作。
上述网络的训练loss与准确率变化图如下
步骤五
步骤四说明了简单粗暴地对网络结构进行叠加和对网络深度进行加深并不能就此换来准确率地增加,其可能带来梯度衰减、顶层过拟合等种种问题。
为了进一步提升准确率我们可以采取地操作有:1.增加训练集以及相应数据增强操作 2.使用更先进的网络结构
在此我们希望采用resnet50的结构,引入block小卷积核的概念并使用残差网络。但此举和Lenet5的结构相差有点多且将resnet的结构生搬硬套以改变Lenet5的做法虽然对准确率的提高可能有所帮助但不太道德。为此我们在训练代码的模型引入阶段进入resnet50的网络,此网络并不包含预训练过程以及对CIFAR10相应的结构改善,我们使用和之前一样的数据增强和处理操作训练50个epoch后得到的准确率为90.3%,可以看出resnet确实是分类网络历史上智慧的一个飞跃,训练loss与准确率曲线如下,其训练一个epoch的时间与步骤四的结构训练时间相当约为2分钟。
- 可视化结果
6.1 测试集预测结果与卷积核可视化
在visualize.py中我们复用原有的数据集处理操作以及步骤三中的网络结构代码,并加载训练好的模型best.ckpt,对测试集进行结果预测与可视化并对模型best.ckpt的卷积核进行输出与可视化。函数visualize_model代码如下
def visualize_model(best_ckpt_path,val_ds):
# 定义网络并加载参数,对验证集进行预测
net = LeNet5()
param_dict = load_checkpoint(best_ckpt_path)
load_param_into_net(net, param_dict)
loss = nn.SoftmaxCrossEntropyWithLogits(sparse=True,reduction='mean')
model = Model(net, loss,metrics={"Accuracy":nn.Accuracy()})
data = next(val_ds.create_dict_iterator())
images = data["image"].asnumpy()
labels = data["label"].asnumpy()
with open(cifar10_path+"/batches.meta.txt","r",encoding="utf-8") as f:
class_name = [name.replace("\n","") for name in f.readlines()]
output = model.predict(Tensor(data['image']))
pred = np.argmax(output.asnumpy(),axis=1)
# 可视化模型预测
for i in range(len(labels)):
plt.subplot(4,8,i+1)
color = 'blue' if pred[i] == labels[i] else 'red'
plt.title('{}'.format(class_name[pred[i]]), color=color)
picture_show = np.transpose(images[i],(1,2,0))
picture_show = picture_show/np.amax(picture_show)
picture_show = np.clip(picture_show, 0, 1)
plt.axis('off')
plt.imshow(picture_show)
plt.savefig(fname="./label/batch_"+str(i)+'.png')
acc = model.eval(val_ds)
print("acc:",acc)
print(type(net.parameters_and_names()))
for i, j in net.parameters_and_names():
wc = j
wc=wc.flatten()
pic_show=np.zeros(len(wc))
for q in range(len(wc)):
pic_show[q]=wc[q].asnumpy()
pic_show=np.resize(pic_show,(32,27))
pic_show=autoNorm(pic_show)
print(pic_show)
plt.matshow(pic_show, cmap=plt.cm.gray)
plt.title(i)
plt.colorbar()
plt.savefig(fname="./conv/"+i+'.png')
break
其输出了一个测试集batch中图片的预测结果,其中红色的字体代表预测结果与图片label不同,蓝色则代表模型预测分类正确
下面是输出的训练好的模型各网络层包括卷积层、池化层等的结构与参数
对conv1.weight参数进行归一化后平铺为32x27的灰度图进行输出
code.rar13.96 KB