FCN 实战
FCN 8s-Caffe-VOC 复现
1. 环境配置
- 编译安装 caffe, 并安装 python 接口:https://github.com/BVLC/caffe
- 下载FCN网络模型:https://github.com/shelhamer/fcn.berkeleyvision.org
2. 数据集说明
-
PASCAL VOC
语义分割标注有20个类和一个背景。
-
Semantic Boundary Dataset (SBD)
SBD 是 PASCAL VOC 数据集更进一步的标注,提供了更多的语义分割和实力分割的 mask。
注意: VOC2012 验证集与 SBD 训练集有交叉的部分。
模型数据集
- 训练集:SBD ( 8498 imgs )
- 验证集:VOC 2012 与 SBD 训练集无交叉部分 ( seg11valid.txt - 736 imgs )
3. 更改模型路径
模型目录:
fcn.berkeleyvision.org/voc-fcn8s
|_caffemodel-url 预训练权重的下载地址
|_deploy.prototxt
|_net.py 生成网络模型的文件,暂用不到
|_solve.py
|_solver.prototxt
|_train.prototxt
|_val.prototxt
将 fcn.berkeleyvision.org/data/pascal 文件下的 seg11valid.txt 拷贝到 VOC2012/ImageSets/Segmentation 文件夹下
~ solve.py
# -*- coding: UTF-8 -*-
+ import sys
+ sys.path.insert(0, '${caffe-root}/python') # caffe python 接口
+ sys.path.append('fcn.berkeleyvision.org') # 把 FCN 模型加入 python 路径
...
* weights = './voc-fcn8s/VGG_ILSVRC_16_layers.caffemodel' # 下载好的预训练权重路径
...
- caffe.set_device(int(sys.argv[1]))
+ caffe.set_device(2) # 设置使用的gpu
...
* solver = caffe.SGDSolver('./voc-fcn8s/solver.prototxt')
...
* val = np.loadtxt('../data/pascal/seg11valid.txt', dtype=str)
~ solver.prototxt
* train_net: "./voc-fcn8s/train.prototxt"
* test_net: "./voc-fcn8s/val.prototxt"
...
* snapshot_prefix: "./voc-fcn8s/snapshot/train" # 先创建该目录
~ train.prototxt
* param_str: "{\'sbdd_dir\': \'./data/sbdd/dataset\',...
~ val.prototxt
* param_str: "{\'voc_dir\': \'../VOCdevkit/VOC2012\',...
数据层为自定义的Python Layer
-
VOCSegDataLayer - param_str
voc_dir - path to PASCAL VOC year dir split - train / trainval / test mean - tuple of mean values to subtract (B, G, R) randomize - load in random order (default: True) seed - seed for randomization (default: None / current time)
-
SBDDSegDataLayer - param_str
sbdd_dir - path to SBDD `dataset` dir split - train / seg11valid mean - tuple of mean values to subtract (B, G, R) randomize - load in random order (default: True) seed - seed for randomization (default: None / current time)
4. 训练模型
在 fcn.berkeleyvision.org 文件夹下
$ python voc-fcn8s/solve.py 2>&1|tee /home/xieqi/workspace/caffe-fcn/voc-fcn8s/log/train-log-1.log
在 1080Ti 11g 上训练每秒大概能迭代 6.5 次,GPU 大约占用 5G。但训练开始的时候 loss 抖动的很严重,训练了 28000 次还是抖动的很严重,跟开始的时候没有区别,不收敛。停止训练,查找原因。
不收敛的原因分析:
- FCN 是将 VGG16 结构中的第6层(长度为4096的一维向量)、第7层(长度为4096的一维向量)、第8层(长度为1000的一维向量,分别对应1000个类别概率)的全连接层转化成一个个的卷积层。卷积核的大小(通道数,宽,高)分别为(4096,1,1)、(4096,1,1)、(1000,1,1)。
- 预训练模型 “VGG_ILSVRC_16_layers.caffemodel” 中有 fc6、fc7 的权值,但与本次实验所要使用的第6层、第7层的权值不一样。预训练的为全连接层,FCN 为卷积层。
解决不收敛的方法:
先把预训练模型加载到 VGG16 的网络中,再使用 FCN 提供的函数sugery.transplant(solver.net, vgg_net) 将全连接层的权值转化为卷积层的权值。
解决不收敛的过程:
- 下载 VGG_ILSVRC_16_layers_deploy.prototxt 文件。
- 修改 solver.py 文件。
~ solver.py
#!/home/xieqi/anaconda2/envs/py2.7/bin/python
# -*- coding: UTF-8 -*-
import sys
sys.path.insert(0, '/home/xieqi/workspace/caffe/python')
sys.path.append('/home/xieqi/workspace/caffe-fcn')
import caffe
import surgery, score
import numpy as np
import os
try:
import setproctitle
setproctitle.setproctitle(os.path.basename(os.getcwd()))
except:
pass
vgg_weights ='../../VGG16/VGG_ILSVRC_16_layers.caffemodel' # 增加
vgg_proto = '../../VGG16/VGG_ILSVRC_16_layers_deploy.prototxt' # 增加
# weights = '/home/xieqi/workspace/caffe-fcn/voc-fcn8s/VGG_ILSVRC_16_layers.caffemodel' # 注释掉
# init
# caffe.set_device(int(sys.argv[1]))
caffe.set_device(2)
caffe.set_mode_gpu()
solver = caffe.SGDSolver('/home/xieqi/workspace/caffe-fcn/voc-fcn8s/solver.prototxt')
# solver.net.copy_from(weights) # 注释掉
vgg_net = caffe.Net(vgg_proto, vgg_weights, caffe.TRAIN) # 增加
surgery.transplant(solver.net, vgg_net) # 增加
del vgg_net # 增加
# surgeries
interp_layers = [k for k in solver.net.params.keys() if 'up' in k]
surgery.interp(solver.net, interp_layers)
# scoring
val = np.loadtxt('/home/xieqi/workspace/caffe-fcn/data/pascal/seg11valid.txt', dtype=str)
for _ in range(25):
solver.step(4000)
score.seg_tests(solver, False, val, layer='score')
5. 测试模型
测试模型需要的文件
- 已训练好的模型
- deploy.prototxt:网络部署文件,可由 train.prototxt 或 val.prototxt 修改创建
- 测试代码:fcn.berkeleyvision.org/infer.py
通过 train.prototxt 文件修改,修改内容。
- 修改输入层
- 去掉 dropout 层
~ 修改输入层
layer {
name: "input"
type: "Input"
top: "data"
input_param {
# These dimensions are purely for sake of example;
# see infer.py for how to reshape the net to the given input size.
shape { dim: 1 dim: 3 dim: 500 dim: 500 }
}
}