最近做对比实验,要比较非深度的方法加上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类的定义如下。data
和float_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),因为最后一层已经是分类任务的输出,作为特征的可推广性不够好