PyTorch图像分类实战——基于ResNet18的RAF-DB情绪识别(附完整代码和效果 ...

打印 上一主题 下一主题

主题 898|帖子 898|积分 2694

PyTorch图像分类实战——基于ResNet18的RAF-DB情绪识别(附完整代码和效果图)




关于作者


作者:小白熊
作者简介:精通python、matlab、c#语言,擅长机器学习,深度学习,机器视觉,目标检测,图像分类,姿态识别,语义分割,路径规划,智能优化算法,数据分析,各类创新融合等等。
联系邮箱:xbx3144@163.com


科研辅导、知识付费答疑、个性化定制以及其他合作需求请联系作者~



媒介

  在本文中,我们将具体介绍如何使用PyTorch框架,结合ResNet18模型,进行图像分类使命。这里我们选择了一个情绪识别数据集——RAF-DB(Real-world Affective Faces Database),来进行实验。通过本文,你将学习到如何准备数据、构建模型、练习模型、评估模型,并可视化练习过程中的损失曲线。



1 模型理论

1.1 深度学习根本

  深度学习是机器学习的一个分支,它通过使用深层神经网络来模拟人脑的学习过程。在图像分类使命中,卷积神经网络(Convolutional Neural Network, CNN)是最常用的模型之一。CNN通过卷积层、池化层、全毗连层等结构,能够自动提取图像中的特性,并进行分类。


1.2 ResNet模型

  ResNet(Residual Network)是一种深度卷积神经网络,它通过引入残差块(Residual Block)来办理深度神经网络中的梯度消失和梯度爆炸题目。残差块通过引入一个恒等映射(Identity Mapping),使得网络在练习过程中能够更容易地学习到特性。ResNet有多个版本,如ResNet18、ResNet34、ResNet50等,其中数字表示网络的层数。ResNet18作为ResNet系列中的一个轻量级模型,具备较好的性能和较低的计算复杂度,非常得当用于图像分类使命


1.3 交织熵损失函数

  在分类使命中,交织熵损失函数(Cross Entropy Loss)是最常用的损失函数之一。它权衡的是模型输出的概率分布与真实标签的概率分布之间的差异。交织熵损失函数越小,表示模型的预测效果越靠近真实标签。


1.4 优化器

  优化器用于更新模型的权重,以最小化损失函数。常用的优化器有SGD(随机梯度降落)、Adam等。SGD是最根本的优化器,它通过计算梯度来更新权重,但容易陷入局部最小值。Adam优化器结合了动量(Momentum)和RMSprop的思想,能够在练习过程中自适应地调解学习率,通常能够取得更好的效果。



2 代码解析

2.1 数据准备

  首先,我们必要准备数据集。这里使用的是RAF-DB数据集,它是一个情绪识别数据集,包含了多种情绪标签的图像。
  1. image_path = './data/RAF-DB'  # 数据集路径,需修改  
  2. labels_num = 7  # 标签类别数量,需修改
复制代码
  我们使用datasets.ImageFolder来加载数据集,它会自动根据文件夹名称来分别标签。然后,我们界说了数据转换(data_transform),包括随机裁剪、随机水平翻转、归一化等操作。
  1. data_transform = {  
  2.     "train": transforms.Compose([transforms.RandomResizedCrop(224),  
  3.                                  transforms.RandomHorizontalFlip(),  
  4.                                  transforms.ToTensor(),  
  5.                                  transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]),  
  6.     "val": transforms.Compose([transforms.Resize((224, 224)),  
  7.                                transforms.ToTensor(),  
  8.                                transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])}
复制代码

2.2 模型构建

  我们选择了ResNet18作为基模型,并修改了最后一层全毗连层的输出维度,以适应我们的分类使命。
  1. net = models.resnet18()  
  2. fc_input_feature = net.fc.in_features  
  3. net.fc = nn.Linear(fc_input_feature, labels_num)
复制代码
  然后,我们加载了预练习的权重,并删除了最后一层的权重,因为我们必要重新练习这一层。
  1. pretrained_weight = torch.hub.load_state_dict_from_url(  
  2.     url='https://download.pytorch.org/models/resnet18-5c106cde.pth', progress=True)  
  3. del pretrained_weight['fc.weight']  
  4. del pretrained_weight['fc.bias']  
  5. net.load_state_dict(pretrained_weight, strict=False)
复制代码

2.3 练习过程

  在练习过程中,我们使用了交织熵损失函数和SGD优化器。同时,我们还设置了学习率调理器(scheduler),它会在每10个epoch后,将学习率乘以0.1。
  1. criterion = nn.CrossEntropyLoss()  
  2. LR = 0.01  
  3. optimizer = optim.SGD(net.parameters(), lr=LR, momentum=0.9)  
  4. scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)
复制代码
  练习过程包括前向传播、计算损失、反向传播和更新权重。我们还使用了tqdm库来显示练习进度。
  1. for epoch in range(epochs):  
  2.     # train  
  3.     net.train()  
  4.     running_loss = 0.0  
  5.     train_bar = tqdm(train_loader, file=sys.stdout)  
  6.     acc1 = 0  
  7.   
  8.     for step, data in enumerate(train_bar):  
  9.         images, labels = data  
  10.         images = images.to(device)  
  11.         labels = labels.to(device)  
  12.   
  13.         output = net(images)  
  14.         optimizer.zero_grad()  
  15.         loss = criterion(output, labels)  
  16.         loss.backward()  
  17.         optimizer.step()  
  18.         running_loss += loss.item()  
  19.         train_bar.desc = "train epoch[{}/{}] loss:{:.3f}".format(epoch + 1, epochs, loss)  
  20.   
  21.         predicted = torch.max(output, dim=1)[1]  
  22.         acc1 += torch.eq(predicted, labels).sum().item()  
  23.   
  24.     train_accurate = acc1 / train_num  
  25.   
  26.     # validate  
  27.     net.eval()  
  28.     with torch.no_grad():  
  29.         val_bar = tqdm(validate_loader, file=sys.stdout)  
  30.         val_loss = 0.0  
  31.         acc = 0  
  32.   
  33.         for val_data in val_bar:  
  34.             val_images, val_labels = val_data  
  35.   
  36.             val_images = val_images.to(device)  
  37.             val_labels = val_labels.to(device)  
  38.   
  39.             output = net(val_images)  
  40.             loss = criterion(output, val_labels)  
  41.             val_loss += loss.item()  
  42.   
  43.             predict_y = torch.max(output, dim=1)[1]  
  44.             acc += torch.eq(predict_y, val_labels).sum().item()  
  45.   
  46.     val_accurate = acc / val_num  
  47.     print('[epoch %d] train_loss: %.3f  val_accuracy: %.3f train_accuracy: %.3f' %  
  48.           (epoch + 1, running_loss / train_steps, val_accurate, train_accurate))  
  49.   
  50.     train_losses.append(running_loss / train_steps)  
  51.   
  52.     if val_accurate > best_acc:  
  53.         best_acc = val_accurate  
  54.         save_path_epoch = os.path.join(save_path, f"resnet_{epoch + 1}_{val_accurate}.pth")  
  55.         torch.save(net.state_dict(), save_path_epoch)
复制代码

2.4 损失曲线绘制

  最后,我们绘制了练习过程中的损失曲线,以便观察模型的练习效果。
  1. plt.figure(figsize=(10, 8))  
  2. plt.plot(range(1, epochs + 1), train_losses, label='train')  
  3. plt.xlabel('Epoch')  
  4. plt.ylabel('Loss')  
  5. plt.title('resnet')  
  6. plt.legend()  
  7. plt.savefig('./runs/loss.jpg')  
  8. plt.show()
复制代码




3 文件夹结构

  为了使代码更加清晰和易于管理,建议使用以下文件夹结构:
  1. project_root/  
  2. │  
  3. ├── data/  
  4. │   └── RAF-DB/  
  5. │       ├── train/  
  6. │       │   ├── class1/  
  7. │       │   ├── class2/  
  8. │       │   ...  
  9. │       └── val/  
  10. │           ├── class1/  
  11. │           ├── class2/  
  12. │           ...  
  13. │  
  14. ├── runs/  # 保存训练好的模型权重和损失曲线图  
  15. │   ├── loss.jpg  
  16. │   ├── resnet_1_xxx.pth  
  17. │   ...  
  18. │  
  19. ├── train.py  # 训练脚本  
  20. │  
  21. ├── class_indices.json  # 类别索引映射文件  
  22. │  
  23. ├── requirements.txt  # 项目依赖包  
  24. │  
  25. └── README.md  # 项目说明文档
复制代码
  在这个结构中,data文件夹用于存放数据集,数据分别参考图像分类模型数据集分别教程:如何分别练习集和验证集
  本文的class_indices.json种别索引映射文件如下:
  1. {
  2.     "0": "anger",
  3.     "1": "disgust",
  4.     "2": "fear",
  5.     "3": "happiness",
  6.     "4": "neutral",
  7.     "5": "sadness",
  8.     "6": "surprise"
  9. }
复制代码



4 完整代码

  1. import os
  2. import sys
  3. import json
  4. import pandas as pd
  5. import matplotlib.pyplot as plt
  6. import numpy as np
  7. import torch.nn as nn
  8. from torchvision import transforms, datasets
  9. import torch.optim as optim
  10. from tqdm import tqdm
  11. import torch
  12. from torch.utils.data import DataLoader
  13. import torchvision.models as models
  14. def main():
  15.     device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
  16.     print("using {} device.".format(device))
  17.     data_transform = {
  18.         "train": transforms.Compose([transforms.RandomResizedCrop(224),
  19.                                      transforms.RandomHorizontalFlip(),
  20.                                      transforms.ToTensor(),
  21.                                      transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]),
  22.         "val": transforms.Compose([transforms.Resize((224, 224)),
  23.                                    transforms.ToTensor(),
  24.                                    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])}
  25.     image_path = './RAF-DB/RAF-DB'  # 数据集路径,需修改
  26.     labels_num = 7  # 标签类别数量,需修改
  27.     assert os.path.exists(image_path), "{} path does not exist.".format(image_path)
  28.     train_dataset = datasets.ImageFolder(root=os.path.join(image_path, "train"),
  29.                                          transform=data_transform["train"])
  30.     train_num = len(train_dataset)
  31.     flower_list = train_dataset.class_to_idx
  32.     cla_dict = dict((val, key) for key, val in flower_list.items())
  33.     # write dict into json file
  34.     json_str = json.dumps(cla_dict, indent=4)
  35.     with open('./class_indices.json', 'w') as json_file:  # json文件路径,需修改
  36.         json_file.write(json_str)
  37.     batch_size = 64  # 批处理大小,可修改
  38.     nw = min([os.cpu_count(), batch_size if batch_size > 1 else 0, 8])  # number of workers
  39.     print('Using {} dataloader workers every process'.format(nw))
  40.     train_loader = torch.utils.data.DataLoader(train_dataset,
  41.                                                batch_size=batch_size, shuffle=True,
  42.                                                num_workers=nw)
  43.     validate_dataset = datasets.ImageFolder(root=os.path.join(image_path, "val"),
  44.                                             transform=data_transform["val"])
  45.     val_num = len(validate_dataset)
  46.     validate_loader = torch.utils.data.DataLoader(validate_dataset,
  47.                                                   batch_size=batch_size, shuffle=False,
  48.                                                   num_workers=nw)
  49.     print("using {} images for training, {} images for validation.".format(train_num,
  50.                                                                            val_num))
  51.     # 初始化模型
  52.     net = models.resnet18()
  53.     fc_input_feature = net.fc.in_features
  54.     net.fc = nn.Linear(fc_input_feature, labels_num)
  55.     # load权重
  56.     pretrained_weight = torch.hub.load_state_dict_from_url(
  57.         url='https://download.pytorch.org/models/resnet18-5c106cde.pth', progress=True)
  58.     del pretrained_weight['fc.weight']
  59.     del pretrained_weight['fc.bias']
  60.     net.load_state_dict(pretrained_weight, strict=False)
  61.     net.to(device)
  62.     criterion = nn.CrossEntropyLoss()  # 交叉熵损失函数
  63.     LR = 0.01
  64.     optimizer = optim.SGD(net.parameters(), lr=LR, momentum=0.9)
  65.     scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)
  66.     epochs = 100  # 训练轮数,可修改
  67.     best_acc = 0.0
  68.     save_path = './runs'
  69.     train_steps = len(train_loader)
  70.     train_losses = []  # 存储每个epoch的训练损失
  71.     for epoch in range(epochs):
  72.         # train
  73.         net.train()
  74.         running_loss = 0.0
  75.         train_bar = tqdm(train_loader, file=sys.stdout)
  76.         acc1 = 0
  77.         for step, data in enumerate(train_bar):
  78.             images, labels = data
  79.             images = images.to(device)
  80.             labels = labels.to(device)
  81.             output = net(images)
  82.             optimizer.zero_grad()
  83.             loss = criterion(output, labels)
  84.             loss.backward()
  85.             optimizer.step()
  86.             running_loss += loss.item()
  87.             train_bar.desc = "train epoch[{}/{}] loss:{:.3f}".format(epoch + 1,
  88.                                                                      epochs,
  89.                                                                      loss)
  90.             predicted = torch.max(output, dim=1)[1]
  91.             acc1 += torch.eq(predicted, labels).sum().item()
  92.         train_accurate = acc1 / train_num
  93.         # validate
  94.         net.eval()
  95.         with torch.no_grad():
  96.             val_bar = tqdm(validate_loader, file=sys.stdout)
  97.             for val_data in val_bar:
  98.                 val_images, val_labels = val_data
  99.                 val_images = val_images.to(device)
  100.                 val_labels = val_labels.to(device)
  101.                 output = net(val_images)
  102.                 loss = criterion(output, val_labels)
  103.                 val_loss += loss.item()
  104.                 predict_y = torch.max(output, dim=1)[1]
  105.                 acc += torch.eq(predict_y, val_labels).sum().item()
  106.         val_accurate = acc / val_num
  107.         print('[epoch %d] train_loss: %.3f  val_accuracy: %.3f train_accuracy: %.3f' %
  108.               (epoch + 1, running_loss / train_steps, val_accurate, train_accurate))
  109.         train_losses.append(running_loss / train_steps)
  110.         if val_accurate > best_acc:
  111.             best_acc = val_accurate
  112.             save_path_epoch = os.path.join(save_path, f"resnet_{epoch + 1}_{val_accurate}.pth")
  113.             torch.save(net.state_dict(), save_path_epoch)
  114.     # 绘制损失曲线
  115.     plt.figure(figsize=(10, 8))
  116.     plt.plot(range(1, epochs + 1), train_losses, label='train')
  117.     plt.xlabel('Epoch')
  118.     plt.ylabel('Loss')
  119.     plt.title('resnet')
  120.     plt.legend()
  121.     plt.savefig('./runs/loss.jpg')
  122.     plt.show()
  123.     print('Finished Training')
  124. if __name__ == '__main__':
  125.     main()
复制代码



5 结语

  在本文中,我们深入探究了如何使用PyTorch框架和ResNet18模型,结合RAF-DB数据集来实现图像情绪识别。通过体系的数据预处置惩罚、模型构建、练习与优化,以及评估等步骤,我们成功地练习出了一个能够识别图像中人物情绪的模型。
  RAF-DB数据集作为本文的核心数据资源,展现出了其独特的代价。它是一个大规模、高质量的面部表情数据库,包含了数千张精挑细选的高分辨率面部图像,每张图像都配备了准确的表情标签。这些图像覆盖了高兴、悲伤、愤怒、惊讶等多种基本及复合表情,为模型练习提供了丰富的数据支持。此外,RAF-DB数据集还具备真实性、完整性、易用性和研究驱动等特点,确保了研究的普适性和可靠性,为情绪分析、人脸识别以及表情识别等范畴的研究者提供了名贵的资源。
  在模型选择方面,我们采用了ResNet18模型。ResNet(Residual Network,残差网络)是一种由微软亚洲研究院提出的深度神经网络结构,其核心在于通过残差毗连(residual connections)办理了深层网络练习中的梯度消失和梯度爆炸题目。在本文中,我们利用ResNet18模型对RAF-DB数据集进行了练习,并成功地构建了一个情绪识别模型。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

慢吞云雾缓吐愁

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表