当利用Faster R-CNN自带的train_net.py文件进行faster R-CNN模型的训练时,我们通常需要输入以下指令:
python ./tools/train_net.py
--solver ./models/pascal_voc/ZF/faster_rcnn_end2end/solver.prototxt
--cfg ./experiments/cfgs/faster_rcnn_end2end.yml
--weights ./data/imagenet_models/ZF.v2.caffemodel
--imdb voc_2007_trainval
这么长一串指令究竟代表了什么?为什么faster R-CNN模型的数据输入层没有见到我们之前经常见到的输入数据的文件路径?内部究竟是怎样进行数据的加载和之后的网络训练呢?这个问题曾经困扰了我一段时间,直到有一天狠下心来仔细研读faster R-CNN工程的源码之后才豁然开朗。
整个网络开始训练的入口函数为trian_net.py文件中的main函数,该首先调用parse_args()函数解析从命令行中输入的参数,也就是“–”后面的内容,在此我们向程序传递了模型的求解配置文件solver.prototxt、模型的配文件faster_rcnn_end2end.yml、以及与预训练网络ZF.v2.caffemodel以及我们的数据集文件,在此我们需要特别注意该参数,因为它对于我们的网络训练时数据集的加载有着密切的关系,后文将对该参数进行详细的解析。
在main函数中接着调用了cfg_from_file()/cfg_from_list()函数,该函数是从faster R-CNN的配置文件中,也就是faster_rcnn_end2end.yml中读取相应的模型训练配置参数,在此我们先不关注这个问题。接下来调用的是combined_roidb()函数,该函数就是我们整个数据集文件加载的第一个调用的函数,进入该函数之后,层层跟踪该函数的调用关系,我们可以绘制出以下调用调用关系层级结构图,如图1所示。
从上图中我们可以看出,通过combined_roidb()函数的层层调用,最终image_path_from_index()函数被调用,而该函数的功能是根据所给出的文件的名称返回该图片的绝对路径。
看到这里可能会有一个疑问,就是文件的名称是从哪里得到的?在输入的网络模型训练参数命令中貌似并没有文件名的信息,那么文件名究竟是怎么得到的呢?为了找到这个答案,我们需要对刚才提到的需要特别注意的网络训练参数进行仔细的研究。在研究之后发现,这个所谓的voc_2007_trainval参数并不是一个简简单单的文件名,而是和数据集以及训练时需要加载的图片有着莫大的关系。
为了找到答案,我们需要从combined_roidb()的第一级调用寻找线索,在get_roidb(imdb_name)函数中传递参数imdb_name,此时imdb_name = voc_2007_trainval,在该函数中首先判别输入的数据集是否存在,但要想判断数据集是否存在,那么一定有一个参考,也就是肯定是在程序执行时会自动生成一个数据集名称的集合,通过该文件向上翻,我们看到了我们需要找的集合,源码如图2所示。
在代码中我们可以看到,在程序执行时会自动生成__sets()集合,而其中存放的字符串就是我们所使用的参数,在此为voc_2007_trainval。所以如果我们在—imdb后输入的参数如果不是__sets()字典中的字符串,那么就会报错:Unknown dataset:xxx。到这里我们只是知道了在这个地方参数应该改输入些什么,还不知道这个参数在接下来是怎么传递到我们的训练集图片寻找函数中。在此我们需要关注红线部分,其为一个lambda表达式,用来实现一个类似于函数的功能。pascal_voc(split,year)通过向其传递split和year参数来创建pascal_voc对象,在此便是spilt = trainval,year = 2007。我们需要特别关注这个split参数,因为它关系到我么的训练集图片名称的命名,train.txt、val.txt、trainval.txt、test.txt,也就是我们想要训练的图片名称所在的文件究竟要是什么名字。在此我们的存放图片名称的txt文件名应为trainval.txt。那么为什么呢?接下来就来分析为什么是这个名字。
在上面的lambda表达式实际上是声明了一个类的对象,并将其参数传递给pascal_voc对象,具体代码如图3所示。
图3 pascal_voc对象初始化源码
在该创建该对象时,自动调用init()函数来初始化对象,在初始化的过程中将split参数传递给了image_set形参,并随机将其赋值给self._image_set私有变量,year参数传递给了self._year。也就是再次self._image_set = trainval、year = 2007,而pascal_voc对象的self.image_index私有变量在对象初始化时通过调用_load_image_set_index()函数来读取ImageSets/Main下的self._image_set +’.txt’文件,也就是trainval.txt文件,以此来获得图片文件的文件名。至此我们已经找到了图片文件的文件名是如何得到的,那么我们也就可以放心的去制作自己的数据集和训练自己的数据了!