徐汇|使用机器学习创建自己的Emojis 表情( 二 )


第三个隐藏层的输出是与面部余弦相似度最大的表情符号 。 最后 , 输出层是一个创建表情函数 , 将这些部分进行组合生成完整的表情符号 , 整个流程如下:




这个架构可以总结为三层:
分割模型 , 将一张自拍分成几个人脸片段 。 在这种情况下 , 分割模型[1
与人脸裁剪模型[2
一起工作
嵌入模型 , 将每个人脸段转换为嵌入空间 。 如前所述 , 可选ResNet50和自动编码器或其他的任意架构
余弦相似度 , 它将人脸嵌入与所有相同类型的部件嵌入进行比较
一些研究虽然我们的模型是由几个神经网络组成的 , 但结构相并不复杂 。 正如在上面所写的 , 这个合成将最相似的头像部分与脸部片段通过余弦相似度对嵌入进行匹配 , 然后将它们组合 。 但这里也有一些主要问题需要确认:
1、如何才能准确地得到这些嵌入 , 从而使比较有意义?
如果选择使用ResNet50 , 则需要将获取分类头(最后一层)之外的特征 , 对于该模型只需要去掉最后一层 。
对于自编码器 , 它是无监督解决方案 , 嵌入空间将是自动编码器中的压缩线性层 , 我们将在图像比较中使用它 。
2、嵌入可视化
出于研究目的 , 我们还编写了一个用于嵌入可视化的脚本 , 该脚本获取一个视频文件作为输入 , 返回一个带有嵌入图形的视频文件作为输出 。 在这个脚本中 , 我们得到嵌入为每帧头像的每个部分和面部图像的图 。粉色代表人脸嵌入 , 紫色代表头像嵌入 。




3、ResNet50的结果
我们使用通过预训练的 ResNet50 进行测试 。看看下面的结果:





测试对象什么也不做 , 但嵌入在每一帧都在变化 。由于嵌入彼此之间没有太大差异 , 并且它们的维度太多 , 因此无法清晰地生成头像 。即使测试对象的情绪发生变化(例如 , 他们的脸上出现微笑) , 嵌入仍然保持不变(见图) 。




4、自编码器作为嵌入提取器
【徐汇|使用机器学习创建自己的Emojis 表情】ResNet50的表现并不好 , 那么自编码器呢? 我们使用下面的架构:




代码如下:
class FaceAutoencoder(nn.Module):
 def __init__(self):
 super(FaceAutoencoder self).__init__()
 self.conv1 = nn.Sequential(
 nn.Conv2d(3 16 3 stride=2 padding=1)
 nn.ReLU()
 )
 self.conv2 = nn.Sequential(
 nn.Conv2d(16 64 3 stride=2 padding=1)
 nn.ReLU()
 )
 self.conv3 = nn.Sequential(
 nn.Conv2d(64 128 5 stride=3 padding=2)
 nn.ReLU()
 )
 self.conv4 = nn.Sequential(
 nn.Conv2d(128 256 5 stride=3 padding=1)
 nn.ReLU()
 )
 self.fc_conv = nn.Sequential(
 nn.Flatten(1)
 nn.Linear(16384 8)
 nn.ReLU()
 )
 self.fc_deconv = nn.Sequential(
 nn.Linear(8 16384)
 nn.ReLU()
 nn.Unflatten(dim=1 unflattened_size=[256 8 8
)
 )
 self.deconv1 = nn.Sequential(
 nn.ConvTranspose2d(256 128 5 stride=3 padding=0)
 nn.ReLU()
 )
 self.deconv2 = nn.Sequential(
 nn.ConvTranspose2d(128 64 5 stride=3 padding=2 output_padding=1)
 nn.ReLU()
 )
 self.deconv3 = nn.Sequential(
 nn.ConvTranspose2d(64 16 3 stride=2 padding=1 output_padding=0)
 nn.ReLU()
 )
 self.deconv4 = nn.Sequential(
 nn.ConvTranspose2d(16 3 3 stride=2 padding=1 output_padding=1)
 nn.ReLU()
 )
 
 def encode(self x):
 x = self.conv1(x)
 x = self.conv2(x)
 x = self.conv3(x)