作者:欧新宇(Xinyu OU)
当前版本:Release v2.0
开发平台:Paddle 2.5.2
运行环境:Intel Core i7-7700K CPU 4.2GHz, nVidia GeForce GTX 1080 Ti
本教案所涉及的数据集仅用于教学和交流使用,请勿用作商用。
最后更新:2024年9月20日
在近年来,随着全球公共卫生事件的频发,口罩作为一种重要的个人防护设备,其佩戴情况对于防止病毒传播、保护公众健康具有重要意义。特别是在高人流量的公共场所,如商场、车站、医院等,口罩佩戴的监测和管理显得尤为重要。然而,传统的人工监测方式不仅效率低下,而且难以覆盖所有区域和时间段。因此,开发一种自动、高效的口罩佩戴检测技术,对于提升公共健康管理水平具有迫切的现实意义。在此背景下,基于卷积神经网络(CNN)的口罩佩戴检测技术应运而生。卷积神经网络作为一种强大的图像处理和识别工具,已经在计算机视觉领域取得了显著成果。其通过模拟人脑神经元的连接方式,能够自动学习和提取图像中的关键特征,并据此进行准确的分类和识别。将CNN应用于口罩佩戴检测,可以实现对监控视频中人脸区域的快速定位和口罩佩戴情况的准确判断,为公共场所的疫情防控提供强有力的技术支持。此外,随着深度学习技术的不断发展和计算能力的提升,基于CNN的口罩佩戴检测技术已经具备了较高的实时性和准确性。通过训练和优化模型,可以进一步提高其性能,使其更好地适应各种复杂场景和光线条件。因此,开展基于CNN的口罩佩戴检测实验研究,不仅具有重要的学术价值,而且对于推动公共健康管理的智能化、自动化具有重要意义。
实验摘要: 对于模型训练的任务,需要数据预处理,将数据整理成为适合给模型训练使用的格式。口罩佩戴是一个简单的二分类的任务,数据集中有2个不同的状态,分别是佩戴口罩和未佩戴口罩,总共185张图片。每个图片的尺寸是任意的,我们需要将图片读入,并按照7:1:2划分训练集、测试集和测试集。
实验目的:
在调用数据集的样本前需要先手动解压数据集到工作目录,
/home/aistudio/work/maskDetect
目录!unzip /home/aistudio/data/data171604/maskDetect.zip -d /home/aistudio/work/
..\Workspace\ExpDataset\maskDetect
目录口罩佩戴 数据集是网上收集获得,包含两个文件夹,分别是 maskimages
和 nomaskimages
,用于保存116张佩戴口罩的图片和69张未佩戴口罩的图片。
特别说明:数据集图片由搜索引擎获取,若涉及侵权,请及时与我联系,我将立即进行删除。
数据集下载地址:https://aistudio.baidu.com/aistudio/datasetdetail/171604
数据列表生成代码只需要执行一次,若已经实现分割好,则可跳过该部分代码。若官方数据集没有数据集的划分列表,或者数据集为自建数据集,则需要手动生成数据集的划分,一般包括训练集、验证集和测试集。要求在数据集文件夹内包含 train.txt
, test.txt
, val.txt
, trainval.txt
和 dataset_info.json
等五个文件。
Q1: 补全下列代码,实现将数据集按照7:1:2的比例分为训练集train, 验证集val 和测试集test(10分) ([Your codes 1~3])
##################################################################################
# 数据集预处理
# 作者: Xinyu Ou (http://ouxinyu.cn)
# 数据集名称:口罩佩戴识别数据集
# 数据集简介: maskDetect口罩佩戴数据集包含185张人脸图像,其中佩戴口罩的116张,没有佩戴口罩的69张。
# 本程序功能:
# 1. 将数据集按照7:1:2的比例划分为训练验证集、训练集、验证集、测试集
# 2. 代码将生成4个文件:训练验证集trainval.txt, 训练集列表train.txt, 验证集列表val.txt, 测试集列表test.txt, 数据集信息dataset_info.json
# 3. 代码输出信息:图像列表已生成, 其中训练验证集样本147,训练集样本128个, 验证集样本19个, 测试集样本38个, 共计185个。
# 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 = 'maskDetect'
root_path = 'D:\\Workspace\\ExpDatasets\\' # Windows: 'D:\\Workspace\\ExpDatasets\\'; Linux: '/home/aistudio/work'
dataset_root_path = os.path.join(root_path, dataset_name)
excluded_folder = ['.DS_Store', '.ipynb_checkpoints'] # 被排除的文件夹
# traversed_folder = ['maskimages', 'nomaskimages'] # 可遍历的文件夹
# Q1-1: 补全下列代码实现数据集列表路径的路径定义和数据集信息文件的路径定义
# [Your codes 1]
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)
if os.path.exists(dataset_info_list):
os.remove(dataset_info_list)
# Q1-2:不全下列代码,按照7:2:1的比例将数据集划分为训练集train、验证集val、测试集test和训练验证集trainval
# [Your codes 2]
class_name_list = os.listdir(data_path)
class_dim = 0
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_id in range(2):
class_name = class_name_list[class_id]
dataset_info['label_dict'][str(class_id)] = class_name # 按照文件夹名称和class_id[0|1]进行匹配
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_id))
f_trainval.write("{0}\t{1}\n".format(os.path.join(data_path, class_name, image), class_id))
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_id))
num_test += 1
else:
f_train.write("{0}\t{1}\n".format(os.path.join(data_path, class_name, image), class_id))
f_trainval.write("{0}\t{1}\n".format(os.path.join(data_path, class_name, image), class_id))
num_train += 1
num_trainval += 1
count += 1
# Q1-3: 将程序运行的相关结果保存到数据集信息json文件中
# [Your codes 3]
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))
display(dataset_info)
图像列表已生成, 其中训练验证集样本147,训练集样本128个, 验证集样本19个, 测试集样本38个, 共计185个。
{'dataset_name': 'maskDetect',
'num_trainval': 147,
'num_train': 128,
'num_val': 19,
'num_test': 38,
'class_dim': 0,
'label_dict': {'0': 'maskimages', '1': 'nomaskimages'}}
实验摘要: 基于CNN的彩色图片识别是一种多分类问题,本项目使用CIFAR-10网络作为CNN模型对手势识别图片进行分类。实验二通过PaddlePaddle构造一个CIFAR10卷积神经的网络,最后一层采用Softmax概率归一化函数完成分类任务。
实验目的:
# 1. 导入依赖库
import os
import cv2
import codecs
import json
import numpy as np
import time # 载入time时间库,用于计算训练时间
import paddle # 载入PaddlePaddle基本库
import matplotlib.pyplot as plt # 载入matplotlib绘图库
import warnings
warnings.filterwarnings("ignore", category=UserWarning)
# 2. 全局参数配置
# 2.1 定义项目基本情况和数据集路径
dataset_name = 'maskDetect'
project_name = 'Project010CNNMaskRecognition'
architecture = 'CIFAR10'
root_path = 'D:\\Workspace\\' # Windows: 'D:\\Workspace\\'; Linux: '/home/aistudio/work'
dataset_root_path = os.path.join(root_path, 'ExpDatasets', dataset_name) # 定义数据集路径
# 2.2 定义结果输出路径
result_root_path = os.path.join(root_path, 'ExpResults', project_name) # 定义结果保存路径
final_models_path = os.path.join(result_root_path, 'final_models', 'best_model') # 定义模型的保存的路径
final_figures_path = os.path.join(result_root_path, 'final_figures') # 定义可视化图的输出路径
# 2.3 定义图像基本信息
img_size = [32, 32, 3] # 此处图像的尺寸为模型的尺寸,而不是图像的真实尺寸
# 2.3 训练参数定义
total_epoch = 40 # 总迭代次数, 代码调试好后考虑Epochs_num = 50
log_interval = 10
eval_interval = 1 # 设置在训练过程中,每隔一定的周期进行一次测试
learning_rate = 0.001 # 学习率
momentum = 0.9 # 动量
BATCH_SIZE = 64 # 设置每个批次的数据大小,同时对训练提供器和测试提供器有效
通过集成飞桨内置的 paddle.io.Dataset
实现数据集类的定义,包括数据列表读取和数据预处理。
Q2:完成数据集类定义的部分代码(10分) ([Your codes 4~7])
要求:
1. 使用它opencv库实现相关功能
2. 注意数据预处理时,将样本压缩至模型输入的尺寸,例如Cifar的32×32,AlexNet的224×224
3. 将图像数据类型转换为32位浮点型
4. 调整数据为paddle默认格式[C,H,W]
5. 将像素归一化到[0,1]之间
import paddle.vision.transforms as T
from paddle.io import DataLoader
# 1. 定义数据集
class Dataset(paddle.io.Dataset): # 继承 paddle.io.Dataset 类
def __init__(self, dataset_root_path, mode='test'):
# 初始化数据集,将样本和标签映射到列表中
assert mode in ['train', 'val', 'test', 'trainval']
self.data = [] # 创建空列表文件,用于保存数据的路径和标签
# Q1-1 读取数据集列表文件,并将路径路径和标签进行拆分,其中测试集若不存在标签则复制为"-1"
# [Your codes 1]
with open(os.path.join(dataset_root_path, mode+'.txt')) as f:
for line in f.readlines():
info = line.strip().split('\t') # 以制表符为分割依据
image_path = os.path.join(dataset_root_path, info[0].strip()) # 数据的真实路径,根据实际情况进行修改
if len(info) == 2: # 包含标签的数据
self.data.append([image_path, info[1].strip()])
elif len(info) == 1: # 不包含标签的数据
self.data.append([image_path, -1])
# Q1-2 使用transform接口定义数据预处理,本例需要规范图像尺度为[20,20], 并将图像转换为Paddle要求的Tensor
# 传入定义好的数据处理方法,作为自定义数据集类的一个属性
prob = np.random.random()
if mode in ['train', 'trainval'] and prob >= 0.5:
self.transforms = T.Compose([
T.Resize([img_size[0],img_size[1]]),
T.ToTensor(),
])
else:
self.transforms = T.Compose([ # 传入定义好的数据处理方法,作为自定义数据集类的一个属性
T.Resize([img_size[0],img_size[1]]),
T.ToTensor(),
])
# 根据索引获取单个样本
def __getitem__(self, index):
# Q2-3: 对self.data变量进行拆分,划分为图像路径及标签,并按照路径进行图像载入
# [Your codes 6]
# 1. 根据索引,从列表中取出一个图像,并将数据拆分成路径和列表
image_path, label = self.data[index]
# 2. 使用cv2进行数据读取可以强制将的图像转化为彩色模式,其中0为灰度模式,1为彩色模式
img = cv2.imread(image_path, 1)
# 3. 将图像数据类型转化为float32(Paddle默认的内部数据格式)
img = np.array(img).astype('float32')
# 4. 将像素值归一化到[0, 1]之间,仅在MLP中使用
# img = img/255.0
# 5. 调整数据形状paddle默认张量格式
img = self.transforms(img)
# 6. CrossEntropyLoss要求label格式为int,将Label格式转换为 int
label = np.array(label, dtype='int64')
return img, label
# Q2-4 获取样本总数
# [Your codes 7]
def __len__(self):
# 步骤四:实现 __len__ 函数,返回数据集的样本总数
return len(self.data)
对于要使用的所有数据均需要设置数据提供器,本例我们给出基于训练集、验证集和测试集和训练验证集划分的设置。
# 1. 实例化数据类
dataset_train = Dataset(dataset_root_path, mode='train')
dataset_val = Dataset(dataset_root_path, mode='val')
dataset_trainval = Dataset(dataset_root_path, mode='trainval')
dataset_test = Dataset(dataset_root_path, mode='test')
# 2. 创建迭代读取器
# 使用paddle.io.DataLoader 定义DataLoader对象用于加载Python生成器产生的数据,
# DataLoader 返回的是一个批次数据迭代器,并且是异步的。
train_reader = DataLoader(dataset_train, batch_size=64, shuffle=True, drop_last=True)
val_reader = DataLoader(dataset_val, batch_size=64, shuffle=False, drop_last=False)
trainval_reader = DataLoader(dataset_trainval, batch_size=64, shuffle=True, drop_last=True)
test_reader = DataLoader(dataset_test, batch_size=64, shuffle=False, drop_last=False)
#####################################################################################################
# 数据迭代器测试
# 1. 输出数据集的基本情况
print('数据集包含训练数据{}个,验证数据{}个,训练验证集{}个,测试数据{}个。'.format(len(dataset_train),len(dataset_val),len(dataset_trainval),len(dataset_test)))
print('数据的形态为:{}'.format(dataset_val[0][0].shape))
# 2. 迭代的读取数据并打印数据的形状
for i, (img, label) in enumerate(val_reader()):
if i > 2:
break
print('验证集batch_{}的图像形态:{}, 标签形态:{}'.format(i, img.shape, label.shape))
数据集包含训练数据128个,验证数据19个,训练验证集147个,测试数据38个。
数据的形态为:[3, 32, 32]
验证集batch_0的图像形态:[19, 3, 32, 32], 标签形态:[19]
定义训练过程中用到的可视化方法, 包括训练损失, 训练集批准确率, 测试集准确率. 根据具体的需求,可以在训练后展示这些数据和迭代次数的关系。值得注意的是, 训练过程中可以每个epoch绘制一个数据点,也可以每个batch绘制一个数据点,也可以每个n个batch或n个epoch绘制一个数据点.
def draw_process_ch6(visualization_log, show_top5=False, figure_path=final_figures_path, figurename='visualization_log', isShow=True):
"""绘制训练过程中的训练误差、训练精度、验证误差和验证精度四个重要输出"""
train_losses = visualization_log['train_losses'] # 训练集的损失值
train_accs_top1 = visualization_log['train_accs_top1'] # 训练集的top1精确度
train_accs_top5 = visualization_log['train_accs_top5'] # 训练集的top5精确度
val_losses = visualization_log['val_losses'] # 验证集的损失值
val_accs_top1 = visualization_log['val_accs_top1'] # 验证集的精确度
val_accs_top5 = visualization_log['val_accs_top5'] # 验证集的精确度
epoch_iters = visualization_log['epoch_iters'] # 周期epoch迭代次数
batch_iters = visualization_log['batch_iters'] # 批次batch迭代次数
# 第一组坐标轴 Loss
_, ax1 = plt.subplots()
ax1.plot(batch_iters, train_losses, color='orange', linestyle='--', label='train_loss')
ax1.plot(epoch_iters, val_losses, color='cyan', linestyle='--', label='val_loss')
ax1.set_xlabel('Iters', fontsize=16)
ax1.set_ylabel('Loss', fontsize=16)
max_loss = max(max(train_losses), max(val_losses))
ax1.set_ylim(0, max_loss*1.2)
# 第二组坐标轴 accuracy
ax2 = ax1.twinx()
ax2.plot(batch_iters, train_accs_top1, 'o-', color='red', markersize=3, label='train_accuracy(top1)')
ax2.plot(epoch_iters, val_accs_top1, 'o-', color='blue', markersize=3, label='val_accuracy(top1)')
if show_top5==True:
ax2.plot(batch_iters, train_accs_top5, 'o-', color='magenta', markersize=3, label='train_accuracy(top5)')
ax2.plot(epoch_iters, val_accs_top5, 'o-', color='pink', markersize=3, label='val_accuracy(top5)')
ax2.set_ylabel('Accuracy', fontsize=16)
max_accs = max(max(train_accs_top1), max(train_accs_top5), max(val_accs_top1), max(val_accs_top5))
ax2.set_ylim(0, max_accs*1.2)
# 3.配置图例
plt.title('Training and Validation Results', fontsize=18)
handles1, labels1 = ax1.get_legend_handles_labels() # 图例1
handles2, labels2 = ax2.get_legend_handles_labels() # 图例2
plt.legend(handles1+handles2, labels1+labels2, loc='best')
plt.grid()
# 4.将绘图结果保存到 final_figures 目录
plt.savefig(os.path.join(figure_path, figurename + '.png'))
# 5.显示绘图结果
if isShow is True:
plt.show()
### 测试可视化函数 ###################################################
if __name__ == '__main__':
try:
log_file = json.loads(open(os.path.join(final_figures_path, 'visualization_log.json'), 'r', encoding='utf-8').read())
draw_process_ch6(log_file, show_top5=True)
except:
print('数据不存在,无法进行绘制')
数据不存在,无法进行绘
实验摘要: 基于CNN的彩色图片识别是一种多分类问题,本项目使用CIFAR-10网络作为CNN模型对手势识别图片进行分类。实验二通过PaddlePaddle构造一个CIFAR10卷积神经的网络,最后一层采用Softmax激活函数完成分类任务。
实验目的:
Q3: 根据拓扑结构图补全网络参数配置表(10分)
Layer | Input | Kernels_num | Kernels_size | Stride | Padding | PoolingType | Output | Parameters |
---|---|---|---|---|---|---|---|---|
Input | 3×32×32 | - | - | - | - | - | 3×32×32 | - |
Conv1 | 3×32×32 | 32 | 3×5×5 | 1 | 0 | - | 32×28×28 | (3×5×5+1)×32=2432 |
Pool1 | 32×28×28 | 32 | 32×2×2 | 2 | 0 | Max | 32×14×14 | 0 |
Conv2 | 32×14×14 | 32 | 32×5×5 | 1 | 0 | - | 32×10×10 | (32×5×5+1)×32=25632 |
Pool2 | 32×10×10 | 32 | 32×2×2 | 2 | 0 | Avg | 32×5×5 | 0 |
Conv3 | 32×5×5 | 64 | 32×4×4 | 1 | 0 | - | 64×2×2 | (32×4×4+1)×64=32832 |
Pool3 | 64×2×2 | 64 | 64×2×2 | 2 | 0 | Avg | 64×1×1 | 0 |
FC1 | (64×1×1)×1 | - | - | - | - | - | 64×1 | (64+1)×64=4160 |
FC2 | 64×1 | - | - | - | - | - | 64×10 | (64+1)×10=650 |
Output | - | - | - | - | - | - | 10×1 | - |
- | - | - | - | - | - | - | - | Total = 65706 |
Q4:根据Cifar10网络拓扑结构图和网络参数配置表完成神经Cifar10模型类定义(10分)([Your codes 8~10])
from paddle.nn import Sequential, Conv2D, MaxPool2D, AvgPool2D, Linear, Dropout, ReLU
# 定义多层感知机(CNN)
class Cifar10(paddle.nn.Layer):
def __init__(self, num_classes=10): # 初始化CIFAR类,并为CIFAR增加对象self.x
super(Cifar10, self).__init__()
# Q3-1: 根据Cifar10拓扑结构图和网络参数配置表完成下列Cifar10模型的类定义
# 各层超参数定义: [Your codes 8]
self.features = Sequential(
Conv2D(in_channels=3, out_channels=32, kernel_size=5, stride=1),
ReLU(),
MaxPool2D(kernel_size=2, stride=2),
Conv2D(in_channels=32, out_channels=32, kernel_size=5, stride=1),
ReLU(),
AvgPool2D(kernel_size=2, stride=2),
Conv2D(in_channels=32, out_channels=64, kernel_size=4, stride=1),
ReLU(),
AvgPool2D(kernel_size=2, stride=2),
)
self.fc = Sequential(
Linear(in_features=64*1*1, out_features=64),
Linear(in_features=64, out_features=num_classes),
)
# Q3-2: 根据Cifar10拓扑结构图和网络参数配置表完成下列Cifar10模型的类定义
# 定义前向传输过程: [Your codes 8]
def forward(self, input): # 为CNN类增加forward方法
x = self.features(input)
x = paddle.flatten(x, 1)
y = self.fc(x)
return y
######输出网络结构和超参数########################################################
# 模型测试
if __name__ == '__main__':
# Q3-3:完成模型测试代码的定义
# [Your codes 10]
model = Cifar10()
paddle.summary(model, (10,3,32,32))
---------------------------------------------------------------------------
Layer (type) Input Shape Output Shape Param #
===========================================================================
Conv2D-1 [[10, 3, 32, 32]] [10, 32, 28, 28] 2,432
ReLU-1 [[10, 32, 28, 28]] [10, 32, 28, 28] 0
MaxPool2D-1 [[10, 32, 28, 28]] [10, 32, 14, 14] 0
Conv2D-2 [[10, 32, 14, 14]] [10, 32, 10, 10] 25,632
ReLU-2 [[10, 32, 10, 10]] [10, 32, 10, 10] 0
AvgPool2D-1 [[10, 32, 10, 10]] [10, 32, 5, 5] 0
Conv2D-3 [[10, 32, 5, 5]] [10, 64, 2, 2] 32,832
ReLU-3 [[10, 64, 2, 2]] [10, 64, 2, 2] 0
AvgPool2D-2 [[10, 64, 2, 2]] [10, 64, 1, 1] 0
Linear-1 [[10, 64]] [10, 64] 4,160
Linear-2 [[10, 64]] [10, 2] 130
===========================================================================
Total params: 65,186
Trainable params: 65,186
Non-trainable params: 0
---------------------------------------------------------------------------
Input size (MB): 0.12
Forward/backward pass size (MB): 4.90
Params size (MB): 0.25
Estimated Total Size (MB): 5.27
---------------------------------------------------------------------------
在飞桨2.0+的环境中,模型验证/测试使用 model.eval_batch()
方法实现,一般包含如下几个步骤:
model.eval_batch()
方法,并载入 [image]
和 [label]
作为输入执行前向出传输。模型的输出为 model.prepare()
方法所指定的评价指标,一般包括损失值、top1和top5精度。此外,在定义eval()函数的时候,我们需要为其指定两个参数:model
是测试的模型,data_reader
是迭代的数据读取器,取值为val_reader(), test_reader(),分别对验证集和测试集。此处验证集和测试集数据的测试过程是相同的,只是所使用的数据不同。
from paddle.static import InputSpec
def eval(model, data_reader, verbose=0):
acc_top1 = []
acc_top5 = []
losses = []
n_total = 0
for batch_id, (image, label) in enumerate(data_reader):#测试集
n_batch = len(label)
n_total = n_total + n_batch
label = paddle.unsqueeze(label, axis=1) # 将图像转换为4D张量
loss, acc = model.eval_batch([image], [label])
losses.append(loss[0]*n_batch)
acc_top1.append(acc[0][0]*n_batch)
acc_top5.append(acc[0][1]*n_batch)
avg_loss = np.sum(losses)/n_total # loss 记录的是当前batch的累积值
avg_acc_top1 = np.sum(acc_top1)/n_total # metric 是当前batch的平均值
avg_acc_top5 = np.sum(acc_top5)/n_total
return avg_loss, avg_acc_top1, avg_acc_top5
##############################################################
if __name__ == '__main__':
try:
# 设置输入样本的维度
input_spec = InputSpec(shape=[None, img_size[2], img_size[0], img_size[1]], dtype='float32', name='image')
label_spec = InputSpec(shape=[None, 1], dtype='int64', name='label')
# 载入模型
network = Cifar10()
model = paddle.Model(network, input_spec, label_spec) # 模型实例化
model.load(final_models_path) # 载入调优模型的参数
model.prepare(loss=paddle.nn.CrossEntropyLoss(), # 设置loss
metrics=paddle.metric.Accuracy(topk=(1,5))) # 设置评价指标
# 执行评估函数,并输出验证集样本的损失和精度
print('开始评估...')
avg_loss, avg_acc_top1, avg_acc_top5 = eval(model, val_reader(), verbose=1)
print('\r [验证集] 损失: {:.5f}, top1精度:{:.5f}, top5精度为:{:.5f} \n'.format(avg_loss, avg_acc_top1, avg_acc_top5), end='')
avg_loss, avg_acc_top1, avg_acc_top5 = eval(model, test_reader(), verbose=1)
print('\r [测试集] 损失: {:.5f}, top1精度:{:.5f}, top5精度为:{:.5f}'.format(avg_loss, avg_acc_top1, avg_acc_top5), end='')
except:
print('数据不存在跳过测试')
开始评估...
[验证集] 损失: 0.01407, top1精度:0.94737, top5精度为:1.00000
[测试集] 损失: 0.00712, top1精度:0.92105, top5精度为:1.00000
在飞桨2.0+中,动态图模式是默认模式,所有的训练
和测试
代码都需要基于动态图进行创建。由于是默认模式,因此不需要再像1.8版本中一样使用守护进程进行启用。
训练部分的具体流程,与验证部分大体相同,主要包括如下几个部分:
model.prepare()
实现定义。model.train_batch()
方法,并载入 [image]
和 [label]
作为输入执行前向出传输。在飞桨2.0+中,反向求导部分不需要进行显示定义,train_batch()会自动执行。此外,在训练过程中,我们可以每个一定的周期调用一次验证函数 eval()
,来对验证集进行测试。一般来说,每个epoch都可以进行一次验证。另外,可视化也训练和验证的loss和accuracy也是训练中常用的模型选择方法。在训练过程中,可以将周期,批次,损失及精度等信息打印到屏幕。
在本项目中,我们在训练中,每100个batch之后会输出一次平均训练误差和准确率;每一轮训练之后,使用测试集进行一次测试,在每轮测试中,均打输出一次平均测试误差和准确率。
注意
注意在下列的代码中,我们每个epoch都会判断当前的模型是否是最优模型,如果是最优模型,我们会进行一次模型保存,并将其名命名为 best_model
。对于复杂的模型和大型数据集上,我们通常还会在每个周期训练结束后都保存一个 checkpoint_model
。这种经常性的模型保存,有利于我们执行EarlyStopping策略,并回退到任意一个时间节点。也便于当我们发现运行曲线不再继续收敛时,就可以结束训练。
Q5. 完成下列模型训练函数的主体部分(20分) ([Your codes 11~13])
from paddle.static import InputSpec
import paddle.optimizer as optimizer
# 初始化绘图列表
visualization_log = { # 初始化状态字典
'train_losses': [], # 训练损失值
'train_accs_top1': [], # 训练top1精度
'train_accs_top5': [], # 训练top5精度
'val_losses': [], # 验证损失值
'val_accs_top1': [], # 验证top1精度
'val_accs_top5': [], # 验证top5精度
'batch_iters': [], # 批次batch迭代次数
'epoch_iters': [], # 周期epoch迭代次数
}
def train(model):
print('启动训练...')
start = time.perf_counter()
num_batch = 0
best_result = 0
best_result_id = 0
elapsed =0
for epoch in range(1, total_epoch+1):
for batch_id, (image, label) in enumerate(train_reader()):
num_batch += 1
# Q5-1. 调用model.train_batch方法进行训练,注意需要对label的尺度进行规范化
# [Your codes 11]
label = paddle.unsqueeze(label, axis=1)
loss, acc = model.train_batch([image], [label])
if num_batch % log_interval == 0: # 每10个batch显示一次日志,适合大数据集
# Q5-2. 从训练的输出中获取损失值,top1精度和top5精度
# [Your codes 12]
avg_loss = loss[0]
acc_top1 = acc[0][0]
acc_top5 = acc[0][1]
elapsed_step = time.perf_counter() - elapsed - start
elapsed = time.perf_counter() - start
print('Epoch:{}/{}, batch:{}, train_loss:[{:.5f}], acc_top1:[{:.5f}], acc_top5:[{:.5f}]({:.2f}s)'
.format(epoch, total_epoch, num_batch, avg_loss, acc_top1, acc_top5, elapsed_step))
# 记录训练过程,用于可视化训练过程中的loss和accuracy
visualization_log['train_losses'].append(float(avg_loss))
visualization_log['batch_iters'].append(num_batch)
visualization_log['train_accs_top1'].append(float(acc_top1))
visualization_log['train_accs_top5'].append(float(acc_top5))
# 每隔一定周期进行一次测试
if epoch % eval_interval == 0 or epoch == total_epoch:
# 模型校验
val_loss, val_acc_top1, val_acc_top5 = eval(model, val_reader())
print('[validation] Epoch:{}/{}, val_loss:[{:.5f}], val_top1:[{:.5f}], val_top5:[{:.5f}]'.format(epoch, total_epoch, val_loss, val_acc_top1, val_acc_top5))
# 记录测试过程,用于可视化训练过程中的loss和accuracy
visualization_log['epoch_iters'].append(num_batch)
visualization_log['val_losses'].append(float(val_loss))
visualization_log['val_accs_top1'].append(float(val_acc_top1))
visualization_log['val_accs_top5'].append(float(val_acc_top5))
# Q5-3. 将性能最好的模型保存为final模型,注意同时保存调优模型和推理模型
# model.save(<path>, training=False|True),True:调优模型 | False:推理模型
# [Your codes 13]
if val_acc_top1 > best_result:
best_result = val_acc_top1
best_result_id = epoch
# finetune model 用于调优和恢复训练
model.save(final_models_path)
# inference model 用于部署和预测
model.save(final_models_path, training=False)
# 输出训练过程数据,将日志字典保存为json格式,绘图数据可以在训练结束后自动显示,也可以在训练中手动执行以显示结果
if not os.path.exists(final_figures_path):
os.makedirs(final_figures_path)
with codecs.open(os.path.join(final_figures_path, 'visualization_log.json'), 'w', encoding='utf-8') as f_log:
json.dump(visualization_log, f_log, ensure_ascii=False, indent=4, separators=(',', ':'))
print('训练完成,最终性能accuracy={:.5f}(epoch={}), 总耗时{:.2f}s, 已将其保存为:best_model'.format(best_result, best_result_id, time.perf_counter() - start))
Q6. 完成主函数的定义(10分) ([Your codes 14])
要求:
1. 对Cifar10模型进行实例化
2. 执行训练函数
import paddle.optimizer as optimizer
#### 训练主函数 ########################################################3
if __name__ == '__main__':
# [Your codes 14]
# 1. 设置输入样本的维度
input_spec = InputSpec(shape=[None, img_size[2], img_size[0], img_size[1]], dtype='float32', name='image')
label_spec = InputSpec(shape=[None, 1], dtype='int64', name='label')
# # 2. 载入设计好的网络,并实例化model变量
network = Cifar10(num_classes=2)
model = paddle.Model(network, input_spec, label_spec)
# 3. 设置学习率、优化器、损失函数和评价指标
# optimizer = optimizer.Momentum(learning_rate=learning_rate, momentum=momentum, parameters=model.parameters())
optimizer = optimizer.Adam(learning_rate=learning_rate, parameters=model.parameters())
model.prepare(optimizer,
paddle.nn.CrossEntropyLoss(),
paddle.metric.Accuracy(topk=(1,5)))
# 4. 启动训练过程
train(model)
print('训练完毕,结果路径{}.'.format(result_root_path))
# 5. 输出训练过程图
draw_process_ch6(visualization_log)
启动训练...
[validation] Epoch:1/40, val_loss:[215.01157], val_top1:[0.36842], val_top5:[1.00000]
[validation] Epoch:2/40, val_loss:[41.97761], val_top1:[0.63158], val_top5:[1.00000]
[validation] Epoch:3/40, val_loss:[19.48559], val_top1:[0.63158], val_top5:[1.00000]
[validation] Epoch:4/40, val_loss:[11.78563], val_top1:[0.57895], val_top5:[1.00000]
Epoch:5/40, batch:10, train_loss:[7.98685], acc_top1:[0.75000], acc_top5:[1.00000](0.72s)
[validation] Epoch:5/40, val_loss:[19.89513], val_top1:[0.63158], val_top5:[1.00000]
[validation] Epoch:6/40, val_loss:[13.44415], val_top1:[0.47368], val_top5:[1.00000]
[validation] Epoch:7/40, val_loss:[15.67827], val_top1:[0.52632], val_top5:[1.00000]
[validation] Epoch:8/40, val_loss:[8.78975], val_top1:[0.57895], val_top5:[1.00000]
[validation] Epoch:9/40, val_loss:[12.52153], val_top1:[0.68421], val_top5:[1.00000]
Epoch:10/40, batch:20, train_loss:[7.65648], acc_top1:[0.68750], acc_top5:[1.00000](0.64s)
[validation] Epoch:10/40, val_loss:[9.97703], val_top1:[0.68421], val_top5:[1.00000]
[validation] Epoch:11/40, val_loss:[7.04698], val_top1:[0.57895], val_top5:[1.00000]
[validation] Epoch:12/40, val_loss:[8.14039], val_top1:[0.57895], val_top5:[1.00000]
[validation] Epoch:13/40, val_loss:[4.73386], val_top1:[0.63158], val_top5:[1.00000]
[validation] Epoch:14/40, val_loss:[5.87361], val_top1:[0.68421], val_top5:[1.00000]
Epoch:15/40, batch:30, train_loss:[2.05521], acc_top1:[0.82812], acc_top5:[1.00000](0.60s)
[validation] Epoch:15/40, val_loss:[3.40760], val_top1:[0.68421], val_top5:[1.00000]
[validation] Epoch:16/40, val_loss:[3.78396], val_top1:[0.68421], val_top5:[1.00000]
[validation] Epoch:17/40, val_loss:[3.53097], val_top1:[0.63158], val_top5:[1.00000]
[validation] Epoch:18/40, val_loss:[2.49506], val_top1:[0.78947], val_top5:[1.00000]
[validation] Epoch:19/40, val_loss:[2.61555], val_top1:[0.89474], val_top5:[1.00000]
Epoch:20/40, batch:40, train_loss:[1.33448], acc_top1:[0.79688], acc_top5:[1.00000](0.69s)
[validation] Epoch:20/40, val_loss:[2.41516], val_top1:[0.84211], val_top5:[1.00000]
[validation] Epoch:21/40, val_loss:[2.53459], val_top1:[0.68421], val_top5:[1.00000]
[validation] Epoch:22/40, val_loss:[2.72820], val_top1:[0.63158], val_top5:[1.00000]
[validation] Epoch:23/40, val_loss:[2.62600], val_top1:[0.68421], val_top5:[1.00000]
[validation] Epoch:24/40, val_loss:[2.51982], val_top1:[0.73684], val_top5:[1.00000]
Epoch:25/40, batch:50, train_loss:[0.71393], acc_top1:[0.89062], acc_top5:[1.00000](0.60s)
[validation] Epoch:25/40, val_loss:[2.46232], val_top1:[0.68421], val_top5:[1.00000]
[validation] Epoch:26/40, val_loss:[2.57792], val_top1:[0.68421], val_top5:[1.00000]
[validation] Epoch:27/40, val_loss:[2.66153], val_top1:[0.68421], val_top5:[1.00000]
[validation] Epoch:28/40, val_loss:[2.67943], val_top1:[0.68421], val_top5:[1.00000]
[validation] Epoch:29/40, val_loss:[2.72051], val_top1:[0.73684], val_top5:[1.00000]
Epoch:30/40, batch:60, train_loss:[0.35859], acc_top1:[0.90625], acc_top5:[1.00000](0.60s)
[validation] Epoch:30/40, val_loss:[2.84241], val_top1:[0.73684], val_top5:[1.00000]
[validation] Epoch:31/40, val_loss:[3.01037], val_top1:[0.73684], val_top5:[1.00000]
[validation] Epoch:32/40, val_loss:[3.16967], val_top1:[0.68421], val_top5:[1.00000]
[validation] Epoch:33/40, val_loss:[3.07462], val_top1:[0.68421], val_top5:[1.00000]
[validation] Epoch:34/40, val_loss:[2.95872], val_top1:[0.73684], val_top5:[1.00000]
Epoch:35/40, batch:70, train_loss:[0.02967], acc_top1:[1.00000], acc_top5:[1.00000](0.60s)
[validation] Epoch:35/40, val_loss:[2.87678], val_top1:[0.73684], val_top5:[1.00000]
[validation] Epoch:36/40, val_loss:[2.80036], val_top1:[0.73684], val_top5:[1.00000]
[validation] Epoch:37/40, val_loss:[2.68849], val_top1:[0.73684], val_top5:[1.00000]
[validation] Epoch:38/40, val_loss:[2.69660], val_top1:[0.73684], val_top5:[1.00000]
[validation] Epoch:39/40, val_loss:[2.73700], val_top1:[0.73684], val_top5:[1.00000]
Epoch:40/40, batch:80, train_loss:[0.02469], acc_top1:[1.00000], acc_top5:[1.00000](0.60s)
[validation] Epoch:40/40, val_loss:[2.72811], val_top1:[0.73684], val_top5:[1.00000]
训练完成,最终性能accuracy=0.89474(epoch=19), 总耗时5.08s, 已将其保存为:best_model
训练完毕,结果路径D:\Workspace\ExpResults\Project010CNNMaskRecognition.
离线测试与验证几乎相同,直接调用 eval()
方法即可,唯一的区别是离线测试通常是先读取保存的模型,再进行测试。
Q7. 完成离线测试的模型载入代码(10分) ([Your codes 15])
# [Your codes 15]
# 1. 设置输入样本的维度
input_spec = InputSpec(shape=[None, img_size[2], img_size[0], img_size[1]], dtype='float32', name='image')
label_spec = InputSpec(shape=[None, 1], dtype='int64', name='label')
# 2. 载入模型
network = Cifar10(num_classes=2)
model = paddle.Model(network, input_spec, label_spec) # 模型实例化
model.load(final_models_path) # 载入调优模型的参数
model.prepare(loss=paddle.nn.CrossEntropyLoss(), # 设置loss
metrics=paddle.metric.Accuracy(topk=(1,5))) # 设置评价指标
# 3. 执行评估函数,并输出验证集样本的损失和精度
print('开始评估...')
avg_loss, avg_acc_top1, avg_acc_top5 = eval(model, val_reader(), verbose=1)
print('\r [验证集] 损失: {:.5f}, top1精度:{:.5f}, top5精度为:{:.5f} \n'.format(avg_loss, avg_acc_top1, avg_acc_top5), end='')
avg_loss, avg_acc_top1, avg_acc_top5 = eval(model, test_reader(), verbose=1)
print('\r [测试集] 损失: {:.5f}, top1精度:{:.5f}, top5精度为:{:.5f}'.format(avg_loss, avg_acc_top1, avg_acc_top5), end='')
开始评估...
[验证集] 损失: 2.61555, top1精度:0.89474, top5精度为:1.00000
[测试集] 损失: 2.18259, top1精度:0.76316, top5精度为:1.00000
【结果分析】
需要注意的是此处的精度与训练过程中输出的测试精度是不相同的,因为训练过程中使用的是验证集, 而这里的离线测试使用的是测试集.
实验摘要: 对训练过的模型,使用测试数据进行推理和评估,查看模型的效果。
实验目的:
Q8:使用训练好的模型对给定的车牌进行预测,尽力而为地获得最优的预测结果(20分)([Your codes 16~18])
# 导入依赖库
import os
import cv2
import json
import numpy as np
import paddle # 载入PaddlePaddle基本库
import matplotlib.pyplot as plt # 载入python的第三方图像处理库
import paddle.vision.transforms as T
# Q8-1:定义全局路径,包括项目名称、结果路径、模型路径等
dataset_name = 'maskDetect'
project_name = 'Project010CNNMaskRecognition'
architecture = 'CIFAR10'
img_size = [32, 32, 3]
root_path = 'D:\\Workspace\\' # Windows: 'D:\\Workspace\\'; Linux: '/home/aistudio/work'
dataset_root_path = os.path.join(root_path, 'ExpDatasets', dataset_name)
final_models_path = os.path.join(root_path, 'ExpResults', project_name, 'final_models', 'best_model') # 定义模型的保存的路径
在预测之前,通常需要对图像进行预处理,包括缩放至训练样本的尺度(32×32),数据格式规范为paddle要求的浮点型数据,且形态为[C,H,W],数值归一化到[0~1] 等操作,这部分操作,要求和训练时的预处理方案,完全一致。我们使用函数load_image()
进行定义。
# Q8-2:定义数据读取函数,包括读入数据,将数据转换为float32,尺度规范为[20,20],格式转换为Tesnsor并转置为4D形态
# [Your codes 17]
def load_image(img_path):
img = cv2.imread(img_path, 1) # 以RGB模式读取图像
img = img.astype('float32') # 将图像数据类型转化为float32
img = img/255.0 # 将像素值归一化到[0,1]之间
transforms = T.Compose([
T.Resize([img_size[0], img_size[1]]),
T.ToTensor(),
T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])
img = transforms(img)
img = paddle.unsqueeze(img, axis=0)
return img
Q7. 完成模型推理的代码(10分) ([Your codes 8])
要求:
1. 对 cifar10 进行实例化
2. 载入保存的模型参数
3. 设置模型运行模式为评估模式
4. 载入样本,并进行预处理
5. 将处理后的样本进行前向传输,输出结果
### Q8-3: 载入模型并实现手势识别预测
# [Your codes 18]
import random
img_pathes = []
labels = []
with open(os.path.join(dataset_root_path, 'test.txt')) as f:
for line in f.readlines():
img_path, label = line.strip().split('\t')
img_pathes.append(img_path)
labels.append(label)
json_dataset_info = os.path.join(dataset_root_path, 'dataset_info.json')
label_names = json.loads(open(json_dataset_info, 'r', encoding='utf-8').read())
i = random.randint(0,38)
img_path = img_pathes[i] # 从预先准备的推理图像文件中随机抽取一幅图像,该文件夹中包含0-9共10个示例图片
label_name = label_names['label_dict'][str(labels[i])]
# 1. 载入模型并进行实例化
model = paddle.jit.load(final_models_path) # 载入推理模型
# 2. 载入待预测图像
img = load_image(img_path) # 载入图像,并对图像进行一定的预处理
# 3. 预测并输出结果
logits = model(img)
pred = np.argmax(logits.numpy())
print(pred)
pred_name = label_names['label_dict'][str(pred)]
print('图片 {} 的标签为: {}, 预测结果为: {}'.format(os.path.basename(img_path), label_name, pred_name))
# 4. 输出图像文件
image = cv2.imread(img_path, 1)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
plt.imshow(image)
图片 8742d6646bc67b3061e86ba2f8b8d613.jpg 的标签为: nomaskimages, 预测结果为: nomaskimages
<matplotlib.image.AxesImage at 0x1d54d491fd0>
实验摘要: 使用训练好的模型对真实世界(摄像头拍摄)的人脸进行推理和验证。
实验目的:
实验建议:
# 导入依赖库
import os
import cv2
import json
import numpy as np
import paddle # 载入PaddlePaddle基本库
import matplotlib.pyplot as plt # 载入python的第三方图像处理库
import paddle.vision.transforms as T
dataset_name = 'maskDetect'
project_name = 'Project010CNNMaskRecognition'
architecture = 'CIFAR10'
img_size = [32, 32, 3]
root_path = 'D:\\Workspace\\' # Windows: 'D:\\Workspace\\'; Linux: '/home/aistudio/work'
dataset_root_path = os.path.join(root_path, 'ExpDatasets', dataset_name)
final_models_path = os.path.join(root_path, 'ExpResults', project_name, 'final_models', 'best_model') # 定义模型的保存的路径
数据预处理应该和训练过程完全一致,包括图像尺度的缩放,数据格式转换和像素值归一化。此外,为了匹配Paddle框架,还需要将数据转换为4D的Tensor形态。
# Q8-2:定义数据读取函数,包括读入数据,将数据转换为float32,尺度规范为[20,20],格式转换为Tesnsor并转置为4D形态
# [Your codes 17]
def load_image(img):
img = img.astype('float32') # 将图像数据类型转化为float32
img = img/255.0 # 将像素值归一化到[0,1]之间
transforms = T.Compose([
T.Resize([img_size[0], img_size[1]]),
T.ToTensor(),
])
img = transforms(img)
img = paddle.unsqueeze(img, axis=0)
return img
def predict(image, model_path = final_models_path):
json_dataset_info = os.path.join(dataset_root_path, 'dataset_info.json')
label_names = json.loads(open(json_dataset_info, 'r', encoding='utf-8').read())
#构建预测动态图过程
model = paddle.jit.load(model_path)
# 输出测评结果
img = load_image(image)
logits = model(img)
pred = np.argmax(logits.numpy())
pred_name = label_names['label_dict'][str(pred)]
print('预测结果为: {}'.format(pred_name))
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
plt.imshow(image)
import cv2
cap = cv2.VideoCapture(0) #打开摄像头
while(1):
# get a frame
ret, frame = cap.read()
# show a frame
cv2.imshow("capture", frame) #生成摄像头窗口
if cv2.waitKey(1) & 0xFF == ord('q'): #如果按下q 就截图保存并退出
predict(frame)
break
cap.release()
cv2.destroyAllWindows()
预测结果为: maskimages