本文代码来自https://github.com/bubbliiiing/faster-rcnn-keras
Faster RCNN网络的训练过程可分为两部分:
1. 建议框网络的训练
2. classifier模型的训练
Faster RCNN网络训练过程的结构图:
在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的分类结果和参数调整的结果。
在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模型训练完成。
利用建议框和公用特征层得到最终的预测框。
在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