意义:通过卷积神经网络(CNN)自动识别图片中的人脸表情属于哪一类情绪
1. 模型的用途
- 输入:一张人脸图片(RGB三通道,尺寸通常为48×48或类似)
- 输出:7种情绪类别的概率分布
- 情绪类别 (通常): 0. 愤怒 (Angry)
- 厌恶 (Disgust)
- 恐惧 (Fear)
- 开心 (Happy)
- 悲伤 (Sad)
- 惊讶 (Surprise)
- 中性 (Neutral)
2. 模型架构逻辑
输入: 3通道图像 (宽×高)
→ 卷积层1 (32个特征图) → 池化
→ 卷积层2 (64个特征图) → 池化
→ 卷积层3 (128个特征图) → 池化
→ 展平
→ 全连接层1 (100个神经元)
→ 全连接层2 (7个神经元 → 输出7种类别)
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
class MoodCNN(nn.Module):
def __init__(self, input_size=48, num_classes=7):
super(MoodCNN,self).__init__()
# 保存参数
self.input_size = input_size
self.num_classes = num_classes
# 卷积层
self.conv1 = nn.Conv2d(3, 32, stride=1, kernel_size=3, padding=1) # 添加padding保持尺寸
self.bn1 = nn.BatchNorm2d(32)
self.conv2 = nn.Conv2d(32, 64, stride=1, kernel_size=3, padding=1)
self.bn2 = nn.BatchNorm2d(64)
self.conv3 = nn.Conv2d(64, 128, stride=1, kernel_size=3, padding=1)
self.bn3 = nn.BatchNorm2d(128)
# 池化层
self.maxpool = nn.MaxPool2d(kernel_size=2, stride=2)
self.dropout = nn.Dropout(0.5)
# 计算全连接层输入维度
# 输入48×48 → conv(48) → pool(24) → conv(24) → pool(12) → conv(12) → pool(6)
conv_output_size = input_size // 8
self.fc_input_size = 128 * 6 * 6
# 全连接层
self.fc1 = nn.Linear(self.fc_input_size, 256)
self.fc2 = nn.Linear(256, num_classes)
def forward(self,x):
# 第一层:卷积+BN+ReLU+池化
x = self.maxpool(F.relu(self.bn1(self.conv1(x)))) # 48→24
# 第二层
x = self.maxpool(F.relu(self.bn2(self.conv2(x)))) # 24→12
# 第三层
x = self.maxpool(F.relu(self.bn3(self.conv3(x)))) # 12→6
# 展平
x=x.view(-1,128*6*6)
# 全连接层
x = F.relu(self.fc1(x))
x = self.dropout(x)
x = self.fc2(x)
return x
def test_model():
"""测试模型结构和维度"""
# 创建模型
model = MoodCNN(input_size=48)
# 打印模型结构
print("=" * 50)
print("模型结构:")
print(model)
print("=" * 50)
# 测试前向传播
batch_size = 8
test_input = torch.randn(batch_size, 3, 48, 48)
# 前向传播
output = model(test_input)
print(f"输入尺寸: {test_input.shape}")
print(f"输出尺寸: {output.shape}")
# 验证输出维度
assert output.shape == (batch_size, 7), f"输出维度错误: {output.shape}"
# 统计参数
total_params = sum(p.numel() for p in model.parameters())
trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f"\n总参数量: {total_params:,}")
print(f"可训练参数量: {trainable_params:,}")
# 测试训练流程
print("\n" + "=" * 50)
print("测试训练循环...")
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 模拟一步训练
labels = torch.randint(0, 7, (batch_size,))
loss = criterion(output, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(f"训练损失: {loss.item():.4f}")
print("训练测试通过!")
print("=" * 50)
return model
def inference_example():
"""推理示例"""
model = MoodCNN()
model.eval() # 切换到评估模式
# 模拟单张图像推理
with torch.no_grad():
single_image = torch.randn(1, 3, 48, 48)
logits = model(single_image)
probabilities = torch.softmax(logits, dim=1)
predicted_class = torch.argmax(probabilities, dim=1)
print(f"预测结果: 第{predicted_class.item()}类")
print(f"各类概率: {probabilities.squeeze().tolist()}")
def train_example():
"""完整的训练示例"""
# 创建模型
model = MoodCNN()
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1)
# 模拟数据
batch_size = 32
train_data = torch.randn(1000, 3, 48, 48)
train_labels = torch.randint(0, 7, (1000,))
dataset = TensorDataset(train_data, train_labels)
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)
# 训练循环
num_epochs = 5
for epoch in range(num_epochs):
running_loss = 0.0
correct = 0
total = 0
for batch_idx, (inputs, targets) in enumerate(dataloader):
# 前向传播
outputs = model(inputs)
loss = criterion(outputs, targets)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 统计准确率
_, predicted = torch.max(outputs, 1)
total += targets.size(0)
correct += (predicted == targets).sum().item()
running_loss += loss.item()
# 调整学习率
scheduler.step()
# 打印统计信息
avg_loss = running_loss / len(dataloader)
accuracy = 100 * correct / total
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {avg_loss:.4f}, Accuracy: {accuracy:.2f}%')
return model
if __name__ == "__main__":
print("1️⃣ 测试模型结构")
model = test_model()
print("\n2️⃣ 推理示例")
inference_example()
print("\n3️⃣ 完整训练示例")
print("开始训练(使用模拟数据)...")
trained_model = train_example()
print("训练完成!")
测试结果:
1️⃣ 测试模型结构
==================================================
模型结构:
MoodCNN(
(conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(bn1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, bias=True, track_running_stats=True)
(conv2): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, bias=True, track_running_stats=True)
(conv3): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(bn3): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, bias=True, track_running_stats=True)
(maxpool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(dropout): Dropout(p=0.5, inplace=False)
(fc1): Linear(in_features=4608, out_features=256, bias=True)
(fc2): Linear(in_features=256, out_features=7, bias=True)
)
==================================================
输入尺寸: torch.Size([8, 3, 48, 48])
输出尺寸: torch.Size([8, 7])
总参数量: 1,275,399
可训练参数量: 1,275,399
==================================================
测试训练循环...
训练损失: 2.0871
训练测试通过!
==================================================
2️⃣ 推理示例
预测结果: 第4类
各类概率: [0.15108568966388702, 0.13161693513393402, 0.1429980993270874, 0.13378497958183289, 0.15436622500419617, 0.14441630244255066, 0.14173167943954468]
3️⃣ 完整训练示例
开始训练(使用模拟数据)...
Epoch [1/5], Loss: 2.6241, Accuracy: 14.90%
Epoch [2/5], Loss: 1.9481, Accuracy: 13.00%
Epoch [3/5], Loss: 1.9470, Accuracy: 11.50%
Epoch [4/5], Loss: 1.9454, Accuracy: 14.50%
Epoch [5/5], Loss: 1.9449, Accuracy: 14.20%
训练完成!