[PocketFlow]解决TensorFLow在COCO数据集上训练挂起无输出的bug

1. 引言


因项目要求,需要在PocketFlow中添加一套PeleeNet-SSD和COCO的API,具体为在datasets文件夹下添加coco_dataset.py, 在nets下添加peleenet_at_coco.py和peleenet_at_coco_run.py。其中网络结构和backbone等在师兄把项目交付给我之前已经基本完成,所以我主要的工作就是处理COCO的数据(转换成tfrecord文件)和简单更改一些调用的接口。另外PocketFlow中已经包含在VOC上的数据处理,且在检测任务上,VOC和COCO的数据集标注很接近,唯一的区别就是feature_map:

# VOC
feature_map = {
        'image/encoded': tf.FixedLenFeature([], dtype=tf.string, default_value=''),
        'image/format': tf.FixedLenFeature([], dtype=tf.string, default_value='jpeg'),
        'image/filename': tf.FixedLenFeature((), dtype=tf.string, default_value=''),
        'image/height': tf.FixedLenFeature([1], dtype=tf.int64),
        'image/width': tf.FixedLenFeature([1], dtype=tf.int64),
        'image/channels': tf.FixedLenFeature([1], dtype=tf.int64),
        'image/shape': tf.FixedLenFeature([3], dtype=tf.int64),
        'image/object/bbox/xmin': tf.VarLenFeature(dtype=tf.float32),
        'image/object/bbox/ymin': tf.VarLenFeature(dtype=tf.float32),
        'image/object/bbox/xmax': tf.VarLenFeature(dtype=tf.float32),
        'image/object/bbox/ymax': tf.VarLenFeature(dtype=tf.float32),
        'image/object/bbox/label': tf.VarLenFeature(dtype=tf.int64),
        'image/object/bbox/difficult': tf.VarLenFeature(dtype=tf.int64),
        'image/object/bbox/truncated': tf.VarLenFeature(dtype=tf.int64),
    }

# COCO 
# change filename(string) to image_id(int64) and remove difficult and truncated
feature_map = {
        'image/encoded': tf.FixedLenFeature([], dtype=tf.string, default_value=''),
        'image/format': tf.FixedLenFeature([], dtype=tf.string, default_value='jpeg'),
        'image/image_id': tf.FixedLenFeature([1], dtype=tf.int64), # 
        'image/height': tf.FixedLenFeature([1], dtype=tf.int64),
        'image/width': tf.FixedLenFeature([1], dtype=tf.int64),
        'image/channels': tf.FixedLenFeature([1], dtype=tf.int64),
        'image/shape': tf.FixedLenFeature([3], dtype=tf.int64),
        'image/object/bbox/xmin': tf.VarLenFeature(dtype=tf.float32),
        'image/object/bbox/ymin': tf.VarLenFeature(dtype=tf.float32),
        'image/object/bbox/xmax': tf.VarLenFeature(dtype=tf.float32),
        'image/object/bbox/ymax': tf.VarLenFeature(dtype=tf.float32),
        'image/object/bbox/label': tf.VarLenFeature(dtype=tf.int64),
    }

所以讲道理,即使对TensorFLow并不熟,完成这些API的难度也不大,确实也只用了半天就写完了,不过之后的调试用了半个月

2. 程序挂起无输出


最开始是训练时一开始程序就卡住,和挂起差不多,而且也没有报错信息,运行截图如下:

因为TensorFlow的计算图机制导致在session.run()之前,所有的输出都是node和tensor的信息,所以个人觉得debug比较困难。尤其是你一上来就整个什么都不输出,我确实有点懵逼,不知道怎么做。上Google搜了搜TensorFlow 卡住 挂起等关键词,基本上有两种:

  • TensorFlow计算和数据读取是异步的,读不到数据导致进程挂起,在代码中加上tf.train.start_queue_runners(sess=sess)开启队列啥的
  • 计算图中有新增节点的操作,导致每次推导时内存占用越来越多,最后进程挂起

但比较气人的是,师兄写的PeleeNet-SSD和VOC的API就没有这种情况,而且我随便改batchsize都可以正常跑起来,所以网络结构应该没问题。至于队列,因为用的是dataset那一套的API,两者也没什么相关,加上之后还是没卵用。不过peleenet那块没问题,那大概是读取数据的API有问题。

于是我一行一行的对比(PyCharm Compare With...)后来新增的脚本和师兄之前写的在VOC上的那套,检查了3遍,是真的看不出有什么问题...然后又检查了将COCO数据集转换为tfrecord的脚本,同上,数据应该没错。不过唯一的收获就是发现bbox的坐标计算有错,导致gt有问题,随后改过来了。后来想想,如果gt是错的话,训练的模型准确率再高,预测时还是gg,这bug也挺难发现的

最后求助于师兄,他给我演示了一下怎么用session.run()调试程序,找出程序卡在哪里。他首先判断因为读不到数据,所以排除peleenet_at_coco.py和peleenet_at_coco_run.py的问题(xxx_run.py几个脚本基本都一样,也不会出什么错,另外一个主要定义的网络结构,是读取数据之后的操作,所以这两个都不用怎么检查)。之后着重调试coco_dataset.py,他找到把数据传入网络的最后一步,是一个定义在coco_dataset.py的函数parse_fn,然后注释掉中间的代码,只原样返回最初的输入,发现能用session.run()读到数据之后再逐步解开剩下代码的注释。这样卡在那一步就能说明哪一块有问题了,最后发现是卡在了数据预处理preprocess_image()

我感觉有点开窍了,但是自己之后又调试了下,发现加上预处理的部分,数据也能读出来...最后的结论是不加预处理数据可以读,加上之后有时可以读,有时会卡住,甚至有点随机的感觉。把预处理换成了简单的resize()统一图片大小之后,batchsize大点就卡的早点,小的话就晚点。那真的有意思,只能接着看预处理部分了。

3. 报错:Reduction axis 0 is empty in shape [0, 5028]


预处理主要包括:

  • 随机扭曲颜色
  • 随机裁剪(bbox坐标值相应变化)
  • 随机水平翻转
  • 缩放到固定尺寸
  • 减去ImageNet训练集的RGB均值

PocketFlow项目中的数据预处理(数据增强)来自HiKapok/SSD.TensorFlow, 这哥们代码写的很好,还有单元测试,属实良心。另外知乎上也有两篇相关的文章:Inside TF-Slim(13) preprocessing(图像增强相关)SSD-TensorFlow 源码解析,以后认真学习一下。

仅保留缩放操作之后,运行COCO训练的脚本,程序报以下错误(VOC仍无问题):

google之后也没找到什么有参考价值的答案,只能看预处理的代码了。因为刚接触目标检测,TensorFLow也用的不熟,读的比较吃力,不过幸好有VOC的单元测试,可以把预处理之后的图像保存到本地,还能在图片上直接绘制bbox和label,稍微修改之后就能用在COCO上了,效果如下:

测试时发现,COCO保存若干张图片后会卡住,而VOC能一直运行。现在终于能确定是数据的问题了,碰巧在COCO保存的图片中发现有少数图片没有bbox框,大概知道问题出在哪里了。看了看之前检查数据的脚本,妈的居然写错了,修改之后,发现了1020张没有label和bbox标注的图片,根据image_id在COCO的instances_train2014.json文件的images列表找到了这些图片,然而在json文件的annotations列表中居然搜不到这些图片的标注!(如:COCO_train2014_000000542614.jpg)清除tfrecord文件里的这些图片的相关数据之后,在COCO上也能正常训练了!

所以调用argmax()时,这些图像的bbox为[],就会出现shape的维度为0而报错的情况。而程序卡住也是因为图像增强时处理到了这些图片,batchsize越大,就越容易遇到(1020/118230)。之前因为输出过自己转换的tfrecord的文件结构,而且随机抽取的图片也能正常显示,所以就一味的相信数据没有问题,还是没有考虑全面,所以浪费了很多时间。

判断tfrecord文件是否含不带bbox的图片的代码

import os
import tensorflow as tf

file_list = tf.gfile.Glob(os.path.join('/DATA/coco/tfrecords', 'val-?????-of-?????'))
i = 0
count = 0
for file in file_list:
    for string_record in tf.python_io.tf_record_iterator(file):
        example = tf.train.Example()
        example.ParseFromString(string_record)

        i += 1
        image_id = example.features.feature['image/image_id'].int64_list.value
        xmax = example.features.feature['image/object/bbox/xmax'].float_list.value
        # xmin = example.features.feature['image/object/bbox/xmin'].float_list.value
        # ymax = example.features.feature['image/object/bbox/ymax'].float_list.value
        # ymin = example.features.feature['image/object/bbox/ymin'].float_list.value

        if xmax == []:
            print(image_id)
            count += 1
    print(file, 'has been checked.')

print(i, 'images have been checked.')
print(count, 'annotations without bbox.')

4. 总结


  1. TensorFlow框架虽然有限制,但调试还是可以调试的,首先要熟悉程序的结构,弄清参数传递的过程
  2. 不要纠缠细节,调试的目的是快速且理性的判断bug的位置
  3. 写程序时要写单元测试,调试时会方便许多
  4. TensorFLow, to make your life difficult:)
posted @ 2019-03-06 20:52  backtosouth  阅读(2153)  评论(7编辑  收藏  举报