- 1 概述
- 2 数据集
- 2.1 数据集下载
- 2.2 数据规模
- 2.3 数据格式
- 3 模型说明
- 3.1 Lenet7
- 3.2 CliqueNet
- 4 代码分析
- 4.1 cliquenet.py中forward函数
- 4.2 cliquenet.py中stage1和stage2函数
- 4.3 cliquenet.py中transition函数
- 5 结果分析
- 5.1 使用cliqueNet网络优点
- 5.2 参数优化
- 5.3 实验环境
- 5.4 模型效果
- 5.5 总结
- 6 源码下载
- 相关笔记
任务目标:人脸表情识别 数据来源:kaggle 模型设计:Lenet7和CliqueNet 度量标准:准确率 实验分类:8类(愤怒 恶心 害怕 快乐 悲伤 惊讶 蔑视 面无表情)
2 数据集 2.1 数据集下载由Microsoft对kaggle提出的数据集进行重新标注的数据集 数据集下载
2.2 数据规模训练集:测试集:验证集 = 8:1:1 1P = 48 * 48 = 2304像素
-csv -3个属性:Label Pixels Usage 实际内容:人脸(灰度) 8中表情:愤怒 恶心 害怕 快乐 悲伤 惊讶 蔑视 面无表情(No Expression) 面部基本居中,大小相似 预处理数据下载
Lenet7原来的网络使用两层卷积层和两层池化层,在实践中增大了模型深度,更好的适应当前数据特征 增加一个卷积和池化层
增加一层全连接层。
作者虽然实现了LEnet7的网络进行了测试,对比CliqueNet后,后者的效果更好,这里就没有详细介绍Lenet5以及它的源码。以下都是介绍CliqueNet的。
3.2 CliqueNet(1)简介 由DenseNet启发 各Layer间双向连接 stage1: 浅层–>高层特征 stage2: 近层更新远层
(2)网络结构 本项目中由于数据集复杂程度不高,使用l三个block,每个block提取特征进行预测。浅层提取到了细节特征,深层提取到了全局信息。 此外在网络中使用Transition模块,使用channel-wise attention来给不同的channel赋予不同的权重。
以下只分析关cliquenet.py文件中关键代码,其余代码请下载完整的参考。
4.1 cliquenet.py中forward函数其中block模块主要对一维和高维图像特征进行提取 transition模块主要通过channel-wise attention机制对cannel的结构进行优化,以使得channel都获得所有维度的特征信息,有益于后面过程的学习。
# 定义了cliquenet的前向传播函数
def forward(x, train=True, regularizer=None):
# 得到之后进行卷积的卷积核张量,输入channel为1,输出channel为64,kernel size为3
w = get_weight([3, 3, 1, 64], 0.1, regularizer)
# 先进行一次卷积,步长为2,使得feature map维度减半,使得模型提取到浅层特征
x = conv2d(x, w, 2)
# 对数据进行批归一化,加快网络训练,减轻梯度消失
x = bn(x, train)
# 使用ReLU激活函数使得模型非线性
x = tf.nn.relu(x)
# 对数据进行最大池化,使得feature map的维度减半
x = tf.nn.max_pool(x, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding='SAME')
## block1
# 将数据送入clique block,block内每一个结点的channel数设为36,一个block内除输入结点外有5个结点
x, feature1 = clique_block(x, regularizer, 64, 36, 3, 5, train)
# 对第一个block提取到的特征信息进行global pooling
feature1 = tf.nn.avg_pool(feature1, ksize=[1, 12, 12, 1], strides=[1, 12, 12, 1], padding='SAME')
# 将上一层的五个更新后结点作为输入,经过transition调整feature map的维度,并利用attention强化特征信息更优的channel,使得下一个block可以更好地利用前层特征信息
x = transition(x, regularizer, 180, 180, 12, train)
## block 2
# 将数据送入clique block,block内每一个结点的channel数设为36,一个block内除输入结点外有5个结点
x, feature2 = clique_block(x, regularizer, 180, 36, 3, 5, train)
# 对第二个block提取到的特征信息进行global pooling
feature2 = tf.nn.avg_pool(feature2, ksize=[1, 6, 6, 1], strides=[1, 6, 6, 1], padding='SAME')
# 将上一层的五个更新后结点作为输入,经过transition调整feature map的维度,并利用attention强化特征信息更优的channel,使得下一个block可以更好地利用前层特征信息
x = transition(x, regularizer, 180, 180, 6, train)
## block3
# 将数据送入clique block,block内每一个结点的channel数设为36,一个block内除输入结点外有5个结点
_, feature3 = clique_block(x, regularizer, 180, 36, 3, 5, train)
# 对第三个block提取到的特征信息进行global pooling
feature3 = tf.nn.avg_pool(feature3, ksize=[1, 3, 3, 1], strides=[1, 3, 3, 1], padding='SAME')
# 将不同层间获取的不同特征进行融合,浅层的特征包含更多的细节信息,而深层特征则包含更多的全局信息
out = tf.concat([feature1, feature2, feature3], axis=3)
# 将张量转换为一个长向量,以用于之后的全连接层
x = tf.reshape(out, [-1, 964])
# 将向量输入全连接层,从而输出对每一个分类的预测分数
x = fc(x, 964, 7, 0.01, regularizer)
# 返回该神经网络的预测结果
return x
4.2 cliquenet.py中stage1和stage2函数
stage1的公式 代码结构是 layer1 = encoding(input) layer2 =encoding(layer1,input) layer3 = encoding(layer2,input) … layer5 = encoding(layer4,input) 因此layer5中存在提取的高维提取特征,并且有很多重复的获取的低维特征。以使得低维和高维的特征共同被利用,加强了information flow,能有效避免梯度消失。
# 定义了stage1阶段
def stage1(x0, w0, w, in_channel, filters, layers=5, train=True):
# 使用输入结点对每一个之外的结点进行初始化
for i in range(layers):
# 如果是第一个结点
if i == 0:
# 就取w0中的第一个张量作为连接输入结点与第一个结点的卷积核
weight = w0[i]
# 第一个结点在stage1只与输入结点相连接
data = x0
# 如果不是第一个结点
else:
# 就取w0中对应的结点以及w中对应的结点拼成之后使用的卷积核
weight = tf.concat([w0[i]] + [w[4 * num + i - 1] for num in range(i)], axis=2)
# 使用拼出的卷积核与前面的结点生成一个新的结点
x = conv2d(data, weight)
# 对数据进行批归一化,加快网络训练,减轻梯度消失
x = bn(x, train)
# 使用ReLU激活函数使得模型非线性
x = tf.nn.relu(x)
# 使用dropout随机使部分结点归零,从而使得模型不易过拟合
x = dropout(x, train)
# 将已经生成的结点concat在一起用于生成下一个结点
data = tf.concat([data, x], axis=3)
# 除去输入结点与第一个结点(第一个结点在stage2的第一步即需要被更新,因此不需要传入下一阶段)
_, x = tf.split(data, [in_channel + filters, filters * (layers - 1)], axis=3)
# 将得到的后面若干个结点传入stage2
return x
stage2:迭代更新layers layer1 = encoding(layer2~5) layer2 =encoding(layer1,3,4,5) layer3 = encoding(layer1,2,4,5)
...
layer5 = encoding(layer1,2,3,4)
# 定义了stage2模块
def stage2(x, w, in_channel, filters, layers=5, train=True):
# 对block内每一个结点进行更新
for i in range(layers):
# 取w中对应的结点拼成卷积核
weight = tf.concat([w[4 * num + i - 1] for num in range(i)] + [w[4 * num + i] for num in range(i + 1, layers)], axis=2)
# 使用拼出的卷积核与最近更新的结点更新最早的结点
data = conv2d(x, weight)
# 对数据进行批归一化,加快网络训练,减轻梯度消失
data = bn(data, train)
# 使用ReLU激活函数使得模型非线性
data = tf.nn.relu(data)
# 使用dropout随机使部分结点归零,从而使得模型不易过拟合
data = dropout(data, train)
# 若不为更新最后一个结点
if i != layers - 1:
# 则将更新最早的结点去除
_, x = tf.split(x, [filters, filters * (layers - 2)], axis=3)
# 将最新更新的结点同其他结点concat在一起,对于非最后结点相当于替换最早更新的结点
x = tf.concat([data, x], axis=3)
# 返回经过stage2后的所有经过更新的结点
return x
4.3 cliquenet.py中transition函数
将layer在channel层面做attention,使得整个layer都学习到上下文的信息。
# 定义了在多个block之间的转换模块
def transition(x, regularizer, in_channel, out_channel, size, train, use_attention=True):
# 得到之后进行的1✖1卷积的卷积核张量,输入channel与输出channel为给定值,在该网络中由于较浅,因此暂不使用compression机制,输入channel与输出channel数相等
w = get_weight([1, 1, in_channel, out_channel], 0.1, regularizer)
# 根据上述卷积核进行卷积,步长为1,使得feature map维度不变
x = conv2d(x, w, 1)
# 对数据进行批归一化,加快网络训练,减轻梯度消失
x = bn(x, train)
# 使用ReLU激活函数使得模型非线性
x = tf.nn.relu(x)
# 使用dropout随机使部分结点归零,从而使得模型不易过拟合
x = dropout(x, train)
# 选择是否使用attention机制,这里采用了channel-wise的attention机制来在转化过程赋予不同channel不同权重,从而使得下一个block的学习效果更好
if use_attention:
# 对数据进行global pooling,从而对于对于每一个channel得到一个值
attention = tf.nn.avg_pool(x, ksize=[1, size, size, 1], strides=[1, size, size, 1], padding='SAME')
# 将得到的每一个张量转换为一个长度为channel数的长向量
attention = tf.reshape(attention, [-1, out_channel])
# 将获得的权重输入一个全连接层,从而对权重进行学习
attention = fc(attention, out_channel, out_channel // 2, 0.01, regularizer)
# 使用ReLU激活函数使得attention的学习模块非线性
attention = tf.nn.relu(attention)
# 将获得的权重输入一个全连接层,从而对权重进行更深的学习
attention = fc(attention, out_channel // 2, out_channel, 0.01, regularizer)
# 使用sigmoid激活函数使得学习到的权重处于0-1的范围内
attention = tf.sigmoid(attention)
# 增大张量维度以方便数据与attention的权重的相乘
attention = tf.expand_dims(attention, 1)
# 增大张量维度以方便数据与attention的权重的相乘
attention = tf.expand_dims(attention, 1)
# 将学习到的channel-wise attention权重分别乘以各自的channel
x = tf.multiply(x, attention)
# 对数据进行最大池化,使得feature map的维度减半,使得下一个block可以学习到更深层的信息
x = tf.nn.avg_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
# 返回经过transition转换后的数据,用于传入下一个block
return x
5 结果分析
5.1 使用cliqueNet网络优点
增强information flow 减少内存 加速训练 避免网络退化 保证实时性
5.2 参数优化进行了数据增强 使用了Validation调整优化器与超参数 比较了Adam、Momentum、SGD优化器,其中Momentum收敛较慢,SGD容易梯度消失。最终选择Adam优化器 Dropout = 0.95
5.3 实验环境操作系统:Ubuntu 16.04LTS GPU:GTX 1080TI Python版本: Python3.5 TensorFlow版本:1.11.0
5.4 模型效果fer2013 Rank最好成绩是0.71。以下是实验结果,最高准确率只有0.653.
真实数据测试如下,比如周星驰图片中的泪光不够明显,影响了模型的预测结果,得出结论:如何增加标志性特征的对比度是非常重要的。
以下是表情包的测试,图片中没有一个标准的人脸形状时,模型的准确率也不高。
通过预训练的方式来找到较好的收敛特征。fine-tune:增光数据集上学习率较大的coarse预训练 使用图像分割的方式,矫正面部位置
6 源码下载项目源码下载
相关笔记以下所有源码以及更详细PDF笔记请在github下载 TensorFolwNotebook-from-Peking-University
- 【北京大学】1 TensorFlow1.x中Python基础知识
- 【北京大学】2 TensorFlow1.x的张量、计算图、会话
- 【北京大学】3 TensorFlow1.x的前向传播推导与实现
- 【北京大学】4 TensorFlow1.x的反向传播推导与实现
- 【北京大学】5 TensorFlow1.x的损失函数和交叉熵举例讲解及实现
- 【北京大学】6 TensorFlow1.x的学习率、滑动平均和正则化实例及实现
- 【北京大学】7 TensorFlow1.x的神经网络模块设计思想举例及实现
- 【北京大学】8 TensorFlow1.x的Mnist数据集实例实现
- 【北京大学】9 TensorFlow1.x的实现自定义Mnist数据集
- 【北京大学】10 TensorFlow1.x的卷积神经网络(CNN)相关基础知识
- 【北京大学】11 TensorFlow1.x的卷积神经网络模型Lenet5实现
- 【北京大学】12 TensorFlow1.x的卷积神经网络模型VGGNet实现
- 【北京大学】13 TensorFlow1.x的项目实战之手写英文体识别OCR技术
- 【北京大学】14 TensorFlow1.x的二值神经网络实现MNIST数据集手写数字识别
- 【北京大学】15 TensorFlow1.x的项目实战之人脸表情识别
- 【北京大学】16 TensorFlow1.x的项目实战之图像风格融合与快速迁移