之前一直在自学深度神经网络的知识,在跟着书本一步一步走的时候,感觉每一个思路,每一句代码都特别容易,实现思路清晰明了,实验代码简单易懂。但当我真正课题需要用到的时候,想跳出书本的框架,自行实现并通透其中的过程时,一上手真的是泪流满面,书本上的两三句话可能在你实验中会遇到各种各样头大的问题。。。
不过还好,自己从头到尾实现一遍,理解的深刻程度也是看书不能比的,下面记录一下我之前的笔记,欢迎交流指导
基于深度学习的表情识别
废话不多说。出发点呢,是因为想自行实现一下,看别人代码总是看得吃力,自己写一遍就印象深刻了,下面让我们来从0开始吧,这次真的是从0开始,所以前期的准备工作都要做一遍。我的实验呢是基于连续维度下的面部表情识别,注意是连续维度下(arousal-valence维度),和以往的基于分类的面部表情识别(七种或N种离散表情)在网络框架上可能有些不同,然后具体的流程可能有:
1. 数据集准备,处理
2. 网络框架自定义
3. 定义训练、测试函数
4. 在笔记本上只要代码可以跑通,就搬移到带GPU的服务器上,将代码改成在GPU上运行
先一个一个分析,最终完整的测试代码,放在文章最后。
数据集、样本选择
首先,实验基于AffectNet数据库,标注包括特征点、离散表情类、AU单元、A/V标注值等等。 这个数据库的话感觉是表情识别数据库中样本量最广,表情类别最多的数据库了吧,图像都是在网上爬的,, 和电影桥段那种数据库可不一样,感觉基于这个数据库比其他的都有挑战性。 因为是小测验,我决定选取就10000个样本吧,然后基于连续维度应该是针对arousal维度和valence维度,但这两个描述维度,涉及到要给网络定义两个不同的损失函数去分别进行学习,所以我只用1个arousal标签,省时省力。
在arousal标签值 -1 到 1之间,我将其划分为10个区间,每个区间选取1000个样本。关于对数据记得划分,以及如何选取样本,可以参考我很早之前这篇文章Affectnet数据集 按标签值对图像进行分类,然后将分成的10个CSV文件合并,合并代码很简单,可以在关于划分数据库时及处理CSV文件的问题研究记录描述这篇文章找到。
最终存储的CSV文件,包含10000个样本的地址目录,以及其arousal标注,像这样就搞定了,方便之后的实验进行:
网络框架定义
因为我的网络输出输出是连续值,不是分类值,所以网络的最终输出层应该选择线性回归层,让它输出连续的预测值。**同时这里要注意:**损失函数要采用回归类的损失函数(如MSEloss等)。
然后网络框架定义,随便定义了一个小网络(仅供参考,这是随便写的,为了保证识别准确率后期自己进行了修改),如下:
class FaceCNN(nn.Module):
# 初始化网络结构
def __init__(self):
super(FaceCNN, self).__init__()
# 第一次卷积、池化
self.conv1 = nn.Sequential(
# 输入通道数in_channels,输出通道数(即卷积核的通道数)out_channels,卷积核大小kernel_size,步长stride,对称填0行列数padding
# input:(bitch_size, 1, 48, 48), output:(bitch_size, 64, 48, 48), (48-3+2*1)/1+1 = 48
nn.Conv2d(in_channels=1, out_channels=64, kernel_size=3, stride=1, padding=1), # 卷积层
nn.BatchNorm2d(num_features=64), # 归一化
nn.ReLU(inplace=True), # 激活函数
# output(bitch_size, 64, 24, 24)
nn.MaxPool2d(kernel_size=2, stride=2), # 最大值池化
)
# 第二次卷积、池化
self.conv2 = nn.Sequential(
# input:(bitch_size, 64, 24, 24), output:(bitch_size, 128, 24, 24), (24-3+2*1)/1+1 = 24
nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(num_features=128),
nn.ReLU(inplace=True),
# output:(bitch_size, 128, 12 ,12)
nn.MaxPool2d(kernel_size=2, stride=2