前言#
参考了 https://zhuanlan.zhihu.com/p/22190532
细节有一些问题,我这里过一遍整个流程,涉及到的问题都会做讲解。包括部署问题,最后我也会讲讲。
文中涉及到的我用的代码都在这里: https://github.com/HansRen1024/Face-Attributes-MultiTask-Classification
正文#
去原作者 github 网站下载 https://github.com/HolidayXue/CodeSnap
这个项目。
将 convert_multilabel.cpp 放到 caffe/tools/ 目录下。
修改 81 行:
‘>>’ should be ‘> >’
然后注释掉 149 行。
命令行在 caffe / 下运行:
make clean
make all -j8
make py
我是用的 CelebA 数据集,都是人脸属性的。
下载地址: https://pan.baidu.com/s/17rp2gKqtvuT48yuPfJK3sA
在 Anno 目录下找到 list_attr_celeba.txt
先命令行运行:
sed -i 's/ / /g' list_attr_celeba.txt
将文档中两个空格都替换为一个空格。
然后可以通过下面代码提取你想要的人脸属性
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
Created on Mon Aug 20 16:57:52 2018
@author: hans
"""
ReadTxt = 'list_attr_celeba.txt'
WriteTxt = 'train.txt'
r = open(ReadTxt,'r')
w = open(WriteTxt,'w')
rLine = r.readline().split('\n')[0]
while rLine:
rLine = r.readline().split('\n')[0]
if not rLine:
break
# image,bangs,eyeglasses,gender,
wLine = rLine.split(' ')[0]+' '+rLine.split(' ')[6]+' '+rLine.split(' ')[16]+' '+rLine.split(' ')[21]+'\n'
w.write(wLine)
r.close()
w.close()
接着命令行运行:
sed -i 's/-1/0/g' train.txt
将 train.txt 文档中 - 1 改成索引 0。
对比了一下原图片,提取的属性是对的。
然后从 train.txt 拿出了一部分放到 val.txt 中
运行脚本,生成可供训练用的 lmdb 数据:
echo "Creating train lmdb..."
~/caffe-multi/build/tools/convert_multilabel \
-resize_height=227 \
-resize_width=227 \
-shuffle=false \
/home/hans/data/face/CelebA/Img/img_align_celeba/ \
train.txt \
./train_db \
./train_lb \
3
echo "Creating val lmdb..."
~/caffe-multi/build/tools/convert_multilabel \
-resize_height=227 \
-resize_width=227 \
-shuffle=false \
/home/hans/data/face/CelebA/Img/img_align_celeba/ \
val.txt \
./val_db \
./val_lb \
3
img_align_celeba 是切好人脸的数据集
后面参数 3 表示我这边提取了三种人脸属性。
最后是修改 mcnn_Attri.prototxt,均值,归一化,数据路径,还有一个很重要,backend 一定要改成 LMDB!
name: "MCNN_Attri"
layer {
name: "data"
type: "Data"
top: "data"
transform_param {
scale: 0.007843
mean_value: 127.5
mean_value: 127.5
mean_value: 127.5
crop_size: 227
}
include {
phase: TRAIN
}
data_param {
source: "/home/hans/data/face/CelebA/attri/doc/train_db"
batch_size: 192
backend: LMDB
}
}
layer {
name: "labels"
type: "Data"
top: "labels"
include {
phase: TRAIN
}
data_param {
source: "/home/hans/data/face/CelebA/attri/doc/train_lb"
batch_size: 192
backend: LMDB
}
}
layer {
name: "data"
type: "Data"
top: "data"
transform_param {
scale: 0.007843
mean_value: 127.5
mean_value: 127.5
mean_value: 127.5
crop_size: 227
}
include {
phase: TEST
}
data_param {
source: "/home/hans/data/face/CelebA/attri/doc/val_db"
batch_size: 128
backend: LMDB
}
}
layer {
name: "labels"
type: "Data"
top: "labels"
include {
phase: TEST
}
data_param {
source: "/home/hans/data/face/CelebA/attri/doc/val_lb"
batch_size: 128
backend: LMDB
}
}
layer {
name: "sliceL"
type: "Slice"
bottom: "labels"
top: "label_attr6"
top: "label_attr16"
top: "label_attr21"
slice_param {
slice_dim: 1
slice_point: 1
slice_point: 2
}
}
有三个输出,slice_point 删到 2。
【2018.08.22 更新】-----------------------------------------------
这个网络结构在 bn2 后分成了六组,每一组都有不同个输出。并且每一组前面都有结构一样的层,然后到各个任务的全连接层和最终输出层。将对应不需要的任务层和组删掉。对于我这三个类别,我发现 gender 是单独一组的,眼镜在第四组,刘海在第六组。为了减少参数量,我将眼镜和刘海都放到了第六组,删除了第四组的前面网络。第一组 gender 的我暂时没改,担心三个任务都放到一组里出现不收敛的情况。不过以后我肯定会做测试。
(训练输出很好,在验证集上准确率非常高,但是实际检测的时候发现对单独在一组的性别分别准确,对眼镜和刘海分别不准确。我下一步还是要将眼镜和刘海分别放在一组,并且将全连接层改成全局平均池化。)
(将刘海和眼镜分别放在了一组,同时删了全连接层。模型是变小了,只有 8M 了。验证集准确率还挺高,98% 左右,但是实际实验效果太差。接下来尝试换换核心网络,用 mobilenet
v2 或者 squeezenet 试试。同时我考虑到可能不一定是网络的原因,观察了一下数据集,有刘海和戴眼镜的图片很少。如果换网络也不行,那么下一步应该要对部分数据做 argument 了。)
【2018.08.29 更新】-----------------------------------------------
在验证集和测试集上结果都很美好,但是拿摄像头或者视频实际应用的时候效果差的离谱。
找到原因了,是我测试时候的预处理方式不对导致的。
【2018.09.04 更新】-----------------------------------------------
几点心得:
- 用全卷积 + 全局池化替代全连接层,效果影响不大,模型参数量不变。
2. 全连接层参数量大,但执行时间很快。也就是说全连接层对模型执行时间影响不大,对模型大小影响较大。
3. 查看了每一层的执行时间,发现大卷积核效率很低,最好都改用 3*3 的小卷积核。
4. 卷基层输出数量最好是 2^n。
最后改 loss_weight,所有任务的 loss_weight 加一起等于 1。我这里只有三个任务,所以 loss_weight 我都设置为 0.3333 了。
剩下的 solver.prototxt 我就不说了,跟单任务的一样。
最后放一张训练输出截图:
后面我会持续更新。。。#
1. 可视化代码 2018.08.21 已上传到 github, show.py
2.caffe-python 推理代码 2018.08.21 已上传到 github, face_attri.py
3.ncnn-C++ 部署代码