>作者:欧新宇(Xinyu OU)
>开发平台:Paddle 2.1 (基于Paddle1.8 Fluid库)
运行环境:Intel Core i7-7700K CPU 4.2GHz, nVidia GeForce GTX 1080 Ti
特别注意:鉴于paddle.fluid将在后期被飞桨弃用,本项目于此次更新后停止更新。
本教案所涉及的数据集仅用于教学和交流使用,请勿用作商用。
最后更新:2021年8月15日
卷积神经网络
验证集精度
测试精度
预测结果
及该类别的概率
。建设中...
实验摘要: 对于模型训练的任务,需要数据预处理,将数据整理成为适合给模型训练使用的格式。蝴蝶识别数据集是一个包含有7个不同种类619个样本的数据集,不同种类的样本按照蝴蝶的类别各自放到了相应的文件夹。不同的样本具有不同的尺度,但总体都是规整的彩色图像。
实验目的:
dataset_info.json
原始的数据集的名字有可能会存在特殊的命名符号,从而导致在某些情况下无法正确识别。因此,可以通过批量改名的重命名方式来解决该问题。通过观察,本数据集相对规范,不需要进行数据清洗。
################################################################################## # 数据集预处理 # 作者: Xinyu Ou (http://ouxinyu.cn) # 数据集名称:蝴蝶识别数据集 # 数据集简介: Butterfly蝴蝶数据集包含7个不同种类的蝴蝶。 # 本程序功能: # 1. 将数据集按照7:1:2的比例划分为训练验证集、训练集、验证集、测试集 # 2. 代码将生成4个文件:训练验证集trainval.txt, 训练集列表train.txt, 验证集列表val.txt, 测试集列表test.txt, 数据集信息dataset_info.json # 3. 代码输出信息:图像列表已生成, 其中训练验证集样本490,训练集样本423个, 验证集样本67个, 测试集样本129个, 共计619个。 # 4. 生成数据集标签词典时,需要根据标签-文件夹列表匹配标签列表 ################################################################################### import os import json import codecs num_trainval = 0 num_train = 0 num_val = 0 num_test = 0 class_dim = 0 dataset_info = { 'dataset_name': '', 'num_trainval': -1, 'num_train': -1, 'num_val': -1, 'num_test': -1, 'class_dim': -1, 'label_dict': {} } # 本地运行时,需要修改数据集的名称和绝对路径,注意和文件夹名称一致 dataset_name = 'Butterfly' dataset_path = 'D:\\Workspace\\ExpDatasets\\' dataset_root_path = os.path.join(dataset_path, dataset_name) excluded_folder = ['.DS_Store', '.ipynb_checkpoints'] # 被排除的文件夹 # 定义生成文件的路径 data_path = os.path.join(dataset_root_path, 'Data') trainval_list = os.path.join(dataset_root_path, 'trainval.txt') train_list = os.path.join(dataset_root_path, 'train.txt') val_list = os.path.join(dataset_root_path, 'val.txt') test_list = os.path.join(dataset_root_path, 'test.txt') dataset_info_list = os.path.join(dataset_root_path, 'dataset_info.json') # 检测数据集列表是否存在,如果存在则先删除。其中测试集列表是一次写入,因此可以通过'w'参数进行覆盖写入,而不用进行手动删除。 if os.path.exists(trainval_list): os.remove(trainval_list) if os.path.exists(train_list): os.remove(train_list) if os.path.exists(val_list): os.remove(val_list) if os.path.exists(test_list): os.remove(test_list) # 按照比例进行数据分割 class_name_list = os.listdir(data_path) with codecs.open(trainval_list, 'a', 'utf-8') as f_trainval: with codecs.open(train_list, 'a', 'utf-8') as f_train: with codecs.open(val_list, 'a', 'utf-8') as f_val: with codecs.open(test_list, 'a', 'utf-8') as f_test: for class_name in class_name_list: if class_name not in excluded_folder: dataset_info['label_dict'][str(class_dim)] = class_name # 按照文件夹名称和label_match进行标签匹配 images = os.listdir(os.path.join(data_path, class_name)) count = 0 for image in images: if count % 10 == 0: # 抽取大约10%的样本作为验证数据 f_val.write("{0}\t{1}\n".format(os.path.join(data_path, class_name, image), class_dim)) f_trainval.write("{0}\t{1}\n".format(os.path.join(data_path, class_name, image), class_dim)) num_val += 1 num_trainval += 1 elif count % 10 == 1 or count % 10 == 2: # 抽取大约20%的样本作为测试数据 f_test.write("{0}\t{1}\n".format(os.path.join(data_path, class_name, image), class_dim)) num_test += 1 else: f_train.write("{0}\t{1}\n".format(os.path.join(data_path, class_name, image), class_dim)) f_trainval.write("{0}\t{1}\n".format(os.path.join(data_path, class_name, image), class_dim)) num_train += 1 num_trainval += 1 count += 1 class_dim += 1 # 将数据集信息保存到json文件中供训练时使用 dataset_info['dataset_name'] = dataset_name dataset_info['num_trainval'] = num_trainval dataset_info['num_train'] = num_train dataset_info['num_val'] = num_val dataset_info['num_test'] = num_test dataset_info['class_dim'] = class_dim with codecs.open(dataset_info_list, 'w', encoding='utf-8') as f_dataset_info: json.dump(dataset_info, f_dataset_info, ensure_ascii=False, indent=4, separators=(',', ':')) # 格式化字典格式的参数列表 print("图像列表已生成, 其中训练验证集样本{},训练集样本{}个, 验证集样本{}个, 测试集样本{}个, 共计{}个。".format(num_trainval, num_train, num_val, num_test, num_train+num_val+num_test))
图像列表已生成, 其中训练验证集样本490,训练集样本423个, 验证集样本67个, 测试集样本129个, 共计619个。
###### 展示数据集列表信息 ###################3 from pprint import pprint with open(dataset_info_list, 'r') as f_info: dataset_info = json.load(f_info) pprint(dataset_info)
{'class_dim': 7, 'dataset_name': 'Butterfly', 'label_dict': {'0': 'admiral', '1': 'black_swallowtail', '2': 'machaon', '3': 'monarch_closed', '4': 'monarch_open', '5': 'peacock', '6': 'zebra'}, 'num_test': 129, 'num_train': 423, 'num_trainval': 490, 'num_val': 67}
实验摘要: 蝴蝶种类识别是一个多分类问题,我们通过卷积神经网络来完成。这部分通过PaddlePaddle手动构造一个Alexnet卷积神经的网络来实现识别功能。本实验主要实现训练前的一些准备工作,包括:全局参数定义,数据集载入,数据预处理,可视化函数定义。
实验目的:
# 1. 导入依赖库 import os import cv2 import numpy as np import time # 载入time时间库,用于计算训练时间 from random import randint # 导入随机数生成函数 import paddle as paddle # 载入PaddlePaddle基本库 import paddle.fluid as fluid # 载入基于fluid框架的paddle from paddle.fluid.dygraph import Linear, Conv2D, Pool2D from PIL import Image # 载入python的第三方图像处理库 import matplotlib.pyplot as plt # 载入matplotlib绘图库 from multiprocessing import cpu_count # plt.rcParams['span.family'] = 'sans-serif' # plt.rcParams['span.sans-serif'] = 'SimHei,Times New Roman'# 中文设置成宋体,除此之外的字体设置成New Roman # np.set_printoptions(precision=5, suppress=True) # 设置numpy的精度,用于打印输出 # 2. 全局参数配置 # 2.1 定义数据集列表文件及模型路径 dataset_name = 'Butterfly' dataset_path = 'D:\\Workspace\\ExpDatasets\\' dataset_root_path = os.path.join(dataset_path, dataset_name) trainval_list = os.path.join(dataset_root_path, 'trainval.txt') train_list = os.path.join(dataset_root_path, 'train.txt') val_list = os.path.join(dataset_root_path, 'val.txt') test_list = os.path.join(dataset_root_path, 'test.txt') architecture = 'Alexnet' result_root_path = 'D:\\Workspace\\ExpResults\\' final_models_path = os.path.join(result_root_path, 'Project09Alexnet', dataset_name + '_' + architecture) # 2.3 训练参数定义 # 定义使用CPU还是GPU,使用CPU时use_cuda = False,使用GPU时use_cuda = True use_cuda = True # True, False 如果设备有GPU,怎么我们可以启用GPU进行快速训练 PLACE = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace() # 2.2 图像基本信息 img_size = 227 img_channel = 3 # 2.3 训练参数定义 total_epoch = 50 # 总迭代次数, 代码调试好后考虑Epochs_num = 50 log_interval = 10 eval_interval = 1 # 设置在训练过程中,每隔一定的周期进行一次测试 learning_rate = 0.001 # 学习率 momentum = 0.9 # 动量 BUF_SIZE = 512 # 设置存储数据的缓存大小 BATCH_SIZE = 128 # 设置每个批次的数据大小,同时对训练提供器和测试提供器有效
# 定义数据集映射函数获取数据的图像矩阵和label def data_mapper(sample): img, label = sample img = cv2.imread(img, 1) img = cv2.resize(img, (img_size, img_size)) # 将图像尺度resize为指定尺寸 img = np.array(img).astype('float32') # 将图像数据类型转化为float32 img = img.transpose((2, 0, 1)) # 调整数据形状paddle默认格式(通道,高度,宽度) img = img/255.0 # 将像素值归一化到[0,1]之间 return img, label # 定义数据集reader,用于从列表文件中批量获取图像 def data_reader(data_list_path): #定义读取函数,从列表文件中读取 def reader(): with open(data_list_path, 'r') as f: lines = f.readlines() for line in lines: img_path, label = line.split('\t') yield img_path, int(label) #使用多线程方式,通过用户自定义的映射器mapper来映射reader返回的样本(到输出队列) return paddle.reader.xmap_readers(data_mapper, reader, cpu_count(), 512)
# 用于训练/测试的数据提供器,每次从缓存中随机读取批次大小的数据 trainval_reader = paddle.batch(paddle.reader.shuffle(reader=data_reader(trainval_list), buf_size=BUF_SIZE), batch_size=BATCH_SIZE, drop_last=False) train_reader = paddle.batch(paddle.reader.shuffle(reader=data_reader(train_list), buf_size=BUF_SIZE), batch_size=BATCH_SIZE, drop_last=False) val_reader = paddle.batch(paddle.reader.shuffle(reader=data_reader(val_list), buf_size=BUF_SIZE), batch_size=BATCH_SIZE, drop_last=False) test_reader = paddle.batch(paddle.reader.shuffle(reader=data_reader(test_list), buf_size=BUF_SIZE), batch_size=BATCH_SIZE, drop_last=False)
# 测试:输出第0个batch的数据形态 for batch_id, data in enumerate(train_reader()): print(data[0][0].shape) break
(3, 227, 227)
定义训练过程中用到的可视化方法, 包括训练损失, 训练集批准确率, 测试集准确率. 根据具体的需求,可以在训练后展示这些数据和迭代次数的关系. 值得注意的是, 训练过程中可以每个epoch绘制一个数据点,也可以每个batch绘制一个数据点,也可以每个n个batch或n个epoch绘制一个数据点.
# 绘制训练batch精度和平均loss def draw_process(title, loss_label, accuracy_label, iters, losses, accuracies): # 第一组坐标轴 Loss _, ax1 = plt.subplots() # plt.subplots(figsize=(14,6)) ax1.plot(iters, losses, color='red', label=loss_label) ax1.set_xlabel('Iters', spansize=20) ax1.set_ylabel(loss_label, spansize=20) max_loss = max(losses) ax1.set_ylim(0, max_loss*1.2) # 第二组坐标轴 accuracy ax2 = ax1.twinx() ax2.plot(iters, accuracies, color='blue', label=accuracy_label) ax2.set_ylabel(accuracy_label, spansize=20) max_acc = max(accuracies) ax2.set_ylim(0, max_acc*1.2) plt.title(title, spansize=24) # 图例 handles1, labels1 = ax1.get_legend_handles_labels() handles2, labels2 = ax2.get_legend_handles_labels() plt.legend(handles1+handles2, labels1+labels2, loc='best') plt.grid()
实验摘要: 蝴蝶种类识别是一个多分类问题,我们通过卷积神经网络来完成。这部分通过PaddlePaddle手动构造一个Alexnet卷积神经的网络来实现识别功能,最后一层采用Softmax激活函数完成分类任务。
实验目的:
需要注意的是,在Alexnet中实际输入的尺度会被Crop为3×227×227
Layer | Input | Kernels_num | Kernels_size | Stride | Padding | PoolingType | Output | Parameters |
---|---|---|---|---|---|---|---|---|
Input | 3×227×227 | |||||||
Conv1 | 3×227×227 | 96 | 3×11×11 | 4 | 0 | 96×55×55 | (3×11×11+1)×96=34944 | |
Pool1 | 96×55×55 | 96 | 96×3×3 | 2 | 0 | max | 96×27×27 | 0 |
Conv2 | 96×27×27 | 256 | 96×5×5 | 1 | 2 | 256×27×27 | (96×5×5+1)×256=614656 | |
Pool2 | 256×27×27 | 256 | 256×3×3 | 2 | 0 | max | 256×13×13 | 0 |
Conv3 | 256×13×13 | 384 | 256×3×3 | 1 | 1 | 384×13×13 | (256×3×3+1)×384=885120 | |
Conv4 | 384×13×13 | 384 | 384×3×3 | 1 | 1 | 384×13×13 | (384×3×3+1)×384=1327488 | |
Conv5 | 384×13×13 | 256 | 384×3×3 | 1 | 1 | 256×13×13 | (384×3×3+1)×256=884992 | |
Pool5 | 256×13×13 | 256 | 256×3×3 | 2 | 0 | max | 256×6×6 | 0 |
FC6 | (256×6×6)×1 | 4096×1 | (9216+1)×4096=37752832 | |||||
FC7 | 4096×1 | 4096×1 | (4096+1)×4096=16781312 | |||||
FC8 | 4096×1 | 1000×1 | (4096+1)×1000=4097000 | |||||
Output | 1000×1 | |||||||
Total = 62378344 |
其中卷积层参数:3747200,占总参数的6%。
从Alexnet开始,包括VGG,GoogLeNet,Resnet等模型都是层次较深的模型,如果按照逐层的方式进行设计,代码会变得非常繁琐。因此,我们可以考虑将相同结构的模型进行汇总和合成,例如Alexnet中,卷积层+激活+池化层
就是一个完整的结构体。
from paddle.fluid.dygraph import Linear, Conv2D, Pool2D from paddle.fluid.layers import dropout import paddle.fluid as fluid import numpy as np # 定义卷积神经网络Alexnet class Alexnet(fluid.dygraph.Layer): name_scope = 'Alexnet' def __init__(self, num_classes=1000): super(Alexnet, self).__init__() self.conv1 = Conv2D(num_channels=3, num_filters=96, filter_size=11, stride=4, padding=0, act='relu') self.pool1 = Pool2D(pool_size=3, pool_stride=2, pool_type='max') self.conv2 = Conv2D(num_channels=96, num_filters=256, filter_size=5, stride=1, padding=2, act='relu') self.pool2 = Pool2D(pool_size=3, pool_stride=2, pool_type='max') self.conv3 = Conv2D(num_channels=256, num_filters=384, filter_size=3, stride=1, padding=1, act='relu') self.conv4 = Conv2D(num_channels=384, num_filters=384, filter_size=3, stride=1, padding=1, act='relu') self.conv5 = Conv2D(num_channels=384, num_filters=256, filter_size=3, stride=1, padding=1, act='relu') self.pool5 = Pool2D(pool_size=3, pool_stride=2, pool_type='max') self.fc6 = Linear(input_dim=256*6*6, output_dim=4096, act='relu') self.fc7 = Linear(input_dim=4096, output_dim=4096, act='relu') self.fc8 = Linear(input_dim=4096, output_dim=num_classes) def forward(self, input): x = self.conv1(input) x = self.pool1(x) x = self.conv2(x) x = self.pool2(x) x = self.conv3(x) x = self.conv4(x) x = self.conv5(x) x = self.pool5(x) x = fluid.layers.reshape(x, shape=[x.shape[0], -1]) x = self.fc6(x) x = dropout(x, 0.5) x = self.fc7(x) x = dropout(x, 0.5) y = self.fc8(x) return y #### 网络测试 if __name__ == '__main__': model = Alexnet() paddle.summary(model, (1,3,227,227))
---------------------------------------------------------------------------
Layer (type) Input Shape Output Shape Param #
===========================================================================
Conv2D-11 [[1, 3, 227, 227]] [1, 96, 55, 55] 34,944
Pool2D-7 [[1, 96, 55, 55]] [1, 96, 27, 27] 0
Conv2D-12 [[1, 96, 27, 27]] [1, 256, 27, 27] 614,656
Pool2D-8 [[1, 256, 27, 27]] [1, 256, 13, 13] 0
Conv2D-13 [[1, 256, 13, 13]] [1, 384, 13, 13] 885,120
Conv2D-14 [[1, 384, 13, 13]] [1, 384, 13, 13] 1,327,488
Conv2D-15 [[1, 384, 13, 13]] [1, 256, 13, 13] 884,992
Pool2D-9 [[1, 256, 13, 13]] [1, 256, 6, 6] 0
Linear-7 [[1, 9216]] [1, 4096] 37,752,832
Linear-8 [[1, 4096]] [1, 4096] 16,781,312
Linear-9 [[1, 4096]] [1, 1000] 4,097,000
===========================================================================
Total params: 62,378,344
Trainable params: 62,378,344
Non-trainable params: 0
---------------------------------------------------------------------------
Input size (MB): 0.59
Forward/backward pass size (MB): 5.96
Params size (MB): 237.95
Estimated Total Size (MB): 244.51
---------------------------------------------------------------------------
测试部分的具体流程包括:
在定义test()函数的时候,我们需要为其指定两个参数:model
是测试的模型,data_reader
是迭代的数据读取器,取值为val_reader(), test_reader(),分别对验证集和测试集。此处验证集和测试集数据的测试过程是相同的,只是所使用的数据不同。
def test(model, data_reader): accs = [] losses = [] model.eval() #评估模式 for batch_id,data in enumerate(data_reader):#测试集 images = np.array([x[0] for x in data], dtype='float32').reshape(-1, img_channel, img_size, img_size) labels = np.array([x[1] for x in data], dtype='int64').reshape(-1,1) image = fluid.dygraph.to_variable(images) label = fluid.dygraph.to_variable(labels) digits = model(image) predict = fluid.layers.softmax(digits) loss = fluid.layers.cross_entropy(predict, label) # 获取批loss值 loss = fluid.layers.mean(loss) # 求单个样本的loss acc = fluid.layers.accuracy(predict,label) losses.append(loss.numpy()) accs.append(acc.numpy()) avg_loss = np.mean(losses) avg_acc = np.mean(accs) return avg_loss, avg_acc
在动态图模式下,所有的训练
和测试
代码都需要基于动态图守护进程fluid.dygraph.guard(PLACE)
。
训练部分的具体流程包括:
在训练过程中,可以将周期,批次,损失及精度等信息打印到屏幕。
值得注意的是,在每一轮的训练中,每100个batch之后会输出一次平均训练误差和准确率。每一轮训练之后,使用测试集进行一次测试,在每轮测试中,均打输出一次平均测试误差和准确率。
注意
注意在下列的代码中,我们每个epoch都执行一次模型保存,这种方式一般应用在复杂的模型和大型数据集上。这种经常性的模型保存,有利于我们执行EarlyStopping策略,当我们发现运行曲线不再继续收敛时,就可以结束训练,并选择之前保存的最好的一个模型作为最终的模型。FinalModel**
def train(model): # 启动训练和在线测试 start = time.perf_counter() print('启动训练...') optimizer = fluid.optimizer.Momentum(learning_rate=learning_rate, momentum=momentum, parameter_list=model.parameters())#优化器选用SGD随机梯度下降,学习率为0.001. # optimizer = fluid.optimizer.AdamOptimizer(learning_rate=learning_rate, parameter_list=model.parameters())#优化器选用SGD随机梯度下降,学习率为0.001. # optimizer = fluid.optimizer.SGDOptimizer(learning_rate=learning_rate, parameter_list=model.parameters())#优化器选用SGD随机梯度下降,学习率为0.001. num_batch = 0 best_result = 0 best_result_id = 0 elapsed = 0 for epoch in range(1, total_epoch+1): model.train() #训练模式 for batch_id, data in enumerate(train_reader()): num_batch += 1 # 定义输入层数据的形状和类型 images = np.array([x[0] for x in data], dtype='float32').reshape(-1, img_channel, img_size, img_size) labels = np.array([x[1] for x in data], dtype='int64').reshape(-1,1) image = fluid.dygraph.to_variable(images) label = fluid.dygraph.to_variable(labels) # 定义输出层 loss+accuracy # 预测结果 = softmax(预测概率),模型的输出是预测概率 # 损失loss = cross_entropy(预测结果 与 label 之间的距离) # 精度acc = accuracy(预测结果 与 label之间的距离) digits = model(image) # 前向传播输出的分值,未进行归一化 predict = fluid.layers.softmax(digits) # 预测结果,归一化概率 = softmax(输出概率) # print(digits) # print(predict) loss = fluid.layers.cross_entropy(predict, label) avg_loss = fluid.layers.mean(loss) # 获取一批的平均loss值 acc = fluid.layers.accuracy(predict, label) # 计算一批的精度 # 执行反向传播算法 avg_loss.backward() # 使用backward() 方法可以执行反向网络 optimizer.minimize(avg_loss) model.clear_gradients() # 将参数梯度清零以保证下一轮训练的正确性 # 每隔log_interval个batch打印一次训练损失, 也可根据TOTAL_EPOCH设定按照周期epoch进行输出 if num_batch % log_interval == 0: # 每log_interval个batch打印一次信息 elapsed_step = time.perf_counter() - elapsed - start elapsed = time.perf_counter() - start print("Epoch:{}/{}, batch:{}, train_loss:{}, train_accuracy:{} ({:.2f}s)".format(epoch,total_epoch,num_batch,avg_loss.numpy(),acc.numpy(),elapsed_step)) # 记录训练过程,用于可视化训练过程中的loss和accuracy all_train_iters.append(num_batch) all_train_losses.append(avg_loss.numpy()[0]) all_train_accs.append(acc.numpy()[0]) # 每隔一定周期进行一次测试 if epoch % eval_interval == 0 or epoch == total_epoch: #模型校验 avg_loss, avg_acc = test(model, val_reader()) print('[validation] Epoch:{}/{}, test_loss:[{:.5f}], test_accuracy:[{:.5f}]'.format(epoch,total_epoch, avg_loss, avg_acc)) # 将性能最好的模型保存为final模型 if avg_acc > best_result: best_result = avg_acc best_result_id = epoch # 保存最优模型 fluid.save_dygraph(model.state_dict(), os.path.join(final_models_path, 'best_model')) print('当前性能最好的模型 epoch_{} 的精度: {:.5f}, 已将其赋值为:best_model'.format(best_result_id, best_result)) # 记录测试过程,用于可视化训练过程中的loss和accuracy all_test_iters.append(epoch) all_test_losses.append(avg_loss) all_test_accs.append(avg_acc) # 输出训练过程图 # 保存最终模型 fluid.save_dygraph(model.state_dict(), os.path.join(final_models_path, 'model_final')) print('训练完成,最终性能accuracy={:.5f}(epoch={}), 总耗时{:.2f}s, 已将其保存为:best_model'.format(best_result, best_result_id, time.perf_counter() - start)) draw_process("Training Process", 'Train Loss', 'Train Accuracy', all_train_iters, all_train_losses, all_train_accs) draw_process("Validation Results", 'Validation Loss', 'Validation Accuracy', all_test_iters, all_test_losses, all_test_accs)
值得注意的是,因为训练数据样本较少(210),而Batch_Size=128,因此每个周期的batch数只有不足2个批次。因此无法按照批次来进行print训练过程,只能按照epoch进行print。而基于epoch的print,需要将其缩进往前移一个层次。
if __name__ == '__main__': # 初始化绘图列表 all_train_iters = [] all_train_losses = [] all_train_accs = [] all_test_losses = [] all_test_iters = [] all_test_accs = [] with fluid.dygraph.guard(PLACE): model = Alexnet() #模型实例化 # 启动训练过程 train(model) # 训练完成,最终性能accuracy=0.97864(epoch=10), 总耗时52.69s, 已将其保存为:best_model # 训练完成,最终性能accuracy=0.98348(epoch=19), 总耗时105.66s, 已将其保存为:best_model
启动训练...
[validation] Epoch:1/50, test_loss:[6.54548], test_accuracy:[0.14925]
当前性能最好的模型 epoch_1 的精度: 0.14925, 已将其赋值为:best_model
[validation] Epoch:2/50, test_loss:[5.50086], test_accuracy:[0.20896]
当前性能最好的模型 epoch_2 的精度: 0.20896, 已将其赋值为:best_model
Epoch:3/50, batch:10, train_loss:[5.1387396], train_accuracy:[0.2265625] (11.00s)
[validation] Epoch:3/50, test_loss:[2.93469], test_accuracy:[0.20896]
当前性能最好的模型 epoch_2 的精度: 0.20896, 已将其赋值为:best_model
[validation] Epoch:4/50, test_loss:[2.40430], test_accuracy:[0.17910]
当前性能最好的模型 epoch_2 的精度: 0.20896, 已将其赋值为:best_model
Epoch:5/50, batch:20, train_loss:[2.2839901], train_accuracy:[0.23076923] (1.52s)
[validation] Epoch:5/50, test_loss:[2.38220], test_accuracy:[0.20896]
当前性能最好的模型 epoch_2 的精度: 0.20896, 已将其赋值为:best_model
[validation] Epoch:6/50, test_loss:[1.97962], test_accuracy:[0.20896]
当前性能最好的模型 epoch_2 的精度: 0.20896, 已将其赋值为:best_model
[validation] Epoch:7/50, test_loss:[2.19476], test_accuracy:[0.17910]
当前性能最好的模型 epoch_2 的精度: 0.20896, 已将其赋值为:best_model
Epoch:8/50, batch:30, train_loss:[2.2994266], train_accuracy:[0.125] (1.94s)
[validation] Epoch:8/50, test_loss:[1.98743], test_accuracy:[0.20896]
当前性能最好的模型 epoch_2 的精度: 0.20896, 已将其赋值为:best_model
[validation] Epoch:9/50, test_loss:[1.87598], test_accuracy:[0.23881]
当前性能最好的模型 epoch_9 的精度: 0.23881, 已将其赋值为:best_model
Epoch:10/50, batch:40, train_loss:[2.2276525], train_accuracy:[0.17948718] (6.75s)
[validation] Epoch:10/50, test_loss:[1.79811], test_accuracy:[0.25373]
当前性能最好的模型 epoch_10 的精度: 0.25373, 已将其赋值为:best_model
[validation] Epoch:11/50, test_loss:[1.79225], test_accuracy:[0.26866]
当前性能最好的模型 epoch_11 的精度: 0.26866, 已将其赋值为:best_model
[validation] Epoch:12/50, test_loss:[1.81987], test_accuracy:[0.20896]
当前性能最好的模型 epoch_11 的精度: 0.26866, 已将其赋值为:best_model
Epoch:13/50, batch:50, train_loss:[2.0737376], train_accuracy:[0.21875] (10.71s)
[validation] Epoch:13/50, test_loss:[1.80168], test_accuracy:[0.29851]
当前性能最好的模型 epoch_13 的精度: 0.29851, 已将其赋值为:best_model
[validation] Epoch:14/50, test_loss:[1.78403], test_accuracy:[0.29851]
当前性能最好的模型 epoch_13 的精度: 0.29851, 已将其赋值为:best_model
Epoch:15/50, batch:60, train_loss:[1.9123802], train_accuracy:[0.2820513] (6.87s)
[validation] Epoch:15/50, test_loss:[1.73530], test_accuracy:[0.37313]
当前性能最好的模型 epoch_15 的精度: 0.37313, 已将其赋值为:best_model
[validation] Epoch:16/50, test_loss:[1.72165], test_accuracy:[0.35821]
当前性能最好的模型 epoch_15 的精度: 0.37313, 已将其赋值为:best_model
[validation] Epoch:17/50, test_loss:[1.70052], test_accuracy:[0.34328]
当前性能最好的模型 epoch_15 的精度: 0.37313, 已将其赋值为:best_model
Epoch:18/50, batch:70, train_loss:[1.7967557], train_accuracy:[0.265625] (5.62s)
[validation] Epoch:18/50, test_loss:[1.66329], test_accuracy:[0.40299]
当前性能最好的模型 epoch_18 的精度: 0.40299, 已将其赋值为:best_model
[validation] Epoch:19/50, test_loss:[1.64117], test_accuracy:[0.43284]
当前性能最好的模型 epoch_19 的精度: 0.43284, 已将其赋值为:best_model
Epoch:20/50, batch:80, train_loss:[1.4582388], train_accuracy:[0.41025642] (11.79s)
[validation] Epoch:20/50, test_loss:[1.62740], test_accuracy:[0.37313]
当前性能最好的模型 epoch_19 的精度: 0.43284, 已将其赋值为:best_model
[validation] Epoch:21/50, test_loss:[1.60874], test_accuracy:[0.25373]
当前性能最好的模型 epoch_19 的精度: 0.43284, 已将其赋值为:best_model
[validation] Epoch:22/50, test_loss:[1.59241], test_accuracy:[0.41791]
当前性能最好的模型 epoch_19 的精度: 0.43284, 已将其赋值为:best_model
Epoch:23/50, batch:90, train_loss:[1.7105892], train_accuracy:[0.359375] (1.78s)
[validation] Epoch:23/50, test_loss:[1.63031], test_accuracy:[0.34328]
当前性能最好的模型 epoch_19 的精度: 0.43284, 已将其赋值为:best_model
[validation] Epoch:24/50, test_loss:[1.58896], test_accuracy:[0.44776]
当前性能最好的模型 epoch_24 的精度: 0.44776, 已将其赋值为:best_model
Epoch:25/50, batch:100, train_loss:[1.8568017], train_accuracy:[0.3846154] (5.18s)
[validation] Epoch:25/50, test_loss:[1.56869], test_accuracy:[0.37313]
当前性能最好的模型 epoch_24 的精度: 0.44776, 已将其赋值为:best_model
[validation] Epoch:26/50, test_loss:[1.52380], test_accuracy:[0.50746]
当前性能最好的模型 epoch_26 的精度: 0.50746, 已将其赋值为:best_model
[validation] Epoch:27/50, test_loss:[1.50590], test_accuracy:[0.41791]
当前性能最好的模型 epoch_26 的精度: 0.50746, 已将其赋值为:best_model
Epoch:28/50, batch:110, train_loss:[1.7291274], train_accuracy:[0.328125] (7.16s)
[validation] Epoch:28/50, test_loss:[1.48486], test_accuracy:[0.41791]
当前性能最好的模型 epoch_26 的精度: 0.50746, 已将其赋值为:best_model
[validation] Epoch:29/50, test_loss:[1.48682], test_accuracy:[0.34328]
当前性能最好的模型 epoch_26 的精度: 0.50746, 已将其赋值为:best_model
Epoch:30/50, batch:120, train_loss:[1.6209711], train_accuracy:[0.43589744] (1.50s)
[validation] Epoch:30/50, test_loss:[1.46470], test_accuracy:[0.41791]
当前性能最好的模型 epoch_26 的精度: 0.50746, 已将其赋值为:best_model
[validation] Epoch:31/50, test_loss:[1.44141], test_accuracy:[0.52239]
当前性能最好的模型 epoch_31 的精度: 0.52239, 已将其赋值为:best_model
[validation] Epoch:32/50, test_loss:[1.44740], test_accuracy:[0.40299]
当前性能最好的模型 epoch_31 的精度: 0.52239, 已将其赋值为:best_model
Epoch:33/50, batch:130, train_loss:[1.6043384], train_accuracy:[0.375] (7.07s)
[validation] Epoch:33/50, test_loss:[1.38747], test_accuracy:[0.47761]
当前性能最好的模型 epoch_31 的精度: 0.52239, 已将其赋值为:best_model
[validation] Epoch:34/50, test_loss:[1.36523], test_accuracy:[0.50746]
当前性能最好的模型 epoch_31 的精度: 0.52239, 已将其赋值为:best_model
Epoch:35/50, batch:140, train_loss:[1.4893553], train_accuracy:[0.33333334] (1.54s)
[validation] Epoch:35/50, test_loss:[1.33817], test_accuracy:[0.41791]
当前性能最好的模型 epoch_31 的精度: 0.52239, 已将其赋值为:best_model
[validation] Epoch:36/50, test_loss:[1.30131], test_accuracy:[0.44776]
当前性能最好的模型 epoch_31 的精度: 0.52239, 已将其赋值为:best_model
[validation] Epoch:37/50, test_loss:[1.29297], test_accuracy:[0.46269]
当前性能最好的模型 epoch_31 的精度: 0.52239, 已将其赋值为:best_model
Epoch:38/50, batch:150, train_loss:[1.4481435], train_accuracy:[0.4375] (1.83s)
[validation] Epoch:38/50, test_loss:[1.27471], test_accuracy:[0.46269]
当前性能最好的模型 epoch_31 的精度: 0.52239, 已将其赋值为:best_model
[validation] Epoch:39/50, test_loss:[1.24825], test_accuracy:[0.49254]
当前性能最好的模型 epoch_31 的精度: 0.52239, 已将其赋值为:best_model
Epoch:40/50, batch:160, train_loss:[1.3468556], train_accuracy:[0.43589744] (1.49s)
[validation] Epoch:40/50, test_loss:[1.23397], test_accuracy:[0.56716]
当前性能最好的模型 epoch_40 的精度: 0.56716, 已将其赋值为:best_model
[validation] Epoch:41/50, test_loss:[1.21647], test_accuracy:[0.50746]
当前性能最好的模型 epoch_40 的精度: 0.56716, 已将其赋值为:best_model
[validation] Epoch:42/50, test_loss:[1.18106], test_accuracy:[0.49254]
当前性能最好的模型 epoch_40 的精度: 0.56716, 已将其赋值为:best_model
Epoch:43/50, batch:170, train_loss:[1.3286947], train_accuracy:[0.484375] (5.61s)
[validation] Epoch:43/50, test_loss:[1.16236], test_accuracy:[0.53731]
当前性能最好的模型 epoch_40 的精度: 0.56716, 已将其赋值为:best_model
[validation] Epoch:44/50, test_loss:[1.12857], test_accuracy:[0.50746]
当前性能最好的模型 epoch_40 的精度: 0.56716, 已将其赋值为:best_model
Epoch:45/50, batch:180, train_loss:[1.3960109], train_accuracy:[0.53846157] (1.52s)
[validation] Epoch:45/50, test_loss:[1.10728], test_accuracy:[0.49254]
当前性能最好的模型 epoch_40 的精度: 0.56716, 已将其赋值为:best_model
[validation] Epoch:46/50, test_loss:[1.08434], test_accuracy:[0.53731]
当前性能最好的模型 epoch_40 的精度: 0.56716, 已将其赋值为:best_model
[validation] Epoch:47/50, test_loss:[1.09052], test_accuracy:[0.62687]
当前性能最好的模型 epoch_47 的精度: 0.62687, 已将其赋值为:best_model
Epoch:48/50, batch:190, train_loss:[1.099474], train_accuracy:[0.59375] (7.21s)
[validation] Epoch:48/50, test_loss:[1.06776], test_accuracy:[0.52239]
当前性能最好的模型 epoch_47 的精度: 0.62687, 已将其赋值为:best_model
[validation] Epoch:49/50, test_loss:[1.06788], test_accuracy:[0.62687]
当前性能最好的模型 epoch_47 的精度: 0.62687, 已将其赋值为:best_model
Epoch:50/50, batch:200, train_loss:[1.2934189], train_accuracy:[0.4871795] (1.51s)
[validation] Epoch:50/50, test_loss:[1.04287], test_accuracy:[0.56716]
当前性能最好的模型 epoch_47 的精度: 0.62687, 已将其赋值为:best_model
训练完成,最终性能accuracy=0.62687(epoch=47), 总耗时103.69s, 已将其保存为:best_model
离线测试同样要基于动态守护框架fluid.dygraph.guard()。测试过程与训练过程中的在线测试流程基本一致,只需要提前实现载入已保存的模型即可,载入模型使用fluid.load_dygraph()方法。
with fluid.dygraph.guard(PLACE): model_dict, _ = fluid.load_dygraph(os.path.join(final_models_path, 'best_model')) model = Alexnet() #模型实例化 model.load_dict(model_dict) #加载模型参数 #启动训练过程 _, avg_acc = test(model, test_reader()) print('测试集精度为:{:.5f}'.format(avg_acc))
测试集精度为:0.25391
【结果分析】
需要注意的是此处的精度与训练过程中输出的测试精度是不相同的,因为训练过程中使用的是验证集, 而这里的离线测试使用的是测试集.
实验摘要: 对训练过的模型,我们通过测试集进行模型效果评估,并可以在实际场景中进行预测,查看模型的效果。
实验目的:
# 导入依赖库 import os import cv2 import json import numpy as np import paddle # 载入PaddlePaddle基本库 import paddle.fluid as fluid # 载入基于fluid框架的paddle from paddle.fluid.dygraph import Linear, Conv2D, Pool2D import matplotlib.pyplot as plt # 载入python的第三方图像处理库 dataset_name = 'Butterfly' architecture = 'Alexnet' result_root_path = 'D:\\Workspace\\ExpResults\\' final_model_path = os.path.join(result_root_path, 'Project09Alexnet', dataset_name + '_' + architecture)
在预测之前,通常需要对图像进行预处理。此处的预处理方案和训练模型时所使用的预处理方案必须是一致的。对于彩色图,首先需要将图像resize为模型的输入尺度,其次需要将模型通道进行调整,转化[C,W,H]为[H,W,C],最后将像素色彩值归一化为[0,1].
# 从测试集列表中随机获取一个测试图片 def data_reader(data_list_path): with open(test_list, 'r') as f: lines = f.readlines() i = randint(1, len(lines)) line = lines[i] img_path, label = line.split('\t') label = int(label) return img_path, label #读取预测图像并进行预处理 def load_image(img_path): img = cv2.imread(img_path, 3) img = cv2.resize(img, (img_size, img_size)) # 将图像尺度resize为指定尺寸 img = np.array(img).astype('float32') # 将图像数据类型转化为float32 img = img.transpose((2, 0, 1)) # 调整数据形状paddle默认格式(通道,高度,宽度) img = img/255.0 # 将像素值归一化到[0,1]之间 return img
img_path, label = data_reader(test_list) # img_path = 'D:\\Workspace\\ExpDatasets\\Gestures\\Infer\\infer_3.jpg' #构建预测动态图过程 with fluid.dygraph.guard(): model = Alexnet()#模型实例化 model_dict, _ = fluid.load_dygraph(os.path.join(final_model_path, 'best_model')) model.load_dict(model_dict)#加载模型参数 model.eval()#评估模式 # 输出测评结果 img = load_image(img_path) img = img[np.newaxis,:, : ,:] img = fluid.dygraph.to_variable(img) result = model(img) print('手势文件 {} 的标签为: {}, 预测结果为: {}'.format(os.path.basename(img_path), label, np.argmax(result.numpy()))) # 输出图像文件 image = cv2.imread(img_path, 1) image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) plt.imshow(image)
手势文件 mno012.jpg 的标签为: 4, 预测结果为: 4