作者:欧新宇(Xinyu OU)
当前版本:Release v2.0
开发平台:Paddle 2.5.2
运行环境:Intel Core i7-7700K CPU 4.2GHz, nVidia GeForce GTX 1080 Ti
本教案所涉及的数据集仅用于教学和交流使用,请勿用作商用。
最后更新:2024年5月9日
# 0. 导入依赖库
import os
import cv2
import numpy as np
import paddle
paddle.vision.set_image_backend('cv2')
# 1. 定义数据集列表文件路径
dataset_name = 'Gestures'
project_name = 'Project007FFNGestures'
dataset_path = 'D:\\Workspace\\ExpDatasets\\'
dataset_root_path = os.path.join(dataset_path, dataset_name)
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')
# 2. 定义训练参数
input_size = [100, 100, 3]
total_epoch = 30 # 迭代次数, 代码调试好后考虑Epochs_num = 500-600
eval_interval = 1 # 设置在训练过程中,每隔一定的周期进行一次测试
log_interval = 10 # 训练日志显示间隔
learning_rate = 0.001 # 学习率
# 3. 定义输出参数
np.set_printoptions(precision=5, suppress=True) # 设置numpy的精度,用于打印输出
architecture = 'MLP'
result_root_path = 'D:\\Workspace\\ExpResults\\'
final_models_path = os.path.join(result_root_path, project_name, 'final_model')
手势识别 数据集是由土耳其一所中学制作,数据集由 Data文件夹
中的训练验证数据和 Infer文件夹
中的预测数据组成,包含0-9共10种数字的手势。以下为该数据集的官方描述:
This dataset is prepared by Turkey Ankara Ayrancı Anadolu high school students.
Image size: 100 x 100 pixels
Color space: RGB
Number of classes: 10 (Digits: 0-9)
Number of participant students: 218
Number of samples per student: 10
Dataset Url:https://github.com/ardamavi/Sign-Language-Digits-Dataset
数据集下载:https://aistudio.baidu.com/aistudio/datasetdetail/54000
根据原始数据集的图片,生成列表文件。此处,按照7:1:2的比例进行训练集、验证集、测试集的划分,并将划分好的数据分别保存成四个列表文件:train.txt, val.txt, trainval.txt, test.txt文件中, 同时生成数据集的基本信息文件dataset_info.json. 数据列表文件的基本格式如下:
D:\Workspace\ExpDatasets\Gestures\Data\0\IMG_5991.JPG 0
D:\Workspace\ExpDatasets\Gestures\Data\1\IMG_1129.JPG 1
D:\Workspace\ExpDatasets\Gestures\Data\1\IMG_1139.JPG 1
其中,第一项为图片的保存路径
,第二项为该图片的分类标签
。中间使用一个Tab(或者1个空格)进行分隔。值得注意的是,数据(图片)保存的路径可以使用绝对路径也可以使用相对路径,但为了避免不必要的麻烦和错误,建议使用绝对路径进行索引。
为简化代码,在本例中请直接下载 Gestures数据集,并调用数据集中写好的数据集划分代码 generate_annotation.py
。下面为调用数据集划分的代码,该代码只需要执行一次。
!python "D:\\Workspace\\ExpDatasets\\Gestures\\generate_annotation.py"
图像列表已生成, 其中训练验证集样本1642,训练集样本1432个, 验证集样本210个, 测试集样本420个, 共计2062个。
深度学习的数据在送入模型之前,通常需要进行一系列的处理,包括转换数据格式、数据集划分、数据形状变形、制作数据迭代读取器、数据增广等。因此,通过封装技术实现对这些操作的整合有利于模型设计的规范和简洁。在Paddle中,有两个核心步骤实现数据集的定义和加载:
batch_size
表示每次进行训练的数据批次的图片数量;shuffle
表示是否对当前读入的数据进行打乱,一般来说对于训练集是需要进行打乱的,而对于验证和测试集,由于每次都是对所有样本进行一次轮询,所以不需要进行打乱;drop_last
表示在分批次之后,最后剩余的样本是否进行丢弃,同样由于测试和验证需要对所有样本进行轮询,因此通常设置为False,而对于训练数据,如果训练数据足够多,通常可以设置为True,如果训练数据比较少,那么建议设置成Fasle,以尽量保证样本的多样性。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'):
# 步骤二:实现 __init__ 函数,初始化数据集,将样本和标签映射到列表中
assert mode in ['train', 'val', 'test', 'trainval']
self.data = [] # 创建空列表文件,用于保存数据的路径和标签
# 读取数据集列表文件,并将路径路径和标签进行拆分,其中测试集若不存在标签则复制为"-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])
# 传入定义好的数据处理方法,作为自定义数据集类的一个属性
self.transforms = T.ToTensor()
# 根据索引获取单个样本
def __getitem__(self, index):
# 步骤三:实现 __getitem__ 函数,定义指定 index 时如何获取数据,并返回单条数据(样本数据、对应的标签)
# Q1:定义属于读取及预处理方法,实现以下八个步骤
# 1. 根据索引,从列表中取出一个图像,并将数据拆分成路径和列表
image_path, label = self.data[index]
# 2. 使用cv2进行数据读取可以强制将的图像转化为彩色模式,其中0为灰度模式,1为彩色模式
img = cv2.imread(image_path, 1)
# 3. 将图像尺度resize为指定尺寸
img = cv2.resize(img, (100, 100))
# 4. 将图像数据类型转化为float32(Paddle默认的内部数据格式)
img = np.array(img).astype('float32')
# 5. 将像素值归一化到[0, 1]之间,仅在MLP中使用
img = img/255
# 6. 调整数据形状paddle默认张量格式
img = self.transforms(img)
# 7. 将图像拉成一维向量
img = paddle.reshape(img, [3*100*100])
# 8. CrossEntropyLoss要求label格式为int,将Label格式转换为 int
label = np.array(label, dtype='int64')
return img, label
# 获取样本总数
def __len__(self):
# 步骤四:实现 __len__ 函数,返回数据集的样本总数
return len(self.data)
########################################################################################
# Q2. 实例化数据类
dataset_train =
dataset_val =
dataset_test =
# Q3. 创建迭代读取器
train_reader =
val_reader =
test_reader =
# 4. 测试读取器
if __name__ == "__main__":
print('数据集包含训练数据{}个,验证数据{}个,测试数据{}个,训练验证数据{}个。'.format(len(dataset_train),len(dataset_val),len(dataset_test),len(dataset_trainval)))
print('数据的形态为:{}'.format(dataset_val[0][0].shape))
for i, (image, label) in enumerate(val_reader()):
print('验证集batch_{}的图像形态:{}, 标签形态:{}'.format(i, image.shape, label.shape))
break
数据集包含训练数据1432个,验证数据210个,测试数据420个。
数据的形态为:[30000]
验证集batch_0的图像形态:[64, 30000], 标签形态:[64]
Paddle的组网可以使用paddle.nn.Layer组网方式,这也是在Paddle2.0之后比较推荐的方法,它主要包括三个步骤:
构建一些比较复杂的网络结构时,可以选择该方式,组网包括三个步骤:
__init__
中定义组网用到的神经网络层(layer);在本范例中,我们使用一个5层的前馈神经网络(多层感知机)对手势识别数据集进行建模。其中第一层为输入层,后面紧跟一个大小为100的隐层和两个大小为256的隐层,以及一个大小为10的输出层。其中,输出层的每个神经元对应于MNIST的类别数,即0-9的10个数字。最后使用交叉熵函数求损失,并用Softmax分类器输出类别。
使用动态图模式比静态图要简单很多,只需要定义模型结构即可。模型定义需要使用Object-Oriented-Designed面向对象的类进行定义。以下为该多层感知机的网络构建函数。此处我们使用动态图模式进行网络结构的定义。首先定义了一个多层感知机的类class myMLP(paddle.nn.Layer)
。在该类别使用__init__(self)
对参数进行初始化,并定义前向传播forward(self)
方法。在MLP中,我们主要用到两个类,分别是nn.Linear()和nn.ReLU()。其中,nn.Linear()是线性核,可以理解为一个全连接层,它主要包括两个参数,分别是输入特征的维度in_features
和输出特征的维度out_features
,nn.ReLU()没有需要配置的参数,它实现将特征进行ReLU非线性变换。
from xml.etree.ElementTree import QName
import paddle.nn as nn
# 1. 定义MLP网络
class myMLP(nn.Layer):
def __init__(self, num_classes=10):
super(myMLP,self).__init__()
self.num_classes = num_classes
self.features = nn.Sequential(
# Q4:根据体系结构图和说明文档设计模型结构
)
def forward(self, input):
output = self.features(input)
return output
# 2. 网络测试
if __name__ == '__main__':
model = myMLP() # 实例化模型
paddle.summary(model, (10,100*100*3)) # 根据数据的维度输出模型的基本结构
---------------------------------------------------------------------------
Layer (type) Input Shape Output Shape Param #
===========================================================================
Linear-1 [[10, 30000]] [10, 100] 3,000,100
ReLU-1 [[10, 100]] [10, 100] 0
Linear-2 [[10, 100]] [10, 256] 25,856
ReLU-2 [[10, 256]] [10, 256] 0
Linear-3 [[10, 256]] [10, 256] 65,792
ReLU-3 [[10, 256]] [10, 256] 0
Linear-4 [[10, 256]] [10, 10] 2,570
===========================================================================
Total params: 3,094,318
Trainable params: 3,094,318
Non-trainable params: 0
---------------------------------------------------------------------------
Input size (MB): 1.14
Forward/backward pass size (MB): 0.09
Params size (MB): 11.80
Estimated Total Size (MB): 13.04
---------------------------------------------------------------------------
在准备好数据集和模型后,就可以将数据送入模型中启动训练评估了,概括地讲包括如下几步:
模型训练:训练包括多轮迭代(epoch),每轮迭代遍历一次训练数据集,并且每次从中获取一小批(mini-batch)样本,送入模型执行前向计算得到预测值,并计算预测值(predict_label)与真实值(true_label)之间的损失函数值(loss)。执行梯度反向传播,并根据设置的优化算法(optimizer)更新模型的参数。观察每轮迭代的 loss 值减小趋势,可判断模型训练效果。该步骤使用训练集/训练验证集。
模型评估:将验证数据集送入训练好的模型进行评估,得到预测值,计算预测值与真实值之间的损失函数值(loss),并计算评价指标值(metric),便于评估模型效果。该步骤使用验证集。
模型推理:将待测试的数据(样本)送入训练好的模型执行推理,观察并验证推理结果(标签)是否符合预期。该步骤使用测试集或自定义数据。
模型测试一般包含两个类型,分别是:
两种测试模式,在代码上基本上是一致的,只是所使用的数据不同。因此,我们可以定义一个eval()
函数来实现代码复用,在进行评估时,分别调用val_reader()
和test_reader()
的即可。
eval_batch()
函数实现推理。该函数会根据model.prepare()
所定义的损失函数和衡量标准进行自动计算。通常,在分类任务中,我们会使用交叉熵来计算损失,并使用精度来对结果进行衡量。eval_batch()
获得的结果进行输出# 模型测试函数,可同时用于在线测试和离线测试
def eval(model, data_reader, verbose=0):
accuracies_top1 = []
accuracies_top5 = []
losses = []
n_total = 0
# Q5: 设计单层循环,实现模型的测试,可以根据输入的数据集,分别对测试集和验证集进行测试
for batch_id, (image, label) in enumerate(data_reader):
return avg_loss, avg_acc_top1, avg_acc_top5
在做好模型训练的前期准备工作后,就可以进行训练了。模型的训练过程通常采用二层循环嵌套方式:内层循环完成整个数据集的一次遍历,采用分批次方式;外层循环根据设置的训练轮次完成数据集的多次遍历。因此需要指定三个关键参数,分别是定义好的模型(Model)、模型迭代读取器(DataLoader)和训练轮次(Epoch)。其中定义好的模型包含了模型的基本结构和前向传输的规则,模型迭代读取器包含了数据样本和内训中每个批次的训练样本数,训练轮次指定了训练时遍历数据集的次数,即外循环轮次。
与验证/测试类似,为了简便,我们可以使用Paddle内置的训练函数model.train_batch()
来实现训练。在使用该函数训练之前,我们还需要对模型model进行一些配置,包括实例化实例化模型,配置模型的输入参数,定义优化器,定义评价函数等。具体包括:
# 设置输入样本的维度
input_spec = InputSpec(shape=[None, 3*100*100], dtype='float32', name='image')
label_spec = InputSpec(shape=[None, 1], dtype='int64', name='label')
# 设置学习率、优化器、损失函数和评价指标
lr = learning_rate
optimizer = optimizer.Adam(learning_rate=lr, parameters=model.parameters())
model.prepare(optimizer,
paddle.nn.CrossEntropyLoss(),
paddle.metric.Accuracy(topk=(1,5)))
此外,我们还可以定义一些提示性的信息用于观察训练过程,例如如下形式:
Epoch:1/30, batch:20, train_loss:[2.47361], acc_top1:[0.09375], acc_top5:[0.56250](0.43s)
[validation] Epoch:1/30, val_loss:[0.04288], val_top1:[0.10000], val_top5:[0.50000]
最优top1测试精度:0.10000 (epoch=1)
以上信息包括周期信息,批次信息,训练和验证损失,top1精度,top5精度以及程序运行时间等。
在Paddle中,模型的保存和载入有很多种模式,但是最常用的是还是基于model.save()来实现,具体有两种情况:
关于两种模型的载入方法,请参考后面的示例代码。
值得注意的是
import os
import time
import json
import paddle
from paddle.static import InputSpec
import paddle.optimizer as optimizer
def train(model):
start = time.perf_counter()
# 初始化临时变量
num_batch = 0
best_result = 0
best_result_id = 0
elapsed = 0
#Q6: 设计两层循环,分别实现根据周期循环和根据batch+_sizie循环,并在训练集上进行训练
#Q7: 调用验证函数,实现对验证集的校验,并输出平均loss,top1精度和top5精度。同时要求将性能最好的模型进行保存到final_model_path
print('训练完毕,最终性能accuracy={:.5f}(epoch={}), 总耗时{:.2f}s'.format(best_result, best_result_id, time.perf_counter() - start))
#### 训练主函数 ########################################################3
if __name__ == '__main__':
# Q8:定义训练主函数,包括以下三个步骤:
# 1. 设置输入样本的维度,包括对图像和标签的定义
# 2. 实例化模型,并输入模型基本信息
# 3. 设置学习率、优化器、损失函数和评价指标
# 启动训练过程
print('启动训练...')
train(model)
print('结果路径为 {}.'.format(final_models_path))
模型参数信息:
---------------------------------------------------------------------------
Layer (type) Input Shape Output Shape Param #
===========================================================================
Linear-5 [[1, 30000]] [1, 100] 3,000,100
ReLU-4 [[1, 100]] [1, 100] 0
Linear-6 [[1, 100]] [1, 256] 25,856
ReLU-5 [[1, 256]] [1, 256] 0
Linear-7 [[1, 256]] [1, 256] 65,792
ReLU-6 [[1, 256]] [1, 256] 0
Linear-8 [[1, 256]] [1, 10] 2,570
===========================================================================
Total params: 3,094,318
Trainable params: 3,094,318
Non-trainable params: 0
---------------------------------------------------------------------------
Input size (MB): 0.11
Forward/backward pass size (MB): 0.01
Params size (MB): 11.80
Estimated Total Size (MB): 11.93
---------------------------------------------------------------------------
{'total_params': 3094318, 'trainable_params': 3094318}
启动训练...
Epoch:1/30, batch:10, train_loss:[4.39355], acc_top1:[0.07812], acc_top5:[0.42188](0.46s)
Epoch:1/30, batch:20, train_loss:[2.74373], acc_top1:[0.07812], acc_top5:[0.40625](0.68s)
[validation] Epoch:1/30, val_loss:[0.04341], val_top1:[0.10952], val_top5:[0.59048]
最优top1测试精度:0.10952 (epoch=1)
Epoch:2/30, batch:30, train_loss:[2.37259], acc_top1:[0.20312], acc_top5:[0.46875](1.33s)
Epoch:2/30, batch:40, train_loss:[2.21174], acc_top1:[0.18750], acc_top5:[0.62500](0.41s)
[validation] Epoch:2/30, val_loss:[0.04209], val_top1:[0.19524], val_top5:[0.70000]
最优top1测试精度:0.19524 (epoch=2)
Epoch:3/30, batch:50, train_loss:[2.22916], acc_top1:[0.15625], acc_top5:[0.59375](0.94s)
Epoch:3/30, batch:60, train_loss:[2.04789], acc_top1:[0.21875], acc_top5:[0.75000](0.60s)
[validation] Epoch:3/30, val_loss:[0.03844], val_top1:[0.28095], val_top5:[0.69048]
最优top1测试精度:0.28095 (epoch=3)
Epoch:4/30, batch:70, train_loss:[1.95388], acc_top1:[0.31250], acc_top5:[0.76562](1.34s)
Epoch:4/30, batch:80, train_loss:[1.81739], acc_top1:[0.43750], acc_top5:[0.85938](0.46s)
[validation] Epoch:4/30, val_loss:[0.03365], val_top1:[0.28095], val_top5:[0.80476]
Epoch:5/30, batch:90, train_loss:[1.82858], acc_top1:[0.39062], acc_top5:[0.85938](0.58s)
Epoch:5/30, batch:100, train_loss:[1.75928], acc_top1:[0.26562], acc_top5:[0.84375](0.57s)
Epoch:5/30, batch:110, train_loss:[1.71472], acc_top1:[0.37500], acc_top5:[0.82812](0.44s)
[validation] Epoch:5/30, val_loss:[0.02836], val_top1:[0.43333], val_top5:[0.84286]
最优top1测试精度:0.43333 (epoch=5)
Epoch:6/30, batch:120, train_loss:[1.34966], acc_top1:[0.53125], acc_top5:[0.93750](1.10s)
Epoch:6/30, batch:130, train_loss:[1.70853], acc_top1:[0.25000], acc_top5:[0.90625](0.36s)
[validation] Epoch:6/30, val_loss:[0.02931], val_top1:[0.44286], val_top5:[0.90952]
最优top1测试精度:0.44286 (epoch=6)
Epoch:7/30, batch:140, train_loss:[1.54897], acc_top1:[0.37500], acc_top5:[0.93750](0.83s)
Epoch:7/30, batch:150, train_loss:[1.46881], acc_top1:[0.39062], acc_top5:[0.93750](0.46s)
[validation] Epoch:7/30, val_loss:[0.02402], val_top1:[0.52857], val_top5:[0.93333]
最优top1测试精度:0.52857 (epoch=7)
Epoch:8/30, batch:160, train_loss:[1.26674], acc_top1:[0.54688], acc_top5:[0.95312](0.98s)
Epoch:8/30, batch:170, train_loss:[1.41724], acc_top1:[0.46875], acc_top5:[0.93750](0.56s)
[validation] Epoch:8/30, val_loss:[0.02306], val_top1:[0.57143], val_top5:[0.94762]
最优top1测试精度:0.57143 (epoch=8)
Epoch:9/30, batch:180, train_loss:[1.14159], acc_top1:[0.65625], acc_top5:[0.96875](0.90s)
Epoch:9/30, batch:190, train_loss:[1.10799], acc_top1:[0.60938], acc_top5:[0.93750](0.55s)
[validation] Epoch:9/30, val_loss:[0.02028], val_top1:[0.57619], val_top5:[0.96190]
最优top1测试精度:0.57619 (epoch=9)
Epoch:10/30, batch:200, train_loss:[1.23004], acc_top1:[0.56250], acc_top5:[0.93750](1.00s)
Epoch:10/30, batch:210, train_loss:[1.08690], acc_top1:[0.59375], acc_top5:[0.93750](0.46s)
Epoch:10/30, batch:220, train_loss:[0.87832], acc_top1:[0.73438], acc_top5:[0.96875](0.45s)
[validation] Epoch:10/30, val_loss:[0.02122], val_top1:[0.60476], val_top5:[0.95238]
最优top1测试精度:0.60476 (epoch=10)
Epoch:11/30, batch:230, train_loss:[0.86703], acc_top1:[0.75000], acc_top5:[0.96875](0.77s)
Epoch:11/30, batch:240, train_loss:[0.92345], acc_top1:[0.62500], acc_top5:[1.00000](0.56s)
[validation] Epoch:11/30, val_loss:[0.03362], val_top1:[0.50476], val_top5:[0.93810]
Epoch:12/30, batch:250, train_loss:[0.94919], acc_top1:[0.68750], acc_top5:[0.96875](0.76s)
Epoch:12/30, batch:260, train_loss:[0.72333], acc_top1:[0.73438], acc_top5:[0.96875](0.45s)
[validation] Epoch:12/30, val_loss:[0.02174], val_top1:[0.54762], val_top5:[0.95714]
Epoch:13/30, batch:270, train_loss:[1.25069], acc_top1:[0.51562], acc_top5:[0.93750](0.56s)
Epoch:13/30, batch:280, train_loss:[1.08153], acc_top1:[0.64062], acc_top5:[0.95312](0.54s)
[validation] Epoch:13/30, val_loss:[0.02122], val_top1:[0.58571], val_top5:[0.94762]
Epoch:14/30, batch:290, train_loss:[0.79241], acc_top1:[0.79688], acc_top5:[0.96875](0.56s)
Epoch:14/30, batch:300, train_loss:[0.74392], acc_top1:[0.75000], acc_top5:[1.00000](0.46s)
[validation] Epoch:14/30, val_loss:[0.01880], val_top1:[0.60476], val_top5:[0.96667]
Epoch:15/30, batch:310, train_loss:[0.72252], acc_top1:[0.73438], acc_top5:[0.98438](0.48s)
Epoch:15/30, batch:320, train_loss:[0.88343], acc_top1:[0.68750], acc_top5:[0.98438](0.44s)
Epoch:15/30, batch:330, train_loss:[1.07243], acc_top1:[0.60938], acc_top5:[0.90625](0.55s)
[validation] Epoch:15/30, val_loss:[0.01571], val_top1:[0.70952], val_top5:[0.97143]
最优top1测试精度:0.70952 (epoch=15)
Epoch:16/30, batch:340, train_loss:[0.96580], acc_top1:[0.70312], acc_top5:[0.96875](0.77s)
Epoch:16/30, batch:350, train_loss:[0.69921], acc_top1:[0.78125], acc_top5:[0.95312](0.65s)
[validation] Epoch:16/30, val_loss:[0.01652], val_top1:[0.69048], val_top5:[0.95714]
Epoch:17/30, batch:360, train_loss:[1.27291], acc_top1:[0.53125], acc_top5:[0.92188](0.46s)
Epoch:17/30, batch:370, train_loss:[0.61907], acc_top1:[0.78125], acc_top5:[1.00000](0.65s)
[validation] Epoch:17/30, val_loss:[0.01634], val_top1:[0.73333], val_top5:[0.96190]
最优top1测试精度:0.73333 (epoch=17)
Epoch:18/30, batch:380, train_loss:[0.81712], acc_top1:[0.70312], acc_top5:[1.00000](0.88s)
Epoch:18/30, batch:390, train_loss:[0.55936], acc_top1:[0.82812], acc_top5:[0.98438](0.55s)
[validation] Epoch:18/30, val_loss:[0.01569], val_top1:[0.70000], val_top5:[0.95714]
Epoch:19/30, batch:400, train_loss:[0.70603], acc_top1:[0.70312], acc_top5:[0.95312](0.59s)
Epoch:19/30, batch:410, train_loss:[0.88434], acc_top1:[0.64062], acc_top5:[0.98438](0.45s)
[validation] Epoch:19/30, val_loss:[0.01592], val_top1:[0.69524], val_top5:[0.96190]
Epoch:20/30, batch:420, train_loss:[0.72619], acc_top1:[0.78125], acc_top5:[0.93750](0.68s)
Epoch:20/30, batch:430, train_loss:[0.68058], acc_top1:[0.76562], acc_top5:[0.98438](0.44s)
Epoch:20/30, batch:440, train_loss:[0.84574], acc_top1:[0.68750], acc_top5:[0.98438](0.44s)
[validation] Epoch:20/30, val_loss:[0.02193], val_top1:[0.61429], val_top5:[0.96667]
Epoch:21/30, batch:450, train_loss:[1.09199], acc_top1:[0.65625], acc_top5:[0.92188](0.56s)
Epoch:21/30, batch:460, train_loss:[0.76067], acc_top1:[0.76562], acc_top5:[0.96875](0.55s)
[validation] Epoch:21/30, val_loss:[0.01762], val_top1:[0.66667], val_top5:[0.96667]
Epoch:22/30, batch:470, train_loss:[0.63677], acc_top1:[0.68750], acc_top5:[0.98438](0.57s)
Epoch:22/30, batch:480, train_loss:[0.52120], acc_top1:[0.79688], acc_top5:[0.98438](0.44s)
[validation] Epoch:22/30, val_loss:[0.01756], val_top1:[0.67619], val_top5:[0.94286]
Epoch:23/30, batch:490, train_loss:[0.32460], acc_top1:[0.87500], acc_top5:[1.00000](0.67s)
Epoch:23/30, batch:500, train_loss:[0.64572], acc_top1:[0.76562], acc_top5:[0.98438](0.55s)
[validation] Epoch:23/30, val_loss:[0.02536], val_top1:[0.57619], val_top5:[0.94762]
Epoch:24/30, batch:510, train_loss:[0.40627], acc_top1:[0.84375], acc_top5:[1.00000](0.46s)
Epoch:24/30, batch:520, train_loss:[0.64004], acc_top1:[0.78125], acc_top5:[0.96875](0.45s)
[validation] Epoch:24/30, val_loss:[0.01460], val_top1:[0.72381], val_top5:[0.96190]
Epoch:25/30, batch:530, train_loss:[0.57668], acc_top1:[0.76562], acc_top5:[1.00000](0.68s)
Epoch:25/30, batch:540, train_loss:[0.91435], acc_top1:[0.68750], acc_top5:[1.00000](0.56s)
Epoch:25/30, batch:550, train_loss:[0.54931], acc_top1:[0.79688], acc_top5:[0.98438](0.44s)
[validation] Epoch:25/30, val_loss:[0.01700], val_top1:[0.66190], val_top5:[0.95714]
Epoch:26/30, batch:560, train_loss:[0.39254], acc_top1:[0.84375], acc_top5:[1.00000](0.58s)
Epoch:26/30, batch:570, train_loss:[0.32121], acc_top1:[0.92188], acc_top5:[1.00000](0.55s)
[validation] Epoch:26/30, val_loss:[0.01788], val_top1:[0.71905], val_top5:[0.96667]
Epoch:27/30, batch:580, train_loss:[0.43097], acc_top1:[0.85938], acc_top5:[1.00000](0.50s)
Epoch:27/30, batch:590, train_loss:[0.82380], acc_top1:[0.67188], acc_top5:[0.98438](0.55s)
[validation] Epoch:27/30, val_loss:[0.01630], val_top1:[0.68571], val_top5:[0.96667]
Epoch:28/30, batch:600, train_loss:[1.02327], acc_top1:[0.64062], acc_top5:[0.98438](0.58s)
Epoch:28/30, batch:610, train_loss:[0.29247], acc_top1:[0.92188], acc_top5:[1.00000](0.35s)
[validation] Epoch:28/30, val_loss:[0.01566], val_top1:[0.71905], val_top5:[0.97619]
Epoch:29/30, batch:620, train_loss:[0.59154], acc_top1:[0.75000], acc_top5:[1.00000](0.76s)
Epoch:29/30, batch:630, train_loss:[0.43663], acc_top1:[0.85938], acc_top5:[1.00000](0.45s)
[validation] Epoch:29/30, val_loss:[0.02399], val_top1:[0.56667], val_top5:[0.94762]
Epoch:30/30, batch:640, train_loss:[0.42324], acc_top1:[0.87500], acc_top5:[0.98438](0.78s)
Epoch:30/30, batch:650, train_loss:[0.42873], acc_top1:[0.89062], acc_top5:[0.98438](0.55s)
Epoch:30/30, batch:660, train_loss:[0.73652], acc_top1:[0.73438], acc_top5:[0.98438](0.35s)
[validation] Epoch:30/30, val_loss:[0.01745], val_top1:[0.69048], val_top5:[0.96190]
训练完毕,最终性能accuracy=0.73333(epoch=17), 总耗时40.19s
结果路径为 D:\Workspace\ExpResults\Project007FFNGestures\final_model.
需要注意的是,以上训练过程是基于随机初始化后的优化过程,因此每次训练获得的结果通常是不相同的,但最终收敛后的结果是近似相等的。
if __name__ == '__main__':
# Q9:调用训练好的模型,分别在验证集和测试集上进行测试,包括以下三个步骤
# 1. 设置输入样本的维度
input_spec = InputSpec(shape=[None, 3*100*100], dtype='float32', name='image')
label_spec = InputSpec(shape=[None, 1], dtype='int64', name='label')
# 2. 载入模型
# 模型实例化
# 模型初始化
# 载入调优模型的参数
# 设置loss
# 设置评价指标
# 3. 执行评估函数,并输出验证集样本的损失和精度
print('开始评估...')
# 对验证集进行评估
print('\r [验证集] 损失: {:.5f}, top1精度:{:.5f}, top5精度为:{:.5f} \n'.format(avg_loss, avg_acc_top1, avg_acc_top5), end='')
# 对测试集进行评估
print('\r [测试集] 损失: {:.5f}, top1精度:{:.5f}, top5精度为:{:.5f}'.format(avg_loss, avg_acc_top1, avg_acc_top5), end='')
开始评估...
[验证集] 损失: 0.01634, top1精度:0.73333, top5精度为:0.96190
[测试集] 损失: 0.01533, top1精度:0.70952, top5精度为:0.96429
paddle.jit.load()
进行模型载入。# 导入依赖库
import os
import cv2
import numpy as np
import paddle # 载入PaddlePaddle基本库
import matplotlib.pyplot as plt # 载入python的第三方图像处理库
# 设置载入模型,此处我们调用训练中获得的最好的一个模型
project_name = 'Project007FFNGestures'
result_root_path = 'D:\\Workspace\\ExpResults\\'
final_models_path = os.path.join(result_root_path, project_name, 'final_model')
获取待测数据包含两个步骤: 读取待测图像和图像预处理。函数 load_image()
实现从指定位置读取待测图像, 并对该图像进行预处理。此处的预处理方案
和训练模型时
所使用的预处理方案必须是一致的。
numpy.array
格式, 32bit浮点数据类型(float32);import paddle.vision.transforms as T
#Q10. 读取预测图像并进行预处理,注意和训练集的处理方法保持一致
def load_image(img_path):
img = # 以RGB模式读取图像
img = # 将图像尺度resize为指定尺寸
img = # 将图像数据类型转化为float32
img = # 将像素值归一化到[0,1]之间
transforms = T.ToTensor()
img = # 调整数据形状paddle默认格式
img = # 将图像拉伸成向量
return img
import random
i = random.randint(0,9)
img_path = 'D:\\Workspace\\ExpDatasets\\Gestures\\Infer\\infer_' + str(i) + '.jpg' # 从预先准备的推理图像文件中随机抽取一幅图像,该文件夹中包含0-9共10个示例图片
# img_path = 'D:\\Workspace\\ExpDatasets\\Gestures\\Infer\\infer_9.jpg' # 手动指定一张图片进行推理测试
# Q11: 使用训练好的模型对给定的图像进行推理,包括以下三个步骤:
# 1. 载入推理模型并进行实例化
model =
# 2. 载入待预测图像
img =
# 3. 使用模型进行前向推理
logits =
pred =
print('手势文件 {} 的标签为: {}, 预测结果为: {}'.format(os.path.basename(img_path), os.path.basename(img_path)[-5], pred))
# 4. 输出图像文件
image = cv2.imread(img_path, 1)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
plt.imshow(image)
手势文件 infer_5.jpg 的标签为: 5, 预测结果为: 5
<matplotlib.image.AxesImage at 0x19834ec5fd0>