折腾faster-rcnn(三)--训练篇

标签(空格分隔): Linux 深度学习


前面,我们已经成功跑起来faster-rcnn,并且自己动手训练了PascalVOC2007图像集,但是笔者接下来继续沿着官方文件跑PascalVOC2012,训练到第二阶段的某个点,程序就开始报错退出。查看logs发现,找不到图像的Annotation文件。遂放弃,转而生成自己的图像集。

图像收集

这里笔者需要先完成毕业设计,所以图像集设计的种类不多,也就三类。毕竟PascalVOC一开始也只有四类呢!看了这么多经验帖,总结需要注意一下几个问题:文件名不要大写,后缀只能是.jpg或.JPEG,图像不要长得太过“瘦长”。这里为了重新命名收集来的文件,笔者自己写了一段Python脚本,变量命名注释极不规范,而且并不能识别某些后缀是jpg但并不是jpg的“假图”,大家看看就好,勿喷。

# -*-coding:utf-8-*-
import os
from PIL import Image
import argparse

'''
This scrpit was designed for rename these jpeg images downloaded from different websites,we can use it to rename
as such format: 0000001.jpg
'''


def readimage(FileDir):
    imageset = []
    for image in os.listdir(FileDir):
        if os.path.isfile(os.path.join(FileDir, image)):
            imageset.append(image)
    return imageset


def RenameFiles(FileDir, count=1):
    extlist = ['.jpg', '.jpeg']
    imageset = readimage(FileDir)
    for image in imageset:
        extension = os.path.splitext(image)[1].lower()
        if FileDir == 'kiss':
            clsnum = 0
        if FileDir == 'hug':
            clsnum = 1
        if FileDir == 'hands':
            clsnum = 2
        if extension in extlist:
            oldname = os.path.join(FileDir, image)
            newimage = '0000' + str(count)
            count = count + 1
            newimagename = str(clsnum) + newimage[-5:] + '.jpg'

            print newimagename
            newname = os.path.join(FileDir, newimagename)
            os.rename(oldname, newname)
        else:
            print "Exist non-jpeg files: " + image
            break


def parse_args():
    """Parse input arguments."""

    parser = argparse.ArgumentParser(description='Rename demo')
    parser.add_argument('-set', dest='dataset', help='choose dataset')
    parser.add_argument('-count', dest='countnum', help='start number is set to 0',
                        default=0, type=int)
    args = parser.parse_args()

    return args


if __name__ == "__main__":
    args = parse_args()
    if args.countnum == 0:
        print "please change countnum"
    else:
        RenameFiles(args.dataset, args.countnum)

图像标定

这里采用的是labelImg的标定工具,发动同学和学姐学弟一起帮我标定2333~~~

图像分类

这里我们需要产生和PascalVOC一样格式的txt文件,这里笔者事先研究过了,产生是有点规律的,具体见我的上一篇博客:折腾faster-rcnn(二)--数据篇
然后是几个集合的比例问题:
一般情况下trainval占总数据集的50%,test占总数据集的50%;train占trainval的50%,val占trainval的50%。上面所占百分比可根据自己的数据集修改,如果数据集比较少,test和val可少一些。这个在生成以上四个文件的脚本里面可以调比例,暂时就用默认比例试试,跑跑。生成这几个文件的脚本如下:

# -*-coding:utf-8-*-
import os
import random

import FileOperate as fo

'''
Generate txt files in directory VOC2012/ImageSet
'''

def GenerateSet(abspath, imgsetdir):
    imgset = []
    imgfiles = fo.readfile(os.path.join(abspath, imgsetdir))
    for imgfile in imgfiles:
        imgset.append(os.path.splitext(imgfile)[0])
    rate = [0.5, 0.5, 0.5, 0.5]
    clsset = ['kiss', 'hug', 'hands']
    txtset = ['trainval.txt', 'train.txt', 'val.txt', 'test.txt']
    trainvalset = []
    testset = []
    trainvalposset = set(random.sample(xrange(len(imgset)), int(len(imgset) * rate[0])))
    testposset = set(xrange(len(imgset))) - trainvalposset
    for pos in trainvalposset:
        trainvalset.append(imgset[pos])
    for pos in testposset:
        testset.append(imgset[pos])
    trainset = []
    valset = []
    trainposset = set(random.sample(xrange(len(trainvalset)), int(len(trainvalset) * rate[1])))
    valposset = set(xrange(len(trainvalset))) - trainposset
    for pos in trainposset:
        trainset.append(trainvalset[pos])
    for pos in valposset:
        valset.append(trainvalset[pos])
    fo.createtext(txtset[0], trainvalset, 2)
    fo.createtext(txtset[1], trainset, 2)
    fo.createtext(txtset[2], valset, 2)
    fo.createtext(txtset[3], testset, 2)
    for trainvalimg in trainvalset:
        imgname = []
        imgname.append(trainvalimg)
        clsname = trainvalimg.split("_")[0]
        if clsset[0] == clsname:
            fo.createtext(clsset[0] + "_" + txtset[0], imgname, 1)
            fo.createtext(clsset[1] + "_" + txtset[0], imgname, -1)
            fo.createtext(clsset[2] + "_" + txtset[0], imgname, -1)
        elif clsset[1] == clsname:
            fo.createtext(clsset[0] + "_" + txtset[0], imgname, -1)
            fo.createtext(clsset[1] + "_" + txtset[0], imgname, 1)
            fo.createtext(clsset[2] + "_" + txtset[0], imgname, -1)
        else:
            fo.createtext(clsset[0] + "_" + txtset[0], imgname, -1)
            fo.createtext(clsset[1] + "_" + txtset[0], imgname, -1)
            fo.createtext(clsset[2] + "_" + txtset[0], imgname, 1)
    for trainimg in trainset:
        imgname = []
        imgname.append(trainimg)
        clsname = trainimg.split("_")[0]
        if clsset[0] == clsname:
            fo.createtext(clsset[0] + "_" + txtset[1], imgname, 1)
            fo.createtext(clsset[1] + "_" + txtset[1], imgname, -1)
            fo.createtext(clsset[2] + "_" + txtset[1], imgname, -1)
        elif clsset[1] == clsname:
            fo.createtext(clsset[0] + "_" + txtset[1], imgname, -1)
            fo.createtext(clsset[1] + "_" + txtset[1], imgname, 1)
            fo.createtext(clsset[2] + "_" + txtset[1], imgname, -1)
        else:
            fo.createtext(clsset[0] + "_" + txtset[1], imgname, -1)
            fo.createtext(clsset[1] + "_" + txtset[1], imgname, -1)
            fo.createtext(clsset[2] + "_" + txtset[1], imgname, 1)
    for valimg in valset:
        imgname = []
        imgname.append(valimg)
        clsname = valimg.split("_")[0]
        if clsset[0] == clsname:
            fo.createtext(clsset[0] + "_" + txtset[2], imgname, 1)
            fo.createtext(clsset[1] + "_" + txtset[2], imgname, -1)
            fo.createtext(clsset[2] + "_" + txtset[2], imgname, -1)
        elif clsset[1] == clsname:
            fo.createtext(clsset[0] + "_" + txtset[2], imgname, -1)
            fo.createtext(clsset[1] + "_" + txtset[2], imgname, 1)
            fo.createtext(clsset[2] + "_" + txtset[2], imgname, -1)
        else:
            fo.createtext(clsset[0] + "_" + txtset[2], imgname, -1)
            fo.createtext(clsset[1] + "_" + txtset[2], imgname, -1)
            fo.createtext(clsset[2] + "_" + txtset[2], imgname, 1)
    for testimg in testset:
        imgname = []
        imgname.append(testimg)
        fo.createtext(clsset[0] + "_" + txtset[3], imgname, 0)
        fo.createtext(clsset[1] + "_" + txtset[3], imgname, 0)
        fo.createtext(clsset[2] + "_" + txtset[3], imgname, 0)


if __name__ == "__main__":
    abspath = os.path.abspath('.')
    imgsetdir = 'JPEGImages'
    GenerateSet(abspath, imgsetdir)

建立图像库

将以上几步搞好后,我们需要仿照PSACALVOC2007的结构建立自己的图像库,目录结构如下图所示

VOCdevkit2007
    |-results
        |-VOC2007
            |-Main #空目录,用来存放test集结果
    |-VOC2007
        |-Annoations #标定文件xml
        |-ImageSets #txt文件存放至此
        |-JPEGImages #jpg图像存放至此

修改模型

这一步肯定是需要的,因为PascalVOC有20类,所以我们需要根据自己的需求稍微修改神经网络参数。所有参数都在/models下,分为cocopascal_voc格式,每种格式又有两到三种网络模型,每种模型还有两种训练方式Alternative training(alt-opt)Approximate joint training(end-to-end)。总之,由于各种原因,本文以Alternative training+ZF model为例。笔者的需求是四类,还有一类在这里叫__background__类,一共五类。
要修改的文件:
1.py-faster-rcnn/models/pascal_voc/ZF/faster_rcnn_alt_opt/stage1_rpn_train.pt

layer {  
  name: 'input-data'  
  type: 'Python'  
  top: 'data'  
  top: 'im_info'  
  top: 'gt_boxes'  
  python_param {  
    module: 'roi_data_layer.layer'  
    layer: 'RoIDataLayer'  
    param_str: "'num_classes': 5" #按训练集类别改,该值为类别数+1  
  }  
}  

2.py-faster-rcnn/models/pascal_voc/ZF/faster_rcnn_alt_opt/stage1_fast_rcnn_train.pt

layer {  
  name: 'data'  
  type: 'Python'  
  top: 'data'  
  top: 'rois'  
  top: 'labels'  
  top: 'bbox_targets'  
  top: 'bbox_inside_weights'  
  top: 'bbox_outside_weights'  
  python_param {  
    module: 'roi_data_layer.layer'  
    layer: 'RoIDataLayer'  
    param_str: "'num_classes': 5" #按训练集类别改,该值为类别数+1  
  }  
}  
layer {  
  name: "cls_score"  
  type: "InnerProduct"  
  bottom: "fc7"  
  top: "cls_score"  
  param { 
  lr_mult: 1.0
  }  
  param {
  lr_mult: 2.0 
  }  
  inner_product_param {  
    num_output: 5 #按训练集类别改,该值为类别数+1  
    weight_filler {  
      type: "gaussian"  
      std: 0.01  
    }  
    bias_filler {  
      type: "constant"  
      value: 0  
    }  
  }  
}  
layer {  
  name: "bbox_pred"  
  type: "InnerProduct"  
  bottom: "fc7"  
  top: "bbox_pred"  
  param { 
  lr_mult: 1.0 
  }  
  param { 
  lr_mult: 2.0 
  }  
  inner_product_param {  
    num_output: 20 #按训练集类别改,该值为(类别数+1)*4,四个顶点坐标  
    weight_filler {  
      type: "gaussian"  
      std: 0.001  
    }  
    bias_filler {  
      type: "constant"  
      value: 0  
    }  
  }  
}  

3.py-faster-rcnn/models/pascal_voc/ZF/faster_rcnn_alt_opt/stage2_rpn_train.pt

layer {  
  name: 'input-data'  
  type: 'Python'  
  top: 'data'  
  top: 'im_info'  
  top: 'gt_boxes'  
  python_param {  
    module: 'roi_data_layer.layer'  
    layer: 'RoIDataLayer'  
    param_str: "'num_classes': 5" #按训练集类别改,该值为类别数+1  
  }  
}  

4.py-faster-rcnn/models/pascal_voc/ZF/faster_rcnn_alt_opt/stage2_fast_rcnn_train.pt

layer {  
  name: 'data'  
  type: 'Python'  
  top: 'data'  
  top: 'rois'  
  top: 'labels'  
  top: 'bbox_targets'  
  top: 'bbox_inside_weights'  
  top: 'bbox_outside_weights'  
  python_param {  
    module: 'roi_data_layer.layer'  
    layer: 'RoIDataLayer'  
    param_str: "'num_classes': 5" #按训练集类别改,该值为类别数+1  
  }  
}  
layer {  
  name: "cls_score"  
  type: "InnerProduct"  
  bottom: "fc7"  
  top: "cls_score"  
  param { 
  lr_mult: 1.0 
  }  
  param { 
  lr_mult: 2.0 
  }  
  inner_product_param {  
    num_output: 5 #按训练集类别改,该值为类别数+1  
    weight_filler {  
      type: "gaussian"  
      std: 0.01  
    }  
    bias_filler {  
      type: "constant"  
      value: 0  
    }  
  }  
}  
layer {  
  name: "bbox_pred"  
  type: "InnerProduct"  
  bottom: "fc7"  
  top: "bbox_pred"  
  param { 
  lr_mult: 1.0
  }  
  param { 
  lr_mult: 2.0 
  }  
  inner_product_param {  
    num_output: 20 #按训练集类别改,该值为(类别数+1)*4,四个顶点坐标  
    weight_filler {  
      type: "gaussian"  
      std: 0.001  
    }  
    bias_filler {  
      type: "constant"  
      value: 0  
    }  
  }  
}  

5.py-faster-rcnn/models/pascal_voc/ZF/faster_rcnn_alt_opt/faster_rcnn_test.pt

layer {  
  name: "cls_score"  
  type: "InnerProduct"  
  bottom: "fc7"  
  top: "cls_score"  
  inner_product_param {  
    num_output: 5 #按训练集类别改,该值为类别数+1  
  }  
}  
layer {  
  name: "bbox_pred"  
  type: "InnerProduct"  
  bottom: "fc7"  
  top: "bbox_pred"  
  inner_product_param {  
    num_output: 20 #按训练集类别改,该值为(类别数+1)*4  
  }  
} 

注意,注意,这里文件夹进行了切换,并不属于模型的参数而是属于支持库的改动:
6.py-faster-rcnn/lib/datasets/pascal_voc.py

class pascal_voc(imdb):
    def __init__(self, image_set, year, devkit_path=None):
        imdb.__init__(self, 'voc_' + year + '_' + image_set)
        self._year = year
        self._image_set = image_set
        self._devkit_path = self._get_default_path() if devkit_path is None \
            else devkit_path
        self._data_path = os.path.join(self._devkit_path, 'VOC' + self._year)
        self._classes = ('__background__',  # always index 0
                         'kiss', 'hug', 'hands','person')# 有几个类别此处就写几个,注意第一个(索引0位置)永远是__background__类

7.py-faster-rcnn/lib/datasets/imdb.py


   def append_flipped_images(self):
        num_images = self.num_images
        widths = [PIL.Image.open(self.image_path_at(i)).size[0]
                  for i in xrange(num_images)]
        for i in xrange(num_images):
            boxes = self.roidb[i]['boxes'].copy()
            oldx1 = boxes[:, 0].copy()
            oldx2 = boxes[:, 2].copy()
            boxes[:, 0] = widths[i] - oldx2 - 1
            boxes[:, 2] = widths[i] - oldx1 - 1
            assert (boxes[:, 2] >= boxes[:, 0]).all()
            entry = {'boxes': boxes,
                     'gt_overlaps': self.roidb[i]['gt_overlaps'],
                     'gt_classes': self.roidb[i]['gt_classes'],
                     'flipped': True}
            self.roidb.append(entry)
        self._image_index = self._image_index * 2

8.根目录执行./experiments/scripts/faster_rcnn_alt_opt.sh 0 ZF pascal_voc,训练和测试一气呵成,并且还有日志记录。如果只需要训练,那么执行(无日志记录):

./tools/train_faster_rcnn_alt_opt.py --gpu 0 --net_name ZF --weights data/imagenet_models/ZF.v2.caffemodel --imdb voc_2007_trainval --cfg experiments/cfgs/faster_rcnn_alt_opt.yml

Q&A:
1.如果标签含有大写字母,那么修改py-faster-rcnn/lib/datasets/pascal_voc.py第209行:

cls = self._class_to_ind[obj.find('name').text.lower().strip()]  

2.出现错误:

TypeError: 'NoneType' object has no attribute '__getitem__'

这是因为某些图像根本不是jpeg图像,只是后缀被改动了,这一下被学姐坑惨了。建议大家训练前统统用图转工具将图像转换一下格式,以绝后患。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,980评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,422评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,130评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,553评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,408评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,326评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,720评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,373评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,678评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,722评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,486评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,335评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,738评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,283评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,692评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,893评论 2 335