[计算机视觉]从零开始构建一个微软how-old.net服务/面部属性识别
大概两三年前微软发布了一个基于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
- VGGFace Dataset used for pre-trained weights (VGGFace V1)
- VGGFace2 Dataset used for pre-trained weights (VGGFace V2)
- UTKFace Dataset used for fine-tuning in this repo
papers
- related paper1 why age estimation is a classification task?
- related paper2 about VGGFace dataset
- related paper3 about VGGFace2 dataset
- related paper4 triplet loss in face recognition
历史文章
[计算机视觉]人脸应用:人脸检测、人脸对比、五官检测、眨眼检测、活体检测、疲劳检测
[AI开发]小型数据集解决实际工程问题——交通拥堵、交通事故实时告警
关注公众号,及时关注文章动态
[计算机视觉]从零开始构建一个微软how-old.net服务/面部属性识别的更多相关文章
- 从零开始构建一个的asp.net Core 项目
最近突发奇想,想从零开始构建一个Core的MVC项目,于是开始了构建过程. 首先我们添加一个空的CORE下的MVC项目,创建完成之后我们运行一下(Ctrl +F5).我们会在页面上看到"He ...
- 从零开始构建一个centos+jdk7+tomcat7的docker镜像文件
从零开始构建一个centos+jdk7+tomcat7的镜像文件 centos7系统下docker运行环境的搭建 准备centos基础镜像 docker pull centos 或者直接下载我准备好的 ...
- 从零开始构建一个的asp.net Core 项目(一)
最近突发奇想,想从零开始构建一个Core的MVC项目,于是开始了构建过程. 首先我们添加一个空的CORE下的MVC项目,创建完成之后我们运行一下(Ctrl +F5).我们会在页面上看到“Hello W ...
- 从零开始构建一个的asp.net Core 项目(二)
接着上一篇博客继续进行.上一篇博客只是显示了简单的MVC视图页,这篇博客接着进行,连接上数据库,进行简单的CRUD. 首先我在Controllers文件夹点击右键,添加->控制器 弹出的对话框中 ...
- .Net 从零开始构建一个框架之基本实体结构与基本仓储构建
本系列文章将介绍如何在.Net框架下,从零开始搭建一个完成CRUD的Framework,该Framework将具备以下功能,基本实体结构(基于DDD).基本仓储结构.模块加载系统.工作单元.事件总线( ...
- 从零开始构建一个Reactor模式的网络库(二)线程类Thread
线程类Thread是对POSIX线程的封装类,因为要构建的是一个Linux环境下的多线程网络库,对线程的封装是很必要的. 首先是CurrentThread命名空间,主要是获取以及缓存线程id: #if ...
- 从零开始构建一个Reactor模式的网络库(一) 线程同步Mutex和Condition
最近在学习陈硕大神的muduo库,感觉写的很专业,以及有一些比较“高级”的技巧和设计方式,自己写会比较困难. 于是打算自己写一个简化版本的Reactor模式网络库,就取名叫mini吧,同样只基于Lin ...
- 从零构建一个react+webpack+typescript的应用
今天要完成在windows下从零开始构建一个react应用的任务 首先,新建一个文件夹,然后在该文件夹下使用命令npm init 初始化一个node项目. 然后安装所需依赖, npm i react ...
- 从零开始构建并编写神经网络---Keras【学习笔记】[1/2]
Keras简介: Keras是由纯python编写的基于theano/tensorflow的深度学习框架. Keras是一个高层神经网络API,支持快速实验,能够把你的idea迅速转换为结果, ...
随机推荐
- Bat 脚本学习 (基础篇)
[转]Bat 脚本学习 2015-01-05 14:13 115人阅读 评论(0) 收藏 举报 基础部分: ============================================== ...
- 新时代前端必备神器 Snapjs之弹动效果
有人说不会 SVG 的前端开发者不叫开发者,而叫爱好者.前端不光是 Angularjs 了,这时候再不学 SVG 就晚了!(如果你只会 jQuery 就当我没说...)这里我就给大家分享一个前几天在别 ...
- JS Math&Date的方法 (下)
Date - 时间日期对象 一:Date 时间对象 - 它是处理时间日期的 时间日期对象 - js提供了一个专门用来创建日期对象的构造函数 Date new Date() 这是一 ...
- Nmap-脚本检测CVE漏洞
Nmap的一个鲜为人知的部分是NSE,即Nmap Scripting Engine,这是Nmap最强大和最灵活的功能之一.它允许用户编写(和共享)简单脚本,以自动执行各种网络任务.Nmap内置了全面的 ...
- redis: 乐观锁(十)
监视:watch 正常业务(单线程): 127.0.0.1:6379> set money 100 #模拟存款100元 OK 127.0.0.1:6379> set moneyout 0 ...
- 如何在非 sudo 用户下运行 docker 命令?
当我们在一台 Linux 系统中安装了 Docker 后, 有时候会遇到下面这样的错误, 我们在运行 docker 的命令时必须加上 sudo, 例如: sudo docker ps, 但是我们其实更 ...
- 解决Typecho Gravatar头像加载缓慢的问题
前言 Typecho评论默认使用的是Gravatar头像,但因为Gravatar网站总是被墙,导致页面加载被拖慢,而且加载半天也还是个裂图,太影响心情,所以我们可以不使用Gravatar头像,换成另一 ...
- Python初学者常见错误问题汇总
1.在客户端和服务端如何传递数组? 答:在客户端和服务端可以使用json进行数据传输.在客户端把数据转换成json字符串,然后使用POST方法发送给服务端. 服务端收集到数据之后,使用json.loa ...
- 五分钟学会Python装饰器,看完面试不再慌
本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是Python专题的第12篇文章,我们来看看Python装饰器. 一段囧事 差不多五年前面试的时候,我就领教过它的重要性.那时候我Pyt ...
- Jmeter:运行报:Error occurred starting thread group :线程组, error message:Invalid duration 0 set in Thread Group:线程组, see log file for more details
最近在用jmeter做压测,上周五压测的脚本,今天早晨结束后. 点击同样的脚本,运行就报Error occurred starting thread group :线程组, error message ...