本次识别是一个图像二分类项目,利用卷积神经网络实现图像中表情的分类。
卷积神经网络(CNN)
卷积神经网络(Convolution Neural Network,简称CNN),CNN 其实可以看作 DNN 的一种特殊形式。它跟传统 DNN 标志性的区别在于两点,Convolution Kernel 以及 Pooling。
项目背景
随着计算机技术和人工智能技术及其相关学科的迅猛发展,整个社会的自动化程度不断提高,人们对类似于人和人交流方式的人机交互的需求日益强烈。计算机和机器人如果能够像人类那样具有理解和表达情感的能力,将从根本上改变人与计算机之间的关系,使计算机能够更好地为人类服务。表情识别是情感理解的基础,是计算机理解人们情感的前提,也是人们探索和理解智能的有效途径。
人脸表情识别是指从给定的静态图像或动态视频序列中分离出特定的表情状态 ,从而确定被识别对象的心理情绪,实现计算机对人脸表情的理解与识别 ,从根本上改变人与计算机的关系,从而达到更好的人机交互。 因此人脸表情识别在心理学、智能机器人、智能监控、虚拟现实及合成动画等领域有很大的潜在应用价值。
一、数据介绍
网上公开的人脸表情图像数据集:
- 包含positive和negative两种表情,共7200余张图片
- 图片为16464,灰度图像
- 本次学习中,取其中的10%作为测试集,90%作为训练集
- 数据集中的图像数据采用了32x32像素的灰度图像格式,每个图像都存储为PNG文件。
在数据处理部分,主要进行了以下操作:
- 图像的缩放:将图像的尺寸统一调整为32x32像素,以适应模型输入要求。
- 归一化处理:将图像的像素值进行归一化,将像素值范围从0到255缩放到0到1之间。
- 划分训练集和验证集:将数据集中的图像按照一定的比例划分为训练集和验证集,用于模型的训练和评估。
二、数据处理
导入所需的包
import os
import zipfile
import random
import json
import cv2
import numpy as np
from PIL import Image
import paddle
import paddle.fluid as fluid
from multiprocessing import cpu_count
from paddle.fluid.dygraph import Linear
from paddle.fluid.dygraph import Pool2D,Conv2D
import matplotlib.pyplot as plt
进行参数配置
定义了一个名为
train_parameters
的字典,其中包含了训练卷积神经网络的各种参数配置。下面是各个参数的解释:
"input_size": [1, 32, 32]
: 输入图片的形状,这里表示每张图片是单通道(灰度图),尺寸为32x32像素。
"class_dim": -1
: 分类数,这个参数目前设置为-1,意味着分类数尚未确定,将在数据准备过程中动态获取。
"src_path":"data/data33766/face_data.zip"
: 原始数据集的路径,即包含人脸图像的ZIP文件。
"target_path":"/home/aistudio/data/dataset"
: 解压后数据集的目标路径,ZIP文件将在这里解压。
"train_list_path": "./train_data.txt"
: 生成的训练数据列表文件的路径,该文件将包含训练集的图像文件路径和对应的标签。
"eval_list_path": "./val_data.txt"
: 生成的验证数据列表文件的路径,该文件将包含验证集的图像文件路径和对应的标签。
"label_dict":{}
: 标签字典,用于将类别标签映射到类别名称。该字典在数据准备过程中将填充。
"readme_path": "/home/aistudio/data/readme.json"
: 生成的数据集说明文件的路径,该文件将包含数据集的详细信息和类别标签。
"num_epochs": 2
: 训练轮数,指定训练循环将运行多少轮。
"train_batch_size": 8
: 训练时每个批次的大小,即每次从数据集中取出多少样本进行训练。
"learning_strategy": {"lr": 0.0001}
: 优化函数相关的配置,这里只包括学习率(lr)的设置,学习率用于调整模型参数更新的步长。
train_parameters = {
"input_size": [1, 32, 32], #输入图片的shape
"class_dim": -1, #分类数
"src_path":"data/data33766/face_data.zip", #原始数据集路径
"target_path":"/home/aistudio/data/dataset", #要解压的路径
"train_list_path": "./train_data.txt", #train_data.txt路径
"eval_list_path": "./val_data.txt", #eval_data.txt路径
"label_dict":{}, #标签字典
"readme_path": "/home/aistudio/data/readme.json", #readme.json路径
"num_epochs": 10, #训练轮数
"train_batch_size": 8, #训练时每个批次的大小
"learning_strategy": { #优化函数相关的配置
"lr": 0.0001 #超参数学习率
}
}
这些参数将在训练和数据准备过程中使用,以配置模型训练和数据加载的相关参数。
将原始数据进行解压
定义一个名为
unzip_data
的函数,用于解压原始数据集的ZIP文件并将其解压到指定的目标路径。下面是这段代码的详细解释:
def unzip_data(src_path, target_path):
: 这是一个函数的定义,函数名为unzip_data
,它接受两个参数,src_path
表示原始数据集的ZIP文件路径,target_path
表示解压后数据集的目标路径。
''' 解压原始数据集,将src_path路径下的zip包解压至data/dataset目录下 '''
: 这是函数的文档字符串(docstring),用于对函数进行简要的说明和注释。
if (not os.path.isdir(target_path)):
: 这是一个条件判断语句,检查目标路径是否存在。如果target_path
目录不存在,就执行以下操作:
z = zipfile.ZipFile(src_path, 'r')
: 创建一个ZIP文件对象z
,打开src_path
路径下的ZIP文件,并以只读模式打开它。
z.extractall(path=target_path)
: 使用ZIP文件对象的extractall
方法,将ZIP文件中的所有内容解压到target_path
目录下。
z.close()
: 关闭ZIP文件对象,释放资源。
else:
: 如果目标路径已经存在,执行以下操作:
print("文件已解压")
: 打印一条消息,提示用户文件已经解压过。
def unzip_data(src_path,target_path):
if(not os.path.isdir(target_path)):
z = zipfile.ZipFile(src_path, 'r')
z.extractall(path=target_path)
z.close()
else:
print("文件已解压")
这个函数用于确保数据集的ZIP文件被解压到指定的目标路径中,如果目标路径已存在,则不重复解压。这是数据准备的一部分,确保数据集的可用性。
生成数据列表
定义一个名为
get_data_list
的函数,其主要目的是生成训练和验证数据的列表,并创建一个数据集的说明文件。下面是这段代码的详细解释:
def get_data_list(target_path, train_list_path, eval_list_path):
: 这是一个函数的定义,函数名为get_data_list
,它接受三个参数:target_path
表示数据集的目标路径,train_list_path
表示生成的训练数据列表文件的路径,eval_list_path
表示生成的验证数据列表文件的路径。
#存放所有类别的信息
: 这是一条注释,用于说明接下来的代码将创建一个列表来存储每个类别的信息。
class_detail = []
: 创建一个空列表class_detail
,用于存储每个类别的详细信息。
data_list_path = target_path
: 将data_list_path
设置为目标路径,表示数据集所在的目录。
class_dirs = os.listdir(data_list_path)
: 列出目标路径下的所有文件和子目录,将它们存储在class_dirs
中。
if '__MACOSX' in class_dirs:
和if '.ipynb_checkpoints' in class_dirs:
: 这两行代码检查是否存在特定的目录名称(__MACOSX
和.ipynb_checkpoints
),如果存在,就从class_dirs
中移除这些目录。
all_class_images = 0
: 初始化一个变量all_class_images
,用于存储数据集中的总图像数量。
class_label = 0
: 初始化一个变量class_label
,用于表示类别标签。
class_dim = 0
: 初始化一个变量class_dim
,用于表示类别数目。
trainer_list = []
和eval_list = []
: 创建两个空列表trainer_list
和eval_list
,用于分别存储训练数据和验证数据的信息。
for class_dir in class_dirs:
: 开始遍历每个类别的目录,这里的class_dir
表示每个类别的文件夹。
class_dim += 1
: 增加类别数目,每遍历一个类别目录,类别数加一。创建一个名为
class_detail_list
的字典,用于存储每个类别的信息,包括类别名称、类别标签、测试集图像数目、训练集图像数目。统计每个类别目录下有多少张图片,通过遍历目录下的图片文件来实现。
name_path = os.path.join(path, img_path)
: 构建每张图片的完整路径,其中path
是类别目录的路径,img_path
是图片文件名。
if class_sum % 10 == 0:
: 检查每张图片的顺序,如果是每10张图片中的一张,就将它加入验证数据列表(eval_list
)。否则,将该图片加入训练数据列表(
trainer_list
)。更新类别的图像数目和总图像数目。
更新
class_detail_list
字典中的相关信息。最后,将类别标签和类别名称的映射关系添加到
train_parameters['label_dict']
中,同时增加类别标签的值。
train_parameters['class_dim'] = class_dim
: 更新训练参数字典中的类别数目字段。
random.shuffle(eval_list)
和random.shuffle(trainer_list)
: 对验证数据和训练数据的列表进行随机打乱,以确保数据的随机性。
with open(eval_list_path, 'a') as f:
和with open(train_list_path, 'a') as f2:
: 打开验证数据和训练数据列表文件,以便将数据信息写入文件。在两个
with
块内,将验证数据和训练数据的信息写入相应的文件。创建一个名为
readjson
的字典,用于存储数据集的详细信息。将数据集的信息填充到
readjson
字典中,包括数据集的目录、总图像数目和类别详细信息。使用
json.dumps
方法将readjson
字典转换为JSON格式,并写入数据集说明文件(readme.json
)中。最后,打印一条消息,表示数据列表生成完成。
def get_data_list(target_path,train_list_path,eval_list_path):
#存放所有类别的信息
class_detail = []
#获取所有类别保存的文件夹名称
data_list_path=target_path
class_dirs = os.listdir(data_list_path)
if '__MACOSX'in class_dirs:
class_dirs.remove('__MACOSX')
if '.ipynb_checkpoints' in class_dirs:
class_dirs.remove('.ipynb_checkpoints')
# #总的图像数量
all_class_images = 0
# #存放类别标签
class_label=0
# #存放类别数目
class_dim = 0
# #存储要写进eval.txt和train.txt中的内容
trainer_list=[]
eval_list=[]
# #读取每个类别,['positive', 'negative']
for class_dir in class_dirs:
if class_dir != ".DS_Store":
class_dim += 1
#每个类别的信息
class_detail_list = {}
eval_sum = 0
trainer_sum = 0
#统计每个类别有多少张图片
class_sum = 0
#获取类别路径
path = os.path.join(data_list_path,class_dir)
print(path)
# 获取所有图片
img_paths = os.listdir(path)
for img_path in img_paths: # 遍历文件夹下的每个图片
if img_path =='.DS_Store':
continue
name_path = os.path.join(path,img_path) # 每张图片的路径
if class_sum % 10 == 0: # 每10张图片取一个做验证数据
eval_sum += 1 # test_sum为测试数据的数目
eval_list.append(name_path + "\t%d" % class_label + "\n")
else:
trainer_sum += 1
trainer_list.append(name_path + "\t%d" % class_label + "\n")#trainer_sum测试数据的数目
class_sum += 1 #每类图片的数目
all_class_images += 1 #所有类图片的数目
# 说明的json文件的class_detail数据
class_detail_list['class_name'] = class_dir #类别名称
class_detail_list['class_label'] = class_label #类别标签
class_detail_list['class_eval_images'] = eval_sum #该类数据的测试集数目
class_detail_list['class_trainer_images'] = trainer_sum #该类数据的训练集数目
class_detail.append(class_detail_list)
#初始化标签列表
train_parameters['label_dict'][str(class_label)] = class_dir
class_label += 1
#初始化分类数
train_parameters['class_dim'] = class_dim
print(train_parameters)
#乱序
random.shuffle(eval_list)
with open(eval_list_path, 'a') as f:
for eval_image in eval_list:
f.write(eval_image)
random.shuffle(trainer_list)
with open(train_list_path, 'a') as f2:
for train_image in trainer_list:
f2.write(train_image)
# 说明的json文件信息
readjson = {}
readjson['all_class_name'] = data_list_path #文件父目录
readjson['all_class_images'] = all_class_images
readjson['class_detail'] = class_detail
jsons = json.dumps(readjson, sort_keys=True, indent=4, separators=(',', ': '))
with open(train_parameters['readme_path'],'w') as f:
f.write(jsons)
print ('生成数据列表完成!')
代码的主要功能是生成训练和验证数据的列表文件,以及创建一个数据集说明文件,用于后续的训练和数据加载。
自定义reader
定义了一个名为
data_reader
的函数,其主要目的是创建一个自定义的数据读取器,用于读取训练或验证数据列表文件中的图像数据并进行预处理。下面是这段代码的详细解释:
def data_reader(file_list):
: 这是一个函数的定义,函数名为data_reader
,它接受一个参数file_list
,表示数据列表文件的路径。
'''自定义reader'''
: 这是函数的文档字符串(docstring),用于对函数进行简要的说明和注释。
def reader():
: 在data_reader
函数内部,定义了一个名为reader
的嵌套函数,它将被返回并用作数据读取器。
with open(file_list, 'r') as f:
: 打开指定的数据列表文件(file_list
)以供读取,使用with
语句确保文件在读取完毕后被正确关闭。
lines = [line.strip() for line in f]
: 从文件中逐行读取数据,去除每行前后的空白字符,并将所有行存储在名为lines
的列表中。
for line in lines:
: 开始遍历lines
列表中的每一行,表示每一行都包含一张图片的信息。
img_path, lab = line.strip().split('\t')
: 将当前行按制表符('\t')分隔为两部分,分别存储在img_path
和lab
中,img_path
表示图像文件的路径,lab
表示图像的标签。
img = Image.open(img_path)
: 使用PIL库的Image.open
方法打开图像文件,将图像文件加载到内存中。
img = img.resize((32, 32), Image.ANTIALIAS)
: 调整图像的尺寸为32x32像素,采用抗锯齿的方式进行缩放。
img = np.array(img).astype('float32')
: 将图像数据转换为NumPy数组,并将数据类型设置为浮点型('float32')。
img = img / 255.0
: 对图像数据进行归一化处理,将像素值范围从0到255缩放到0到1之间。
yield img, int(lab)
: 使用生成器(yield
)将处理后的图像数据img
和对应的标签lab
返回给调用者。这使得数据可以逐批次地被读取,而不需要一次性加载整个数据集。
return reader
: 最后,返回定义好的reader
函数,它被用作数据读取器,在训练和验证过程中将被调用来获取数据。
def data_reader(file_list):
def reader():
with open(file_list, 'r') as f:
lines = [line.strip() for line in f]
for line in lines:
img_path, lab = line.strip().split('\t')
img = Image.open(img_path)
img = img.resize((32, 32), Image.ANTIALIAS)
img = np.array(img).astype('float32')
img = img/255.0
yield img, int(lab)
return reader
代码定义了一个数据读取器,用于从数据列表文件中逐行读取图像数据并进行预处理,返回处理后的图像数据和标签。这是数据加载和预处理的一部分,用于将原始图像数据准备成模型可以处理的格式。
参数初始化
主要是从训练参数配置字典
train_parameters
中获取一些关键的路径和参数,以备后续在数据处理和模型训练中使用。以下是对这段代码的详细解释:
src_path = train_parameters['src_path']
: 这行代码从train_parameters
字典中获取了一个名为src_path
的路径参数,它表示原始数据集的压缩文件路径。该参数用于指定数据集的源文件。
target_path = train_parameters['target_path']
: 同样,这行代码从train_parameters
字典中获取了一个名为target_path
的路径参数,它表示数据集解压后的目标路径。数据集将被解压到这个目录下。
train_list_path = train_parameters['train_list_path']
: 这行代码获取了train_list_path
参数,它表示训练数据列表文件的路径。训练数据列表文件包含了训练数据的文件路径和对应的标签。
eval_list_path = train_parameters['eval_list_path']
: 类似地,这行代码获取了eval_list_path
参数,表示验证数据列表文件的路径。验证数据列表文件包含了验证数据的文件路径和标签。
batch_size = train_parameters['train_batch_size']
: 最后,这行代码获取了train_batch_size
参数,表示训练时每个批次的大小。它决定了在每次训练迭代中加载多少个样本进行模型训练。
src_path=train_parameters['src_path']
target_path=train_parameters['target_path']
train_list_path=train_parameters['train_list_path']
eval_list_path=train_parameters['eval_list_path']
batch_size=train_parameters['train_batch_size']
这些参数和路径信息是从配置文件中获取的,用于指定数据集的位置和训练时的批次大小等重要信息,以确保代码能够正确加载和处理数据。
解压原始数据到指定路径
unzip_data(src_path,target_path)
划分训练集与验证集,乱序,生成数据列表
主要用于生成数据列表文件之前,首先清空原有的
train.txt
和eval.txt
文件内容,然后调用get_data_list
函数生成新的数据列表。以下是对这段代码的详细解释:
with open(train_list_path, 'w') as f:
: 这行代码打开train_list_path
所表示的训练数据列表文件(通常是一个文本文件),以便后续的写操作。参数'w'
表示以写入(write)模式打开文件。
f.seek(0)
: 这是文件操作的一部分,它将文件指针(读写位置)移动到文件的开头,以确保后续写入的数据会从文件开头开始。
f.truncate()
: 这个方法用于截断文件,将文件大小截断为0字节,即清空文件内容。在这里,它用于清空train.txt
文件的内容。类似的操作也被执行在
eval_list_path
所表示的验证数据列表文件上,清空eval.txt
文件的内容。
get_data_list(target_path, train_list_path, eval_list_path)
: 这行代码调用了get_data_list
函数,传递了target_path
、train_list_path
和eval_list_path
作为参数,以生成新的数据列表文件。这个函数会在清空文件内容后生成数据列表。
#每次生成数据列表前,首先清空train.txt和eval.txt
with open(train_list_path, 'w') as f:
f.seek(0)
f.truncate()
with open(eval_list_path, 'w') as f:
f.seek(0)
f.truncate()
#生成数据列表
get_data_list(target_path,train_list_path,eval_list_path)
总之,代码的作用是清空训练数据列表文件(
train.txt
)和验证数据列表文件(eval.txt
)的内容,以准备生成新的数据列表。这样可以确保每次运行代码生成数据列表时,之前的内容不会影响新的数据列表。数据列表文件通常包含图像文件路径和对应的标签,用于在训练和验证过程中加载数据。
构造数据提供器
用于创建训练数据读取器(
train_reader
)和验证数据读取器(eval_reader
),这些读取器用于在训练和验证过程中加载数据。以下是对这段代码的详细解释:
train_reader = paddle.batch(data_reader(train_list_path), batch_size=batch_size, drop_last=True)
: 这行代码创建了一个训练数据读取器train_reader
。让我们分解它:
data_reader(train_list_path)
: 这部分调用data_reader
函数,该函数接受训练数据列表文件路径train_list_path
作为参数,并返回一个数据读取器函数。这个数据读取器函数用于逐批次加载训练数据。
batch_size=batch_size
: 参数batch_size
指定了每个批次中包含的样本数量。在训练中,通常将数据划分为小批次进行处理,这有助于加速训练和减小内存消耗。
drop_last=True
: 这个参数表示如果最后一个批次的数据样本数量不足一个完整的批次大小(小于batch_size
),是否丢弃该批次。通常设置为True
,以确保每个批次都包含相同数量的样本,避免不完整的批次对训练造成影响。类似地,下面的代码创建了验证数据读取器
eval_reader
,并使用了相同的参数设置。验证数据读取器用于加载验证数据,以便在训练过程中进行模型性能评估。
train_reader = paddle.batch(data_reader(train_list_path),
batch_size=batch_size,
drop_last=True)
eval_reader = paddle.batch(data_reader(eval_list_path),
batch_size=batch_size,
drop_last=True)
这两个数据读取器(
train_reader
和eval_reader
)将在训练和验证过程中用于逐批次加载数据,供模型训练和评估使用,有助于高效地训练和验证模型。
运行结果
/home/aistudio/data/dataset/Positive
和/home/aistudio/data/dataset/Negative
:这两行输出显示了数据集中的两个类别("Positive" 和 "Negative")的目录路径。这些目录包含了相应类别的图像数据。
{'input_size': [1, 32, 32], 'class_dim': 2, 'src_path': 'data/data33766/face_data.zip', 'target_path': '/home/aistudio/data/dataset', ...}
:这部分输出显示了训练参数配置字典train_parameters
的内容。其中包含了许多重要的训练参数和路径信息,如输入图片的大小、类别数、数据集路径、数据列表文件路径、标签字典等。
生成数据列表完成!
:这是一个提示信息,表示数据列表生成过程已经完成。在这个阶段,数据列表文件已经生成,其中包含了训练数据和验证数据的路径以及标签信息。
总之,这些输出信息表明数据处理和配置过程顺利完成,数据列表已生成并准备用于训练和验证模型。你可以继续进行模型的训练和评估等后续步骤。
三、网络结构
定义网络
定义一个卷积神经网络(CNN)模型,该模型是一个自定义的继承自
fluid.dygraph.Layer
的类MyCNN
。以下是对这段代码的详细解释:
class MyCNN(fluid.dygraph.Layer):
:这行代码定义了一个名为MyCNN
的类,继承了fluid.dygraph.Layer
,这意味着MyCNN
类可以用于创建一个支持动态图训练的神经网络模型。
def __init__(self):
:这是MyCNN
类的构造函数,用于初始化模型的各个组件。
super(MyCNN, self).__init__()
:这一行调用了父类fluid.dygraph.Layer
的构造函数,确保正确初始化神经网络模型。下面的代码块定义了神经网络的层次结构:
self.hidden1 = Conv2D(1, 32, 3, 1)
:定义了一个卷积层,其中输入通道数为1,输出通道数为32,卷积核大小为3x3,步长为1。self.hidden2 = Conv2D(32, 64, 3, 1)
:定义了另一个卷积层,输入通道数为32,输出通道数为64,卷积核大小为3x3,步长为1。self.hidden3 = Pool2D(pool_size=2, pool_type='max', pool_stride=2)
:定义了一个最大池化层,池化核大小为2x2,池化类型为最大池化,步长为2。self.hidden4 = Conv2D(64, 128, 3, 1)
:再次定义了一个卷积层,输入通道数为64,输出通道数为128,卷积核大小为3x3,步长为1。self.hidden5 = Linear(128*12*12, 2, act='softmax')
:定义了一个全连接层,输入大小为128x12x12,输出大小为2,激活函数为softmax。
def forward(self, input):
:这是MyCNN
类的前向传播函数,用于定义模型的前向计算过程。在这个函数中,按照网络结构将输入数据input
依次传递给不同的层,并最终返回模型的输出。
x = self.hidden1(input)
:将输入数据input
传递给第一个卷积层hidden1
,得到卷积结果x
。x = self.hidden2(x)
:将第一个卷积层的输出传递给第二个卷积层hidden2
,得到卷积结果x
。x = self.hidden3(x)
:将第二个卷积层的输出传递给池化层hidden3
,进行池化操作,得到池化结果x
。x = self.hidden4(x)
:将池化层的输出传递给第三个卷积层hidden4
,得到卷积结果x
。x = fluid.layers.reshape(x, shape=[-1, 128*12*12])
:将卷积层的输出展平成一维向量。y = self.hidden5(x)
:将展平后的一维向量传递给全连接层hidden5
,得到模型的输出y
,并应用softmax激活函数。
class MyCNN(fluid.dygraph.Layer):
def __init__(self):
super(MyCNN,self).__init__()
self.hidden1 = Conv2D(1,32,3,1)
self.hidden2 = Conv2D(32,64,3,1)
self.hidden3 = Pool2D(pool_size=2,pool_type='max',pool_stride=2)
self.hidden4 = Conv2D(64,128,3,1)
self.hidden5 = Linear(128*12*12,2,act='softmax')
def forward(self,input):
x = self.hidden1(input)
# print(x.shape)
x = self.hidden2(x)
# print(x.shape)
x = self.hidden3(x)
# print(x.shape)
x = self.hidden4(x)
# print(x.shape)
x = fluid.layers.reshape(x, shape=[-1, 128*12*12])
y = self.hidden5(x)
return y
这个神经网络模型包含了卷积层、池化层和全连接层。函数描述了信号的前向传播过程。
用于绘制训练过程中准确率和损失的变化曲线,以便对训练过程进行可视化分析。以下是对这段代码的详细解释:
Batch=0
:定义了一个变量Batch
并初始化为0,用于记录批次的数量。
Batchs=[]
:定义了一个空列表Batchs
,用于存储每个批次的数量,将在后续用于绘制横坐标。
all_train_accs=[]
:定义了一个空列表all_train_accs
,用于存储每个批次的训练准确率,将在后续用于绘制准确率变化曲线。
def draw_train_acc(iters, train_accs):
:定义了一个函数draw_train_acc
,用于绘制训练准确率变化曲线。函数接受两个参数,iters
表示横坐标数据,train_accs
表示纵坐标数据。
plt.title(title, fontsize=24)
:设置图表的标题。plt.xlabel("batch", fontsize=14)
:设置横坐标的标签为"batch",并指定字体大小。plt.ylabel("acc", fontsize=14)
:设置纵坐标的标签为"acc",并指定字体大小。plt.plot(iters, train_accs, color='green', label='training accs')
:绘制折线图,iters
为横坐标数据,train_accs
为纵坐标数据,指定颜色为绿色,添加标签为"training accs"。plt.legend()
:显示图例。plt.grid()
:显示网格线。plt.show()
:显示绘制好的图表。
all_train_loss=[]
:定义了一个空列表all_train_loss
,用于存储每个批次的训练损失,将在后续用于绘制损失变化曲线。
def draw_train_loss(iters, train_loss):
:定义了一个函数draw_train_loss
,用于绘制训练损失变化曲线。函数接受两个参数,iters
表示横坐标数据,train_loss
表示纵坐标数据。
plt.title(title, fontsize=24)
:设置图表的标题。plt.xlabel("batch", fontsize=14)
:设置横坐标的标签为"batch",并指定字体大小。plt.ylabel("loss", fontsize=14)
:设置纵坐标的标签为"loss",并指定字体大小。plt.plot(iters, train_loss, color='red', label='training loss')
:绘制折线图,iters
为横坐标数据,train_loss
为纵坐标数据,指定颜色为红色,添加标签为"training loss"。plt.legend()
:显示图例。plt.grid()
:显示网格线。plt.show()
:显示绘制好的图表。
Batch=0
Batchs=[]
all_train_accs=[]
#定义draw_train_acc,绘制准确率变化曲线
def draw_train_acc(iters, train_accs):
title="training accs"
plt.title(title, fontsize=24)
plt.xlabel("batch", fontsize=14)
plt.ylabel("acc", fontsize=14)
plt.plot(iters, train_accs, color='green', label='training accs')
plt.legend()
plt.grid()
plt.show()
#定义draw_train_loss,绘制损失变化曲线
all_train_loss=[]
def draw_train_loss(iters, train_loss):
title="training loss"
plt.title(title, fontsize=24)
plt.xlabel("batch", fontsize=14)
plt.ylabel("loss", fontsize=14)
plt.plot(iters, train_loss, color='red', label='training loss')
plt.legend()
plt.grid()
plt.show()
这些函数可以在训练过程中用于实时监测训练准确率和损失的变化趋势,以帮助评估模型的性能和调整训练策略。
用动态图进行训练
with fluid.dygraph.guard():
:这个语句块创建了一个动态图环境,它告诉飞桨,接下来的操作将在动态图模式下进行,这意味着我们可以使用Python编程语言的控制流(如循环和条件语句)来定义和修改模型,而不需要显式地构建静态计算图。
model = MyCNN()
:这里创建了一个名为MyCNN
的卷积神经网络模型的实例,MyCNN
是一个自定义的神经网络类,用于定义模型的结构。
model.train()
:这一行将模型设置为训练模式,这通常会影响一些层的行为,例如Dropout或Batch Normalization,在训练模式下这些层会表现不同于测试模式。
opt = fluid.optimizer.AdamOptimizer(learning_rate=train_parameters['learning_strategy']['lr'], parameter_list=model.parameters())
:这里创建了一个Adam优化器,用于更新模型的参数。learning_rate
参数是学习率,parameter_list
参数指定了要优化的模型参数列表,这里使用了model.parameters()
来获取模型的所有参数。
epochs_num = 3
:这里设置了训练的总轮数,即遍历整个训练数据集的次数。
for pass_num in range(epochs_num):
:这是外层循环,用于控制训练的轮数。
for batch_id, data in enumerate(train_reader()):
:这是内层循环,用于遍历训练数据集中的每个批次。
images = np.array([x[0].reshape(1, 32, 32) for x in data], np.float32)
:将数据集中的图片数据转换为NumPy数组,这里是为了适应模型输入的大小。
labels = np.array([x[1] for x in data]).astype('int64')
:将数据集中的标签数据转换为NumPy数组,并指定数据类型为整数。
image = fluid.dygraph.to_variable(images)
和label = fluid.dygraph.to_variable(labels)
:将NumPy数组转换为PaddlePaddle的Variable
,这是动态图中的Tensor类型,以便进行计算。
predict = model(image)
:使用模型进行前向传播,得到模型的预测结果。
loss = fluid.layers.cross_entropy(predict, label)
:计算交叉熵损失,用于度量模型的预测与实际标签之间的差距。
avg_loss = fluid.layers.mean(loss)
:计算平均损失,用于在训练过程中监控损失的变化。
acc = fluid.layers.accuracy(predict, label)
:计算准确率,用于评估模型的性能。
if batch_id != 0 and batch_id % 20 == 0:
:当批次数达到20的倍数时,执行下面的代码,主要是为了每隔一段时间输出训练过程中的信息。
avg_loss.backward()
:计算损失的梯度,用于反向传播更新模型参数。
opt.minimize(avg_loss)
:通过优化器更新模型参数,以减小损失。
model.clear_gradients()
:清除模型参数的梯度,为下一批次的训练做准备。
fluid.save_dygraph(model.state_dict(), 'MyCNN')
:保存训练好的模型参数,以便后续的测试或推理使用。最后,使用
matplotlib
库中的draw_train_acc
和draw_train_loss
函数,绘制训练过程中的准确率和损失变化曲线,用于可视化训练过程中的性能变化。
with fluid.dygraph.guard():
model = MyCNN()
model.train() #训练模式
opt=fluid.optimizer.AdamOptimizer(learning_rate=train_parameters['learning_strategy']['lr'], parameter_list=model.parameters())#优化器选用SGD随机梯度下降,学习率为0.001.
# epochs_num = train_parameters['num_epochs'] #迭代次数
epochs_num = 3 #迭代次数
for pass_num in range(epochs_num):
for batch_id,data in enumerate(train_reader()):
images=np.array([x[0].reshape(1,32,32) for x in data], np.float32)
labels = np.array([x[1] for x in data]).astype('int64')
labels = labels[:, np.newaxis]
image=fluid.dygraph.to_variable(images)
label=fluid.dygraph.to_variable(labels)
predict=model(image)
loss=fluid.layers.cross_entropy(predict,label)
avg_loss=fluid.layers.mean(loss)#获取loss值
acc=fluid.layers.accuracy(predict,label)#计算精度
if batch_id!=0 and batch_id%20==0:
Batch = Batch+20
Batchs.append(Batch)
all_train_loss.append(avg_loss.numpy()[0])
all_train_accs.append(acc.numpy()[0])
print("train_pass:{},batch_id:{},train_loss:{},train_acc:{}".format(pass_num,batch_id,avg_loss.numpy(),acc.numpy()))
avg_loss.backward()
opt.minimize(avg_loss)
model.clear_gradients()
fluid.save_dygraph(model.state_dict(),'MyCNN')#保存模型
draw_train_acc(Batchs,all_train_accs)
draw_train_loss(Batchs,all_train_loss)
总结:代码演示了使用动态图模式训练卷积神经网络模型的完整流程,包括数据的处理、模型的定义和训练过程。模型在每个批次中计算损失和准确率,并使用优化器更新参数,最终保存训练好的模型。
可以看到,随着训练的进行,训练损失逐渐减小,训练准确率逐渐提高,这表明模型正在学习并逐渐提高性能。
最后,通过
matplotlib
库绘制了训练准确率和损失的变化曲线。这些曲线用于可视化训练过程中性能的变化,但由于具体的绘图部分未在提供的代码中显示,所以无法详细解释绘图的效果。不过,通常来说,这些曲线应该显示出准确率随着训练次数的增加而提高,损失随着训练次数的增加而降低。
四、损失函数
模型的损失函数采用了交叉熵损失函数,用于衡量模型的输出与实际标签之间的差异。
五、模型评估
with fluid.dygraph.guard():
:这个语句块创建了一个动态图环境,告诉飞桨接下来的操作将在动态图模式下进行。
accs = []
:创建一个空列表,用于存储每个批次的准确率。
model_dict, _ = fluid.load_dygraph('MyCNN')
:加载之前保存的训练好的模型参数。fluid.load_dygraph
函数用于加载模型参数,'MyCNN' 是模型参数文件的名称。
model = MyCNN()
:创建一个新的模型实例,这里使用与训练时相同的模型结构。
model.load_dict(model_dict)
:将加载的模型参数设置到新的模型实例中,这样模型就具有了训练好的参数。
model.eval()
:将模型设置为评估模式,这会关闭一些训练模式下的特性,例如Dropout和Batch Normalization在评估模式下会表现不同。
for batch_id, data in enumerate(eval_reader()):
:遍历测试数据集中的每个批次。
images = np.array([x[0].reshape(1, 32, 32) for x in data], np.float32)
:将测试数据集中的图片数据转换为NumPy数组,这里也是为了适应模型输入的大小。
labels = np.array([x[1] for x in data]).astype('int64')
:将测试数据集中的标签数据转换为NumPy数组,指定数据类型为整数。
image = fluid.dygraph.to_variable(images)
和label = fluid.dygraph.to_variable(labels)
:将NumPy数组转换为PaddlePaddle的Variable
,以便进行模型评估。
predict = model(image)
:使用训练好的模型进行前向传播,得到模型的预测结果。
acc = fluid.layers.accuracy(predict, label)
:计算每个批次的准确率,用于评估模型的性能。
accs.append(acc.numpy()[0])
:将每个批次的准确率添加到accs
列表中。
avg_acc = np.mean(accs)
:计算所有批次的平均准确率,这是整个测试数据集上的准确率。
print(avg_acc)
:输出模型在测试数据集上的平均准确率。
with fluid.dygraph.guard():
accs = []
model_dict, _ = fluid.load_dygraph('MyCNN')
model = MyCNN()
model.load_dict(model_dict) #加载模型参数
model.eval() #训练模式
for batch_id,data in enumerate(eval_reader()):#测试集
images=np.array([x[0].reshape(1,32,32) for x in data],np.float32)
labels = np.array([x[1] for x in data]).astype('int64')
labels = labels[:, np.newaxis]
image=fluid.dygraph.to_variable(images)
label=fluid.dygraph.to_variable(labels)
predict=model(image)
acc=fluid.layers.accuracy(predict,label)
accs.append(acc.numpy()[0])
avg_acc = np.mean(accs)
print(avg_acc)
这里加载了训练好的模型参数,然后在测试数据集上运行该模型以评估其性能。最后,输出平均准确率,用于评估模型在测试数据上的表现。
模型评估的输出结果为 0.90384614,这表示在测试数据集上,该模型的平均准确率为约 90.38%。这意味着模型在测试数据上的分类准确率为90.38%,模型在这个项目上表现良好。
读取预测图像,进行预处理
用于读取和预处理图像的函数。它执行以下步骤:
使用OpenCV (
cv2
)库读取指定路径 (path
) 的图像文件。将读取的彩色图像转换为灰度图像,这是通过
cv2.cvtColor()
函数使用cv2.COLOR_BGR2GRAY
进行灰度化处理。调整图像大小为 32x32 像素。这是通过
cv2.resize()
函数实现的。将图像数据转换为浮点数类型,并对像素值进行归一化处理,将像素值缩放到 [0, 1] 的范围。
最终,将处理后的图像数据以形状为
(1, 32, 32)
的NumPy数组返回,以便供模型进行预测。
def load_image(path):
img = cv2.imread(path)
gray_image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray_image = cv2.resize(gray_image,(32,32))
gray_image = gray_image.reshape(1,32,32)
gray_image = np.array(gray_image).astype('float32')
gray_image = gray_image/255.0
return gray_image
infer_path = 'work/face_img.png'
构建预测动态图过程
用于使用训练好的模型进行图像预测,以下是代码的主要步骤:
通过
fluid.dygraph.guard()
创建动态图环境。获取标签字典
LABEL
,该字典存储了训练数据集中的类别标签信息。实例化用于图像分类的模型
model
,这里使用了之前定义的MyCNN
模型。载入预训练好的模型参数,通过
fluid.load_dygraph('MyCNN')
从文件中加载保存的模型参数。将模型设置为评估模式,通过
model.eval()
可以确保模型在评估模式下运行。使用
load_image(infer_path)
函数加载要进行预测的图像,该函数将图像读取、灰度化、调整大小和归一化处理,并返回适合模型输入的NumPy数组。将处理后的图像数据转换为合适的形状,
infer_img
的形状为(1, 1, 32, 32)
。将处理后的图像数据转换为动态图变量,
infer_img
转化为fluid.dygraph.to_variable(infer_img)
。使用训练好的模型进行预测,通过
model(infer_img)
得到预测结果result
。打印预测结果
result
,这通常是模型的输出,可能是各个类别的概率值。使用PIL库中的
Image.open(infer_path)
打开要进行预测的图像并显示它。通过
LABEL[str(np.argmax(result.numpy()))]
取出预测结果的最大值对应的标签,即预测的类别,这是根据模型输出的概率值中概率最高的类别。
with fluid.dygraph.guard():
LABEL = train_parameters['label_dict']
print(train_parameters['label_dict'])
model=MyCNN()#模型实例化
model_dict,_=fluid.load_dygraph('MyCNN')
model.load_dict(model_dict)#加载模型参数
model.eval()#评估模式
infer_img = load_image(infer_path)
infer_img=np.array(infer_img).astype('float32')
infer_img=infer_img[np.newaxis, :]
print(infer_img.shape)
infer_img = fluid.dygraph.to_variable(infer_img)
result=model(infer_img)
print(result.numpy())
display(Image.open(infer_path))
print(LABEL[str(np.argmax(result.numpy()))])
总之,代码加载了预训练的CNN模型,使用该模型对给定的图像进行分类预测,并输出预测结果,同时还显示了输入的图像和预测的类别。
打印了标签字典
LABEL
,它包含了类别标签的映射关系,其中'0'
对应 'Positive' 类别,'1'
对应 'Negative' 类别。接下来,输出了经处理后的预测图像的形状
(1, 1, 32, 32)
,表示这是一个单通道的 32x32 图像,该形状与模型的输入匹配。模型的预测结果
result
是一个形状为(1, 2)
的NumPy数组,其中包含两个值。这些值代表了模型对输入图像为两个类别 'Positive' 和 'Negative' 的预测概率。在这个例子中,第一个值0.8130825
是 'Positive' 的概率,第二个值0.18691747
是 'Negative' 的概率。比例差不多为8:2,模型认为这个图像属于 'Positive' 类别的概率更高。最后,代码根据预测结果中概率最高的类别,即 'Positive',打印了对应的类别标签,表示这个图像被分类为 'Positive' 类别。
综上所述,代码输出了图像的分类预测结果,模型认为给定图像属于 'Positive' 类别的概率较高。
六、经典算法
卷积神经网络(CNN)
CNN是图像分类任务中的重要算法,具有良好的特征提取和分类能力。它适用于图像识别、目标检测等任务。优点包括对图像特征的自动学习和处理,但缺点可能包括需要大量数据和计算资源。
每种算法都有其适用的情况和局限性,具体选择取决于任务需求和数据特点。
七、项目运行
运行录屏
八、总结
介绍了基于PaddlePaddle的卷积神经网络(CNN)模型的图像分类任务,并提供了有关数据集、数据处理、网络结构、损失函数、超参数调节、模型评估等方面的详细信息。同时,还简要介绍了深度学习研究领域的三种经典算法以及它们的优缺点。