用于姿态不变人脸识别的DR-GAN网络
------Disentangled Representation Learning GAN for Pose-Invariant Face Recognition
2017 cvpr
一.摘要
背景:
-
人脸图像之间的较大的姿态差异是人脸识别一个关键的挑战。在神经网络中,同一个人的两张姿态差异很大的图片所学习到的特征表示也不一样。仅仅角度不同,学习到特征也不同。
-
一般识别性能较好的人脸识别算法,比如arcface,facenet等等都是针对正脸图像的识别,这些算法运用到侧脸图像中,识别性能则很差。
-
传统的姿态不变人脸识别方法要么对非正面人脸图像进行正面化处理,要么从非正脸图像学习姿态不变的表示。
本文思路:
同时进行人脸正面化处理和从非正脸图像学习姿态不变的表示,即提出一种和姿态无关的人脸特征。将脸部的pose信息从特征中分离出去,这样用于人脸识别的特征提取就能适用到人脸识别的各种角度。
创新点: -
提出了DR-GAN网络
-
生成器的编码解码结构允许DR-GAN去学习生成和判别表示(编码器就是识别网络,解码器就是生成网络)以及图像合成。
-
编码器输出的人脸特征是不具有姿态信息的,通过给解码器提供姿态编码和姿态估计来实现。
-
通过提供给解码器的姿态代码和判别器中的姿态估计,该表示明显地与其他人脸变化(如pose)分离。
-
DR-GAN能够将一个或者多个图像作为输入,生成一个统一的表示以及任意数量的合成图像。
-
在受控的和野外的数据集上定量定性的展示了卓越性
二 介绍
现有的姿态不变人脸识别大致分为两种策略: -
利用人脸正面化,用传统的识别方法将侧脸的人脸合成一个正脸。
Face Frontalization:由于自遮挡,从侧面生成正面非常具有挑战性,现有的人脸正面化方法可分为三类:1.基于3D的方法 2.统计的方法 3.深度学习的方法。 -
另一种方式是Representation Learning研究聚焦于通过一个联合模型或者多个特定姿态模型直接从非正脸中学习识别力的特征。
三.本文得方法及相应代码块: -
传统的GAN网络,G采用随机噪声矢量合成图像。本文的G采用人脸图像,姿态代码c,(姿态代码pose code是one-hot向量,目标姿态yt为1)和随机噪声向量z串联起来作为输入。经过一系列的卷积提取特征,全连接,将特征“encode”reshape为[ batch_size,channel,1,1]的tesor,送入到decoder中。Encoder 和decoder实对称的过程,相当于卷积与反卷积。
处理输入图像transform = transforms.Compose([ transforms.Scale((100, 100)), # 图像压缩 transforms.RandomCrop(96),#将图像进行中心切割 transforms.RandomHorizontalFlip(),#将图像进行水平翻转,概率为0.5 一半翻转一半不翻转 transforms.ToTensor(),#将pil图像转换为Tensor transforms.Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5)) #将tensor正则化 image=(image-mean)/std
Pose code :
我们使用一个one-hot向量c来指定合成图像的离散pose
self.batchsize = len(self.pose)
self.frontal_pose = torch.LongTensor(np.random.randint(self.N_p, size = self.batchsize))
If self.is_Train:
self.input_pose = one_hot(self.frontal_pose, self.N_p)
Noise:正态分布的噪声
self.batchsize = len(self.pose)
self.noise = torch.FloatTensor(np.random.normal(loc=0.0, scale=0.3, size=(self.batchsize, self.N_z)))
真样本和假样本:
self.identity = torch.LongTensor(self.identity)
self.fake_identity = torch.zeros(self.batchsize).long()
前向传播:
def forward(self, input):
self.set_input(input)
self.syn_image = self.G(self.image, self.input_pose, self.noise)
self.syn = self.D(self.syn_image)
self.syn_identity = self.syn[:, :self.N_d+1]
self.syn_pose = self.syn[:, self.N_d+1:]
self.real = self.D(self.image)
self.real_identity = self.real[:, :self.N_d+1]
self.real_pose = self.real[:, self.N_d+1:]
定义生成器的后向传播:
def backward_G(self):
self.Loss_G_syn_identity = self.criterion(self.syn_identity, self.identity)
self.Loss_G_syn_pose = self.criterion(self.syn_pose, self.frontal_pose)
self.L1_Loss = self.L1_criterion(self.syn_image, self.image)
self.Loss_G = self.Loss_G_syn_identity + self.Loss_G_syn_pose + self.w_L1 * self.L1_Loss #W 预测特征表示学习的质量
self.Loss_G.backward(retain_graph=True)
定义判别器的后向传播:
def backward_D(self):
self.Loss_D_real_identity = self.criterion(self.real_identity, self.identity)
self.Loss_D_real_pose = self.criterion(self.real_pose, self.pose)
self.Loss_D_syn = self.criterion(self.syn_identity, self.fake_identity)
self.fake_identity = torch.zeros(self.batchsize).long()
self.Loss_D = self.Loss_D_real_identity + self.Loss_D_real_pose + self.Loss_D_syn
self.Loss_D.backward()
初始化权重
所有的权重被初始化为以0为中心,以0.02为标准差的正态分布
def weights_init_normal(m):
if isinstance(m, nn.ConvTranspose2d):
init.uniform_(m.weight.data, 0.0, 0.02)
elif isinstance(m, nn.Conv2d):
init.uniform_(m.weight.data, 0.0, 0.02)
elif isinstance(m, nn.Linear):
init.uniform_(m.weight.data, 0.0, 0.02)
elif isinstance(m, nn.BatchNorm2d):
init.uniform_(m.weight.data, 1.0, 0.02)
init.constant_(m.bias.data, 0.0)
学习率更新
def update_learning_rate(self):
for scheduler in self.schedulers:
scheduler.step()
lr = self.optimizers[0].param_groups[0]['lr']
print('learning rate = %.7f'%lr)
D相当于一个类似encoder 的卷积网络,用来提取特征。最后做id和pose 的分类,D相当于一个分类网络。N_p是样本人脸都呈现几种角度,人脸的角度呈现9种离散值,从[-60,60],间隔为15度。d是训练样本中人的数量。N_z是噪声,用于生成服从均匀分布从[-1,1]的长度为50的向量。D的数据集中有Nd个人,但是最后一个卷积层的输出为Nd+1个类别。也就是说D要多分类N个样本多出来的类别是假样本。把生成的假图像记为x+,把x+分到多余的身份类别上,把x按照属于哪个人的人脸分类,将x+ 和x都按照人脸呈现哪种角度来分类。
解码器:
def __init__(self, N_p=2, N_z=50):
super(Decoder, self).__init__()
Fconv_layers = [
Fconv_unit(320, 160), #Bx160x6x6
Fconv_unit(160, 256), #Bx256x6x6
Fconv_unit(256, 256, unsampling=True), #Bx256x12x12
Fconv_unit(256, 128), #Bx128x12x12
Fconv_unit(128, 192), #Bx192x12x12
Fconv_unit(192, 192, unsampling=True), #Bx192x24x24
Fconv_unit(192, 96), #Bx96x24x24
Fconv_unit(96, 128), #Bx128x24x24
Fconv_unit(128, 128, unsampling=True), #Bx128x48x48
Fconv_unit(128, 64), #Bx64x48x48
Fconv_unit(64, 64), #Bx64x48x48
Fconv_unit(64, 64, unsampling=True), #Bx64x96x96
Fconv_unit(64, 32), #Bx32x96x96
Fconv_unit(32, 3) #Bx3x96x96
]
A series of fractionally-strided convolutions (FConv)transforms the (320 + Np+ Nz)-dim concatenated vector into a synthetic image ˆx = G(x, c, z), which is the same size as x.
变换(320 + Np +Nz)的连接向量转化为合成图像ˆx = G (x, c、z)和x的size大小相同
self.Fconv_layers = nn.Sequential(*Fconv_layers)
self.fc = nn.Linear(320+N_p+N_z, 320*6*6) #对输入数据做线性变换
def forward(self, input):
x = self.fc(input)
x = x.view(-1, 320, 6, 6)
x = self.Fconv_layers(x)
return x
编码器:
class Encoder(nn.Module):
def __init__(self):
super(Encoder, self).__init__()
conv_layers = [
conv_unit(3, 32), #Bx32x96x96
conv_unit(32, 64), #Bx64x96x96
conv_unit(64, 64, pooling=True), #Bx64x48x48
conv_unit(64, 64), #Bx64x48x48
conv_unit(64, 128), #Bx128x48x48
conv_unit(128, 128, pooling=True), #Bx128x24x24
conv_unit(128, 96), #Bx96x24x24
conv_unit(96, 192), #Bx192x24x24
conv_unit(192, 192, pooling=True), #Bx192x12x12
conv_unit(192, 128), #Bx128x12x12
conv_unit(128, 256), #Bx256x12x12
conv_unit(256, 256, pooling=True), #Bx256x6x6
conv_unit(256, 160), #Bx160x6x6
conv_unit(160, 320), #Bx320x6x6
nn.AvgPool2d(kernel_size=6) #Bx320x1x1 #图像变成一维将所有的特征拉成一列
]
self.conv_layers = nn.Sequential(*conv_layers) #按照传入的顺序添加到序列中
def forward(self, input):
x = self.conv_layers(input)#图片输入这个编码器
x = x.view(-1, 320) #最后得到的特征被拉成一列
return x
本文提出两种DR-GAN,一种是单幅图像输入(一个人一张图片)的网络,输入为:batch_size,channel,height,width的tensor。另外一种是多幅图像输入的网络(一个人多张图片)如果不说明,n为6.[n* batch_size,channels, height, width],同一个人有n张图,有batch_size个人。N个同一个人的多张图片输入编码器得到N个f(x),每一个f(x)乘以w(w是预测特征表示学习的质量),运用dropout策略,用sigmoid激活函数将w的值约束在[-1,1]之间。需要将这n 个f(x)的特征融合到一起。在特征提取全连接的过程中,也学习到了n个预测值,将每一个nf(x)加权平均得到最终的特征映射函数f(x)。
多幅图像编码器:
Besides extracting f(x), Genc also estimates a confident coefficient w for each image, which predicts the quality of the learnt representation.With n input images {xi}ni=1, the fused representation is the weighted average of all representations.
多幅图像的编码器,提取f(x).同时学习一个参数w,这个参数预测学习到的表示的质量。将n张图片的表示W然后加权平均。
def forward(self, input):
x = self.conv_layers(input)
x = x.view(-1, 321)
t = x[:, :320] #t为f(x)
w = x[:, 320] #w预测学到特征表示的质量
batchsize = len(w) #获得w的数量
r = Variable(torch.zeros(t.size())).type_as(t)
for i in range(batchsize): #按照w的数量循环
r[i] = t[i] * w[i] #特征融合
r = torch.sum(r, 0, keepdim=True).div(torch.sum(w))
return torch.cat((t,r.type_as(t)), 0)
损失函数:
本文的DR-GAN用于人脸特征的学习。利用对抗性损失来提高合成图像的质量,并在判别器中进行身份分类来保持身份。DR-GAN有两种变体:一种是以一幅图像作为输入的基本模型,称为单幅DR-GAN。另一种扩展模型,称为多幅DR-GAN,用于利用每个主题的多幅图像
单幅DR-GAN的损失函数:
样本x的标签为y={yd,yp},yd是这个人的身份类别,yp是人脸呈现的角度类别(9类)。
D的输出是[nd+np+1]维的向量。Nd+1用来做身份分类,np用来做姿态的分类。
D的损失为:

前一项对应真实数据x,,对x的身份和姿态进行分类的交叉熵取相反数。第二项对应假数据x+。假图像分类到假样本的交叉熵的相反数。这里假样本对应的pose code用人来提供的,是one-hot向量。两项加在一起,最大化他们的和,就是最小化分类交叉熵。
G的损失函数为:

第一项是对假样本的分类,G想把假样本分类到对应的真实样本所属的类上。所以第一项身份表示是yd,而不是多一个维度。第二项是要将假样本分类到正确的姿态类别上。
有疑问的代码:
疑问代码:
1:
def Tensor2Image(img):
img = img.cpu()
Input (FloatTensor) img = img * 0.5 + 0.5 ???不懂为什么*0.5+0.5
output (PIL.Image) img = transforms.ToPILImage()(img)
return img
2:
if unsampling:
layers = [nn.ConvTranspose2d(in_channels, out_channels, 3, 2, 1),nn.ZeroPad2d([0, 1, 0, 1])] #后面的系数部分???
else:
layers = [nn.ConvTranspose2d(in_channels, out_channels, 3, 1, 1)]
1万+

被折叠的 条评论
为什么被折叠?



