人脸属性识别之PyTorch简单实现

GibsonAnna 发布于6月前 阅读567次
0 条评论

人脸属性识别之PyTorch简单实现

人脸属性识别已经是一个解决的比较好的问题了。这里是花了一天时间做的一个简单的验证性项目。工程完整代码(GitHub)在

WynMew/FaceAttribute

训练数据使用CUHK的 Large-scale CelebFaces Attributes (CelebA) Dataset . 该数据集有40个属性标定(Attribute Label). 情况如下[1]:

人脸属性识别之PyTorch简单实现

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解决这个问题:

https:// github.com/davisking/dl ib/blob/master/python_examples/face_detector.py

在检测完之后,会有部分的图像中不能检测出人脸,这部分数据就不管了。我们使用能检测出人脸的数据做训练和测试。为了训练,我们需要知道每个检测到的人脸的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)

完整代码为

https:// github.com/WynMew/FaceA ttribute/blob/master/dataloadercelebA.py

模型

数据集有限,同时仅仅是验证目的,所以这里从现成的模型开始。我们选用ResNet [3]开始finetune. 验证使用了ResNet18和ResNet34两个相对较小的模型作为feature提取器:

https:// github.com/WynMew/FaceA ttribute/blob/master/AttrPreModelRes18_256V0.py https:// github.com/WynMew/FaceA ttribute/blob/master/AttrPreModelRes34_256V0.py

在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)

完整代码为

https:// github.com/WynMew/FaceA ttribute/blob/master/TrainAttrPreRes18V0.py https:// github.com/WynMew/FaceA ttribute/blob/master/TrainAttrPreV0.py

分别对应ResNet18 和 ResNet34.

人脸属性识别之PyTorch简单实现

可以看到模型很快收敛了。

模型评估

训练完各个epoch之后,我们希望能评估模型训练的效果。

流程为设置模型,读取模型参数,作出预测和统计(预测值大于0的视为预测label 1)。完整代码为

https:// github.com/WynMew/FaceA ttribute/blob/master/AttrEvaRes18_256V0.py https:// github.com/WynMew/FaceA ttribute/blob/master/AttrEvaRes34_256V0.py

人脸属性识别之PyTorch简单实现

评估发现,在训练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.

查看原文: 人脸属性识别之PyTorch简单实现

  • redswan
  • yellowlion
  • purplebutterfly
  • goldencat
  • goldenpanda
  • tinyfrog
  • heavylion
  • organicmeercat
  • ctolib
需要 登录 后回复方可回复, 如果你还没有账号你可以 注册 一个帐号。