文章目录
问题导入
图像分类是根据图像的语义信息将不同类别图像区分开来,是计算机视觉中重要的基本问题。本实验将使用经典神经网络模型AlexNet预测手势图片所表示的数字。
一、基本概念
1. 动态图DyGraph
(1)PaddlePaddle动态图:
PaddlePaddle的 动态图DyGraph模式 是一个更加灵活易用的模式,是一种动态的图执行机制,可以立即执行结果,无需构建整个图。PaddlePaddle DyGraph可以提供:
- 更加灵活便捷的代码组织结构:使用python的执行控制流程和面向对象的模型设计
- 更加便捷的调试功能:直接使用python的打印方法即时打印所需要的结果,从而检查正在运行的模型结果便于测试更改
- 和静态执行图通用的模型代码:同样的模型代码可以使用更加便捷的DyGraph调试,执行,同时也支持使用原有的静态图模式执行
(2)动态图机制的优点:
动态图机制不同于以往的静态图,无需构建整个图就可以立即执行结果。这使得我们在编写代码以及调试代码时更加直观、方便,我们无需编写静态图框架,这省去了我们大量的时间成本。利用动态图机制,我们能够更加快捷、直观地构建我们的深度学习网络。
本项目采用的是动态图机制,动态图机制的使用方法请参考:
2. AlexNet模型
本实验使用的模型是经典卷积神经网络模型——AlexNet。
AlexNet是2012年ImageNet竞赛冠军获得者Hinton和他的学生Alex Krizhevsky设计的,也是在那年之后,更多的更深的神经网络被提出,卷积神经网络乃至深度学习重新引起了广泛的关注。
二、实验数据集
本次实验使用的数据集是由土耳其一所中学制作,数据集由Main
文件夹中的训练/测试数据集和Infer
文件夹中的预测数据集组成,包含0-9
共10种数字的手势图片,实验图片都是大小为100 * 100像素、RGB格式的图像。
这是数据集的下载链接:手势识别数据集 - Baidu AI Studio
三、实验步骤
0. 导入模块
import os
import zipfile
import random
import numpy as np
from PIL import Image
from multiprocessing import cpu_count
import matplotlib.pyplot as plt
import paddle
from paddle import fluid
from paddle.fluid.dygraph import Conv2D, Pool2D, Linear, Dropout
1. 数据准备
-
数据预处理
训练/测试数据集包含0-9共九种数字手势,总共2073张图片,我们将取其中的90%作为训练集,剩下的10%作为测试集。数据预处理的流程如下:
(1)由于数据集中的数据是以压缩包的形式存放的,因此我们需要先解压数据压缩包。
(2)接着,我们需要按1:9比例划分测试集和训练集,分别生成包含数据地址的列表。
(3)然后,我们需要分别构建用于训练和测试的数据提供器,其中训练数据提供器是乱序、按批次提供数据的。
def unzip_data(target_path, path1, path2): # 将原数据集解压至指定路径
SRC_PATH = "./data/data51418/Gestures.zip"
if not os.path.isdir(path1) or not os.path.isdir(path2):
z = zipfile.ZipFile(SRC_PATH, "r") # 打开压缩文件,创建zip对象
z.extractall(path=target_path) # 解压zip文件至target_path
z.close()
print("数据集解压完成!")
def data_reader(data_list): # 批量读取图片
def data_mapper(sample): # 读取图片并对图片进行归一化处理
img, label = sample
img = Image.open(img).resize((224, 224), Image.ANTIALIAS) # 缩放为224*224的高质量图像
img = np.array(img).astype("float32") # 把图像变成一个numpy数组以匹配数据馈送格式
img = img.transpose((2, 0, 1)) # 将图像矩阵由“rgb,rgb,rbg...”转置为“rr...,gg...,bb...”
img = img / 255.0 # 将图像数据归一化
return img, label
def reader():
for data in data_list:
img_path, label = data[0], data[1]
yield img_path, int(label) # 返回图像地址和标签(for循环结束前程序不会停止)
return paddle.reader.xmap_readers(data_mapper, reader, cpu_count(), 512)
# ------ 1.1.划分数据集 ------
BATCH_SIZE = 32 # 数据批次大小
CLASS_DIM = 10 # 手势的种类数
DATA_PATH = './data/Main' # 训练测试集路径
INFER_PATH = './data/Infer' # 预测数据集路径
train_list, test_list = [], [] # 暂存训练集路径和测试集路径
unzip_data('./data', DATA_PATH, INFER_PATH) # 解压数据集
file_folders = os.listdir(DATA_PATH) # DATA_PATH路径下的文件夹
for folder in file_folders:
images = os.listdir(os.path.join(DATA_PATH, folder))
for idx, img in enumerate(images):
img_path = os.path.join(DATA_PATH, folder, img)
value = [img_path, folder] # 记录图片链接及其标签代号
if idx % 10 == 0:
test_list.append(value)
else:
train_list.append(value)
random.shuffle(train_list) # 打乱训练集数据
random.shuffle(test_list) # 打乱测试集数据
# ------ 1.2.训练数据集准备 ------
BUF_SIZE, BATCH_SIZE = 512, 32
train_reader = paddle.batch(
paddle.reader.shuffle(
data_reader(train_list), buf_size=BUF_SIZE
), # 每次缓存BUF_SIZE个训练数据项,并打乱
batch_size=BATCH_SIZE
) # 按批次读取乱序后的训练数据,批次大小为BATCH_SIZE
# ------ 1.3.测试数据集准备 ------
test_reader = paddle.batch(
data_reader(test_list), batch_size=BATCH_SIZE
) # 按批次读取测试数据,批次大小为BATCH_SIZE
2. 网络配置
class AlexNet(fluid.dygraph.Layer): # AlexNet神经网络类
def __init__(self, class_dim):
super(AlexNet, self).__init__()
''' 函数参数含义:
Conv2D(通道数,卷积核数,卷积核大小,卷积步长,padding填充长度,act激活函数)
Pool2D(池化核大小,池化步长,池化类型)
Linear(输入大小,输出大小,act激活函数)
Dropout(dropout发生的概率)
'''
self.conv1 = Conv2D(3, 96, 11, stride=4, padding=2, act='relu')
self.pool1 = Pool2D(pool_size=3, pool_stride=2, pool_type='max')
self.conv2 = Conv2D(96, 256, 5, stride=1, padding=2, act='relu')
self.pool2 = Pool2D(pool_size=3, pool_stride=2, pool_type='max')
self.conv3 = Conv2D(256, 384, 3, stride=1, padding=1, act='relu')
self.conv4 = Conv2D(384, 384, 3, stride=1, padding=1, act='relu')
self.conv5 = Conv2D(384, 256, 3, stride=1, padding=1, act='relu')
self.pool3 = Pool2D(pool_size=3, pool_stride=2, pool_type='max')
self.fc1 = Linear(256 * 6 * 6, 4096, act='relu')
self.drop1 = Dropout(p=0.5)
self.fc2 = Linear(4096, 4096, act='relu')
self.drop2 = Dropout(p=0.5)
self.fc3 = Linear(4096, class_dim, act='softmax')
def forward(self, x): # 前向传播参数,连接各层组成神经网络
x = self.conv1(x)
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.pool3(x)
x = fluid.layers.reshape(x, [-1, 256*6*6])
x = self.fc1(x)
x = self.drop1(x)
x = self.fc2(x)
x = self.drop2(x)
y = self.fc3(x)
return y
3. 模型训练
EPOCH_NUM, train_iter = 9, 0
train_iters, train_costs, train_accs = [], [], []
def draw_train_progress(iters, loss, accs):
def draw_training_loss(): # 绘制训练误差图像
plt.subplot(121)
plt.title("Training Loss", fontsize=25)
plt.xlabel("iter", fontsize=18)
plt.ylabel("loss", fontsize=18)
plt.plot(iters, loss, color="r")
plt.grid()
def draw_training_acc(): # 绘制训练准确率图像
plt.subplot(122)
plt.title("Training Accuracy", fontsize=25)
plt.xlabel("iter", fontsize=18)
plt.ylabel("accuracy", fontsize=18)
plt.plot(iters, accs, color="g")
plt.grid()
plt.figure(figsize=[15, 5])
draw_training_loss()
draw_training_acc()
plt.show()
with fluid.dygraph.guard(): # 用动态图进行训练
model = AlexNet(CLASS_DIM) # 模型实例化
model.train() # 开启训练模式
opt = fluid.optimizer.Adam(
learning_rate=fluid.dygraph.NaturalExpDecay(
learning_rate=1e-4, # 初始学习率
decay_steps=128, # 学习率衰减步长
decay_rate=0.3 # 学习率衰减率
), # 在学习率上使用自然指数衰减
parameter_list=model.parameters()
) # 定义优化器,采用Adam优化算法
for pass_id in range(EPOCH_NUM): # 训练EPOCH_NUM轮
for batch_id, data in enumerate(train_reader()):
# 将数据集中的图像、标签数据转化为numpy.array格式的数据:
image = np.array([x[0].reshape(3, 224, 224) for x in data], np.float32)
label = np.array([x[1] for x in data]).astype("int64")[:, np.newaxis]
# 将数据转化为fluid.dygraph能够接受的Variable类型的对象:
image = fluid.dygraph.to_variable(image)
label = fluid.dygraph.to_variable(label)
predict = model(image) # 训练模型
train_loss = fluid.layers.cross_entropy(predict, label) # 计算交叉熵
avg_loss = fluid.layers.mean(train_loss) # 求平均损失值
train_acc = fluid.layers.accuracy(predict, label) # 计算准确率
train_iter += BATCH_SIZE
train_iters.append(train_iter) # 迭代次数
train_costs.append(avg_loss.numpy()[0]) # 训练误差
train_accs.append(train_acc.numpy()[0]) # 训练准确率
if batch_id != 0 and batch_id % 50 == 0: # 输出训练信息
print("Pass:%2d;Batch:%2d;Loss:%.6f;Accuracy:%.4f" %
(pass_id, batch_id, train_costs[-1], train_accs[-1]))
avg_loss.backward() # 进行反向传播操作
opt.minimize(avg_loss) # 调用优化器中的minimize()方法更新参数
model.clear_gradients() # 每轮参数更新后需重置梯度,以保证下轮的正确性
draw_train_progress(train_iters, train_costs, train_accs) # 绘制训练过程
fluid.save_dygraph(model.state_dict(), "AlexNet") # 保存训练好的模型
模型训练结果如下:
Pass: 0;Batch:50;Loss:2.262135;Accuracy:0.1875
Pass: 1;Batch:50;Loss:0.803589;Accuracy:0.7188
Pass: 2;Batch:50;Loss:0.597640;Accuracy:0.7812
Pass: 3;Batch:50;Loss:0.457550;Accuracy:0.8438
Pass: 4;Batch:50;Loss:0.340216;Accuracy:0.8750
Pass: 5;Batch:50;Loss:0.106287;Accuracy:0.9688
Pass: 6;Batch:50;Loss:0.100151;Accuracy:0.9688
Pass: 7;Batch:50;Loss:0.022734;Accuracy:1.0000
Pass: 8;Batch:50;Loss:0.009387;Accuracy:1.0000
4. 模型评估
with fluid.dygraph.guard(): # 用动态图进行模型评估
test_costs, test_accs = [], []
model = AlexNet(CLASS_DIM) # 模型实例化
model_dict, _ = fluid.load_dygraph("AlexNet") # 加载模型参数
model.load_dict(model_dict) # 将参数载入到新模型中
model.eval() # 开启评估模式
for batch_id, data in enumerate(test_reader()):
# 将数据集中的图像、标签数据转化为特定numpy数组格式的数据:
image = np.array([x[0].reshape(3, 224, 224) for x in data], np.float32)
label = np.array([x[1] for x in data]).astype("int64")[:, np.newaxis]
# 将数据转化为fluid.dygraph能够接受的Variable类型的对象:
image = fluid.dygraph.to_variable(image)
label = fluid.dygraph.to_variable(label)
predict = model(image) # 模型测试
test_loss = fluid.layers.cross_entropy(predict, label) # 计算交叉熵
avg_loss = fluid.layers.mean(test_loss) # 求平均损失值
test_costs.append(avg_loss.numpy()[0])
test_acc = fluid.layers.accuracy(predict, label) # 计算准确率
test_accs.append(test_acc.numpy()[0])
test_loss = np.mean(test_costs) # 计算平均损失值
test_acc = np.mean(test_accs) # 计算平均准确率
print("Eval \t Avg_Loss:%.5f;Accuracy:%.5f" % (test_loss, test_acc))
模型评估结果如下:
Eval Avg_Loss:0.14303;Accuracy:0.96627
5. 模型预测
def load_image(path): # 图片预处理
# (1) 打开并展示图像:
img = Image.open(path) # 打开图像
display(img) # 显示图像
# (2) 格式化图像:
img = img.resize((224, 224), Image.ANTIALIAS)
img = np.array(img).astype("float32") # 把图像变成一个numpy数组以匹配数据馈送格式
img = img.transpose((2, 0, 1))
img = img / 255.0 # 将数据进行归一化处理
return img
with fluid.dygraph.guard(): # 用动态图进行模型预测
model = AlexNet(CLASS_DIM) # 模型实例化
model_dict, _ = fluid.load_dygraph("AlexNet") # 加载模型参数
model.load_dict(model_dict) # 将参数载入到新模型中
model.eval() # 开启评估模式
truth_lab = random.randint(0, 9) # 预测图片的真实标签
infer_path = INFER_PATH + '/infer_%d.JPG' # 预测图片的路径
infer_img = load_image(infer_path % truth_lab) # 获取预测图片
infer_img = np.array(infer_img).astype("float32")
infer_img = infer_img[np.newaxis, :, :, :]
# 将数据转化为fluid.dygraph能够接受的Variable类型的对象:
infer_img = fluid.dygraph.to_variable(infer_img)
result = model(infer_img) # 模型预测,返回长度为10的概率数组
infer_lab = np.argmax(result.numpy()) # 返回数组result中的最大值的索引值
print("\n该图片的真实标签为%d,模型预测结果为%d" % (truth_lab, infer_lab))
模型预测结果如下:
该图片的真实标签为7,模型预测结果为7
写在最后
- 如果您发现项目存在问题,或者如果您有更好的建议,欢迎在下方评论区中留言讨论~
- 这是本项目的链接:实验项目 - Baidu AI Studio,点击
fork
可直接在AI Studio运行~- 这是我的个人主页:个人主页 - Baidu AI Studio,来AI Studio互粉吧,等你哦~