[计算机视觉]从零开始构建一个微软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迅速转换为结果, ...
随机推荐
- 微信小程序页面通信
目录 微信小程序页面通信 方式一:通过URL 方式二:通过全局变量 方式三:通过本地存储 方式四:通过路由栈 微信小程序页面通信 方式一:通过URL // A 页面 wx.navigateTo({ u ...
- Netty是如何处理新连接接入事件的?
更多技术分享可关注我 前言 前面的分析从Netty服务端启动过程入手,一路走到了Netty的心脏——NioEventLoop,又总结了Netty的异步API和设计原理,现在回到Netty服务端本身,看 ...
- Present CodeForces - 1323D (思维+二分)
题目大意比较简单,就是求一堆(二元组)的异或和. 思路:按位考虑,如果说第k位为1的话,那么一定有奇数个(二元组)在该位为1.二元组内的数是相加的,相加是可以进位的.所以第k位是0还是1,至于k为后边 ...
- Plant 矩阵快速幂,,,,有点忘了
题目链接:https://codeforces.com/contest/185/problem/A 题目大意就是求n次以后 方向朝上的三角形的个数 以前写过这个题,但是忘了怎么做的了,,,又退了一遍 ...
- 快速搭建网站信息库(小型Zoomeye)
前言:本来是不想重复造车轮的,网上资料有开源的fofa,和一些设计.有的架设太复杂了,好用东西不会用,整个毛线.还有的没有完整代码. 设计方案: 测试平台:windows 测试环境:php ...
- 如何在非 sudo 用户下运行 docker 命令?
当我们在一台 Linux 系统中安装了 Docker 后, 有时候会遇到下面这样的错误, 我们在运行 docker 的命令时必须加上 sudo, 例如: sudo docker ps, 但是我们其实更 ...
- Redis 5.0.9 安装
目录 系统环境 系统版本 内核版本 安装步骤 安装 gcc 依赖 下载 Redis 解压 Redis 切换到 redis 解压目录下,执行编译 指定目录安装 启动 Redis 服务 最后 系统环境 系 ...
- Flutter 步骤进度组件
老孟导读:最近文章更新拖后腿了,一直忙着网站改版的事情,今天总算落地了,全新的Flutter网站即将上线,敬请期待.网站目前收集197个组件的详细用法,还有150多个组件待整理. Stepper S ...
- linux内核第一宏 container_of
内核第一宏 list_entry()有着内核第一宏的美称,它被设计用来通过结构体成员的指针来返回结构体的指针.现在就让我们通过一步步的分析,来揭开它的神秘面纱,感受内核第一宏设计的精妙之处. 整理分析 ...
- mysql 之 函数
聚合函数 avg()函数 - 计算一组值或表达式的平均值. count()函数 - 计算表中的行数. instr()函数 - 返回子字符串在字符串中第一次出现的位置. sum()函数 - 计算一组值或 ...