赞
踩
前言:
ExtraTimeProj
人脸关键点检测项目
数据:
python文件:
本阶段,深入体会流程,完成 detector.py,并运行,进而完成训练,生成第一阶段的 detector 模型。再次运行 detector.py 并由其调用predict.py,用所训模型在人脸图片上画出关键点。
人脸关键点检测,目的是通过 CNN 方法,进而在已有人脸检测框的基础上【注意:不是在全图上】,进行关键点检测,输出关键点坐标。
所谓人脸关键点,是一系列人为定义的点。人们主观认为,这些点最能体现人脸信息,比如轮廓、五官样貌等。人脸关键点并不唯一,少到 4 点 5 点,多道 100 点 200 点都有。具体需要多少,要视实际情况而定。本项目点数适中,采用 21 点。
数据是任何 CNN 项目最为关键的一环,没有之一。甚至整个项目,如果没有数据,那么生成训练用数据能够占到整个项目一半以上的时间,有时甚至贯穿整个项目。【实际工作中,这一步很可能将由实习生或者数据标注公司完成,千万不要忽略这一步的重要性以及耗时】数据结构如下:
文件夹 I 与 II 中,分别有 1000 张图,共 2000 张。
I 与 II 中除图片外,还另有 label.txt,为标注信息。每行为一张图像的具体标注内容。
共分 3 部分:
具体请看实例:
这是 label.txt 中一行所包含的信息,标注中,可能有<0 的情况出现,可以当做 0 或者忽略该行信息即可【请牢记:此时所有数据坐标均是关于原始图像大小的。通常情况下,原始坐标信息总是关于原始图像的。虽然几乎不可能应用原始坐标,但是为了数据的可重用性,原始坐标信息总是应该保持为关于原始图像大小的】因此,在训练中,为了让程序知悉图片位置以及得以应用标注信息,需要完成“生成数据列表”的任务。
生成 train/test.txt
A. 建议首先画出人脸边框以及相应关键点以熟悉程序操作、坐标表示以及检验标注数据是否正确,如下图所示:【为省地方,只截取了部分图】
B. 我们可以看到,有时由于标注的不仔细或不同标注任务标注标准不同,关键点可能会超出人脸框的范围。所以适当扩大人脸框的范围是必要的。这里,可以选取原始人脸框的 0.25 倍进行 expand。expand 时,请注意扩增后的人脸框不要超过图像大小。如下图:
C. 真正有用的部分是人脸以及人脸关键点。所以,希望对人脸进行截取。同时截取过后人脸关键点坐标即变为相对于截取后的人脸图了。此步非常简单,只需要用关键点坐标减去人脸边框左上角点坐标即可:
D. 生成 train/test.txt.此时我们已可以生成训练以及测试数据集。(如果严谨些,还可生成验证数据集,这里只是项目举例,所以只简单生成训练与测试数据。) 训练与测试数据的比例,依个人喜好,通常 7:3 至 9:1 均能接受。并且此时可以选择对数据进行 shuffle。(不 shuffle 也可。Caffe 或 pytorch 里均有可以 shuffle 的功能)最终训练集/测试集应该长成这样:
E. 验证:为确保生成数据的准确性。生成后仍需要验证。具体可为利用生成的数据截取人脸,并画出关键点,检验正确性。以上为数据准备的全部流程。
A. 利用Netron/Netscope工具查看网络结构
B. 知识点:
数据在网络中的维度顺序是什么?
经过conv1_1后,根据(W+2P-K)/S+1=(112+2*0-5)/2+1=54,所以这步过后是8x54x54(除法那步向下取整)
经过avg_pool后,根据(W+2P-K)/S+1=(54+0-2)/2+1=27, 所以这步过后是8x27x27
经过conv2_1后,(27+0-3)/1+1=25,这步过后是16x25x25
经过conv2_2后,(25+0-3)/1+1=23,这步过后是16x23x23
经过avg_pool后,(23+0-2)/2+1=12(注意程序这里用了ceil),这步过后是16x12x12
经过conv3_1后,(12+0-3)/1+1=10,这步过后是24x10x10
经过conv3_2后,(10+0-3)/1+1=8,这步过后是24x8x8
经过avg_pool后,(8+0-2)/2+1=4,这步过后是24x4x4
经过conv4_1后,(4+2-3)/1+1=4,这步过后是40x4x4
经过conv4_2后,(4+2-3)/1+1=4,这步过后是80x4x4
经过ip1后,为128
经过ip2后,为128
经过ip3后,为42
nn.Conv2d()中参数含义与顺序?
输入的C,出来的C,kernel大小,stride,padding
nn.Linear()是什么意思?参数含义与顺序?
输入的C,输出的C
nn.PReLU()与 nn.ReLU()的区别?示例中定义了很多 nn.PReLU(),能否只定义一个PReLU?
输入为负数时不都为0。每个PReLU有个控制斜率的参数,所以不能只定义一个。
nn.AvgPool2d()中参数含义?还有什么常用的 pooling 方式?
kernel size和stride。maxpool吧
view()的作用?
改变了维度,在这里等于flatten
体会 forward 中网络如何被构建。
注意返回值返回的并不是 loss
C. 开始新建一个 detector.py,写自己的 class Net
D.训练框架的搭建:
打开 detector.py,由主函数 main()出发。
A. 第一部分
首先是参数设置部分,这些参数控制着整个程序。
B. 第二部分
此部分是一些“程序控制代码”,包括:
如何设置 GPU
device = torch.device(“cuda” if use_cuda else “cpu”)
如何将数据/网络传入 CPU/GPU
. to(device)
如何读取数据
这个就不拷贝了,程序里挺长的
C. 第三部分
此部分是关于“训练控制代码”,知识点包括:
如何设置 loss
loss 都有哪些。分别有什么作用(常用的即可)
MSE,cross entropy,L1loss等
如何设置优化器
SGD的话主要就是lr和momentum了
D. 第四部分
此部分是定义“程序处于什么阶段”的部分。
这里我们看到有四个阶段。分别是:
a. 训练
此部分将引导我们进入“训练代码”。训练代码是程序主体。模型学习来源于此部分
b. 测试
此部分将引导我们进入“测试代码”。如何总体评价我们训练的模型好坏,将由此部分做出
c. Finetune
有时我们会用别人训好的模型进行 finetune,或接着训练自己的模型。
d. 预测
有时我们也会直观检测我们训好的代码。比如,应用训好代码画出人脸关键点。
主体程序框架的搭建
在 detector.py 中完成main 程序主体。
读取数据:
此时get_train_test_set()函数一定显得无比突兀。
数据的预处理与读取就藏于这里。我们可以通过文档加载了解到这个函数应该藏于 data.py。
A. 第一部分,关于主体:
首先,找到主体:
不难看到根源在 load_data()中。
另外main 主函数是用于检验处理后的数据的准确性的,切记随时检查准确性非常重要。
B. 第二部分,关于 load_data():
知识点:
‘Image’ 为我们的图像
‘Landmarks’ 为我们的脸部关键点
Normalize:作用由 channel_norm 函数体现,为了将图片 normalize。
这是一个相对传统的做法,目前人们发现做不做这个 normalize对最终结果的影响似乎不是很大。
ToTensor:作用是将数据转成 pytorch 可使用的格式
此部分为最终“将变换作用于数据”的部分。
可以看到这个 class 有三个部分,分别是:
__init__
__len__
__getitem__
此三部分,均不需显示调用。
它们分别对应:
类的初始化
一共有多少数据
对于每批数据应当做何变换
最关键的部分为__getitem__
,这是变换的主体。
返回的将是真正用于训练的数据,因而它是一个 dictionary。代表了:
‘image’:图像是什么
‘landmarks’:关键点是什么
需要格外注意的是在处理图像的过程中有将图像 resize 到 train_boarder(这里是 112x112),但是 landmarks 得到的确是相对于 expand 过后的人脸 crop。
FaceLandmarksDataset()
a. data.py
b. 按照刚才的流程,重写读取数据部分
c. 补全在 FaceLandmarksDataset()中,对于 landmarks 的操作。使你的 landmarks 是针对 train_boarder size 的(示例中是 112x112),而非原始 expand facial crop 的。【另这里处理数据是用的 PIL 库,完全可以将其替换成熟悉的 OpenCV 库来进行图像操作。另外请注意,PIL 库中图像是 RGB顺序,OpenCV 为 BGR,两者不同】
main 函数
data.py 中:
以便验证自己的数据变换是正确的。
detector.py->def train().
这里就是训练代码的主体。包含两部分,其一是真正的训练部分;其二是 validating 部分。因为除了 train,我们要在 train 的过程中,实时监测训练结果,避免过拟。
知识点:
print 的格式化如何实现的
optimizer.zero()与 optimizer.step()的作用是什么?
pytorch里头梯度会累积,zero就把所有梯度先赋零;step就是走一步learning step
model.eval()产生的效果?
model.state_dict()的目的是?
把model各层用dict表示出来,以方便读写
何时系统自动进行 bp?
在计算forward的时候会顺便把梯度算出来
如果自己的层需要 bp,如何实现?如何调用?
需要自己把梯度存到weights里,step的时候更新这些weights
训练
至此训练的部分已经完整:
A. 在 detector.py 中完成 def train 函数
B. train 函数应由 main 函数 train 部分调用
C. 保存训练好的 model
D. 存留 log 信息的代码
E. train 函数 return 的部分,作用在于可以存下各个阶段的 loss,用于后续绘制loss 走势
F. 如果顺利至少在 train 时loss 低至 3 以下,在 validation 时loss 低至9 以下。如果你在应用自己的 criterion,那么请忽略此条。一切以画出的结果是否准确为准
在 detector.py 中还有 Test、Predict 与 Finetune 三部分,分别在 main 函数args.phase==Test、Predict 与 Finetune 中定义。
请注意Test 的目的是直接利用已训练好 model作用在 test 数据集上。(为简便,这里 test 和 valid 可用一个数据集。)看平均 loss。
Predict 目的为利用已训练好 model 作用在某张图片上画出预测的 landmarks,直观看效果。相当于 model 的应用。
Finetune 为利用已训练好 model,继续训练。(通常finetune 会需要更小的 lr)。
Test\Predict\Finetune
A. load 已有 model
B. Finetune 时,有时还要固定某些层不参与训练,如何 freeze 某些层
我是直接将requires_grad设置成False
至此一套完整的流程已经全部建立
涉及到了:处理原始数据、网络搭建、流程控制、数据预处理、训练、检验等多个步骤。
此阶段,是对于上一阶段的补充。stage1 仅仅是起步。毕竟网络很简单、 loss 很简单、训练策略很简单、数据很简单等等。总之,一切都很简单。所以这个阶段是使 stage1 的各个部分变得 fancy。没有一定之规,总之,给出一个秒杀 stage1 的 model。
关于数据:
数据预处理非常简单。所以还可以尝试很多:
关于训练方法 :
训练采用最基本的 SGD,尝试更多方法?
关于网络:
网络非常简单,就是个线性网络。如果用其他的呢?
以下为扩展内容
关于目标:
目标非常简单,就是直接回归坐标。但有的时候,我们也会回归 heatmap。这也可以是一个可以尝试的部分。
关于 loss:
我们的 loss 非常简单,就是 MSE,可以尝试下其他 loss,比如 smooth l1 loss。如果更改了训练目标,也应当配合训练目标适当更改 loss。
现实项目往往更加复杂。比如:现实情况下,你怎么能保证输入就是张人脸呢?因此真实场景中,是否应该再加入分类分支呢?
任务完成真实场景下人脸关键点检测:
A. 为网络加入分类分支。
真实场景下,不能保证输入就是人脸。
如果没有分类分支,那么一旦输入为非人脸,虽然此时不应输出任何关键点,但是网络势必仍然会输出伪关键点。因而一种解决方法就是为网络加入分类分支。如果分类认为输入图片为人脸,则输出人脸关键点;如果分类认为输入为非人脸,则此时不输出人脸关键点。
B. 生成非人脸数据。
非人脸数据的生成可由图片中不含有人脸的部分得到。可以认为,如果一个image crop,其与人脸重叠部分 iou<0.3,就是非人脸。
C. 重新生成 train/test.txt
以前生成的 train/test.txt 格式如下:
文件名 | 人脸框 | 人脸关键点
现在要生成新的 train/test.txt格式如下:
文件名 | 图像框 | 人脸关键点 | 1 【此时是人脸,称正样本】
文件名 | 图像框 | 0【此时非人脸,称负样本】
D. 对于 loss 的处理
这是最复杂的部分:
Loss 分为两部分:a. 分类 loss + b. 坐标 loss
关于 a:需要注意正负样本比例。同时还可以利用 weighted cross entropy loss 控制侧重于训练正样本还是负样本。学习如何使用 weighted loss;
关于 b:需要注意只有正样本拥有这个 loss,负样本是没有这个 loss 的。所以如何来控制?可以在样本计算 loss 时加入 mask,这个 mask 可以由 label 来生成。比如一个 batch 有 4 个样本,分别为:负样本、人脸、负样本、负样本。则这个mask 可以为[0, 1, 0, 0]。此时真正计算 b loss 的就只有第二个样本了。关于 mask的应用,可以参考 yolo-pytorch 版本。比如这个连接中 response、not response loss 的部分。
除此之外,由于 loss 分为两个部分,还可以给两个部分加入不同的权重,权重多的,会被侧重训练。如:
L
o
s
s
=
a
∗
l
o
s
s
a
+
b
∗
l
o
s
s
b
Loss=a*loss_a + b*loss_b
Loss=a∗lossa+b∗lossb
此时,如果 a>b,则会侧重训练 loss_a。
E. 关于检测
除了对于坐标 loss 的监测,还要对分类 loss 进行监测,并给出 accuracy。同时,此时的 accuracy 不能仅仅是总体样本的 accuracy,我们还要分别看正负样本的accuracy,所以这个需要去统计各个 batch 中,正负样本的个数并加以计算
至此stage3 也算是告一段落。
Stage3 是较为复杂的综合类问题。是可以当做真实项目处理的实际问题。可以看到与 stage1的区别。
通过三个阶段,每个阶段的提高,希望同学们能有所收获。
Stage1 的目的,仅仅在于熟悉流程,熟悉编程。
Stage2 的目的,在于应用一些小技巧和一些常用的框架,属于“熟悉主流文化”
Stage3 的目的,是希望熟悉对实际问题的感觉,里面会涉及到更多技巧与思想。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。