介绍
本我的项目的起源是我选修的北航与华为单干的《AI开源计算零碎前沿技术》课程大作业。课程请到华为的各位专家介绍了华为目前的AI软硬件体系,并解说了许多人工智能畛域的常识。

我的大作业选题是用轻量级的网络模型backbone,实现手机端人脸检测算法,并应用MindSpore Lite在端侧推理部署。比拟遗憾的是整体我的项目开发进度慢于预期,加上挪动端指标检测APP的源码中应用了JNI等我不相熟的接口,最初没有实现挪动端的部署,仅实现了人脸检测+关键点检测神经网络的搭建、训练和测试。

后期调研之后,我决定基于MindSpore框架实现论文《Joint Face Detection and Alignment using Multi-task Cascaded Convolutional Networks》中的人脸识别和关键点检测网络MTCNN。该网络的构造并不简单,包含PNet、RNet、ONet这三个构造类似的网络。因为我对Python和机器学习都是初学者,我的项目过程中遇到了不少问题。解决过程中有了一些教训心得,斗胆在此做些分享。

本我的项目中的MTCNN局部代码基于夜雨飘零1的Github我的项目和华为MindSpore官网文档撰写,包含数据集生成、网络结构定义、网络训练、模型测试的代码。

本我的项目的Gitee地址。

0 训练环境
笔记本训练环境:

CPU:i7-11800H
GPU:RTX 3070
Windows10:
CUDA11.6+cudnn8.x
MindSpore1.7.0-CPU
Pytorch1.11.0
Ubuntu22.04:
CUDA11.1+cudnn8.0.4
MindSpore1.7.0-GPU
服务器训练环境:

minsspore1.7.0-cuda10.1-py3.7-ubuntu18.04, GPU: 2*V100(64GB), CPU: 16核128GB
minsspore1.7.0-cuda10.1-py3.7-ubuntu18.04, GPU: 1*P100(16GB), CPU: 8核64GB
1 数据集下载
WIDER Face下载训练数据压缩包WIDER Face Training Images,解压的WIDER_train文件夹搁置到dataset文件夹下。并下载Face annotations,解压把外面的wider_face_train_bbx_gt.txt文件放在dataset目录下,

在Deep Convolutional Network Cascade for Facial Point Detection下载Training set并解压,将外面的lfw_5590和net_7876文件夹搁置到dataset下

最终,dataset目录下应该有文件夹lfw_5590,net_7876,WIDER_train,有标注文件testImageList.txt,trainImageList.txt,wider_face_train.txt,wider_face_train_bbx_gt.txt(这四个txt文件已放在dataset文件夹下)。

2 文件夹性能阐明
dataset:最开始只蕴含原始数据集,后续会保留用于训练每个网络的数据集。

infer_models:保留训练出的模型PNet.ckpt,RNet.ckpt,ONet.ckpt,我训练出的模型文件曾经放在该文件夹下。

models:

Loss .py:定义损失函数。
PNet .py,RNet .py,ONet .py:定义网络。
load_models:包含PNet .py、RNet .py、ONet.py三个文件,实际上RNet.py和ONet.py与models文件夹中的文件截然不同,只有PNet做了改变。这是因为训练中PNet的输出是12*12的图片,通过卷积之后最初两维都是1,进行了squeeze操作。但在推理过程中,输出PNet的是图像金字塔,再应用squeeze会产生谬误,故load_models文件夹中的PNet缩小了squeeze操作。

train_PNet:蕴含生成训练PNet的数据集的文件generate_PNet_data.py和训练PNet的文件train_PNet.py。train_RNet和train_ONet文件夹相似。

utils:蕴含加载图片、解决图片的函数。

infer_camera.py:调用电脑摄像头,对摄像头失去的图片进行推理,实时显示人脸回归框和五个关键点。

infer_path.py:解决指定门路的图片,辨认图片中的人脸,显示人脸回归框和五个关键点(可解决多人)。

modelToMNDIR .py:将.ckpt格局的模型转换为mindir格局。

3 模型训练过程
MTCNN是一个级联网络模型,蕴含PNet,RNet,ONet三个网络。这三个网络的计算结果一个比一个准确,后两个网络都会起到对其前一个网络的后果进行进一步筛选的作用。生成数据集时会调用之前网络进行推理,将之前网络的推理后果用于生成下一个网络的数据集,网络训练过程比拟繁琐。

训练步骤:

进入train_PNet文件夹,运行generate_PNet_data.py生成训练PNet的数据集;运行train_PNet.py训练PNet。
进入train_RNet文件夹,运行generate_RNet_data.py生成训练RNet的数据集;运行train_RNet.py训练RNet。
进入train_ONet文件夹,运行generate_ONet_data.py生成训练ONet的数据集;运行train_ONet.py训练ONet。
训练完结后,如果想验证模型训练后果,能够运行infer_camera.py,调用电脑的摄像头,模型参数正确则能够显示人脸回归框和关键点。也能够运行infer_path.py,对指定图片进行推理。

4 我的项目心得
最开始网络训练时loss不降落,我推断出是梯度反向流传的问题,但迟迟没有解决。感激ms技术交换群的wgx老师,帮我批改了loss函数和训练PNet的代码(训练RNet和ONet的代码是相似的),并标准了我的模型定义。

4.1 模型训练
MindSpore中数据集的加载形式和Pytorch不同,所以我自定义了数据库加载类GetDatasetGenerator来获取数据。

在Pytorch中,网络模型和损失函数是离开计算的,应用起来比拟自在。MindSpore中模型的训练流程相比之下要固定很多,但也的确对代码做出了简化。因为该模型的损失函数对类别、回归框、关键点三局部的损失做加权求和,故须要自定义损失函数。在ms中自定义的损失函数要利用到网络中须要再定义一个类,我定义的这个类名为NetWithLossCell。该类中调用网络backbone失去计算结果,再将后果送到loss函数中计算损失。训练过程中如果想查看损失函数,则应该在model.train中传入callbacks,用LossMonitor来输入损失。

夜雨飘零老师的代码里训练过程中还会输入模型的精度,这在Pytorch中是间接计算的,但ms的Model类想返回两个值十分艰难,所以wgx老师帮忙我通过在callbacks中传入一个精度计算函数来实现了这个性能。该函数的实质是对数据集做一次eval,因为传入的是整个训练集所以十分耗时,我在训练过程中没有应用。实践上来说如果能够提取训练集的一部分传入精度计算函数就能够节约大量工夫并取得精度。

4.2 API映射问题
Pytorch版代码中,MaxPool2d的ceil参数设置为True。因为mindspore中的MaxPool2d办法没有ceil这个参数,故间接应用会导致后果的shape与论文中的不同,于是我在进行pool之前先进行了padding,能够保障每层的输入后果与论文中统一。

pytorch版代码中的其余办法都能够在mindspore中找到性能甚至名称一样的办法,写法上留神一下即可。

4.3 内存溢出和推理速度过慢问题
生成训练RNet和ONet用的数据集时会采纳图像金字塔的思维,这会导致输出到MindSpore中的图像shape一直变动。如果是在GRAPH_MODE下训练模型,仿佛会让框架反复生成网络,导致程序的内存占用量一直回升,最终在Linux零碎中导致内存耗尽程序主动退出。如果是PYNATIVE_MODE,程序的内存占用量会一直回升,但最终占用量不会像在GRAPH_MODE下那么夸大,生成RNet的数据集用了25G左右,我开了32G的虚拟内存之后就能够应酬。不过生成ONet用的数据集时,因为生成数据集的写法问题,导致内存占用量太大,我在linux零碎中新开的虚拟内存没方法挂载上根节点,故最初是在服务器上实现了ONet数据集的生成和ONet的训练。

对于内存溢出,还有一个我不分明起因的问题,在minsspore1.7.0-cuda10.1-py3.7-ubuntu18.04, GPU: 1P100(16GB), CPU: 8核64GB 这一环境上运行generate_ONet_data.py的时候产生了内存溢出的状况,64GB内存不够用,于是我开了minsspore1.7.0-cuda10.1-py3.7-ubuntu18.04, GPU: 2V100(64GB), CPU: 16核128GB 这个环境,但在这个环境下内存最多用了48G,代码是截然不同的。能够发现,ms框架在不同的环境上运行成果有肯定的区别。

最开始的训练过程中,我发现ms代码的模型解决数据集中的一张图片用时比pytorch版本代码长很多。剖析后发现起因还是最开始loss不降落,意味着PNet等模型没有学到什么货色,后续生成训练RNet的数据集时就多了许多谬误的回归框等数据,因而增大了数据量,减少了解决工夫。起初调整模型使得模型能够正确推理,失去正确的数据集之后,ms版本代码的推理速度就失常了,并且哪怕是PYNATIVE_MODE推理速度依然比pytorch版本代码快(因为怕推理过程中再呈现内存泄露,故没敢用GRAPH_MODE训练)。