人脸属性识别已经是一个解决的比较好的问题了。这里是花了一天时间做的一个简单的验证性项目。工程完整代码(GitHub)在
训练数据使用CUHK的 Large-scale CelebFaces Attributes (CelebA) Dataset . 该数据集有40个属性标定(Attribute Label). 情况如下[1]:
CelebA Label分布(蓝色为正样本)
可见其中各个Label的正负样本都是不均衡的,而且大部分的Label都不实用。这里我选择了6个比较实用的Attribute Label做试验:Attractive(魅力), EyeGlasses(眼镜), Male(男性), MouthOpen(张嘴), Smiling(微笑), Young(年轻).
数据预处理
数据预处理的目的在于减少数据集内数据分布的差异性,有减少类内距离的同时增加类间距离的实际效果。在人脸数据处理方面,常用的有人脸检测和对齐。由于人脸属性识别任务比较简单,在这里的验证中我只使用了人脸检测(抠出图像中的人脸)。
在GitHub的代码中( https:// github.com/WynMew/FaceA ttribute/blob/master/detMTCNN_celebA.py ),人脸检测使用MTCNN[2]实现,这依赖于caffe,并且效果在当今看来已经差强人意了。如果希望不依赖caffe, 可以使用dlib解决这个问题:
在检测完之后,会有部分的图像中不能检测出人脸,这部分数据就不管了。我们使用能检测出人脸的数据做训练和测试。为了训练,我们需要知道每个检测到的人脸的Label. 这就需要从celebA提供的图像Label中查找图像名称和对应的Label值。代码为 https:// github.com/WynMew/FaceA ttribute/blob/master/AttrListGen.py
预处理完后将数据分解为三部分: Train, Val 和Test.
数据读取
PyTorch提供了数据读取接口(torch.utils.data.Dataloader),可供很方便的读取数据,对数据进行变换(data augmentation) 和调试。在dataloader中我们需要读取图像, 并归一化:
self.normalize = transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
读取6个label, 并转换成Tensor.
def __getitem__(self, idx)
class ToTensorDict(object)
完整代码为
模型
数据集有限,同时仅仅是验证目的,所以这里从现成的模型开始。我们选用ResNet [3]开始finetune. 验证使用了ResNet18和ResNet34两个相对较小的模型作为feature提取器:
在feature提取之后,简单的接上了6个暴力分类器:
class Classifier(nn.Module):
def __init__(self, output_dim=1):
super(Classifier, self).__init__()
self.fc1 = nn.Sequential(
nn.Linear(2048, 512),
nn.ReLU(True),
nn.Dropout(p=0.5),
nn.Linear(512, 128),
nn.ReLU(True),
nn.Dropout(p=0.5),
nn.Linear(128, output_dim),
)
self.fc1.cuda()
self.fc2 = nn.Sequential(
nn.Linear(2048, 512),
nn.ReLU(True),
nn.Dropout(p=0.5),
nn.Linear(512, 128),
nn.ReLU(True),
nn.Dropout(p=0.5),
nn.Linear(128, output_dim),
)
self.fc2.cuda()
self.fc3 = nn.Sequential(
nn.Linear(2048, 512),
nn.ReLU(True),
nn.Dropout(p=0.5),
nn.Linear(512, 128),
nn.ReLU(True),
nn.Dropout(p=0.5),
nn.Linear(128, output_dim),
)
self.fc3.cuda()
self.fc4 = nn.Sequential(
nn.Linear(2048, 512),
nn.ReLU(True),
nn.Dropout(p=0.5),
nn.Linear(512, 128),
nn.ReLU(True),
nn.Dropout(p=0.5),
nn.Linear(128, output_dim),
)
self.fc4.cuda()
self.fc5 = nn.Sequential(
nn.Linear(2048, 512),
nn.ReLU(True),
nn.Dropout(p=0.5),
nn.Linear(512, 128),
nn.ReLU(True),
nn.Dropout(p=0.5),
nn.Linear(128, output_dim),
)
self.fc5.cuda()
self.fc6 = nn.Sequential(
nn.Linear(2048, 512),
nn.ReLU(True),
nn.Dropout(p=0.5),
nn.Linear(512, 128),
nn.ReLU(True),
nn.Dropout(p=0.5),
nn.Linear(128, output_dim),
)
self.fc6.cuda()
def forward(self, x):
x = x.view(x.size(0), -1)
x1 = self.fc1(x)
x2 = self.fc2(x)
x3 = self.fc3(x)
x4 = self.fc4(x)
x5 = self.fc5(x)
x6 = self.fc6(x)
return x1, x2, x3, x4, x5, x6
然后将二者接起来:
feature = self.FeatureExtraction(img)
Attractive, EyeGlasses, Male, MouthOpen, Smiling, Young = self.classifier(feature)
训练
在训练部分需要设置model, GPU, optimizer, learning rate, loss等参数:
torch.cuda.set_device(2)
cwd = os.getcwd()
print(cwd)
model = AttrPre()
model.cuda()
init_lr = 1e-4
optimizer = optim.SGD(model.parameters(), lr= init_lr, momentum=0.5)
loss = nn.MSELoss()
这里optimizer和loss使用了简单的参数。
同时,我们希望learning rate随着训练epoch的增加而变化:
def adjust_lr(optimizer, epoch, maxepoch, init_lr, power = 0.9):
lr = init_lr * (1-epoch/maxepoch)**power
for param_group in optimizer.param_groups:
param_group['lr'] = lr
return lr
在每个epoch之后存储模型参数:
checkpoint_name = os.path.join('AttrPreResNet18Det256V0_MSEloss.pth.tar')
save_checkpoint({
'epoch': epoch + 1,
'state_dict': model.state_dict(),
'best_test_loss': best_test_loss,
'optimizer': optimizer.state_dict(),
}, is_best, checkpoint_name)
完整代码为
分别对应ResNet18 和 ResNet34.
可以看到模型很快收敛了。
模型评估
训练完各个epoch之后,我们希望能评估模型训练的效果。
流程为设置模型,读取模型参数,作出预测和统计(预测值大于0的视为预测label 1)。完整代码为
评估发现,在训练5到9个epoch之后模型达到最优。我测试的最优结果如下:
Attractive: 0.8123
EyeGlasses: 0.9947
Male: 0.9550
MouthOpen: 0.9291
Smiling: 0.9028
Young: 0.8530
可以看到,即使使用如此简单暴力的方法,结果还是不错的。只在Attractive,Smiling和Young这几个Label标定比较主观,缺乏准确性的属性上效果比较差。不过模型预测给出的是评分,可以用此方法给出人脸的魅力值,情绪值等参数。
[1] Rudd, Ethan M., M. Günther, and T. E. Boult. "MOON: A Mixed Objective Optimization Network for the Recognition of Facial Attributes." European Conference on Computer Vision Springer, Cham, 2016:19-35.
[2] Zhang, Kaipeng, et al. "Joint Face Detection and Alignment Using Multitask Cascaded Convolutional Networks." IEEE Signal Processing Letters 23.10(2016):1499-1503.
[3] He, Kaiming, et al. "Deep Residual Learning for Image Recognition." (2015):770-778.