大概两三年前微软发布了一个基于Cognitive Service API的how-old.net网站,用户可以上传一张包含人脸的照片,后台通过调用深度学习算法可以预测照片中的人脸、年龄以及性别,然后将结果绘制到原图片上返回给用户。那时候深度学习技术在国内刚流行不久(2016年前后),当时这个网站一度引起IT/非IT界的关注。现在已经过去三四年了,深度学习技术在国内互联网‘日渐普及’,大家也见怪不怪。本篇文章从零开始,教大家实现一个类似how-old.net的服务,即通过一张包含了人脸的照片,预测年龄、性别以及种族。

熟悉英文的同学可以直接移步github源码阅读README文件。

 

任务目标

我们的目标很简单,构建一个神经网络,或者基于已有比较流行的网络结构进行改造,通过素材数据训练后,我们的网络能够预测照片中每张人脸的年龄、性别以及种族。最后将结果绘制显示在原图片中。我们主要目标是完成神经网络的训练以及推理和结果显示,至于完整实现how-old.net需要考虑的那种Web get/post restful API请求暂不需要考虑了,毕竟不是本文重点。

 

工具准备

训练神经网络或者做深度学习相关的工作,我们最好有一台带有GPU的电脑,一般都是Ubuntu系统,但是我这篇文章的代码是在自己办公PC上开发调试的,有些开源项目并不官方支持Windows10系统,因为这个系统坑比较多,建议大家平时调试还是优先使用Ubuntu。

  • Windows 10

  • Python 3.5

  • tensorflow-gpu 2.1

  • dib/face_recognition (demo中检测人脸)

  • OpenCV3.4

  • scipy、numpy、h5py、pillow

  • Cuda 10.0

  • Cudnn 7.5

  • CPU i7-7700K 4.2GHz

  • GPU GTX 1080

  • 16G RAM

我这个硬件配置相对来讲算是比较高了,差点的机器也能跑,可能训练慢点,推理也慢点。

 

实现原理

如果对深度学习有一点了解,那么解决本次问题的原理其实相当明了。这是一个典型的分类问题(分类和回归的区别请参见这里:https://www.cnblogs.com/xiaozhi_5638/p/11792274.html),我们可以定义三个单独的网络分别去预测年龄、性别以及种族,也可以只定义一个网络(包含三个输出)同时预测年龄、性别以及种族,前者训练相对来讲简单、网络更容易拟合,但是网络训练完之后实际上线推理速度相对要慢,因为需要推理三次(下图中间),或者并行推理(最上面,需要同时占用硬件资源),而后者训练麻烦,收敛效果可能没有前者好,但是一旦训练完成后,后面推理速度较快,一次推理出三种结果(下图最下面)。我们本次采用后面那种方式,即定义一个网络,能够同时推理出年龄、性别以及种族。

神经网络的训练有两种方式,一种从零开始,网络的权重随机初始化,之后权重调整全靠你自己的数据集;另外一种方式就是在别人已经训练好的权重基础上再去调整,以到达解决我们自己任务的目的,这种方式的好处就是网络的权重不再是随机初始化了,而是初始化为一些比较靠谱的值,我们再在这些靠谱的值基础上进行调整,这种方式肯定比随机初始化要好。除非你的数据集相当庞大并且丰富,否则神经网络的训练一般都是采用使用第二种方式。第二种方式也叫“迁移学习”,类似将别人学习好了的经验拿过来用,这样当然要比从零开始学习更容易。迁移学习的关键点是要能找到合适的、跟我们任务类似的、别人训练好的权重,这句话比较拗口,简单来讲就是,你借鉴的经验必须是有效的,别人10年钓鱼的经验对于你去考驾照来讲是无效的,同样的,你不能借鉴别人10年开车的经验来学习Python编程。那么什么经验是有效的呢?举几个例子,你可以借鉴别人10年开车的经验去学习叉车操控,你也可以借鉴别人学Java编程的经验去学习Python编程。对深度学习来讲,如果别人已经训练好了一个网络,该网络可以做1000个人的人脸分类,那么你可以‘借用’这个网络的权重去做另外100个人的人脸分类(这100不在1000之内),因为这两个任务有相似性。如果别人已经训练好了一个网络,该网络可以用来为艺术系学生的水彩画作业打分,那么你不可以(或者很难)‘借用’这个网络的权重去为艺术系学生的素描画作业打分,因为水彩画和素描画相关性不是很大。当然,这里的能不能借用并不是绝对的,只是能借用程度的多少不一。在实际迁移学习训练过程中,我们可以通过‘冻结’部分网络层的权重,让其不参与我们自己数据集的训练,保持初始值不变,那么这些冻结层的权重就是我们前面说的到‘借用’了,冻结哪些层可以随机调整,代表我们借用的程度。关于迁移学习更具体的数学描述请参考这篇文章:https://www.cnblogs.com/xiaozhi_5638/p/12202074.html

 

实现过程

解决本次任务时,我们借用牛津大学著名的VGGFace网络结构和其权重,该网络主要用来对2622(VGGFace V1)以及8631(VGGFace V2)个人脸进行分类,网络结构和预训练的权重在网络上均可以下载。我们的任务目标是对人脸的年龄、性别以及种族进行预测,任务类型和VGGFace差不多,都跟人脸有关,所以迁移学习在这里完全可以使用。具体做法为:

(1)去掉原VGGFace V2顶部的分类全连接层(MLP层),该层主要对前面提取的人脸特征进行8631分类;

(2)再在(1)的基础上接上3个输出分支,每个分支都是做分类任务,分别负责年龄、性别以及种族的预测;

(3)修改原VGGFace V2的输入尺寸,由原来的(224, 224, 3)改为(200, 200, 3),主要原因是我们自己的训练数据集UTKFace的原始尺寸是200*200,当然你也可以保持不变,训练时将图像缩放;

(4)冻结VGGFace V2网络结构的全部层(不包括我们接上去的3个分支),这个意思是借用VGGFace V2的全部经验,权重保持不变;

(5)开始使用UTKFace数据集进行训练;

(6)观察loss的变化效果,如果发现效果不理想,可以适当回到(4)步,将冻结层数减少,这个意思是借用部分VGGFace V2的经验;

当然了,网络的训练远没有上面写的这么简单,需要不断去尝试,修改各种超参数、甚至调整VGGFace V2本身网络结构(部分权重忽略)。

 

这里再说一下,为什么年龄预测是一个分类问题而不是回归问题?其实有一篇论文对这个有介绍:https://www.cv-foundation.org/openaccess/content_iccv_2015_workshops/w11/papers/Rothe_DEX_Deep_EXpectation_ICCV_2015_paper.pdf 这个论文用实验结果告诉我们分类比回归的效果要好。下面是使用UTKFace数据集进行训练的结果,图中蓝色线为val_loss,图中橙色线条为train_loss,由于训练过程中使用了数据增强,train_loss比val_loss要高一点,属于正常现象:

 

源码介绍

face.py

定义我们自己的神经网络结构,输入尺寸,冻结层数等等,可以根据需要自己调整。

 

face_train.py

使用UTKFace数据集训练我们的神经网络,你可以修改代码来适配其他数据集。

 

face_demo.py

单张图demo

 

face_video_demo.py

视频文件demo,或者usb 摄像头demo

 

face_weights/

训练过程中,ModelCheckPoint产生的权重文件,demo可以通过model.load_weights()加载使用。

 

train_data/

存放训练数据。

 

vggface_weights/

原VGGFace V2预训练的权重文件,我们在训练的时候需要先加载该权重,然后再在基础上进行微调。注意我使用的是ResNet50的结构,VGGFaceV2内部还支持SeNet50的网络结构。

使用不同的网络结构需要使用不同的预训练权重。

 

logs/

Tensorboard产生的日志文件,用于对训练进度的监控。

 

多说无益,直接看源码更易懂,更多细节请关注公众号。建国同志和大大镇楼。

 

参考

websites

datasets

papers

 

 

历史文章

[计算机视觉]人脸应用:人脸检测、人脸对比、五官检测、眨眼检测、活体检测、疲劳检测 

[AI开发]一个例子说明机器学习和深度学习的关系  

[AI开发]零代码公式让你明白神经网络的输入输出  

[AI开发]小型数据集解决实际工程问题——交通拥堵、交通事故实时告警   

[AI开发]零代码分析视频结构化类应用结构设计  

[AI开发]零数学公式告诉你什么是(卷积)神经网络  

[AI开发]视频结构化类应用的局限性   

[计算机视觉]基于内容的图像搜索实现 

[AI开发]目标检测之素材标注  

关注公众号,及时关注文章动态

版权声明:本文为xiaozhi_5638原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/xiaozhi_5638/p/12838186.html