当前位置: 首页 > article >正文

python卷积神经网络人脸识别示例实现详解

目录

一、准备

1)使用pytorch

2)安装pytorch

3)准备训练和测试资源

二、卷积神经网络的基本结构

三、代码实现

1)导入库

2)数据预处理

3)加载数据

4)构建一个卷积神经网络

 5)模型训练

6)模型测试

四、测试结果 

五、模型导出

1)保存模型的状态参数

 2)保存完整模型

 六、总结


一、准备

1)使用pytorch

为什么建议使用pytorch来构建卷积神经网络呢?因为pytorch是基于python开发的一个神经网络工具包,它已经实现了激活函数定义、权重矩阵定义、卷积计算、正向传播、反向传播、权重矩阵更新等神经网络的基本操作,而不需要我们再去编写代码实现这些功能,只需调用相应的函数就可以搭建好我们所需的网络结构。pytorch极大方便了我们构建神经网络,加快了神经网络开发速度,我们只需要关注网络的结构层次,而不用关心所建立的网络具体训练和预测过程。

2)安装pytorch

pytorch有CPU版和GPU版,GPU版需要使用到英伟达的显卡来加快网络速度,安装过程也稍显复杂。本文重点是对python卷积神经网络示例解析,因此安装CPU版。在pycharm开发工具的终端中直接执行命令:pip install torch torchvision torchaudio,即可完成pytorch的CPU版本安装。

3)准备训练和测试资源

因为使用pytorch开发卷积神经网络实现人脸识别,pytorch对数据存放有一定要求,因此需要将相关资源放在特定的目录结构下。训练目录和测试目录结构如下图所示,在文件夹中放入相应的图片资源即可。文件路径可以自行定义,但是同一个人的照片必须放在同一个文件夹下,pytorch根据该文件夹的名称自动将相应的图片资源归于一类。

二、卷积神经网络的基本结构

一个简单的卷积神经网络包括输入层、卷积层、池化层、扁平化层和全连接层。网络的复杂度体现在卷积、池化层的大小和数量上。卷积神经网络主要用于处理有大量数据输入的情景,如图像识别是最好的例子,可以极大减少运算量。下面以一个人脸识别的实际例子,详细讲解卷积神经网络的搭建流程。

三、代码实现

1)导入库

使用PyCharm工具新建一个.py文件,在文件中导入下面需要使用到的相关库。在导入下面的库前,需要确保已经安装好了pytorch相关依赖包。

import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader

2)数据预处理

以下定义了一组针对图片进行预处理的方法,包括3个。第一个是将图像调整为100*100像素大小,这样我们就可以输入任何大小图片,并能保证数据输入到网络中的大小是一致的。第二个是将图像数据转换为张量格式,这时pytorch自定义的一种数据格式,可以简单理解为类似于多维向量。第三个是对图像进行归一化处理,其目的是使数据的均值为 0,标准差为 1。这样做可以加速模型的训练过程,提高模型的稳定性和收敛速度。函数参数如下:

  • mean:一个长度为 3 的列表,分别代表图像三个通道(RGB)的均值。这里 [0.485, 0.456, 0.406] 是在 ImageNet 数据集上统计得到的三个通道的均值。

  • std:一个长度为 3 的列表,分别代表图像三个通道(RGB)的标准差。[0.229, 0.224, 0.225] 是在 ImageNet 数据集上统计得到的三个通道的标准差。

 参数的取值可以直接使用上面提供的经验值。对于输入图像的每个像素RGB值 ,归一化后的像素RGB值 按照以下公式计算:

其中,mean 和 std 分别是对应通道的均值和标准差。

transform = transforms.Compose([
    transforms.Resize((100, 100)),  # 调整图像大小
    transforms.ToTensor(),  # 将图像转换为张量
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])  # 归一化
])

3)加载数据

使用dataset.ImageFolder()加载需要的训练图像和测试凸显数据。这里的数据路径需要注意,并没有到具体的文件名。比如在D:/zhaopian/train目录下,程序会自动读取该目录下的子文件夹,并将子文件夹中的图像数据归集到以该子文件夹命名的类别中。参数transform=transform就是调用上述第二步定义的数据预处理方法集,在读取每一张图像数据时将自动依次执行上述3个方法。并将最终转换好的数据返回。

创建数据加载器是为了便于网络训练时分批次加载数据,避免一次加载所有图像数据导致内存不足。

  • batch_size=32:指定每个批次中包含的数据样本数量。这里设置为 32,表示每次从 train_dataset 中取出 32 个样本组成一个批次进行训练。batch_size 的选择会影响模型的训练速度和性能,较大的 batch_size 可以加快训练速度,但可能会导致内存不足;较小的 batch_size 可以增加模型的泛化能力,但训练速度会变慢。

  • shuffle=True:一个布尔值,用于指定是否在每个训练周期(epoch)开始时打乱数据集的顺序。设置为 True 可以增加数据的随机性,避免模型学习到数据的特定顺序,有助于提高模型的泛化能力。

train_dataset = datasets.ImageFolder(root='D:/zhaopian/train', transform=transform)
test_dataset = datasets.ImageFolder(root='D:/zhaopian/test', transform=transform)
all_samples = test_dataset.samples
print("数据集中的类别:", train_dataset.classes)

# 创建数据加载器
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)

4)构建一个卷积神经网络

自定义一个卷积神经网络类FaceCNN,继承自nn.Module。该网络包含两个卷积层,两个池化层,两个全连接层。在卷积层和全连接层之间均使用了相同的激活函数ReLU:

函数 nn.Conv2d(3, 16, kernel_size=3, padding=1) 的作用是定义一个二维卷积层,参数3表示输入的数据是3个通道,这里对应的是彩色图像的RGB三组数据,即每张图会有3个100*100的矩阵输入到该卷积层中。参数16表示该卷积层有16个卷积核,每个核的大小均是3*3(参数kernel_size=3确定的)的矩阵,这16个3*3的卷积核初始值是随机的,它的值是通过模型训练最终确定的,也就是神经网络中的权重矩阵。定义16个卷积核意味着什么呢?当一幅彩色图像输入该卷积层时,该图像有3组矩阵,每组矩阵与一个卷积核进行卷积,得到3个卷积后的矩阵,然后在将这3个卷积后的矩阵对应位置取平均值,得到一个最终的卷积均值矩阵。16个卷积核依次执行上述操作,则经过该卷积层后,会得到16个卷积均值矩阵。参数padding=1表示对输入数据进行扩充,1表示在数据四周加一行,填充的目的通常是为了保持输入和输出特征图的尺寸一致。那么,经过该卷积层后,最终会得到16个100*100的卷积均值矩阵。

函数nn.MaxPool2d(2)的作用是定义一个2*2的二维池化层,该池化层输出池中的最大值。在最大池化操作中,池化窗口会在输入特征图上滑动,对每个窗口内的元素进行操作。这里设置为 2,表示使用一个 2x2 的池化窗口。当池化窗口在特征图上滑动时,会选取每个 2x2 区域内的最大值作为该区域的输出值。此处需要注意的是,经过2*2的二维池化层后,输出的矩阵变为了50*50,因为池化层的步幅等于它的大小,这与卷积不同,卷积默认步幅为1。

函数nn.Conv2d(16, 32, kernel_size=3, padding=1) 用于定义第二个二维卷积层。在该卷积层中,前面两个参数分别是16和32,16对应的是前面的16个卷积核,因为一幅图像经过第一层卷积后会输出16组数据,所以此处第二层卷积时,输入的数据就是16组。32表示第二层卷积后输出32组50*50的矩阵。

函数nn.Linear(32 * 25 * 25, 128)的作用是定义一个全连接层,全连接层的每个神经元都与前一层的所有神经元相连接,通过对输入进行线性变换(加权求和)并加上偏置,实现从输入特征到输出特征的映射。第一个参数32 * 25 * 25=20000,表示输入数据的数量,128表示输出数据的数量。全连接层线性变换公式为:

 其中, x是输入向量,W 是权重矩阵,b 是偏置向量, y是输出向量。权重矩阵W的形状为 (20000, 128), b的形状为 (128,1)。W和b都是在模型训练中需要更新的矩阵。

在建立的神经网络中还定义了前向传播函数forward(),它由开发者自行设计数据在网络中传播的方向。图像数据首先经过第一个卷积层,然后通过激活函数,再进入第一个池化层,随后依次进入第二个卷积层,第二个激活函数,第二个池化层。x = x.view(-1, 32 * 25 * 25)的作用是将得到的数据展平为一维数据。从前面的输出可知,x是一组由32个25*25矩阵组成的数据集,x.view会将该数据集整合为一个一维数组,数组的元素总量保持不变。

数据在经过展平后,进入了第一全连接层,由网络结构可知,第一个全连接层输出的是一个128个元素的数组,数据量被极大压缩了。然后再次经过激活函数,随后进入第二个全连接层,第二个全连接层输出的数组由n个元素组成,n=len(train_dataset.classes),就是我们训练模型时提供的n个人数。但需要注意的是,此处输出的结果与人脸识别并无直接关系,我们需要对该结果进行进一步处理,让它与人脸对应起来。

class FaceCNN(nn.Module):
    def __init__(self):
        super(FaceCNN, self).__init__()
        self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1)  # 定义第一个二维卷积层
        self.relu1 = nn.ReLU()  # 定义第一个激活函数
        self.pool1 = nn.MaxPool2d(2)  # 定义第一个二维池化层
        self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1)  # 定义第二个二维卷积层
        self.relu2 = nn.ReLU()  # 定义第二个激活函数
        self.pool2 = nn.MaxPool2d(2)  # 定义第二个二维池化层
        self.fc1 = nn.Linear(32 * 25 * 25, 128)  # 定义第一个全连接层,展平后输出128个特征量
        self.relu3 = nn.ReLU()  # 定义第三个激活函数
        self.fc2 = nn.Linear(128, len(train_dataset.classes))  # 定义第二个全连接层,展平后输出n个特征量,n为人数

    def forward(self, x):
        x = self.pool1(self.relu1(self.conv1(x)))
        x = self.pool2(self.relu2(self.conv2(x)))
        x = x.view(-1, 32 * 25 * 25)
        x = self.relu3(self.fc1(x))
        x = self.fc2(x)
        return x

 5)模型训练

首先用自己定义卷积神经网络类实例化一个对象。然后定义一个损失函数,这里直接使用了pytorch库中提供的交叉熵损失函数对象,交叉熵损失函数(Cross-Entropy Loss)是分类问题中常用的一种损失函数,尤其适用于多分类任务。它能够有效衡量模型预测的概率分布与真实标签的概率分布之间的差异。该函数的具体实现比较复杂,如果不做算法研究,可以暂时不用管其具体实现,只需要知道在分类问题中适用该函数。

optimizer = optim.Adam(model.parameters(), lr=0.001) 这行代码在 PyTorch 中用于创建一个 Adam 优化器对象 optimizer,该优化器将用于更新模型 model 的参数。Adam(Adaptive Moment Estimation)是一种常用的优化算法,它结合了 AdaGrad 和 RMSProp 两种算法的优点。Adam 算法能够自适应地调整每个参数的学习率,同时利用梯度的一阶矩估计(均值)和二阶矩估计(方差)来更新参数。它具有收敛速度快、对不同类型的数据集和模型都有较好表现等优点,因此在深度学习中得到了广泛应用。我们同样也不需要去关心它的具体实现,PyTorch已经帮我们完成了相应的操作。其中的参数定义如下:

  • model.parameters():这是 optim.Adam 的第一个参数,它是一个生成器,用于返回模型 model 中所有需要更新的参数。在 PyTorch 中,模型的参数通常是可学习的张量,通过调用 model.parameters() 可以获取这些参数,优化器将根据这些参数的梯度信息来更新它们。

  • lr=0.001lr 是学习率(learning rate)的缩写,它是 optim.Adam 的一个重要超参数,用于控制每次参数更新的步长。学习率决定了模型在训练过程中朝着损失函数最小值前进的速度。这里将学习率设置为 0.001,表示每次更新参数时,参数的变化量是梯度乘以 0.001。如果学习率设置得过大,模型可能会跳过损失函数的最小值,导致无法收敛;如果学习率设置得过小,模型的训练速度会变得很慢。

# 初始化模型
model = FaceCNN()

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 训练模型
num_epochs = 20  # 训练10次
for epoch in range(num_epochs):
    model.train()  # 将模型设置为训练模式。
    running_loss = 0.0
    for i, (images, labels) in enumerate(train_loader):
        optimizer.zero_grad()  # 每次反向传播之前,将优化器中所有参数的梯度清零
        outputs = model(images)  # 自动调用模型的 forward 方法
        loss = criterion(outputs, labels)  # 计算损失值
        loss.backward()  # 调用 loss.backward() 开始反向传播过程,计算每个可训练参数的梯度,并将这些梯度存储在参数的 .grad 属性中。
        optimizer.step()  # 优化器会根据存储在参数 .grad 属性中的梯度,按照指定的优化算法更新模型的参数。
        running_loss += loss.item()

6)模型测试

模型测试的代码实现如下。测试时首先将模型设置为评估模式,在这里同样使用批量传入测试图像数据的方式。

_, predicted = torch.max(outputs.data, 1)用于找出在模型输出的结果outputs.data中,每一行数据的最大值在该行中的索引位置,torch.max 函数的第二个参数,指定了在哪个维度上进行最大值查找。这里设置为 1,表示在每一行(即每个样本)上查找最大值。需要进一步说明的是,由于我们采用批量处理的方式,outputs.data应是一个二维数组,每一行表示模型对一张图的处理结果。根据前面网络结构定义可知,若是输入3个人的人脸照片,则每一行应该有3个元素。这里取最大值,是因为模型输出的结果表征的是模型预测的概率,概率越大说明输入图像是对应人脸的概率越大。而对应的人脸在labels中表征,在本例中输入3个人的照片,则它是一个包含3个元素[0,1,2]的数组,每个值对应一个人。因此, torch.max找到最大值在每一行中的索引位置后,该位置也就表征了对应的人。

# 测试模型
model.eval()  # 将模型切换到评估模式。
correct = 0
total = 0
with torch.no_grad():  # 不计算导数
    for index, (images, labels) in enumerate(test_loader):
        outputs = model(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()
        labels_array = labels.numpy()
        predicted_array = predicted.numpy()
        # 计算当前批次的起始索引
        start_idx = index * test_loader.batch_size
        # 计算当前批次的结束索引
        end_idx = start_idx + images.size(0)
        # 获取当前批次的图像文件路径
        batch_samples = all_samples[start_idx:end_idx]
        # 提取图像文件名称
        batch_image_names = [sample[0].split('\\')[2] for sample in batch_samples]
        for i in range(len(predicted_array)):
            print(f'图名: {batch_image_names[i]}', f'实际名字: {train_dataset.classes[labels_array[i]]}',
                  f'预测名字: {train_dataset.classes[predicted_array[i]]}')
print(f'总测试数量: {total}', f'正确识别数量: {correct}')
print(f'Test Accuracy: {100 * correct / total}%')

四、测试结果 

从网络上随机下载了一些公开图片,对网路进行了训练和测试,得到结果如下。实际看神经网络的识别准确率还是比较高的。模型的完整代码和使用的资源可在这里下载。

五、模型导出

在完成神经网络模型训练后,需要将训练好的模型导出,以供后续使用,具体方法有两种。

1)保存模型的状态参数

在训练后的模型下加入下列代码,则模型中的所有参数均会保存到对应的.pth文件中。

torch.save(model.state_dict(), 'model_state_dict.pth')

在重新使用模型时,需要再次实例化模型对象,然后加载保存的模型参数。因此,重新使用时我们需要知道模型的结构定义,即重写FaceCNN类。

# 加载模型的状态字典
loaded_model = FaceCNN()
loaded_model.load_state_dict(torch.load('model_state_dict.pth'))
loaded_model.eval()  # 设置为评估模式

 2)保存完整模型

我们也可以直接保存完整的模型文件,在重新使用时直接加载该模型即可。这种方式简单,但缺少灵活性。

# 保存整个模型
torch.save(model, 'whole_model.pth')

# 加载整个模型
loaded_model = torch.load('whole_model.pth')
loaded_model.eval()  # 设置为评估模式

 六、总结

通过一个人脸识别示例,详细说明了利用pytorch模块搭建卷积神经网络的实现流程,并对代码进行了逐行解释。pytorch极大方便了神经网络开发,让开发人员可以不用关注网络中具体的算法实现,而更加侧重在网络模型搭建上。在本例试验测试中,模型的训练次数和网络结构参数的调整均会对图像识别的准确造成大幅度影响,仍需要通过大量测试优化网络结构参数。


http://www.kler.cn/a/540948.html

相关文章:

  • 手写一个C++ Android Binder服务及源码分析
  • 集成右键的好用软件,支持多线程操作!
  • 算法15(力扣347)——前k个高频元素
  • C++20新特性
  • ffmpeg -formats
  • 第433场周赛:变长子数组求和、最多 K 个元素的子序列的最值之和、粉刷房子 Ⅳ、最多 K 个元素的子数组的最值之和
  • 如何保证Redis和MySQL数据的一致性刨析
  • 求助DeepSeek帮我开发一个直线审批流程设计页面Vue2.0
  • Linux系统编程之信号基础知识
  • 在cursor/vscode中使用godot C#进行游戏开发
  • Vue智慧商城(1)
  • 【AIGC魔童】DeepSeek v3推理部署:华为昇腾NPU/TRT-LLM
  • 【前端框架与库】「深入理解 Vue 插槽」:类型、用法与实际场景解析,增强组件复用性的利器
  • 9 Pydantic复杂数据结构的处理
  • Expo运行模拟器失败错误解决(xcrun simctl )
  • React 中如何使用 Redux 进行状态管理?
  • 详尽指南:如何在雷电模拟器中设置代理IP
  • [前端]CRX持久化
  • 网络通信的精髓:透彻理解 TCP/IP 的三次握手与四次挥手
  • SpringBoot单机模式,能否支持一万用户请求并发?
  • kafka查看topic消息是否堆积
  • ChatGPT搜索 vs. 推理:如何选择和使用
  • 解锁面向对象编程:Python 类与对象详解
  • 22.[前端开发]Day22-CSS单位-CSS预处理器-移动端视口
  • Kubernetes架构原则和对象设计(三)
  • HarmonyOS Next 方舟字节码文件格式介绍