用Caffe提取深度特征

用Caffe提取深度特征

最近做对比实验,要比较非深度的方法加上deep feature之后的效果。于是就用Caffe提了一把特征,过程不困难但是有点繁琐,姑且记录下来,留个参考。

准备工作

用Caffe提取深度特征,需要以下几样东西:

  • 能够运行的Caffe环境
  • 提取特征的深度网络定义(prototxt)
  • 这个网络的参数(caffemodel)
  • 需要提取特征的数据

配好环境是必须的,不用多说。网络定义和网络参数,可以用自己学的网络,也可以用公开发表的经典网络,比如AlexNet, VGG之类的。Caffe官网提供了一些经典网络,可以参考这里。被提特征的数据需要处理成能被DataLayer或者ImageDatalayer读取的格式。

提取特征

做好准备工作之后,就运行build/tools/feature_extraction.bin提取特征,命令行参数如下:

1
extract_features pretrained_net_param  feature_extraction_proto_file \
extract_feature_blob_name1[,name2,...]  save_feature_dataset_name1[,name2,...] \
num_mini_batches  db_type  [CPU/GPU] [DEVICE_ID=0]

参数1是模型参数(.caffemodel)文件的路径。

参数2是描述网络结构的prototxt文件。程序会从参数1的caffemodel文件里找对应名称的layer读取参数。

参数3是需要提取的blob名称,对应网络结构prototxt里的名称。blob名称可以有多个,用逗号分隔。每个blob提取出的特征会分开保存。

参数4是保存提取出来的特征的数据库路径,可以有多个,和参数3中一一对应,以逗号分隔。如果用LMDB的话,路径必须是不存在的(已经存在的话要改名或者删除)。

参数5是提取特征所需要执行的batch数量。这个数值和prototxt里DataLayer中的Caffe的DataLayer(或者ImageDataLayer)中的batch_size参数相乘,就是会被输入网络的总样本数。设置参数时需要确保batch_size * num_mini_batches等于需要提取特征的样本总数,否则提取的特征就会不够数或者是多了。

参数6是保存特征使用的数据库类型,支持lmdb和leveldb两种(小写)。推荐使用lmdb,因为lmdb的访问速度更快,还支持多进程同时读取。

参数7决定使用GPU还是CPU,直接写对应的三个大写字母就行。省略不写的话默认是CPU。

参数8决定使用哪个GPU,在多GPU的机器上跑的时候需要指定。省略不写的话默认使用0号GPU。

读取和转换

使用extract_feature.bin提取的特征是保存在数据库里的。在使用特征时需要读取数据库。这里介绍如何在Python环境中读取LMDB中的特征数据。

首先,需要安装Python的LMDB接口库:

1
pip install lmdb

需要管理员权限安装的在命令前加sudo

然后需要编译caffe.proto的Python版,我们需要它来访问Datum这个数据结构。跟随官方教程配置好Python环境,运行make pycaffe编译成功的话,$CAFFE_ROOT/python/目录里就会有编译好的代码。

安装好LMDB库和之后,在代码中引用该库:

1
2
import lmdb
from caffe.proto import caffe_pb2

打开LMDB数据库:

1
2
fea_lmdb = lmdb.open(lmdb_name) #打开数据库
txn = img_lmdb.begin()	#获取transaction对象

extract_feature.bin在保存数据的时候,以从0开始递增的整数作为数据库条目的key。按照key的顺序遍历数据库:

1
2
3
4
for idx in xrange(feature_num): 
    value = txn.get(str(idx)) #获取Datum序列化成的字符串,注意key要转换为字符串
    datum = caffe_pb2.Datum() #创建Datum对象
    datum.ParseFromString(value) #从字符串中反序列化Datum对象

需要注意的是,数据库中的key固定是字符串格式,所以要先转换。数据库里的value是Datum对象序列化成的字符串,用ParseFromString()方法进行反序列化。

caffe.proto中对Datum类的定义如下。datafloat_data分别保存字节型和浮点型的数据,channels,height,width描述了数据的维度与形状。

1
2
3
4
5
6
7
8
9
10
11
12
message Datum {
  optional int32 channels = 1;
  optional int32 height = 2;
  optional int32 width = 3;
  // the actual image data, in bytes
  optional bytes data = 4;
  optional int32 label = 5;
  // Optionally, the datum could also hold float data.
  repeated float float_data = 6;
  // If true data contains an encoded image that need to be decoded
  optional bool encoded = 7 [default = false];
}

因为提取的特征向量是实数,所以我们从float_data属性中获取数据。repeated类型的float_data在Python中作为一个list处理,所以可以用列表推导式(list comprehension)进行操作。数据的输出格式根据实际需要定义就好,关键是能获取到原始数据。这里我们把每个向量转换成一个字符串,相邻两维之间用空格分隔。

1
2
#把float_data中的所有项转换成字符串,以空格分隔连接起来
data_str = ' '.join([str(dim) for dim in datum.float_data]) + "\n"

注意事项

  • 提取特征时,网络运行在Test模式下
    • Dropout层在Test模式下不起作用,不必担心dropout影响结果
    • Train和Test的参数写在同一个Prototxt里的时候,改参数的时候注意不要改错地方(比如有两个DataLayer的情况下)
  • 减去均值图像
    • 提取特征时,输入的图像要减去均值
    • 应该减去训练数据集的均值
  • 提取哪一层
    • 不要提取Softmax网络的最后一层(如AlexNet的fc8),因为最后一层已经是分类任务的输出,作为特征的可推广性不够好

你可能感兴趣的:(Caffe)