当前位置: 首页 > 后端技术 > Python

FasterRCNN目标检测的Loss

时间:2023-03-26 16:08:13 Python

部分主要介绍fasterrcnn中的error。fasterrcnn的误差可以分为两部分,rpnLoss和fastrcnnLoss。fasterrcnn的最终误差是rpnLoss和fastrcnnLoss之和。rpnLoss和fastrcnnLoss的前向传播过程基本相同。它们都包括分类误差和定位误差。分类误差采用交叉熵分类误差(CrossEntropy),定位误差采用SmoothL1误差。在Losspropagation的过程中,关键点之一就是如何将网络的预测与真实的groundframe联系起来。这也是误差计算过程中难以理解的部分。下面我将详细介绍这部分内容。RPNLossRPNLoss前向传播过程rpnLoss的前向传播过程如下:首先计算anchor和bbox(真实帧)的iou(shape->len(anchor)*len(bbox)),取出最高的iou与每个anchor及其索引(行索引),并取出与每个bbox具有最高iou的行索引。根据上一步得到的iou和positionindex,结合设定的阈值(前景iou阈值和背景iou阈值),给每个anchor分配不同的标签值(-1->don'tcare,0->背景,1->前景)。根据设置的anchor保留数和前景anchor比例随机采样前景和前景anchor。为所有锚点计算坐标偏移量,每个锚点对应于具有最高iou的bbox。最后结合rpn网络的输出,分别计算分类误差和定位误差。代码实现AnchorTargetLayer类的代码如下所示。这个类连接锚点和地面真值框(bbox)。它的主要功能是匹配,即为每个anchor匹配一个bbox,并返回匹配的坐标偏移量和前后背景。标签。classAnchorTargetCreator(object):"""匹配每个bbox到anchorargs:n_sample(int):指定要生成的regioncandidates个数pos_iou_thresh(float):如果iou超过阈值,则认为是前景neg_iou_thresh(float):iou低阈值被认为是背景pos_ratio(float):指定前景候选在最终输出候选中的比例"""def__init__(self,n_sample=256,pos_iou_thresh=0.7,neg_iou_thresh=0.3,pos_ratio=0.5):self.n_sample=n_sampleself.pos_iou_thresh=pos_iou_threshself.neg_iou_thresh=neg_iou_threshself.pos_ratio=pos_ratiodef__call__(self,bbox,anchor,img_size):"""*:math:`S`锚点数*:数学:`bboxnumberArgs:bbox(数组):bbox坐标。形状->:数学:`(R,4)`。锚点(数组):锚点坐标。形状->:数学:`(S,4)`.img_size(tupleofints):atuple:obj:`H,W`,图像的宽度和高度。返回:#NOTE:它的比例不仅仅是偏移***loc**:将锚点匹配到bbox.shape的坐标偏移->:math:`(S,4)`。***label**:anchor的标签:obj:`(1=positive,0=negative,-1=ignore)`。形状->:math:`(S,)`。"""img_H,img_W=img_sizen_anchor=len(anchor)#丢弃超出边界的anchorsinside_index=_get_inside_index(anchor,img_H,img_W)anchor=anchor[inside_index]#为每个anchor指定一个bbox(target)来计算坐标偏移量argmax_ious,label=self._create_label(inside_index,anchor,bbox)#anchor->bbox,计算相应的坐标偏移量loc=bbox2loc(anchor,bbox[argmax_ious])#mapuptooriginalsetofanchorslabel=_unmap(label,n_anchor,inside_index,fill=-1)loc=_unmap(loc,n_anchor,inside_index,fill=0)returnloc,labeldef_create_label(self,inside_index,anchor,bbox):#label:1为正,0为正negative,-1isdontcare#初始化anchorlabel,填充-1。#-1表示不关心,0表示背景,1表示前景/对象label=np.empty((len(inside_index),),dtype=np.int32)label.fill(-1)argmax_ious,max_ious,gt_argmax_ious=\self._calc_ious(anchor,bbox,inside_index)#首先分配负标签,以便正标签可以破坏它们#对于每个一个anchor,与其拥有小于背景iou阈值的最高iou,不如将label设置为1label[max_ious=self.pos_iou_thresh]=1#前景采样,如果剩余anchors的数量更大比所需的锚点,执行随机抽样(pos_index,size=(len(pos_index)-n_pos),replace=False)label[disable_index]=-1#背景采样,如果剩余anchor的数量大于需要的anchor,进行随机抽样n_neg=self.n_sample-np.sum(label==1)neg_index=np.where(label==0)[0]如果len(neg_index)>n_neg:disable_index=np.random.choice(neg_index,size=(len(neg_index)-n_neg),replace=False)label[disable_index]=-1returnargmax_ious,labeldef_calc_iou(self,anchor,bbox,inside_index):#计算anchor和bbox的iou,ious->(len(inside_index),len(bbox))ious=bbox_iou(anchor,bbox)#得到每个anchor最大的bbox(realframe)的位置iouargmax_ious=ious.argmax(axis=1)#根据位置、shape取出最大的iou->(len(inside_index),)max_ious=ious[np。arange(len(inside_index)),argmax_ious]#得到最大anchor与每个bboxiou的位置gt_argmax_ious=ious.argmax(axis=0)#根据位置、shape取出最大的iou->(len(bbox),)gt_max_ious=ious[gt_argmax_ious,np.arange(ious.shape[1])]#获取对应行索引中的最大iougt_argmax_ious=np.where(ious==gt_max_ious)[0]returnargmax_ious,max_ious,gt_argmax_iousdef_unmap(data,count,index,fill=0):如果len(data.shape)==1:ret=np.empty((count,),dtype=data.dtype)ret.fill(fill)ret[index]=其他数据:ret=np.empty((count,)+data.shape[1:],dtype=data.dtype)ret.fill(fill)ret[index,:]=datareturnretdef_get_inside_index(anchor,H,W):#获取图像内的所有锚点。index_inside=np.where((anchor[:,0]>=0)&(anchor[:,1]>=0)&(anchor[:,2]<=H)&(anchor[:,3]<=W))[0]returnindex_insideFastR-CNNLoss这里直接展示ProposalTargetCreator类的详细代码,连接候选区域和真实框。主要功能是将每个bbox匹配到roi,返回采样的roi,采样的roi的坐标偏移量和采样的roi的类别标签代码实现类ProposalTargetCreator(object):"""匹配每一个bbox到roiargs:n_sample(int):预留roi个数pos_ratio(float):设置前景roi占最终预留roi的比例pos_iou_thresh(float):iou大于阈值被认为是前景neg_iou_thresh_hi(float):[neg_iou_thresh_lo,neg_iou_thresh_hi]中的iou被认为是背景neg_iou_thresh_lo(float):"""def__init__(self,n_sample=128,pos_ratio=0.25pos_iou_thresh=0.5,neg_iou_thresh_hi=0.5,neg_iou_thresh_lo=0.0):self.n_sample=n_sampleself.pos_ratio=pos_ratioself.pos_iou_thresh=pos_iou_threshself.neg_iou_thresh_hi=neg_iou_thresh_hiself.neg_iou_thresh_lo=neg_iou_thresh_lodef__call__(self,roi,bbox,label,loc_normalize_mean=(0.,0.,0.,0.),loc_normalize_std=(0.1,0.1,0.2,0.2)):"""*:math:`S`roi样本数*:math:`L`包含后面地级不同的概率数。Args:roi(array):rpn输出的roi。shape->:math:`(R,4)`bbox(数组):groundtruthbox。形状->:math:`(R',4)`。label(array):groundtruthbox的类别标签。形状->:math:`(R',)`。loc_normalize_mean(tupleoffourfloats):指定的坐标偏移归一化平均值。四个浮点数的元组):指定的坐标偏移归一化标准偏差。返回:***sample_roi**:采样roi。形状->:math:`(S,4)`。***gt_roi_loc**:从采样的roi到bbox需要应用的坐标偏移量。形状->:数学:`(S,4)`。***gt_roi_label**:分配给采样roi的类别标签。形状->:math:`(S,)`.shape->:math:`[0,L]`。"""n_bbox,_=bbox.shape#计算最终输出前景roi的个数pos_roi_per_image=np.round(self.n_sample*self.pos_ratio)#计算roi和bbox的iou,iou->(len(roi),len(bbox))iou=bbox_iou(roi,bbox)#对每个anchor,找到最大iou的bbox的位置gt_assignment=iou.argmax(axis=1)#对于每个anchor,求最大ioumax_iou=iou.max(axis=1)#类的偏移范围从[0,n_fg_class-1]到[1,n_fg_class]。#label为0表示背景gt_roi_label=label[gt_assignment]+1#前景采样,如果剩余roi的数量大于需要的roi,则进行随机采样#选择roi大于pos_iou_thresh的roi。pos_index=np.where(max_iou>=self.pos_iou_thresh)[0]pos_roi_per_this_image=int(min(pos_roi_per_image,pos_index.size))如果pos_index.size>0:pos_index=np.random.choice(pos_index,size=pos_roi_per_this_image,replace=False)#背景采样,如果剩余roi的个数大于需要的roi,则继续随机采集样#选择处于[neg_iou_thresh_lo,neg_iou_thresh_hi]区间的roi。neg_index=np.where((max_iou=self.neg_iou_thresh_lo))[0]neg_roi_per_this_image=self.n_sample-pos_roi_per_this_imageneg_roi_per_this_image=int(min(neg_roi_per_this_image,neg_index.size))ifneg_index.size>0:neg_index=np.random.choice(neg_index,size=neg_roi_per_this_image,replace=False)#合并前景采样和背景采样。keep_index=np.append(pos_index,neg_index)gt_roi_label=gt_roi_label[keep_index]gt_roi_label[pos_roi_per_this_image:]=0sample_roi=roi[keep_index]#roi->bbox,计算对应的坐标偏移量gt_roi_loc=bbox2loc(sample_roi,bbox[gt_assignment[keep_index])#使用指定坐标偏移的均值和方差归一化gt_roi_loc=((gt_roi_loc-np.array(loc_normalize_mean,np.float32))/np.array(loc_normalize_std,np.float32))returnsample_roi,gt_roi_loc,gt_roi_label损失函数在FasterRCNN中,分类使用交叉熵损失函数,回归使用SmoothL1损失。SmoothL1误差函数用于计算网络中的坐标偏移误差,但是在中有一点需要注意计算坐标偏移误差的过程,标签是背景的边界框不会计算定位误差SmoothL1误差的数学形式如下:$$L_{1;smooth}=\begin{cases}|x|\quad&if|x|>\alpha;\\\frac{1}{|\alpha|}x^2\quad&if|x|\leq\alpha\end{cases}$$使用SmoothL1误差的原因主要是:L1误差对异常值不敏感,如果使用L2误差在网络训练初期,由于候选框的分布差异很大,与真实框的差距较大,可能导致梯度爆炸。当输入较小时,梯度波动较小。式中,α为超参数,通常设置为1,使误差函数连续。平滑L1误差结合了L1误差和L2误差。当输入较小时,表现为L2误差,否则表现为L1误差。在计算rpnLoss时,α为3;在fastrcnnLoss的计算过程中,α为1。总结本节详细介绍fasterrcnn误差计算过程中的预测和真实匹配过程,即如何将anchor对应到bbox(AnchorTargetLayer),如何将roi对应到bbox(ProposalTargetLayer),并显示它们的关键代码。了解了这个匹配过程后,我们就基本了解了整个网络的错误传播过程。误差的具体计算比较容易理解,就不详细介绍了。需要注意的是AnchorTargetLayer和ProposalTargetLayer只参与网络的前向传播过程,不需要计算梯度。下一节我会介绍如何训练fasterrcnn。参考文献[1]RegionofInterestPooling[2]Howtointerpretsmoothl1loss?[3]GirshickR.Fastr-cnn[C]//IEEE计算机视觉国际会议论文集。2015:1440-1448。