当前位置:   article > 正文

人脸表情识别系统项目完整实现详解——(三)训练MobileNet深度神经网络识别表情

人脸表情识别系统项目完整实现详解——(三)训练MobileNet深度神经网络识别表情

在这里插入图片描述

摘要:之前的表情识别系统升级到v3.0版本,本篇博客详细介绍使用PyTorch框架来构建并训练MobileNet V3模型以进行实现表情识别,给出了完整实现代码和数据集可供下载。从构建数据集搭建深度学习模型数据增强早停等多种技术,到模型训练过程的每个细节进行逐行代码讲解。再到最终的模型评估,给出了绘制训练和验证的损失与准确率曲线、混淆矩阵精确率-召回率(PR)曲线以及F1分数的详细分析。本文结构如下:

➷点击跳转至文末所有涉及的完整代码文件下载页☇

最新升级版—人脸表情识别系统v3.0(SSD+MobileNet/Xception,UI界面演示)


1. 表情识别思路与方案

        四年前,我写了一篇关于利用深度学习算法进行表情识别的博客:人脸表情识别系统介绍——上篇(python实现,含UI界面及完整代码),虽然去年前年也都有更新这个系列,不过还是来一版这个的后续。当然本文采用的是先检测人脸,再进行表情分类的方式,也可以一步到位采用目标检测算法,同时检测人脸并识别表情,在我的另一篇博客基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的人脸表情识别系统(附完整资源+PySide6界面+训练代码)中就使用YOLO系列算法直接实现了表情识别。

        在进行表情识别之前,先说明一下整个系统需要的实现步骤,包括图像采集、图像预处理、人脸检测、表情分类和输出结果。首先,输入的图像会经过一系列的预处理步骤,这可能包括调整大小、归一化像素值以及可能的数据增强操作,为接下来的步骤提供统一和标准化的输入。然后是人脸检测,这一步骤关键在于定位图像中的人脸,为表情分类提供准确的切割图像。有了这两个步骤可以实现对分割出的人脸图像进行表情分类。

在这里插入图片描述

表情分类是将人脸图像映射到一组预定义的表情类别上。这一过程需要借助深度学习模型来识别和理解图像中人脸的各种表情细节,这些细节往往隐藏在面部的微小变化之中。完成这些步骤后,模型会输出它对于图像中人物表情的判断,这可以用于进一步的分析或作为其他系统的输入。

在博主前面的几篇博客中,已经发布文章详细介绍了数据处理和人脸检测的步骤。在文章:“人脸表情识别系统项目完整实现详解——(一)深度学习数据集处理”中,介绍了如何从csv文件下载图像数据,并将其转换为模型可以处理的图片文件格式。

第二篇文章:“人脸表情识别系统项目完整实现详解——(二)使用SSD模型检测人脸”,详细介绍了使用SSD算法进行人脸检测的具体步骤。SSD是一种性能卓越的目标检测算法,它能够在图像中快速准确地定位人脸,这对于我们后续的表情分类是需要的一步。

在这里插入图片描述

这篇博客我们将重点介绍使用MobileNet模型对人脸表情进行分类的过程。MobileNet以其轻量化和高效率而著称,在表情识别任务中显示出了巨大的潜力1。这里将详细说明如何使用PyTorch搭建MobileNet的结构2,并调整它以适应我们的表情分类任务,后面介绍训练和验证模型的过程。在后面的博客里面可以介绍如何将其做成一个完整的系统。

在这里插入图片描述


2 训练MobileNet深度神经网络

2.1 数据集构建

在构建一个高效的表情识别模型时,其实关键任务是确保模型既能理解复杂的人脸表情特征,又能在现实世界的多变环境中准确运行。为此我们采用了MobileNetV3——一个专为移动和边缘计算设备优化的轻量级深度学习网络。这个网络的源码可以在开源项目https://github.com/d-li14/mobilenetv3.pytorch中获得。

(1)设置随机数种子:在模型训练的开端,设置随机种子能够确保我们的实验结果可复现,不论我们运行多少次实验,只要随机种子相同,输出结果也应该是一致的。在代码中,博主通过设置torch.manual_seed和相关的CUDA种子:

def set_seed(seed=42):
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)
    torch.cuda.manual_seed_all(seed)  # 如果使用多GPU,还需要设置
    torch.backends.cudnn.benchmark = False
    torch.backends.cudnn.deterministic = True
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这里使用的设备可以是CPU也可以是GPU,取决于我们的硬件条件。PyTorch通过torch.device使得这一过程的设置很方便,只需指定要使用的设备类型。如果检测到GPU可用,那么利用它的计算能力来加速训练过程。

# 设置随机种子以确保实验的可重复性
set_seed(2035)

# 根据设备的可用性选择使用CPU或GPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(f"Using {device} device")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

(2)训练验证集转换器:在数据预处理方面,博主采用了一组转换操作来增强模型的泛化能力。这些操作包括随机裁剪、翻转、旋转和亮度调整,其实是模仿了现实世界中的各种变化,可以使模型在面对未见过的数据时,表现得更加稳定。你可以看到所有的图像最终都被转换成了归一化的张量,这是因为深度学习模型通常在这种处理过的数据上表现得更好。

# 定义用于训练集的图像预处理操作
train_transforms = transforms.Compose([
    transforms.RandomResizedCrop(224),
    transforms.RandomHorizontalFlip(),
    transforms.RandomRotation(10),
    transforms.ColorJitter(brightness=0.2),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])

# 定义用于测试集的图像预处理操作
test_transforms = transforms.Compose([
    transforms.Resize(224),  # 调整图像大小至256x256
    # transforms.CenterCrop(224),  # 从中心裁剪出224x224大小的图像
    transforms.ToTensor(),  # 将图像转换为Tensor
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),  # 归一化
])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

数据转换不仅要应用于训练数据,也得为测试数据定义一组转换流程,虽然没有包含数据增强操作,但包括了重要的尺寸调整和归一化步骤。这样确保可以在一个统一的数据分布上评估模型性能。

在这里插入图片描述

(3)数据集划分:这里数据集是按照文件夹结构组织的,也就是说每个类别的图像都存放在独立的文件夹中。PyTorch通过datasets.ImageFolder提供了一种非常便捷的方式来加载这类结构的数据集。之后可以使用random_split方法将数据集分为训练集和测试集,确保了数据的多样性和模型的可靠性。

data_directory = './datasets/fer2013_images'
dataset = datasets.ImageFolder(root=data_directory)

train_size = int(0.8 * len(dataset))
test_size = len(dataset) - train_size
train_dataset, test_dataset = random_split(dataset, [train_size, test_size])

# 应用预处理操作
train_dataset.dataset.transform = train_transforms
test_dataset.dataset.transform = test_transforms
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

上面这一步骤确保我们有足够的数据用于训练,同时也为模型的验证保留了一部分数据。这样的划分之后,能够在保持数据独立性的同时,对模型的泛化能力进行有效评估。

(4)构建数据集的DataLoaderDataLoader是PyTorch中一个非常强大的工具,它能够以批量的方式加载数据,还可以进行多进程处理,并支持自动批量数据的内存管理,这在大规模数据训练时尤其有用。将这些预处理操作应用于我们的数据集之后,这里定义数据集的DataLoader,为训练集设置随机洗牌,以避免模型在训练过程中学习到批次的顺序,有助于模型学习到更加稳健的特征。

# 定义训练参数
num_epochs = 400  # 训练轮数
batch_size = 32  # 每批处理的图像数量
n_worker = 1  # DataLoader使用的工作线程数量
train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True, num_workers=n_worker, pin_memory=True)
test_loader = DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False, num_workers=n_worker, pin_memory=True)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

batch_sizenum_workerspin_memory是几个关键参数,影响着数据加载的效率。batch_size决定了每次喂给模型多少图像,这直接关联到模型训练的速度和内存使用量。num_workers定义了加载数据时使用的进程数,正确设置此参数可以显著提高数据加载效率,特别是在CPU资源充足的机器上。pin_memory则是告诉DataLoader将数据加载到CUDA的固定内存中,这样可以加速数据从CPU传输到GPU的过程。


2.2 模型构建

构建和调整模型是深度学习工作流中最关键的部分之一。在我们的表情识别项目中,我们选择了MobileNetV3作为基础架构。由于MobileNetV3在保持轻量级的同时,还提供了与复杂模型相媲美的性能,这对于我们的实时识别表情应用很重要。

在这里插入图片描述

模型构建过程首先从加载MobileNetV3的结构开始,然后把模型放到我们选择的设备上。在PyTorch中,这通过调用.to(device)方法实现,这能够确保模型的计算会在CPU或GPU上进行,取决于我们的设备选择(看是否安装了pytorch-gpu)。这是模型优化过程中的一个基础步骤,可以显著提高模型训练和推理的速度。

model = mobilenetv3(mode='small').to(device)
  • 1

然后,利用预训练的权重来初始化模型。预训练是深度学习领域常用的一种技术,它能够帮助加速训练过程,同时提高模型的泛化能力。这种方法特别适用于数据集较小或计算资源有限的情况。在我们的代码中,通过加载预训练权重,这里的模型已经具备了一些识别图像的基础能力。

state_dict = torch.load('./models/mobilenetv3_small_67.4.pth.tar')
model.load_state_dict(state_dict)
  • 1
  • 2

由于这里我们的任务是识别7种不同的表情,因此需要调整MobileNetV3的分类器部分,以便它的输出层与我们的类别数相匹配。可以通过替换最后一个线性层的输出特征数实现这一点,这种调整其实是在迁移学习中非常典型的做法。

model.classifier[1] = nn.Linear(model.classifier[1].in_features, 7)
model.to(device)
  • 1
  • 2

为了训练模型,我们定义交叉熵损失函数,这是处理多类别分类问题的标准选择。它可以度量模型输出的概率分布和实际标签之间的差异,从而指导模型朝正确类别的方向学习。

criterion = nn.CrossEntropyLoss()
  • 1

最后,博主这里选择的是Adam优化器,设定了一个学习率。Adam是一种自适应学习率的优化算法,它结合了RMSProp和Momentum两种优化算法的优点。在实践中,Adam优化器被证明对于多种不同的深度学习任务都是有效的,这使得它成为一个非常流行的选择。

optimizer = optim.Adam(model.parameters(), lr=0.0001)
  • 1

通过上述步骤,我们成功地构建并配置了适用于表情识别任务的MobileNetV3模型。在后续的训练过程中,我们将不断地调整和评估模型,以确保最终得到的模型可以在实际应用中准确快速地识别用户的表情。


2.3 训练过程

在训练过程中,我们需要使用一些常见技术,包括模型的正则化、性能监控以及早停机制,以确保我们的模型不仅能从数据中学习,还能保持对新数据的泛化能力。

(1)早停设置:在本项目的训练过程中,首先定义了早停机制的相关参数。早停是一种防止深度学习模型过拟合的技术,其工作原理是监控模型在验证集上的性能。如果模型在一定数量的连续训练周期(由patience参数定义)中未显示出性能提升,则训练过程将被提前终止。这不仅节省了时间和计算资源,还提高了模型的泛化能力,因为它防止了在训练集上的过度拟合。

patience = 20
min_val_loss = np.inf
patience_counter = 0
  • 1
  • 2
  • 3

接下来,我们使用一个自定义的训练监控器,它能够记录每个训练周期的性能并最终绘制出一个性能变化图。这是一个自定义的类,可以帮助我们可视化训练过程,并在必要时作出调整。

monitor = TrainingMonitor(file_path='runs/training_performance.png')
  • 1

(2)训练过程:在每个训练周期的开始,我们将模型设置为训练模式,使得模型的参数能够更新。然后,初始化一些变量来追踪损失和准确性,并设置一个进度条来监控训练进度。通过遍历训练数据集,并在每个批次上执行前向传播、损失计算、反向传播和参数更新,便可以完成模型的训练步骤。

# 开始训练周期,遍历设定的训练轮数
for epoch in range(num_epochs):
    model.train()  # 设置模型为训练模式

    # 初始化用于累计的变量:运行损失、正确分类的训练样本数和总训练样本数
    running_loss = 0.0
    correct_train = 0
    total_train = 0

    # 初始化训练阶段的进度条
    train_pbar = tqdm(train_loader, desc=f"Epoch {epoch + 1}/{num_epochs} [Training]", leave=False)

    # 遍历训练数据集
    for images, labels in train_pbar:
        images, labels = images.to(device), labels.to(device)  # 将图像和标签数据移至指定设备

        optimizer.zero_grad()  # 清空之前的梯度

        outputs = model(images)  # 获取模型的预测输出
        loss = criterion(outputs, labels)  # 计算损失
        loss.backward()  # 反向传播求梯度
        optimizer.step()  # 根据梯度更新模型参数

        # 累计损失和正确分类的数量
        running_loss += loss.item()
        _, predicted = torch.max(outputs.data, 1)
        total_train += labels.size(0)
        correct_train += (predicted == labels).sum().item()

    train_pbar.close()  # 关闭训练阶段的进度条

    # 计算训练准确率
    train_accuracy = 100 * correct_train / total_train
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

(3)进行验证集评估:在每个训练周期之后,将模型切换到评估模式,并在验证数据集上运行模型,以计算损失和准确性。这一步骤不涉及梯度计算,因为我们在这一阶段不会更新模型的权重。

    # 验证模型性能
    model.eval()  # 设置模型为评估模式
    val_loss = 0.0
    correct_val = 0
    total_val = 0

    # 初始化验证阶段的进度条
    val_pbar = tqdm(test_loader, desc=f"Epoch {epoch + 1}/{num_epochs} [Validation]", leave=False)
    with torch.no_grad():  # 关闭梯度计算
        # 遍历验证数据集
        for images, labels in val_pbar:
            images, labels = images.to(device), labels.to(device)
            outputs = model(images)
            loss = criterion(outputs, labels)

            val_loss += loss.item()
            _, predicted = torch.max(outputs.data, 1)
            total_val += labels.size(0)
            correct_val += (predicted == labels).sum().item()

    val_pbar.close()  # 关闭验证阶段的进度条

    # 计算验证准确率
    val_accuracy = 100 * correct_val / total_val

    # 打印每轮的训练损失、验证损失和验证准确率
    print(f'\nEpoch {epoch + 1}, Train Loss: {running_loss / len(train_loader)}, '
          f'Val Loss: {val_loss / len(test_loader)}, Accuracy: {val_accuracy}%')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

(4)打印训练损失和准确率:这里代码会打印出训练损失、验证损失和验证准确率,并根据早停策略判断是否应该提前结束训练。如果在patience定义的周期数内验证损失没有下降,就会结束训练,并保存当前效果最好的模型。

    # 实现早停机制以避免过拟合
    if val_loss < min_val_loss:
        min_val_loss = val_loss
        torch.save(model.state_dict(), 'runs/best_mobilenet_model.pt')  # 保存表现最好的模型
        patience_counter = 0
    else:
        patience_counter += 1
        if patience_counter >= patience:  # 如果验证损失不再下降,则提前停止训练
            print("Early stopping triggered")
            break
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

最后,需要调用监控器的更新和绘图功能,来记录和展示模型的训练和验证性能。这一步是训练流程中的可视化环节,让我们能够直观地评估模型训练的结果。

    # 更新训练监控器数据
    monitor.update(running_loss / len(train_loader), val_loss / len(test_loader), train_accuracy, val_accuracy)

# 训练完成后,绘制训练和验证的性能变化图
monitor.plot()
  • 1
  • 2
  • 3
  • 4
  • 5

通过以上步骤,我们确保了模型在训练过程中能够稳步提升其性能,并通过早停策略避免了过拟合。这种方法论不仅可以应用于表情识别,还适用于广泛的机器学习和深度学习任务。

(5)分析训练和验证损失:以上训练过程结束后,可以得到观察训练和验证损失的图表如下图所示。我们可以看到随着训练轮数(Epochs)增加,训练损失持续下降,这表明模型在训练数据上学习得越来越好,正在减少误差。验证损失在初始几个周期后下降了一段时间,在大约5个epoch后开始上升,这是过拟合的一个明显迹象。意味着模型开始在训练数据上过于精确地学习数据特征,以至于它无法很好地泛化到未见过的验证数据上。这里不用担心,因为我们设置了保存历史验证集最佳的模型才保留,同时还有早停,这张图是为了便于分析。

在这里插入图片描述

对于上图右边训练和验证准确率的图表,我们看到训练准确率随着epoch增加而持续提高,这进一步证明了模型在训练数据上的性能正在改善。与此同时,验证准确率在初期也有所提高,但很快达到平稳状态。尽管训练准确率继续增长,但验证准确率没有显著提高,这表面模型在这之后已经进入过拟合。可采用的策略包括引入正则化技术,如dropout或weight decay,收集更多的训练数据,或者调整模型的复杂性。这里过拟合的模型并不会被保存,我们依然采用的是泛化能力最好的模型作为最终模型。


2.4 模型评估

模型评估是机器学习项目的一个关键环节,不仅帮助我们理解模型在实际情况中的表现,还可以找出模型可能存在的问题和改进空间。在本文中,我们将详细讲解如何使用PyTorch进行表情识别模型的评估。

(1)设置随机数种子:首先,我们需要确保模型评估的可重现性,所以像训练过程一样,设置随机种子还是有必要的。然后,我们检查一下是否有GPU可用,以利用其计算能力进行快速评估。

set_seed(2024)
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
  • 1
  • 2

(2)载入训练好的模型:模型评估的下一步是加载经过训练的模型权重。我们先创建模型的实例,调整分类器层以适应我们的7个类别,然后加载权重。可以通过下面几行代码就能完成:

model = mobilenetv3(mode='small').to(device)
model.classifier[1] = nn.Linear(model.classifier[1].in_features, 7)
state_dict = torch.load('models/best_mobilenet_model_f1_72.pt')
model.load_state_dict(state_dict)
  • 1
  • 2
  • 3
  • 4

(3)构建测试集:在准备好模型后,我们定义一个图像转换流程来处理数据集。这个流程会将图像调整到适合模型的大小、转换为张量,并对其进行标准化,确保输入数据与模型训练时的格式相同。

transform = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])
  • 1
  • 2
  • 3
  • 4
  • 5

处理数据的过程非常简单:定义数据集、应用上述的转换、创建一个DataLoader来批量加载数据,并进行评估。

# 加载并处理数据集
data_directory = './datasets/fer2013_images'
dataset = datasets.ImageFolder(root=data_directory, transform=transform)

# 分割数据集为训练集和验证集
val_size = int(0.2 * len(dataset))  # 验证集大小
train_size = len(dataset) - val_size  # 训练集大小
_, val_dataset = random_split(dataset, [train_size, val_size])

# 创建DataLoader来批量加载数据
batch_size = 8
val_loader = DataLoader(dataset=val_dataset, batch_size=batch_size, shuffle=False)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

(4)评估阶段:评估阶段的重点是收集模型的预测标签、真实标签以及预测分数,这些要用于计算不同的性能指标。这里在不进行梯度计算的上下文中执行模型的前向传播,因为在评估模式下,我们就不要再进行反向传播或任何形式的模型训练。

with torch.no_grad():  
    for images, labels in tqdm(val_loader, desc="Evaluating", leave=True):
        images = images.to(device)
        labels = labels.to(device)
        outputs = model(images)
        _, preds = torch.max(outputs, 1)
        scores = torch.softmax(outputs, dim=1)

        y_true.extend(labels.cpu().numpy())
        y_pred.extend(preds.cpu().numpy())
        y_scores.extend(scores.cpu().detach().numpy())
y_scores = np.array(y_scores)  # 将预测分数转换为NumPy数组,便于后续处理
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

(5)绘制混淆矩阵:利用这些收集的数据来绘制混淆矩阵,这是评估分类模型性能的一个常见思路。混淆矩阵不仅提供了每个类别的预测准确性的视图,还可以让我们看到模型在哪些类别上混淆了预测。

cm = confusion_matrix(y_true, y_pred, normalize='true')
  • 1

在图形表示方面,我们采用了热力图的形式,它通过不同的颜色深浅来表达数值的大小,这种直观的表示方式可以帮助我们更快地理解模型的性能。

plt.figure(figsize=(10, 8))
sns.heatmap(cm, annot=True, fmt=".2f", cmap="Blues", xticklabels=class_names, yticklabels=class_names)
plt.title('Normalized Confusion Matrix')
plt.ylabel('True Label')
plt.xlabel('Predicted Label')
plt.savefig('./runs/confusion_matrix.png')
plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

通过混淆矩阵,我们能够了解模型在各个类别上的具体表现,并据此作出必要的模型调整,结果如下图所示:

在这里插入图片描述

(6)绘制P-R曲线:在表情识别模型的评估阶段,我们不仅需要考虑传统的准确率,还需要深入分析模型的预测能力。为此,精确率-召回率(PR)曲线和F1分数成为了评估分类性能的重要指标。

# 计算并绘制所有类别的PR曲线和整体AP
y_true_binary = label_binarize(y_true, classes=np.arange(len(class_names)))  # 将真实标签二值化

precision = dict()  # 初始化精确度字典
recall = dict()  # 初始化召回率字典
average_precision = dict()  # 初始化平均精确度字典

for i in range(len(class_names)):  # 对于每个类别
    # 计算每个类别的精确度和召回率
    precision[i], recall[i], _ = precision_recall_curve(y_true_binary[:, i], y_scores[:, i])
    average_precision[i] = average_precision_score(y_true_binary[:, i], y_scores[:, i])  # 计算平均精确度

# 计算微平均PR曲线和AP
precision["micro"], recall["micro"], _ = precision_recall_curve(y_true_binary.ravel(), np.array(y_scores).ravel())
average_precision["micro"] = average_precision_score(y_true_binary, y_scores, average="micro")  # 计算微平均精确度

# 绘制PR曲线
plt.figure(figsize=(8, 8))  # 设置图像大小
plt.plot(recall['micro'], precision['micro'],
         label=f'Micro-average PR curve (area = {average_precision["micro"]:0.2f})', linestyle=':', linewidth=4)

colors = cycle(['navy', 'turquoise', 'darkorange', 'cornflowerblue', 'teal'])  # 定义颜色循环
for i, color in zip(range(len(class_names)), colors):  # 为每个类别绘制PR曲线
    plt.plot(recall[i], precision[i], color=color, lw=2,
             label=f'PR for class {class_names[i]} (area = {average_precision[i]:0.2f})')

plt.xlabel('Recall')  # 设置x轴标签
plt.ylabel('Precision')  # 设置y轴标签
plt.title('Precision-Recall Curve')  # 设置标题
plt.legend(loc='best')  # 显示图例
plt.savefig('./runs/precision_recall_curve.png')  # 保存PR曲线图像
plt.show()  # 显示PR曲线图像
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

PR曲线展示了在不同阈值下模型精确率和召回率的关系。在理想情况下,我们希望模型的召回率和精确率都尽可能高,这意味着曲线会趋向于右上角。平均精确度(AP)则提供了一个PR曲线下的面积量度,它反映了模型在所有阈值水平上的整体表现。微平均PR曲线和AP(micro-average)对所有类别的预测进行总结,给出了模型在所有分类任务上的总体性能指标。

在这里插入图片描述

(7)绘制F1 Score曲线:F1分数是精确率和召回率的调和平均,它将二者结合成一个单一的指标。在分类问题中,F1分数特别有用,因为它能在不均衡类别分布的情况下,提供比准确率更公正的性能评估。通过为不同的预测阈值计算F1分数,我们能够找到模型性能最优化的点。

# 为每个类别计算F1 Score
f1_scores = dict()  # 初始化存储每个类别F1分数的字典
best_f1_scores = dict()  # 初始化存储每个类别最佳F1分数的字典
best_thresholds = dict()  # 初始化存储每个类别对应最佳阈值的字典

for i in range(len(class_names)):  # 遍历所有类别
    # 对不同的阈值计算F1分数
    f1_scores[i] = [f1_score(y_true_binary[:, i], y_scores[:, i] > threshold) for threshold in
                    np.linspace(0, 1, 100)]
    best_idx = np.argmax(f1_scores[i])  # 找到最佳F1分数的索引
    best_f1_scores[i] = f1_scores[i][best_idx]  # 获取最佳F1分数
    best_thresholds[i] = np.linspace(0, 1, 100)[best_idx]  # 获取对应的最佳阈值

# 计算微平均F1分数
thresholds = np.linspace(0, 1, 100)  # 定义阈值范围
micro_f1_scores = []  # 初始化微平均F1分数列表

for threshold in thresholds:  # 遍历所有阈值
    y_pred_binary = y_scores > threshold  # 应用阈值,生成二值预测
    micro_f1 = f1_score(y_true_binary, y_pred_binary, average='micro')  # 计算微平均F1分数
    micro_f1_scores.append(micro_f1)  # 添加到列表

best_micro_f1 = np.max(micro_f1_scores)  # 找到最佳微平均F1分数
best_threshold = thresholds[np.argmax(micro_f1_scores)]  # 找到对应的最佳阈值
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

绘制出的F1分数曲线能够清晰展示在不同阈值下的模型性能。通过选择在所有类别上表现最佳的阈值,我们能够调整模型以在实际应用中达到最佳的准确率和召回率平衡。

# 绘制F1分数曲线
plt.figure(figsize=(10, 6))  # 设置图像大小
colors = cycle(['navy', 'turquoise', 'darkorange', 'cornflowerblue', 'teal', 'maroon', 'darkgreen'])  # 设置颜色循环
for i, color in zip(range(len(class_names)), colors):  # 为每个类别绘制F1分数曲线
    plt.plot(np.linspace(0, 1, 100), f1_scores[i], color=color, lw=2, label=f'Class {class_names[i]}')

# 绘制微平均F1分数曲线
plt.plot(thresholds, micro_f1_scores, color='black', lw=2, linestyle='--',
         label=f'Overall Micro-average (best={best_micro_f1:.2f} at threshold={best_threshold:.2f})')

plt.xlabel('Threshold')  # 设置x轴标签
plt.ylabel('F1 Score')  # 设置y轴标签
plt.title('F1 Score by Class and Overall Micro-average')  # 设置标题
plt.legend(loc='lower left')  # 显示图例
plt.grid(True)  # 显示网格

plt.savefig('./runs/f1_score_curve.png')  # 保存F1分数曲线图
plt.show()  # 显示F1分数曲线图
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

在所有评估指标计算完毕后,我们得出了模型的总体准确率,这是一个直观的性能度量,反映了模型预测正确标签的能力。

# 计算模型的准确率
correct_predictions = np.sum(np.array(y_true) == np.array(y_pred))  # 计算正确预测的数量
total_predictions = len(y_true)  # 总预测数量
accuracy = correct_predictions / total_predictions  # 计算准确率

print(f"Accuracy: {accuracy * 100:.2f}%")  # 打印准确率
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

以上步骤为我们提供了一个全面的性能评估框架,从而可以确信模型在处理真实世界数据时的鲁棒性和可靠性。我们通过不同的图表和统计数据深入理解了模型的强项和弱点,这为进一步优化模型和选择合适的运营策略奠定了基础,得到的曲线图如下图所示:

在这里插入图片描述


3. 系统界面效果

        本系列博客的目的是实现一个人脸表情识别系统。我们以PySide6作为GUI库,提供了一套直观且友好的用户界面,这节先演示一下各个主要界面的功能和设计,将在后面的几个章节介绍训练MobileNet深度神经网络识别表情的详细步骤。

(1)系统提供了基于SQLite的注册登录管理功能。用户在首次使用时需要通过注册界面进行注册,输入用户名和密码后,系统会将这些信息存储在SQLite数据库中。注册成功后,用户可以通过登录界面输入用户名和密码进行登录。这个设计可以确保系统的安全性,也为后续添加更多个性化功能提供了可能性。

在这里插入图片描述

(2)在主界面上,系统提供了支持图片、视频、实时摄像头和批量文件输入的功能。用户可以通过点击相应的按钮,选择要进行表情识别的图片或视频,或者启动摄像头进行实时检测。在进行检测时,系统会实时显示检测结果,并将检测记录存储在表格中。

在这里插入图片描述

(3)此外,系统还提供了一键更换pt模型的功能。用户可以通过点击界面上的"更换模型"按钮,选择不同训练好的模型进行检测。与此同时,资源包中附带的数据集也可以用于重新训练模型,以满足用户在不同场景下的检测需求。

在这里插入图片描述

(4)为了提供更个性化的使用体验,这里系统支持界面修改,用户可以自定义图标、文字等界面元素。例如,用户可以根据自己的喜好,选择不同风格的图标,也可以修改界面的文字描述。

在这里插入图片描述


下载链接

    若您想获得博文中涉及的实现完整全部资源文件(包括测试图片、视频,py, UI文件,训练数据集、训练代码、界面代码等),这里已打包上传至博主的面包多平台,见可参考博客与视频,已将所有涉及的文件同时打包到里面,点击即可运行,完整文件截图如下:
在这里插入图片描述

完整资源中包含数据集及训练代码,环境配置与界面中文字、图片、logo等的修改方法请见视频,项目完整文件下载请见演示与介绍视频的简介处给出:➷➷➷

演示与介绍视频:https://www.bilibili.com/video/BV1fK421v7Rb/

在这里插入图片描述

    在文件夹下的资源显示如下,下面的链接中也给出了Python的离线依赖包,读者可在正确安装Anaconda和Pycharm软件后,复制离线依赖包至项目目录下进行安装,另外有详细安装教程:(1)Pycharm软件安装教程;(2)Anaconda软件安装教程;(3)Python环境配置教程

离线依赖安装教程:https://www.bilibili.com/video/BV1hv421C7g8/
离线依赖库下载链接:https://pan.baidu.com/s/1y6vqa9CtRmC72SQYPh1ZCg?pwd=33z5 (提取码:33z5)


4. 总结与展望

这篇博客实现了一个基于MobileNetV3进行表情识别的深度学习模型。博主利用了PyTorch来构建和优化模型,确保处理速度和准确性的平衡。通过具体的数据预处理和增强策略,提高了模型对不同表情的识别能力,同时避免了过拟合现象的产生。在模型训练过程中,我们在代码中监测了损失和准确率,以便识别并解决模型在学习过程中可能出现的问题。在评估阶段,混淆矩阵、F1分数和PR曲线的详细介绍,应该能使大家能够全面了解模型的性能和局限性。

在后面的研究中,博主建议大家看看这几个方面。首先,数据肯定是深度学习模型不可或缺的部分,我们可以通过扩大和多样化数据集,来进一步提高模型的泛化能力和鲁棒性。其次,模型架构和超参数的调整也有助于改进性能,可以通过引入新的层、正则化技术或改进的训练技巧,可能会炼丹得到更好的模型。最后,将我们这里的模型应用于实际场景,如情绪分析或人机交互,将是检验其实用性的真正标尺。


5. 结束语

        由于博主能力有限,博文中提及的方法即使经过试验,也难免会有疏漏之处。希望您能热心指出其中的错误,以便下次修改时能以一个更完美更严谨的样子,呈现在大家面前。同时如果有更好的实现方法也请您不吝赐教。


  1. Nan Y, Ju J, Hua Q, et al. A-MobileNet: An approach of facial expression recognition[J]. Alexandria Engineering Journal, 2022, 61(6): 4435-4444. ↩︎

  2. Howard A G, Zhu M, Chen B, et al. Mobilenets: Efficient convolutional neural networks for mobile vision applications[J]. arXiv preprint arXiv:1704.04861, 2017. ↩︎

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/314049
推荐阅读
相关标签
  

闽ICP备14008679号