PyTorch从零手写CNN实现FER2013人脸表情识别(可视化最强·完整实战·单篇完结)
1. 项目概述
人脸表情识别是人机交互、情感计算、智能监控、心理分析领域的核心基础任务。相较于物体分类、缺陷检测,表情识别高度依赖细微面部纹理、眉眼嘴局部特征变化,类间差异极小、极易混淆,对模型特征提取能力与细节感知能力要求更高。本文基于PyTorch从零搭建专属CNN网络,针对人脸灰度表情特征优化结构,完整实现FER2013数据集七类表情分类全流程。项目包含精细化人脸数据预处理、针对性数据增强、表情专用CNN模型、完整训练验证、Loss/Acc曲线可视化、样本预测可视化、错例分析,可视化维度为五套项目中最丰富,可直接用于课程大作业、毕设、竞赛 baseline。
2. 数据集介绍
FER2013(Facial Expression Recognition 2013)是业界通用的人脸表情识别标准数据集,广泛用于表情算法评测、情感计算模型训练,数据来源于真实网络人脸图像,包含光照变化、轻微侧脸、遮挡、肤色差异等真实场景干扰,贴合实际落地场景。
数据集核心参数:
-
样本总量:35887张人脸图像
-
图像尺寸:统一48×48单通道灰度人脸图
-
分类类别:7类基础人脸表情
-
具体标签:愤怒、厌恶、恐惧、开心、平淡、悲伤、惊讶
-
数据特点:部分类别样本不均衡、恐惧与悲伤、平淡与悲伤类间相似度极高,识别难度大
-
任务属性:细粒度情感分类,依赖人脸微特征提取
3. 环境依赖
通用深度学习环境,支持全平台CPU/GPU训练,兼容前面所有项目环境。
pip install torch torchvision matplotlib numpy -i https://pypi.tuna.tsinghua.edu.cn/simple
4. 数据集预处理与专属数据增强
针对人脸表情识别特性设计增强策略:人脸无固定朝向、轻微平移缩放不改变表情类别,因此采用轻微旋转、平移、水平翻转;摒弃剧烈变换,避免破坏五官相对位置导致表情语义失真。训练集强增强提升泛化,测试集纯净标准化,保证评测公平性。
import torch
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import numpy as np
# FER2013标准化参数
mean = (0.5,)
std = (0.5,)
# 人脸专属增强:不破坏五官结构
train_transform = transforms.Compose([
transforms.RandomHorizontalFlip(p=0.5),
transforms.RandomRotation(10),
transforms.RandomAffine(degrees=0, translate=(0.1, 0.1)),
transforms.ToTensor(),
transforms.Normalize(mean, std)
])
test_transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(mean, std)
])
# 加载数据集
train_dataset = torchvision.datasets.FER2013(
root="./fer2013", split="train", download=True, transform=train_transform
)
test_dataset = torchvision.datasets.FER2013(
root="./fer2013", split="test", download=True, transform=test_transform
)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=0)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False, num_workers=0)
# 表情标签对应
emotion_labels = ["愤怒", "厌恶", "恐惧", "开心", "平淡", "悲伤", "惊讶"]
print(f"训练集样本数:{len(train_dataset)}")
print(f"测试集样本数:{len(test_dataset)}")
print("表情类别:", emotion_labels)
# 人脸样本可视化
def show_fer_sample():
data_iter = iter(train_loader)
images, labels = next(data_iter)
img = torchvision.utils.make_grid(images[:8])
img = img / 2 + 0.5
np_img = img.numpy().transpose(1, 2, 0)
plt.figure(figsize=(12,6))
plt.imshow(np_img, cmap="gray")
plt.title("FER2013人脸表情样本")
plt.axis("off")
plt.show()
show_fer_sample()
5. 人脸表情专用CNN网络模型
针对48×48小尺寸灰度人脸、细微表情特征设计专用深层CNN结构。多层卷积逐级提取五官边缘、纹理、五官相对位置特征,搭配BN层加速收敛、Dropout抑制过拟合,适配表情细粒度区分难点。网络参数量适中,兼顾精度与训练速度,专门适配人脸表情分类任务。
import torch.nn as nn
import torch.nn.functional as F
class FER2013_CNN(nn.Module):
def __init__(self, num_classes=7):
super(FER2013_CNN, self).__init__()
# 特征提取模块1
self.conv1 = nn.Conv2d(1, 64, kernel_size=3, padding=1)
self.bn1 = nn.BatchNorm2d(64)
self.conv2 = nn.Conv2d(64, 64, kernel_size=3, padding=1)
self.bn2 = nn.BatchNorm2d(64)
self.pool1 = nn.MaxPool2d(2, 2)
self.drop1 = nn.Dropout(0.3)
# 特征提取模块2
self.conv3 = nn.Conv2d(64, 128, kernel_size=3, padding=1)
self.bn3 = nn.BatchNorm2d(128)
self.conv4 = nn.Conv2d(128, 128, kernel_size=3, padding=1)
self.bn4 = nn.BatchNorm2d(128)
self.pool2 = nn.MaxPool2d(2, 2)
self.drop2 = nn.Dropout(0.4)
# 特征提取模块3
self.conv5 = nn.Conv2d(128, 256, kernel_size=3, padding=1)
self.bn5 = nn.BatchNorm2d(256)
self.conv6 = nn.Conv2d(256, 256, kernel_size=3, padding=1)
self.bn6 = nn.BatchNorm2d(256)
self.pool3 = nn.MaxPool2d(2, 2)
self.drop3 = nn.Dropout(0.5)
# 分类头
self.fc1 = nn.Linear(256 * 6 * 6, 1024)
self.drop4 = nn.Dropout(0.5)
self.fc2 = nn.Linear(1024, num_classes)
def forward(self, x):
x = F.relu(self.bn1(self.conv1(x)))
x = F.relu(self.bn2(self.conv2(x)))
x = self.pool1(x)
x = self.drop1(x)
x = F.relu(self.bn3(self.conv3(x)))
x = F.relu(self.bn4(self.conv4(x)))
x = self.pool2(x)
x = self.drop2(x)
x = F.relu(self.bn5(self.conv5(x)))
x = F.relu(self.bn6(self.conv6(x)))
x = self.pool3(x)
x = self.drop3(x)
x = x.view(-1, 256 * 6 * 6)
x = F.relu(self.fc1(x))
x = self.drop4(x)
x = self.fc2(x)
return x
# 设备适配
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = FER2013_CNN().to(device)
print("人脸表情识别模型初始化完成")
print(model)
6. 模型训练与验证
针对表情数据集样本不均衡、难样本多的特点,采用交叉熵损失函数,Adam优化器配合适中学习率,避免步长过大跳过最优解。严格区分训练、测试模式,全程记录损失与精度数据,保证实验可复现。
import torch.optim as optim
# 超参数
EPOCHS = 50
LR = 0.0009
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=LR)
# 记录指标
train_loss_list = []
train_acc_list = []
test_loss_list = []
test_acc_list = []
for epoch in range(EPOCHS):
# 训练
model.train()
train_loss = 0.0
train_correct = 0
total_train = 0
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
train_loss += loss.item()
_, predicted = torch.max(outputs.data, 1)
total_train += labels.size(0)
train_correct += (predicted == labels).sum().item()
avg_train_loss = train_loss / len(train_loader)
train_acc = 100 * train_correct / total_train
# 测试
model.eval()
test_loss = 0.0
test_correct = 0
total_test = 0
with torch.no_grad():
for images, labels in test_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
test_loss += loss.item()
_, predicted = torch.max(outputs.data, 1)
total_test += labels.size(0)
test_correct += (predicted == labels).sum().item()
avg_test_loss = test_loss / len(test_loader)
test_acc = 100 * test_correct / total_test
train_loss_list.append(avg_train_loss)
train_acc_list.append(train_acc)
test_loss_list.append(avg_test_loss)
test_acc_list.append(test_acc)
print(f"第{epoch+1:2d}轮 | 训练损失:{avg_train_loss:.4f} | 训练精度:{train_acc:.2f}% | 测试损失:{avg_test_loss:.4f} | 测试精度:{test_acc:.2f}%")
# 保存模型
torch.save(model.state_dict(), "fer2013_cnn_best.pth")
print("人脸表情识别模型保存成功")
7. 超强可视化模块(本项目特色)
包含训练曲线可视化 + 批量预测结果可视化 + 真实标签VS预测标签对比,是五套项目中可视化效果最丰富、报告展示效果最好的一套。
7.1 损失与精度曲线可视化
plt.rcParams["font.family"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.plot(train_loss_list, label="训练损失", color="#e74c3c")
plt.plot(test_loss_list, label="测试损失", color="#3498db")
plt.title("表情识别损失变化曲线")
plt.xlabel("迭代轮数")
plt.ylabel("损失值")
plt.legend()
plt.subplot(1, 2, 2)
plt.plot(train_acc_list, label="训练精度", color="#e74c3c")
plt.plot(test_acc_list, label="测试精度", color="#3498db")
plt.title("表情识别准确率变化曲线")
plt.xlabel("迭代轮数")
plt.ylabel("准确率(%)")
plt.legend()
plt.tight_layout()
plt.show()
7.2 人脸表情预测结果对比可视化
def visualize_prediction():
model.eval()
data_iter = iter(test_loader)
images, labels = next(data_iter)
images = images.to(device)
with torch.no_grad():
outputs = model(images)
_, preds = torch.max(outputs, 1)
# 反归一化
images = images.cpu() * 0.5 + 0.5
images = images.numpy().transpose(0, 2, 3, 1)
# 展示前12张预测结果
plt.figure(figsize=(14, 8))
for i in range(12):
plt.subplot(3, 4, i+1)
plt.imshow(images[i], cmap="gray")
true_label = emotion_labels[labels[i]]
pred_label = emotion_labels[preds[i]]
color = "green" if true_label == pred_label else "red"
plt.title(f"真实:{true_label}\n预测:{pred_label}", color=color)
plt.axis("off")
plt.tight_layout()
plt.show()
visualize_prediction()
8. 创新优化与拓展方向
-
引入注意力机制,聚焦眉眼嘴关键表情区域,抑制人脸背景无效信息
-
添加混淆矩阵,精准统计各类表情错分比例,针对性优化难样本
-
使用加权交叉熵损失,解决FER2013样本不均衡问题
-
引入学习率衰减,提升模型后期收敛精度
-
替换ResNet、VGG等经典网络,对比不同骨干网络表情识别性能
-
结合摄像头实时推理,实现动态实时人脸表情识别系统
9. 常见问题解决
-
表情识别精度偏低:该数据集本身难度高,属于正常现象,可通过注意力机制、迁移学习提升
-
训练过拟合:提升Dropout系数、加强数据增强、降低学习率
-
数据集下载失败:手动下载放入对应目录,关闭网络代理重试
-
预测图标签乱码:代码内置中文字体适配,直接运行即可正常显示
更多推荐




所有评论(0)