之前使用 Tensorflow Detection API 训练 SSD 网络,改里边的 depth_multiplier 参数使网络层数降低,确实可以提高推理速度,但是因为该 API 训练的网络里有一个定制的操作符TFLite_Detection_PostProcess 不能在GPU上运行,导致推理时需要在 GPU CPU 上切换影响推理速度(猜测,因为 tflite 的 delegate 也会有类似现象),所以准备用pytorch从轮子造起,这样灵活性比较强。在github上找到了一个MobilenetV3-ssd的项目,folk过来实践一下:github连接

工作的主要流程:

1.  实现MobilenetV3_ssd网络

2.  在大型数据集上进行预训练

3.  制作自己的数据集并训练

4.  剪枝

5.  部署

这里其实有一些坑的,不过在 dirty work 以后发现能达到自己想要的后果就没有去填了,后边遇到相关疑问会记录下来。

 1. 实现MobilenetV3_ssd网络

        这里首先第一个坑是是否应该选择 MobilenetV3_ssd,当初选这个网络是因为认为这个比较成熟(机器人之前用的是mbv2_ssd),本着尽量少做改动的原则就选了这个,后来发现mbv3和mbv2还是有一些地方不太一样的,例如mbv3里有se模块,这个就是做不同 channel 的权重的,和我后变用的结构化剪枝(砍通道)似乎是功能重复了,而且在砍的时候se模块直接忽略了。如果重新再做一次这个工作我可能会选择 Yolo 或者自己简化一个 Mobilenet, 但是简化哪部分也不太清楚,因为它那些trick也不知道哪个作用大。里入pw-dw-pw这种卷积形式,是否要改成常规的卷积。挺想验证一下的,但是神经网络训练需要资源,不是大厂基本是不会有充足的资源给使用的,自己去搞的一些GPU、数据集之类的资源比较费时费力,效率低,浪费生命。 接下来就是自己采坑的过程。

       首先我们任务简单,抄来的代码里是用的 MobilenetV3_large, 我们不需要,直接改成 MobilenetV3_small,这个照着论文里的结构改就行了,其实后来想一想,因为要大量缩减通道的,这里用 Large 应该也没什么问题,到时候多砍一些就行了,没有验证。

2. 使用COCO数据集训练

        抄来的代码使用的 VOC 数据集,数据量上,而且我看里边好多图片质量也不是很好,原作者也推荐用 COCO 训练一下,因为之前用过 COCO,这里正好改一下数据集合。

1) loss 为 inf

        print(" loss : %d"%loss.item())
        losses.update(loss.item(), images.size(0))

float 的 inf 是不能转成 int 打印的,程序到这就会中断

分析

        loss 由 MutiBoxLoss 模块产生

criterion = MultiBoxLoss(priors_cxcy=model.priors_cxcy).to(device)

        该模块接收从dataset里拿到的标注和模型的前向输出,来编码loss,分为两个部分:

        conf_loss

        loc_loss

        分别打印,发现是 loc_loss 为 -inf ,这是由于该 loss 的计算方式里使用了torch.log ,

torch.log(cxcy[:, 2:] / priors_cxcy[:, 2:]) * 5

当真数是 0 时就会输出 -inf ,那么看真数是怎么计算的。产生问题的是 w,h 就是标注里的

maxy - miny 为 0 了,到数据转换文件里查看,发现果真有一张图片的标注不对,真是太坑了...

<xmin>205</xmin>
<ymin>381</ymin>
<xmax>210</xmax>
<ymax>381</ymax>

图片编号:000000361919

看来数据清洗还是很重要的

2)  batchnorm 错误

错误信息

    raise ValueError('Expected more than 1 value per channel when training, got input size {}'.format(size))
ValueError: Expected more than 1 value per channel when training, got input size torch.Size([1, 64, 1, 1])

这个错误是由于batch_norm需要输入的batch里的tensor数量大于1,把dataloader里添加一个参数解决问题

drop_last=True

参考链接:

pytorch报错:ValueError: Expected more than 1 value per channel when training, got input size torch.Size([1,512,1,1]) - zmbreathing - 博客园

3) 训练

         这里给原版的代码添加了可视化,用的tensorboardX,coco数据集有11万张图片,我租的服务器就是一张3090,训练起来那是相当的慢,分布式训练咱也不会,只能用时间换空间了,训练了快半个月,期间也有一些坑,简单记录一下:

        首先是租服务器训练,我用的是ubuntu远程ssh操作,自己电脑的终端总会无响应,一无响应训练直接就终止了,后来找到了一种方法,就是用 screen 把远程运行的进程挂起,这样自己终端无响应了也没关系,训练程序还在后台跑。

        其次是有一次竟然遇到了机房停电,直接GG,后来为了防止这种意外时间发生,用 shell 写了个脚本,可以定时从服务器把训练的 checkpoint 拷贝回本机,都是一些比较朴(ben)素(bi)的办法。

        这里调整不同的训练参数之类的我就没有尝试了,训练太慢了,数据集小一些可以多尝试尝试,调参实在太恶心,我就没调了。

3. 制作自己的数据集并进行稀疏化训练

        因为我们的问题比较简单,训练图片就采集了5000张左右,数据增强也没怎么做,这里要注意的是,数据集里的图片不要都是一个特点,例如被检测目标都是正面、都位于图片中心等等,尽量多样化。 这里稀疏化训练是为了把bn的参数都驱赶为0,这样接近0的bn参数对应的channel就可以砍掉,代码非常好改,就在要惩罚的bn层参数添加一句:

param.grad.add_(0.01 * torch.sign(param.data))

        把Bn的histgram打印到tensorboard里可以明显看出大多数参数都被惩罚到0了。

        这里还有一个坑,就是之前数据单一,导致过拟合了,后来解决办法是混入coco里的数据,我这里比例是1/4,可以尝试 别的,我这个work了就没试别的了。

        终于,训练出一个还可以的网络,eval 时能有 80% 的准确率。

4. 剪枝

        这里的工作比较复杂,因为我们我们这个网络有pw-dw-pw的结构,dw要保证输入输出通道相同,所以对block里的第一个pw卷积和第二个dw卷积进行裁剪时需要保证通道数相同,这样在dw里才能把group设置成通道数,实现dw卷积,这里取了一个并集。

        还有一个问题是因为我自己的模型时把prior也改了,第一个feature层对应的目标太小了,我这里就没有检测,这样会导致后边预测部分的网络结构变了,这里按理来说应该也进行相应的参数拷贝,我懒得搞了,就直接重新练了,而且也没有freeze参数,正常来说这么做应该是不对的,但是也work了,就不去尝试别的了,因为真的比较浪费时间。

5. 部署

        部署过程中也涉及一些问题:

        目前网络输出是两个tensor, 一个是bbox的编码,一个是scores。

        首先是对bbox进行解码,因为这个输出不是直接bbox的四个参数,而是经过编码的,公式比较简单,直接反过来就可以解码。注意不同的网络编码顺序可能不一样,例如cx,cy,w,h 和 cy,cx,h,w。

        score 向量需要先用 softmax 变成概率,然后挑出概率最大的,这里是one-hot形式的,也比较简单。

        这里还需要注意的一个问题是对图片的均值归一化处理,不同的数据集是不同的,就是 norm 和 mean, 之前使用的 mbv2_ssd 只归一化了,但是我们抄来这个代码里是均值归一化,mean 不为0,这里要注意一下。其实应该在自己的数据集里求出这个 norm 和 mean, 这里没有搞,能 work 就先不管了,记录一下。

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐