Faster RCNN目标检测算法的训练过程(附代码)(2)

Faster RCNN目标检测算法的训练过程

本文代码来自https://github.com/bubbliiiing/faster-rcnn-keras
Faster RCNN网络的训练过程可分为两部分:

1. 建议框网络的训练
2. classifier模型的训练
Faster RCNN网络训练过程的结构图:
Faster RCNN目标检测算法的训练过程(附代码)(2)_第1张图片

1、RPN网络的训练

(1)预处理

train.py中,首先定义网络训练的参数。

if __name__ == "__main__":
    config = Config()
    NUM_CLASSES = 3
    # 训练50世代
    EPOCH = 50
    # 每个世代训练20步
    EPOCH_LENGTH = 20
    # 开始使用1e-4训练,20世代后使用1e-5训练
    Learning_rate = 1e-4
    bbox_util = BBoxUtility(overlap_threshold=config.rpn_max_overlap,ignore_threshold=config.rpn_min_overlap)
    annotation_path = '2007_train.txt'

定义待识别物体的种类数(+1表示背景)
定义迭代的epoch数,*epoch这个单词读/ˈepək/ *O(∩_∩)O哈哈~
定义每一个epoch迭代的次数,还有学习率。
加载已经打好标签的图片生成的.txt文件。文件中存放的是每一张用于训练的如图片存放的路径,还有其中每一个物体框的位置。(这个是自己对数据集打标签以后生成的)


model_rpn, model_classifier,model_all = get_model(config,NUM_CLASSES)
    # 载入预训练权重的模型
    base_net_weights = "model_data/voc_weights.h5"
    
    model_all.summary()
    # 载入预训练的rpn模型
    model_rpn.load_weights(base_net_weights,by_name=True)
    # 载入classifier模型
    model_classifier.load_weights(base_net_weights,by_name=True)

    with open(annotation_path) as f: 
        lines = f.readlines()

    np.random.seed(10101)
    # 读取.txt文件,打乱顺序后读取每一张图片数据
    np.random.shuffle(lines)
    np.random.seed(None)

    gen = Generator(bbox_util, lines, NUM_CLASSES, solid=True)
    rpn_train = gen.generate()
    log_dir = "logs"
    # 训练参数设置
    logging = TensorBoard(log_dir=log_dir)
    callback = logging
    callback.set_model(model_all)
    
    model_rpn.compile(loss={
                'regression': smooth_l1(),
                'classification': cls_loss()
            },optimizer=keras.optimizers.Adam(lr=Learning_rate)
    )
    model_classifier.compile(loss=[
        class_loss_cls, 
        class_loss_regr(NUM_CLASSES-1)
        ], 
        metrics={'dense_class_{}'.format(NUM_CLASSES): 'accuracy'},optimizer=keras.optimizers.Adam(lr=Learning_rate)
    )
    model_all.compile(optimizer='sgd', loss='mae')

载入预训练的模型,分别载入rpn模型的权重和classifier模型的权重。
训练过程中,打乱.txt文件中每一行的顺序,然后读取其中的图片内容。然后设置训练的参数,compile 进行编译。


接着将训练过程的信息显示在终端

            print("Learning rate decrease")
        
        progbar = generic_utils.Progbar(EPOCH_LENGTH) 
        print('Epoch {}/{}'.format(i + 1, EPOCH))
        for iteration, batch in enumerate(rpn_train):
            if len(rpn_accuracy_rpn_monitor) == EPOCH_LENGTH and config.verbose:
                mean_overlapping_bboxes = float(sum(rpn_accuracy_rpn_monitor))/len(rpn_accuracy_rpn_monitor)
                rpn_accuracy_rpn_monitor = []
                print('Average number of overlapping bounding boxes from RPN = {} for {} previous iterations'.format(mean_overlapping_bboxes, EPOCH_LENGTH))
                if mean_overlapping_bboxes == 0:
                    print('RPN is not producing bounding boxes that overlap the ground truth boxes. Check RPN settings or keep training.')
            
            X, Y, boxes = batch[0],batch[1],batch[2]
            
            loss_rpn = model_rpn.train_on_batch(X,Y)
            write_log(callback, ['rpn_cls_loss', 'rpn_reg_loss'], loss_rpn, train_step)

学习率下降显示在终端。
训练过程中显示训练的是第几个epoch,显示有多少个先验框满足和真实框的重合程度的条件。
显示训俩过程中rpn的分类结果和参数调整的结果。


(2)rpn网络的训练过程

frcnn_training.py中,generate函数中
打乱顺序读取.txt文件中的图片。数据增强,以提高模型的鲁棒性。
utils.py中,函数assign_boxes计算每一个先验框和真实框的重合程度,筛选出重合程度较高的先验框,说明这些先验框可以通过调整得到建议框。

    def assign_boxes(self, boxes, anchors):
        # 首先计算先验框的个数
        self.num_priors = len(anchors)
        self.priors = anchors
        # 真实框编码后的结果,真实框中是否存在物体
        # 构建0矩阵,第一个维度是先验框的总数,第二和维度是真实框对应先验框的位置还有概况中是否存在物体
        assignment = np.zeros((self.num_priors, 4 + 1))

        assignment[:, 4] = 0.0
        if len(boxes) == 0:
            return assignment
            
        # 对每一个真实框都进行iou计算
        # 计算真实框和所有框的重合程度得到要忽略的先验框
        ingored_boxes = np.apply_along_axis(self.ignore_box, 1, boxes[:, :4])
        # 取重合程度最大的先验框,并且获取这个先验框的index
        ingored_boxes = ingored_boxes.reshape(-1, self.num_priors, 1)
        # (num_priors)
        ignore_iou = ingored_boxes[:, :, 0].max(axis=0)
        # (num_priors)
        ignore_iou_mask = ignore_iou > 0
        # 把要忽略的先验框在0矩阵中第二个维度值设为-1
        assignment[:, 4][ignore_iou_mask] = -1

        # (n, num_priors, 5)
        encoded_boxes = np.apply_along_axis(self.encode_box, 1, boxes[:, :4])
        # 每一个真实框的编码后的值,和iou
        # (n, num_priors)
        encoded_boxes = encoded_boxes.reshape(-1, self.num_priors, 5)

        # 取重合程度最大的先验框,并且获取这个先验框的index
        # (num_priors)
        best_iou = encoded_boxes[:, :, -1].max(axis=0)
        # (num_priors)
        best_iou_idx = encoded_boxes[:, :, -1].argmax(axis=0)
        # (num_priors)
        best_iou_mask = best_iou > 0
        # 某个先验框它属于哪个真实框
        best_iou_idx = best_iou_idx[best_iou_mask]

        assign_num = len(best_iou_idx)
        # 保留重合程度最大的先验框的应该有的预测结果
        # 哪些先验框存在真实框
        encoded_boxes = encoded_boxes[:, best_iou_mask, :]

        assignment[:, :4][best_iou_mask] = encoded_boxes[best_iou_idx,np.arange(assign_num),:4]
        # 4代表为背景的概率,为0
        assignment[:, 4][best_iou_mask] = 1
        # 通过assign_boxes我们就获得了,输入进来的这张图片,应该有的预测结果是什么样子的
        return assignment

首先定义一个0矩阵assigment,矩阵的第一个维度是先验框的数量,第二个维度是先验框的位置和框中是否存在物体的维度之和(4+1)。
计算真实框和所有先验框的重合程度,利用重合程度对先验框进行筛选:
在函数ignore_box中完成
这里是舍弃一部分先验框,实际上还是起到对先验框的筛选作用

    def ignore_box(self, box):
        iou = self.iou(box)
        # 计算真实框和所有先验框的重合程度,如果重合成的在0.3-0.7之间,那么把这些先验框忽略掉
        ignored_box = np.zeros((self.num_priors, 1))
        # 找出每一个和真实框的重合程度在0.3-0.7之间的框
        assign_mask = (iou > self.ignore_threshold)&(iou<self.overlap_threshold)
        if not assign_mask.any():
            assign_mask[iou.argmax()] = True
        ignored_box[:, 0][assign_mask] = iou[assign_mask]
        return ignored_box.ravel()

如果先验框和真实框的重合程度在0.3-0.7之间,则把这些先验框设为负样本。
assignment中将这些先验框对应的第二个维度的值设为-1。
encode_box中计算真实框和每一个先验框的重合程度,如果超过0.7,说明该先验框可以通过调整得到建议框。

    def encode_box(self, box, return_iou=True):
        iou = self.iou(box)
        encoded_box = np.zeros((self.num_priors, 4 + return_iou))

        # 找到每一个真实框,重合程度较高的先验框
        # 如果先验框和真实框的重合程度大于0.7,则说明可以通过该先验框进行调整得到真实框
        assign_mask = iou > self.overlap_threshold
        if not assign_mask.any():
            assign_mask[iou.argmax()] = True
        if return_iou:
            encoded_box[:, -1][assign_mask] = iou[assign_mask]
        # 找到对应的先验框
        assigned_priors = self.priors[assign_mask]
        # 逆向编码,将真实框转化为FasterRCNN预测结果的格式
        # 先计算真实框的中心与长宽
        box_center = 0.5 * (box[:2] + box[2:])
        box_wh = box[2:] - box[:2]
        # 再计算重合度较高的先验框的中心与长宽
        assigned_priors_center = 0.5 * (assigned_priors[:, :2] +
                                        assigned_priors[:, 2:4])
        assigned_priors_wh = (assigned_priors[:, 2:4] -
                              assigned_priors[:, :2])
        # 逆向求取FasterRCNN应该有的预测结果,即利用真实框和与真实框重合度较高的建议框进行编码
        # 获得网络应该有的预测结果
        encoded_box[:, :2][assign_mask] = box_center - assigned_priors_center
        encoded_box[:, :2][assign_mask] /= assigned_priors_wh
        encoded_box[:, :2][assign_mask] *= 4

        encoded_box[:, 2:4][assign_mask] = np.log(box_wh / assigned_priors_wh)
        encoded_box[:, 2:4][assign_mask] *= 4
        return encoded_box.ravel()

分别将真实框和与真实框具有较高重合程度的先验框转化成Faster RCNN网络的格式,也就是中心点坐标和宽高的格式。利用先验框和真实框中心点坐标的偏差和宽高并进行编码,得到网络应有的训练结果。


接着回到frcnn.py中,上一步已经通过assign_boxes中求出了网络应有的预测结果。然后处理数据不平衡的问题。

num_regions = 256
                
                classification = assignment[:,4]
                regression = assignment[:,:]
                
                mask_pos = classification[:]>0
                num_pos = len(classification[mask_pos])
                if num_pos > num_regions/2:
                    val_locs = random.sample(range(num_pos), int(num_pos - num_regions/2))
                    temp_classification = classification[mask_pos]
                    temp_regression = regression[mask_pos]
                    temp_classification[val_locs] = -1
                    temp_regression[val_locs,-1] = -1
                    classification[mask_pos] = temp_classification
                    regression[mask_pos] = temp_regression
                    
                mask_neg = classification[:]==0
                num_neg = len(classification[mask_neg])
                mask_pos = classification[:]>0
                num_pos = len(classification[mask_pos])
                if len(classification[mask_neg]) + num_pos > num_regions:
                    val_locs = random.sample(range(num_neg), int(num_neg + num_pos - num_regions))
                    temp_classification = classification[mask_neg]
                    temp_classification[val_locs] = -1
                    classification[mask_neg] = temp_classification
                    
                classification = np.reshape(classification,[-1,1])
                regression = np.reshape(regression,[-1,5])

                tmp_inp = np.array(img)
                tmp_targets = [np.expand_dims(np.array(classification,dtype=np.float32),0),np.expand_dims(np.array(regression,dtype=np.float32),0)]

正负样本的总数设为256,通常情况下负样本的数量远大于正样本的数量,那么就需要舍弃一部分的负样本。如果负样本数量超出128,则舍弃剩余的负样本。
至此,rpn模型训练完成。

(3)classifier网络的训练过程

利用建议框和公用特征层得到最终的预测框。
在classifier模型中,模型的预测结果对建议框调整得到最终的预测框。

results = bbox_util.detection_out(P_rpn,anchors,1, confidence_threshold=0)
	R = results[0][:, 2:]
    # 建议框和真实框计算重合程度,筛选出那些可以通过调整得到真实框的建议框
    X2, Y1, Y2, IouS = calc_iou(R, config, boxes[0], width, height, NUM_CLASSES)

操作类似于rpn模型的先验框筛选操作:
在函数calc_iou中,把真实框和建议框都转化成公用特征层的格式,计算建议框和真实框的重合程度,如果config.classifier_min_overlap <= best_iou < config.classifier_max_overlap,也就是重合程度在0.1-0.5之间,则把该建议框设为负样本,重合程度超过0.5,则把该建议框设置为正样本。
然后处理数据不平衡的问题,把正样本的数量和负样本的数量控制在32。
回到train.py中,将正负样本输入到classifier模型中,模型的预测结果和网络真实框输出的结果进行对比,得到loss,利用loss值进行训练,得到网络最终的训练结果。


感谢:
https://www.bilibili.com/video/BV1U7411T72r?p=15
https://github.com/bubbliiiing/faster-rcnn-keras

你可能感兴趣的:(python,深度学习)