【AlexNet】数字手势识别(动态图版)

文章目录


问题导入

图像分类是根据图像的语义信息将不同类别图像区分开来,是计算机视觉中重要的基本问题。本实验将使用经典神经网络模型AlexNet预测手势图片所表示的数字。


一、基本概念

1. 动态图DyGraph

(1)PaddlePaddle动态图:

PaddlePaddle的 动态图DyGraph模式 是一个更加灵活易用的模式,是一种动态的图执行机制,可以立即执行结果,无需构建整个图。PaddlePaddle DyGraph可以提供:

  • 更加灵活便捷的代码组织结构:使用python的执行控制流程和面向对象的模型设计
  • 更加便捷的调试功能:直接使用python的打印方法即时打印所需要的结果,从而检查正在运行的模型结果便于测试更改
  • 和静态执行图通用的模型代码:同样的模型代码可以使用更加便捷的DyGraph调试,执行,同时也支持使用原有的静态图模式执行

(2)动态图机制的优点:

动态图机制不同于以往的静态图,无需构建整个图就可以立即执行结果。这使得我们在编写代码以及调试代码时更加直观、方便,我们无需编写静态图框架,这省去了我们大量的时间成本。利用动态图机制,我们能够更加快捷、直观地构建我们的深度学习网络。

本项目采用的是动态图机制,动态图机制的使用方法请参考:

2. AlexNet模型

本实验使用的模型是经典卷积神经网络模型——AlexNet。
AlexNet是2012年ImageNet竞赛冠军获得者Hinton和他的学生Alex Krizhevsky设计的,也是在那年之后,更多的更深的神经网络被提出,卷积神经网络乃至深度学习重新引起了广泛的关注。

【AlexNet】数字手势识别(动态图版)【AlexNet】数字手势识别(动态图版)

二、实验数据集

本次实验使用的数据集是由土耳其一所中学制作,数据集由Main文件夹中的训练/测试数据集和Infer文件夹中的预测数据集组成,包含0-9共10种数字的手势图片,实验图片都是大小为100 * 100像素、RGB格式的图像。

这是数据集的下载链接:手势识别数据集 - Baidu AI Studio

三、实验步骤

【AlexNet】数字手势识别(动态图版)

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

【AlexNet】数字手势识别(动态图版)

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))

模型预测结果如下:
【AlexNet】数字手势识别(动态图版)

该图片的真实标签为7,模型预测结果为7

写在最后

  • 如果您发现项目存在问题,或者如果您有更好的建议,欢迎在下方评论区中留言讨论~
  • 这是本项目的链接:实验项目 - Baidu AI Studio,点击fork可直接在AI Studio运行~
  • 这是我的个人主页:个人主页 - Baidu AI Studio,来AI Studio互粉吧,等你哦~
上一篇:AI框架类FAQ


下一篇:猫狗分类