Faster-RCNN训练自己数据集遇到的问题集锦
最近在实验中使用Faster RCNN模型处理一批自研遥感图像数据。这些数据属于某遥感图像数据集——RSOD类型,并其标注格式与Pascal VOC框架相似。然而由于本项目的标注工作由本团队的学生负责,在这一过程中存在部分标记不准确的情况。这些错误在后续模型训练中带来了诸多困扰。以下是在实际运行过程中遇到的具体问题,请予以注意:为了加快调试速度,在确保程序功能已基本完善的情况下(即程序完全调通前),建议将训练迭代次数设置为较低的数值(例如100次)。
错误目录 :
1 **./tools/**train_faster_rcnn_alt_opt.py is not found
2 assert (boxes[:, 2] >= boxes[:, 0]).all()
3 'module' object has no attribute 'text_format'
4 Typeerror:Slice indices must be integers or None or have index method
5 TypeError: 'numpy.float64' object cannot be interpreted as an index
6 error=cudaSuccess(2 vs. 0) out of memory ?
7**loss_bbox = nan,** result: Mean AP=0.000
8 AttributeError: 'NoneType' object has no attribute 'astype'
运行以下命令:sudo ./train_faster_rcnn_alt_opt.sh 0 ZF pascal_voc时会显示错误信息:"指定的Python文件路径不存在"
运行以下命令:sudo ./train_faster_rcnn_alt_opt.sh 0 ZF pascal_voc时会显示错误信息:"指定的Python文件路径不存在"
因sh文件路径错误需切换至py-faster-rcnn目录运行该脚本:sudo ./experiments/scripts/train_faster_rcnn_alt_opt.sh 0 ZF pascal_voc
错误2: 在调用append_flipped_images函数时出现:assert (boxes[:, 2] >= boxes[:, 0]).all()
查阅资料发现这一问题的出现主要是由于我们的数据集标注错误所导致。因为我们采用了自定义的数据集,在这种情况下可能会出现一些特殊的场景和异常情况。可能导致x坐标被归零的情况是由于根据Pascal VOC的数据标注规范,默认是从1开始计数。因此,在Faster RCNN代码中会将这些坐标转换为基于0的索引,并会对Xmin、Xmax、Ymin、Ymax进行减一操作。这将导致数值溢出;当x=0时,在减一操作后数值将溢出至最大值65535。此外,在实际应用中还可能出现标记坐标被标记为负数或超出图像范围的情形;对于这种情况我们需要采取更加严格的数据校验机制来避免其发生或者在检测到异常时及时触发误报提醒功能以确保模型能够正常工作并提高检测精度
(1)修改lib/datasets/imdb.py,在boxes[:, 2] = widths[i] - oldx1 - 1后插入:
for b in range(len(boxes)):
if boxes[b][2]< boxes[b][0]:
boxes[b][0] = 0
该方法本质上存在局限性。该方法认为可能的溢出情况仅局限于 boxes[b][0] ,然而实际情况表明 boxes[b][2] 同样可能发生溢出。因此并不推荐采用该方法。
优化lib/datasets/pascal_voc.py中load_pascal_annotation函数的读取逻辑。
该函数用于解析Pascal VOC格式的标注文件。
其中所有-1均需去除(因为Pascal VOC标注采用的是基于1的索引系统)。如果当前数据集的标注仅基于0索引(无需处理超出图像边界或负数坐标的特殊情况),上述操作即可解决问题。
x1 = float(bbox.find('xmin').text)#-1
y1 = float(bbox.find('ymin').text)#-1
x2 = float(bbox.find('xmax').text)#-1
y2 = float(bbox.find('ymax').text)#-1
(3)标注文件矩形越界
我执行了上面两步,运行stage 1 RPN, init from ImageNet Model时还是报错。说明可能不仅仅是遇到x=0的情况了,有可能标注本身有错误,比如groundtruth的x1<0或x2>imageWidth。决定先看看到底是那张图像的问题。在lib/datasets/imdb.py的
assert (boxes[:, 2] >= boxes[:, 0]).all()
这句前面加上:
print self.image_index[i]
输出内容
更正这一处标注错误后,在我以为已经成功解决问题时(也就是认为大功告成的时候),仍然出现了错误。咬着牙对自己说:"我有耐心"——因为我知道自己肯定会克服这个困难的。此次错误发生在"使用RPN生成proposals并从ImageNet加载模型的Stage 1 Fast R-CNN"这一阶段。奇怪的是,在此阶段调用append_flipped_images函数处理的是rpn产生的proposals而非标注文件中的groundtruth。为什么会这样呢?奇怪的是groundtruth没有问题而proposals为何会溢出呢?结论:无需删除缓存!应将py-faster-rcnn/data/cache中的文件和py-faster-rcnn/data/VOCdevkit2007/annotations_cache中的文件全部删除。这篇博客给了我很大的启发——在此之前我一直在执着地排查标注问题而没有找到根本原因(如果只是想解决问题就没有必要再继续深入分析了),但作为一个记录思考的过程却是很有意义的
首先我打算对lib/datasets/imdb.py中的所有proposal进行排查以确定问题所在。与此同时我也会重点检查每张图像的具体情况
assert (boxes[:, 2] >= boxes[:, 0]).all()
这句前面加上:
print ("num_image:%d"%(i))
接着执行以下操作:首先运行程序并获取训练集中的图像索引列表(无需关注具体图像名称)。当出现告警事件时,请识别到告警事件时最后一个记录的索引值,并记录下来(例如,在某次测试中发现告警时最后一个处理的索引编号是320)。下一步需要检查该图片上所有检测到的目标标记是否正常。同样地,在触发告警之前需要执行以下操作:
if i==320:
print self.image_index[i]
for z in xrange(len(boxes)):
print ('x2:%d x1:%d'%(boxes[z][2],boxes[z][0]))
if boxes[z][2]<boxes[z][0]:
print"here is the bad point!!!"
再次运行后看日志,发现here is the bad point!!!出现在一组“x2=-64491 x1=1011”后,因为我的图像宽度是1044,而1044-65535=-64491,所以其实是x2越界了,因boxes[:, 2] = widths[i] - oldx1 - 1,其实也就是图像反转前对应的oldx1=65534溢出,为什么rpn产生的proposal也会溢出呢?正常情况下,rpn产生的proposal是绝不会超过图像范围的,除非——标准的groundtruth就超出了!而groundtruth如果有问题,stage 1 RPN, init from ImageNet Model这个阶段就应该报错了,所以是一定是缓存的问题。
错误3 :pb2.text_format(...)这里报错**'module' object has no attribute 'text_format'** 。
为了在./lib/fast_rcnn/train.py文件中导入google.protobuf.text_format模块,在./lib/fast_rcnn/train.py文件里import google.protobuf.text_format。网上有网友指出将protobuf版本回退至2.5.0这一做法。但这样做会导致caffe编译出现新问题:‘无法导入名称symbole database’。这还需要从github获取对应的缺失文件进行补充,因此不建议采取此方法。
错误4: 当程序在lib/proposal_target_layer.py中运行时会抛出TypeError异常:"Slice indices必须是整数、None或具备__index__方法的对象"
注释
解决方案
在126行后加上:
start=int(start)
end=int(end)
在166行后加上:
fg_rois_per_this_image=int(fg_rois_per_this_image)
在 py-faster-rcnn 工具包下的 ROI 数据层模块的 mini batch 处理函数 sample_rois 中出现 TypeError 错误
解决方法:这一现象与错误(4)实质上是一个问题,并非独立存在而是由numpy版本引发的结果;同样地,在网上很多答案提到降低版本的方法并不推荐使用;相反的是更加稳妥的做法是修改工程代码;此处提供了解决方案:修改minibatch.py文件
第26行:
fg_rois_per_image = np.round(cfg.TRAIN.FG_FRACTION * rois_per_image)
改为:
fg_rois_per_image = np.round(cfg.TRAIN.FG_FRACTION * rois_per_image).astype(np.int)
第173行:
cls = clss[ind]
改为:
cls = int(clss[ind])
另外还有3处需要加上.astype(np.int),分别是:
#lib/datasets/ds_utils.py line 12 :
hashes = np.round(boxes * scale).dot(v)
#lib/fast_rcnn/test.py line 129:
hashes = np.round(blobs['rois'] * cfg.DEDUP_BOXES).dot(v)
#lib/rpn/proposal_target_layer.py line 60 :
fg_rois_per_image = np.round(cfg.TRAIN.FG_FRACTION * rois_per_image)
错误6:error=cudaSuccess(2 vs. 0) out of memory ?
GPU内存不足,有两种可能:(1)batchsize太大;(2)GPU被其他进程占用过多。
解决方法:第一步是实时监控GPU使用情况:通过执行命令watch -n 1 nvidia-smi来持续查看并分析当前GPU的负载状态。在运行训练任务的过程中持续观察其使用趋势。如果观察到明显的负载异常现象,请进一步排查可能的原因。具体而言,在确认存在外部进程对硬件资源造成重大压力时,请及时终止相关进程(例如使用kill -9 NVIDIA-process-ID命令)以释放系统资源。当发现训练任务消耗过多计算资源时,请考虑适当优化算法或降低批量处理参数设置(如减少batchsize),从而有效缓解系统压力。
当处理lib/fast_rcnn/bbox_transform.py文件时,在计算\log(gt\_widths / ex\_widths)的过程中触发了RuntimeWarning: invalid value encountered in log targets_dw = np.log(gt_widths / ex_widths),随后导致loss_bbox出现nan值,并最终使得综合而言的Mean AP=1\times 1e^{-3}。
在网上的讨论中常有人提到应当降低学习率。然而这并非根本解决之道而是仅仅延缓了错误反馈的时间这一做法虽能缓解短期问题却难以从根本上解决问题此外在参数更新过程中若学习率设置过低则可能导致模型陷入局部最优解的风险增大
在深入排查后发现该问题源于数据集标注越界问题。具体而言,在图像处理过程中可能出现以下几种越界情况:超出范围的具体表现包括:x坐标小于零、x坐标大于宽度、y坐标小于零、y坐标大于高度以及y坐标的前后颠倒等六种形式。值得注意的是,在发布者的代码中仅在append_flipped_images函数中添加了 boxes[:, 2] >= boxes[:, 0] 的验证逻辑(即仅断言水平翻转后的x轴坐标满足 x2>=x1)。这表明问题可能出在x坐标的标注上,并非完全无法处理所有异常情况(如y轴方向上的异常)。然而,在实际应用中我们发现对于 y 坐标的方向性校验完全没有检查这一关键环节
为了寻找遇到错误提示的lib/fast_rcnn/bbox_transform.py文件中的函数bbox_transform。代码注释参考此处。
targets_dw = np.log(gt_widths / ex_widths)
前面加上:
print(gt_widths)
print(ex_widths)
print(gt_heights)
print(ex_heights)
assert(gt_widths>0).all()
assert(gt_heights>0).all()
assert(ex_widths>0).all()
assert(ex_heights>0).all()
运行后发现AssertionError出现在assert(ex_heights>0).all()处,并观察到存在某些样本其anchor点的高度被计算为负值。这意味着height维度与标注信息中的y轴方向相对应。类似之前的错误案例(如错误2),我认为这可能源于标注信息在y轴方向上的不准确。因此我决定回到lib/datasets/imdb.py文件中查看append_flipped_images函数的相关实现,并在其逻辑中增加对y轴标注信息的有效性验证机制
#源代码中没有获取图像高度信息的函数,补充上
def _get_heights(self):
return [PIL.Image.open(self.image_path_at(i)).size[1]
for i in xrange(self.num_images)]
def append_flipped_images(self):
num_images = self.num_images
widths = self._get_widths()
heights = self._get_heights()#add to get image height
for i in xrange(num_images):
boxes = self.roidb[i]['boxes'].copy()
oldx1 = boxes[:, 0].copy()
oldx2 = boxes[:, 2].copy()
print self.image_index[i]#print image name
assert (boxes[:,1]<=boxes[:,3]).all()#assert that ymin<=ymax
assert (boxes[:,1]>=0).all()#assert ymin>=0,for 0-based
assert (boxes[:,3]<heights[i]).all()#assert ymax<height[i],for 0-based
assert (oldx2<widths[i]).all()#assert xmax<withd[i],for 0-based
assert (oldx1>=0).all()#assert xmin>=0, for 0-based
assert (oldx2 >= oldx1).all()#assert xmax>=xmin, for 0-based
boxes[:, 0] = widths[i] - oldx2 - 1
boxes[:, 2] = widths[i] - oldx1 - 1
#print ("num_image:%d"%(i))
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
然后运行程序后,在处理图像y时如果出现标注错误会抛出AssertError异常。接着查看最后一条打印的日志信息以获取对应的图像名称,并前往该图像的Annotation文件中查看具体错误标记位置。修改完成后请确保删除相应的py-faster-rcnn/data/cache缓存目录。再次运行程序后如果仍出现AssertError则需更新该图片的标准标注并清除缓存目录……如此反复操作直到所有标注错误均已修正。最终系统评估指标MAP将不再归零。
第8条错误指出:经过训练工作顺利完成(mAP=0.66),可以进行后续测试。具体参考这篇博客博客的详细说明。(由于内容较为冗长此处略做概述)在实际操作中可能会遇到以下问题(如图所示):当尝试运行./ demo.py时出现以下错误提示:
执行过程中出现以下错误提示:
im_orig = im.astype(np.float32, copy=True)
AttributeError: 'NoneType' object has no attribute 'astype'
请根据具体情况排查问题并及时反馈相关问题!
解决方法:仔细检查路径和文件名,查看demo.py里路径相关的文件。
以上。
