简介:
本文根据利用深度学习模型,根据人脸的表情信息对人的性别进行分类,数据集来自kaggle。是一个二分类问题,比较简单,可以当做自己的第一个深度学习模型。动手深刻感受深度学习。
数据集:
原始数据集如下所示:
其中所给的训练集图像有3000张,测试集图像有1084张,同时还提供了训练集的CSV文件如下所示:
image_id | is_male | left_eye_x | left_eye_y | right_eye_x | right_eye_y | nose_x | nose_y | mouth_left_x | mouth_left_y | mouth_right_x | mouth_right_y |
0000.jpg | 1 | 152 | 186 | 236 | 183 | 196 | 234 | 158 | 262 | 231 | 260 |
0001.jpg | -1 | 146 | 191 | 232 | 192 | 190 | 229 | 155 | 263 | 228 | 264 |
0002.jpg | 1 | 157 | 188 | 229 | 188 | 194 | 235 | 164 | 258 | 224 | 257 |
0003.jpg | -1 | 152 | 210 | 232 | 210 | 191 | 248 | 159 | 280 | 222 | 281 |
0004.jpg | -1 | 151 | 189 | 237 | 189 | 192 | 227 | 155 | 254 | 232 | 254 |
其中表格中中1代表男性,-1代表女性,同时还提供了左眼,右眼,鼻子,左嘴巴端点,右嘴巴端点的坐标。以像素值表示。这些数据本文并没有用到。只用了图片作为卷积输入。
1.相关工作
1.1. 预处理:
所谓的预处理,是将计算特征之前,排除掉跟脸无关的一切干扰,因此,就有了数据增强,图片大小的重置、图片的剪裁、向量化和归一化等。
数据增强,包含在线和离线两种[1]:主流的离线数据增强,包含随机扰动,变换(旋转,平移,翻转,缩放,对齐),噪声添加如椒盐噪声,斑点噪声,以及亮度,饱和度变化,以及在眼睛之间添加2维高斯随机分布的噪声。同时,还有其他的如GAN生成脸[2],3DCNN辅助AUs生成表情。
除此之外为了方便数据输入模型,我们需要将图片存储的位置和图片的标签放到一个文本文件中,如下所示:
其中1代表男性,0代表女性。这个处理需要使用make_data_txt.py 这个文件,主要是制作训练集,测试集。里面文件路径需要替换成自己的。
1.2. 深度特征学习
主要是基于CNN的网络模型,具有非常高性能的特征表达能力[3]。特别适合基于图像的分类问题,另外一个基于DBN网络的特征提取,以及DAE转门用作特征提取。最后,还有一部分是基于序列建模的RNNs,如LSTM等。因为我们的这个作业复杂度不高,所以主要采用CNN的网络模型。
1.3.面部表示分类
主要是说,可以基于深度学习,直接学习特征,预测概率(softmax),也可以把学习的深度特征,用SVM等浅层分类器进行分类[3]。
2 本文工作
本文的核心框架为pytorch,接下来将从图片的处理方法,模型的结构和模型的结果展示三个部分进行模型说明。
2.1模型的图片处理
这里我使用pytorch的transform进行数据的预处理,主要用到了图片大小的重置、图片的剪裁、向量化和归一化。
图片大小的重置:考虑到使用两层卷积层和池化层,这里决定设置一个普通的数据256像素。原图片大小为384*384。然后进行中心剪裁,这里剪裁后的大小为224*224.
图片归一化:使得预处理的数据被限定在一定的范围内,从而消除奇异样本数据导致的不良影响。
2.2 网络结构
本模型使用的网络结构较为简单,首先是一层卷积层,然后跟着一个池化层,然后又是一层卷积跟着一个池化。接下来就是全连接层。一共有三层全连接层。经过全连接层之后将结果进行输出。这里不同的是没有使用激活函数,也是只用了两层的卷积。
对于网络参数的设置如下:
这里所有的填充都是0,所以没有添加到上述表格中。最终的输出是是一个1*2的张量,里面是预测的概率。
2.3 结果展示和分析
本模型训练的参数是总共训练10 epochs,训练的batch_size是4。使用的是CPU进行训练的。
10个epochs的准确度如下所示:
模型大概训练了40分钟,最终训练集的正确率达到99.875,测试集的正确率达到99.333。然后将此模型的识别结果提交到kaggle上时,得到的评分是99.472.
3 代码
训练模型的代码如下所示:
# coding=utf-8
import os
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.autograd import Variable
from torch.utils.data import Dataset
from torchvision import transforms, datasets, models
from PIL import Image
epochs = 10 # 训练次数
batch_size = 4 # 批处理大小
num_workers = 0 # 多线程的数目
use_gpu = torch.cuda.is_available()
PATH='./mymodel.pt'
# 对加载的图像作归一化处理, 并裁剪为[224x224x3]大小的图像
data_transform = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
class MyDataset(Dataset):
def __init__(self, txt_path, transform = None):
fh = open(txt_path, 'r')
imgs = []
for line in fh:
line = line.rstrip()
words = line.split()
imgs.append((words[0], int(words[1])))
self.imgs = imgs
self.transform = transform
def __getitem__(self, index):
fn, label = self.imgs[index]
img = Image.open(fn).convert('RGB')
if self.transform is not None:
img = self.transform(img)
return img, label
def __len__(self):
return len(self.imgs)
# 配置参数
random_state = 1
torch.manual_seed(random_state) # 设置随机数种子,确保结果可重复
torch.cuda.manual_seed(random_state)
torch.cuda.manual_seed_all(random_state)
np.random.seed(random_state)
# random.seed(random_state)
train_dataset = MyDataset(txt_path='train_data.txt', transform=data_transform)
train_loader = torch.utils.data.DataLoader(train_dataset,
batch_size=batch_size,
shuffle=True,
num_workers=num_workers)
test_dataset = MyDataset(txt_path='test.txt', transform=data_transform)
test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size, shuffle=True, num_workers=num_workers)
# 创建模型
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5)
self.maxpool = nn.MaxPool2d(2, 2)
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16 * 53 * 53, 1024)
self.fc2 = nn.Linear(1024, 512)
self.fc3 = nn.Linear(512, 2)
def forward(self, x):
x = self.maxpool(F.relu(self.conv1(x)))
x = self.maxpool(F.relu(self.conv2(x)))
x = x.view(-1, 16 * 53 * 53)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
net = Net()
if(os.path.exists('mymodel.pt')):
net=torch.load('mymodel.pt')
if use_gpu:
net = net.cuda()
print(net)
# 定义loss和optimizer
cirterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.0001, momentum=0.9)
def train():
for epoch in range(epochs):
running_loss = 0.0
train_correct = 0
train_total = 0
for i, data in enumerate(train_loader, 0):
inputs, train_labels = data
if use_gpu:
inputs, labels = Variable(inputs.cuda()), Variable(train_labels.cuda())
else:
inputs, labels = Variable(inputs), Variable(train_labels)
optimizer.zero_grad()
outputs = net(inputs)
_, train_predicted = torch.max(outputs.data, 1)
train_correct += (train_predicted == labels.data).sum()
loss = cirterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
train_total += train_labels.size(0)
print('train %d/%d epoch loss: %.3f acc: %.3f ' % (epoch + 1, epochs,running_loss / train_total, 100 * train_correct / train_total))
# 模型测试
correct = 0
test_loss = 0.0
test_total = 0
test_total = 0
net.eval()
for data in test_loader:
images, labels = data
if use_gpu:
images, labels = Variable(images.cuda()), Variable(labels.cuda())
else:
images, labels = Variable(images), Variable(labels)
outputs = net(images)
_, predicted = torch.max(outputs.data, 1)
loss = cirterion(outputs, labels)
test_loss += loss.item()
test_total += labels.size(0)
correct += (predicted == labels.data).sum()
print('test %d/%d epoch loss: %.3f acc: %.3f ' % (epoch + 1,epochs, test_loss / test_total, 100 * correct / test_total))
torch.save(net, 'mymodel.pt')
print('mymodels save!')
train()
4 总结
这个模型是个比较简单的二分类问题,因此模型也比较简单,可以帮助我们更好地理解深度学习的数据处理和输入,数据的处理流程,作为我们的第一个深度学习模型是比较好的。顺便提一下,模型没有用到特征增强,我同学有用到这一方面,准确率可以达到100%。感兴趣的小伙伴可以尝试改进模型。
完整项目以及介绍见 GitHub
引用
[1] 宋雨,王帮海,曹钢钢.结合数据增强与特征融合的跨模态行人重识别[J/OL].计算机工程与应用:1-10[2022-12-26].http://kns.cnki.net/kcms/detail/11.2127.TP.20221213.1638.017.html
[2]范丽丽. 基于深度学习的视觉特征学习关键技术研究[D].吉林大学,2022.DOI:10.27162/d.cnki.gjlin.2022.002224.
[3]杨兆龙. 面部表情识别方法研究实现[D].福州大学,2017.