1. 设计思想
对于人类来说,描述一张图片的内容是非常重要的。但因这个过程并没有标准答案,因此对于计算机来说这并不是一个简单地过程。我们希望通过本次实验能够设计一个模型完成让计算机给图片设定 caption 的目标。更进一步,如果在图片中检测到人脸,我们希望能识别出人的情绪表情。最终呈现出如图 3.1 的效果:
图 3.1 实现目标
2. 实验环境和工具
Jupyter Notebook:
。Tensorflow
。Keras
3. 实验过程
3.1 看图说话
3.1.1 数据集介绍
Flickr8k Dataset:该数据集已经成为研究基于句子的图片描述的基准,该数据集包括了 8052 张图片,每张图片包括了 5 句相关的描述性句子,示例如下:
图 1 数据集的示例
3.1.2 实验环境和工具
Jupyter Notebook:
。Tensorflow
。Keras
3.1.3 数据预处理
3.1.3.1 基本的数据清理
大写转换为小写,删除标点符号,去除单复数等,实现效果如图 4.1 所示:
图 4.1 原数据表示
图 4.2 处理后的数据表示
3.1.3.2 Unique words 的统计
将所有在描述语言中出现过的单词组成一个 vocabulary,统计在 vocabulary 中出现过的单词。起初计算出约 40000 个语句中总共出现 8763 个单词,但由于许多单词只出现两三次,对于预测性的模型来说,无实质性的帮助,因此接下来我们只考虑在所有语句中出现次数大于十的单词,计算出此时的 vocabulary 中就变为 1651 个单词。更进一步,我们还要多增加一个 0 padding,因此总单词数为 1652。可参考图 4.3 的流程:
图 4.3 Unique words 统计
3.1.3.3 特征向量的提取
运用 InceptionV3 模型将图片转换为一个固定长度(length=2018)的向量,使其可以作为输入到神经网路。
InceptionV3 原来是给图片分类的模型,由于我们的目标只是提取图片的特征向量,我们就移去了最后的 softmax 层,从倒数第二层中提取特征向量,如图 4.4 所示:
图 4.4 特征向量的提取
3.1.3.4 词编码
将每个 vocabulary 中的词编码为一个固定大小的向量,并创建两个 Pyhon 的 Dictionary,分别为 wordtoix[‘abc’]:返回’abc’的索引;ixtoword[k]:返回索引为“k“的单词,每个单词的索引为 1-1652 的其中一个整数。
3.1.3.5 计算长度
计算 caption 的最大长度:34
3.1.3.6 data matrix 的构建过程
(在此举一个例子以更好地阐释)
eg. 以两张训练图片一张测试图片组成,如图 4.5 所示:
图 4.5 实例
将 image1、image2 转换为长度为 2048 地特征向量
给清理过的 caption 加上头尾标志(startseq、endseq)
Caption_1 -> “startseq the black cat sat on grass endseq”
Caption_2 -> “startseq the white cat is walking on road endseq”
vocab = {black, cat, endseq, grass, is, on, road, sat, startseq, the, walking, white}
给 vocabulary 中的单词分配整数索引
black -1, cat -2, endseq -3, grass -4, is -5, on -6, road -7, sat -8, startseq -9, the -10, walking -11, white -12
为了预测 caption 中的第 t+1 个单词,我们可以通过前 t 个单词组成的部分的 caption 和图片的特征向量来进行。预测 caption 从 startseq 开始直到 endseq 结束,如图 4.6 所示:
图 4.6 单词依次预测以组成 caption
将每个单词以索引来表示,效果如 4.7 所示:
图 4.7 将 partial caption 用索引表示
将 caption 补全为同一长度,统一的长度即为之前计算出的 caption 最大数 34,补全的元素为 0,即所谓的 0 padding,效果如 4.8 所示:
图 4.8 zero padding
3.1.4 模型搭建
图 5.1 模型的流程思路
如图 5.1 所示,我们希望以 partial caption 和图片的特征向量为输入,因此起初会有两个 tensor。首先 partial caption 经过预处理得到长度为 34 的向量后经过一个 embedding 层,把每个单词都映射到一个长度为 200 的向量,经过一层 Dropout 防止过拟合,之后经过一层 LSTM(选择 LSTM 的原因:LSTM 在自然语言的处理中能发挥不错的作用,并且相比普通的 RNN,LSTM 在更长的序列中有更好的表现)得到一个(batch_size,256)的输出。
同时,图片的特征向量经过一层 Dropout 防止过拟合,之后再经过一层全连接层同样得到一个(batch_size,256)的输出。
我们把两个格式相同的 tensor 合为一个,以便更好的训练得出最终结果,之后再经过一个全连接层后,经最后一层 softmax 层,产生涵盖 1652 个在 vocabulary 出现的单词的概率分布,基于 greedy search,概率分布最大的单词即我们要选择的输出单词。具体实例如图 5.2 所示
图 5.2 迭代的具体实现实例
迭代循环的终止条件有两个:
以“endseq“结尾,模型认为 caption 已经完成
句长大于 34,为了避免一直迭代下去,强制终止
3.1.5 模型的训练
我们训练这个模型设定了 epoch 为 30,前 20 个 epoch 的学习率设为 0.001,batch size 设为 3。当完成了 20 次迭代后,将学习率降为 0.0001 并且将 batch size 设为 6。用这些超参数的原因是因为当训练到达后半程时,模型逐渐趋向平缓,我们必须减小学习率才能在最低点边缩小步长,以趋近最低点。并且,适当的增加 batch size 使梯度的更新更加有效。
3.1.6 模型评估
本模型的预测结果使用 BLEU 进行预测。
BLEU 能作为机器翻译的一个评估指。它采用了 N-gram 的匹配规则,能够算出比较译文和参考译文之间 n 组词的相似的一个占比。随着 n-gram 的增大,总体的精度得分是呈指数下降的,所以一般 N-gram 最多取到 4-gram。
一般情况 1-gram 可以代表原文有多少词被单独翻译出来,可以反映译文的充分性,2-gram 以上可以反映译文的流畅性,它的值越高说明可读性越好。这两个指标是能够跟人工评价对标的。
图 5.3.1 1-gram 准确度(该例为 5/6)
图 5.3.2 2-gram 准确度(该例为 3/5)
图 5.3.3 3-gram 准确度(该例为 1/4)
N-gram 的一个弊端是其译文准确度的匹配关系不能很好地体现译文长度不准确的问题。因此,针对翻译译文长度比参考译文要短的情况,就需要一个惩罚的机制去控制。在此便引入了惩罚因子的概念。惩罚因子的计算公式如下:
图 5.4 惩罚因子 BP 的计算
C 是测试译文的词数,r 是参考译文的词数
BLEU 算法就是在这两个概念的基础上整合得到,其计算公式如图 5.5 所示,BLEU 值越大表示测试译文与参考译文越接近,反之则差别越大。
图 5.5 BLEU 算法的计算公式
经过分析,我们发现 BLEU 尽管在一定程度上可以作为测试出的 caption 和原 caption 的评估指标,也比较方便和快捷,但它无法考虑语法上的准确性,测评的精度也会收到常用词的干扰。同时 BLEU 无法考虑同义词或相似表达的情况,因此作为该实验的评估指标还是存在一定的缺陷。
3.1.7 测试结果
3.1.7.1 较理想的测试结果:
图 6.1 比较理想的测试结果
3.1.7.2 不太理想的测试结果
图6.2 不太理想的测试结果
3.1.7.3 结果分析:
针对实验结果,我们发现测试样例中有匹配度高的 caption,也有结果不太理想的测试结果,分析后我们认为:部分测试对图片中的数目、颜色或人物性别的检测出现差错,可能是特征提取的弊端造成的,因此未来优化时可以采取更好的特征提取模型(如 vgg 等);此外由于该数据集不够大,对模型的训练并不能达到很理想的效果,因此我们需要更大的数据集去训练它(实际操作中,我们有尝试用更大的数据集,但因为 CPU 的限制,最终没有跑出来);针对优化,我们还提出了可以调整超参数优化模型训练、用交叉验证集避免过拟合、并寻找比 BLEU 更好的检测结果的方法等。
3.2 微表情识别
3.2.1 数据集
3.2.1.1 来源
– Human Images Source-CK+: http://www.consortium.ri.cmu.edu/ckagree/
– Human Images Source-Kaggle Dataset: https://www.kaggle.com/jonathanoheix/face-expression-recognition-dataset
– Human Images Source-Jaffebase Dataset: http://www.kasrl.org/jaffe.html
– Animated Images Source-FERG_DB: https://grail.cs.washington.edu/projects/deepexpr/ferg-db.htmlhttps://grail.cs.washington.edu/projects/deepexpr/ferg-db.html)
3.2.1.2 特点展示
CK+ Dataset:
最左边的每个文件夹对应一个人;
中间的每个文件夹对应一个人的一组表情序列图片;
最右边的一组图片记录的是从面无表情到表情饱满的过程:见下图
Kaggle Dataset:已经划分好 train 和 validation 集合,见下图:
Jaffebase Dataset:文件夹中所有的图片都可以通过文件名字划分表情类别,见下图
FERG_DB Dataset:动漫表情,且图片已经按照表情类别分类,见下图:
3.2.2 实验思路
目的:对输入的图像检测人脸,并且基于人类的七种基本表情,对输入图像的人脸进行情绪识别
将人类和动漫表情划分为 7 种不同的类别,划分后每一种表情都有两个文件夹,分别人类和动漫表情,所以总计一共有 14 个文件夹;
将每一个文件夹中的文件读入成为一个 DataFrame,总计 14 个 DataFrame,将所有人类表情的 DataFrame 结合起来,将所有的动漫表情的 DataFrame 结合起来,所有表情总计 1 万多张
最后得到两个 DataFrame,一个是人类表情,一个是动漫表情。
七种表情分别是:
-ANGER -DISGUST -FEAR -HAPPY -NEUTRAL -SAD -SURPRISE
3.2.3 实验过程
3.2.3.1 数据集准备
CK+ Dataset:
处理表情序列,从序列中选择有表情的图片。并运用linux 脚本将表情按照标签分为7类,因为ck+数据集下载下来每一个文件夹都有对应的表情标签,按照标签给每个表情文件夹分类,见下图
Jaffebase Dataset
从文件名中提取所属类别:
准备后的结果:
14 folders:7 human image folders and 7 animated image folders
1w+ images in total
3.2.3.2 数据预处理
Crop and Resize
Convert to gray-scale
Detect face: OpenCV HAAR Cascade.
Crop the image to the face.
Resize the image to 350*350.
Save the image.
之所以将图片都转化为灰度是因为我们的图片一部分是灰度,一部分是彩色,而考虑到颜色并不影响表情的识别,所以为了保持一致性,我们将所有图片都转化为灰度图片。
过程见下图:
Train-Test-Split:
train:validation:test:5:2:3
对于人类和动漫表情图片,我们划分训练集-检验集-测试集
Human images distribution:
Animated images distribution:
处理好后得到6个set【3个为Human images的train-validation-test set,3个为Animated images的train-validation-test set】,将Human images的train set和Animated images 的train set合并为Combined train set,结果得到5个DataFrames。
其中 Combined train set 用于训练模型,Human images 的 Validation set 和 Animated images 的 Validation set 用于 cross validation(交叉检验)。
3.2.3.3 第一个模型:借助 VGG16
模型搭建
使用pre-trained model的原因:
对于图像处理我们没有足够的计算能力和足够的图片,卷积本身的运算时间花费不小,所以为了减少时间开销,我们决定 transfer learning。
利用 Transfer learning 的概念,我们将其他 pre-trained model 得到的参数转移到我们的数据中,这样我们将数据传递给这些模型,提取图像的特征(bottleneck_features)。
VGG16 神经网络以数百万幅图像的数据集为基础进行了训练。VGG16 包含 16 层,其中 13 层是卷积层。我们利用 VGG16 作为 pre-trained model 生成 bottleneck_features。
从VGG16中提取bottleneck_features
利用代码 model.predict(), 我们把图片一张一张传入 VGG16 模型,得到 bottleneck_features 并且将其作为 numpy.array 存储, 通过这种方式我们实现 transfer learning。
【只采用 VGG16 第 13 层前面的部分,第 12 层的输出作为 bottleneck_features】
搭建模型 MLP
在 VGG16 提取图片 bottleneck_features 的基础上,我们将这些特征传递给 MLP(MLP 作为顶级模型),然后利用 MLP 减少损失函数的值,并且更新 MLP 和 CNN 中的权重。
# model architecture
def model(input_shape):model = Sequential()model.add(Dense(512, activation='relu', input_dim = input_shape))model.add(Dropout(0.1))model.add(Dense(256, activation='relu'))model.add(Dense(128, activation='relu'))model.add(BatchNormalization())model.add(Dense(64, activation='relu'))model.add(Dense(output_dim = no_of_classes, activation='softmax'))return model
【借助 MLP,通过 backpropagation 降低 lostfunction 的值】
搭建 5 层全连接层,所有层都是用 relu 作为激活函数,第一层包含 512 个激活单元,第二层包含 256 个激活单元,第三层包含 128 个激活单元,第四层包含 64 个激活单元,第五层包含 7 个 softmax 单元,softmax 是用于多分类的逻辑回归。
该模型会生成 7 个概率值,这些概率值的和为 1,所有结果会传递给 cross-entropy 损失函数。
可以注意到 dropout rate 非常小,起初我们尝试了 0.3,但是在 15 个 epochs 之后训练集和检验集的 loss 值不会减小。在渐渐尝试减小 dropout 的过程中,确定 dropout rate 为 0.1 时,训练集和检验集的 loss 值会降低,同时准确度会提升
读取bottleneck_features, 放入MLP
模型训练
训练 20 个 eopchs,结果如下
Multi-Class Log-Loss
Training Loss: 0.70 to 0.04
CV Human loss: 2.25 to 0.05
CV Animated loss: 1.47 to 0.01
Train Accuracy: 75% to 99%
CV Human Accuracy: 44% to 86%
CV Animated Accuracy: 70% to 99%
模型评估
我们把人类和动漫表情图片分开,以便能够分别在这两个集合上面进行测验
Confusion Metric
Confusion and Recall Matrix Human Images
In confusion metric:
Biased on the “angry” class
In recall metric
Many images in “sad” class which are predicted in “angry”.
Conclusion
Model is not completely tell “angry” and “sad”
Confusion and Recall Matrix Animated Images
% accuracy.
Why?
The ratio of train images from animated to human is approximately 9:1.
Learning human features is hard as compared to animated images.
The size of face
expression angles
etc
未来展望
微调 VGG16 的最后 2、3 层卷积层
寻找有更多 variance 的图片
图片尺寸大于 400*400
一张图片中多个人脸时能够检测大部分人脸并识别情绪
3.2.3.4 模型二
和模型一使用相同的经过预处理后划分好 train-validation-test 集合的数据
模型搭建
from keras.layers import Dense, Input, Dropout, GlobalAveragePooling2D, Flatten, Conv2D, BatchNormalization, Activation, MaxPooling2D
from keras.models import Model, Sequential
from keras.optimizers import Adam# number of possible label values
nb_classes = 7# Initialising the CNNmodel = Sequential()# 1 - Convolutionmodel.add(Conv2D(64,(3,3), padding='same', input_shape=(48, 48,1)))model.add(BatchNormalization())model.add(Activation('relu'))model.add(MaxPooling2D(pool_size=(2, 2)))model.add(Dropout(0.25))# 2nd Convolution layermodel.add(Conv2D(128,(5,5), padding='same'))model.add(BatchNormalization())model.add(Activation('relu'))model.add(MaxPooling2D(pool_size=(2, 2)))model.add(Dropout(0.25))# 3rd Convolution layermodel.add(Conv2D(512,(3,3), padding='same'))model.add(BatchNormalization())model.add(Activation('relu'))model.add(MaxPooling2D(pool_size=(2, 2)))model.add(Dropout(0.25))# 4th Convolution layermodel.add(Conv2D(512,(3,3), padding='same'))model.add(BatchNormalization())model.add(Activation('relu'))model.add(MaxPooling2D(pool_size=(2, 2)))model.add(Dropout(0.25))# Flatteningmodel.add(Flatten())# Fully connected layer 1st layermodel.add(Dense(256))model.add(BatchNormalization())model.add(Activation('relu'))model.add(Dropout(0.25))# Fully connected layer 2nd layermodel.add(Dense(512))model.add(BatchNormalization())model.add(Activation('relu'))model.add(Dropout(0.25))model.add(Dense(nb_classes, activation='softmax'))opt = Adam(lr=0.0001)model.compile(optimizer=opt, loss='categorical_crossentropy', metrics=['accuracy'])
模型评估
模型测试
测试案例 1:
测试案例 2:
3.3 模型整合
我们把每个训练出的模型进行接口的匹配连接。最终呈现出:先将图片输入看图说话的模型分析出图片的 captio,将 caption 存储下来。然后将图片再输入微表情识别的模型,若在模型中检测到人脸,就对人脸的表情进行分析得到情绪,最终将情绪和 caption 进行匹配。最终输出图片和对应的 caption 和 emotion(若检测不到人脸则不输出 emotion)示例如下:
♻️ 资源
大小:9.70MB
➡️ 资源下载:https://download.csdn.net/download/s1t16/87354365