SSD论文总结
目录
一、one-stage 检测器和two-stage检测器
SSD属于one-stage的物体检测器
1) ont-stage 检测器
- 主要以YOLO和SSD为代表,这种one-stage的检测主要是对图片不同位置进行采样预测,利用卷积层输出的相对于原图相对位置的特征,直接预测对象类别和对象位置信息。
- 如YOLO,最后输出相对于原图的7x7的网格,也就是对图中的49个位置进行均匀采样预测,如使用anchor技术,对同一位置进行不同长宽比例的预测。而SSD则对多个分辨率的fmaps进行密集的均匀采样预测。
- 我们可以发现,这种均匀采样存在一定缺陷:导致正负例的比例不均衡,均匀采样中大部分对应着背景图。这会导致模型精度不高。论文Focal Loss就是一种从损失方面针对这种问题进行优化的方法。而OHEM技术也对这种正负不均衡问题的一种优化技术。
2) two-stage 检测器
主要以R-CNN系列算法,主要是通过启发式方法Selective search或者RPN产生较为准确的候选框,然后再对这些候选框进行分类与回归。有利用多级方式,则预测精度更加准确。因而RefineDet 论文就是利用两个SSD级联,一个相当于RPN对anchor修正,第二个SSD对第一个的修正anchor后的输出进行预测,取得更好的结果。
二、SSD与YOLOv1
- SSD用CNN来直接进行检测,而YOLOv1使用全连接进行检测;
- SSD将背景background当做一个类别来预测(对于20类的VOC,则实际预测21类),而YOLO只对对象类别进行预测(实际预测为20类)
- SSD利用了多层不同分辨率的特征图进行预测输出,而且对大尺度的图进行小物体检测,小尺度的图进行大物体检测;从而更有利于多scale的物体的检测
- SSD采用了Faster rcnn中的anchor技术,利用不同长宽比先验框进行对象检测;而YOLOv1没有用anchors技术。
- SSD的这几个改进,一定程度上克服了YOLOv1**难以检测小目标,定位不准**等不足。
三、网络结构中的一些tricks
1)SSD的结构示意图
可以看出,后面几层用于对象预测(类别和位置的预测)
2) L2Norm的使用
该技术来自于ParseNet,conv4_3作为第一个输出预测的特征图,其大小为38x38,该层比较靠前,norm较大,因而使用L2Norm对特征图进行归一化,保证其与后面高语义层的差异不是很大。
- 注意L2Norm与BatchNorm(BN)的区别,BN是在batch_size,width,height三个维度上进行归一化,;而L2Norm仅仅在channel每个像素的维度上进行归一化,归一化后一般设置一个可训练的放缩变量gamma。
pytorch代码
class L2Norm2d(nn.Module):
'''L2Norm layer across all channels'''
def __init__(self,scale): # scale是参数初始化的值,代码中设置为20
super(L2Norm2d, self).__init__()
self.scale = Parameter(torch.Tensor([scale])) # self.scale是可学习的
'''
因为conv4_3层比较靠前,而且norm较大所以加一个L2Norm使其与后面层差别不大
不同于BN,这是在通道维度上进行Norm (spatial normalization)
'''
def forward(self,x,dim=1):
'''out = scale * x / sqrt(\sum x_i^2)'''
return self.scale[0] * x * x.pow(2).sum(dim).unsqueeze(1).clamp(min=1e-12).rsqrt().expand_as(x)
3) dilation的使用
这个技术来自于DeepLab-LargeFOV,这个下采样操作是代替原来网络的pool5层,这样能速度会慢与使用这种操作的20%。
下图为不同dilation rate的设置
(a)是普通的 3\times3 卷积,其视野就是
3×3
3
×
3
,(b)是扩张率为1,此时视野变成
7×7
7
×
7
,(c)扩张率为3时,视野扩大为
15×15
15
×
15
,但是视野的特征更稀疏了。Conv6采用
3×3
3
×
3
大小但dilation rate=6的扩展卷积。
该图片来自于https://www.zhihu.com/people/xiaohuzc/posts?page=1 博文
4) 不同大小的fmaps对应的anchor_sizes设计
SSD中conv4_3, Conv7,Conv8_2,Conv9_2,Conv10_2,Conv11_2共6个卷积层用作预测输出,它们的大小分别是38,19,19,5,3,1。针对不同大小的fmaps,使用不同数目的先验框以及不同大小比例的先验框;除去conv4_3, 其他5层的anchor_size的计算公式如下:
sk=smin+smax−sminm−1(k−1),k∈[1,m]
s
k
=
s
m
i
n
+
s
m
a
x
−
s
m
i
n
m
−
1
(
k
−
1
)
,
k
∈
[
1
,
m
]
- 其中 m=5, smin=2,smax=0.9 s m i n = 2 , s m a x = 0.9 , 然后每层的anchor_size乘以对应层的锚长宽比anchor_ratio,即可得到每层的anchor数目。anchor_ratio = {1,2,3,1/2,1/3};
- 注意一些细节,每层anchor的计算并不统一;其中ratio的数目设置为[4,6,6,6,4,4],4表示只有两种比例,而6表示使用所有的长宽比;也就是只对Conv7,Conv8_2,Conv9_2使用所有的长宽比,其他的层只使用两个比例。最终所有层的anchor_sizes={30,60,111,162,213,264,315}, 对于conv4_3, sk=smin/2 s k = s m i n / 2 ,由于计算ratio=1时,需要k+1的 sk s k ,所以conv11_2的后一个 sk=300×105/100=315 s k = 300 × 105 / 100 = 315
- 另外,对于ratio=1的情况,对应两种anchor, { sk,sk s k , s k }和{ sksk+1−−−−−√,sksk+1−−−−−√ s k s k + 1 , s k s k + 1 }。
- 所以SSD300一共预测
38×38×4+19×19×6+10×10×6+5×5×6+3×3×4+1×1×4=8732 38 × 38 × 4 + 19 × 19 × 6 + 10 × 10 × 6 + 5 × 5 × 6 + 3 × 3 × 4 + 1 × 1 × 4 = 8732
四、训练过程中一些tricks
1) hard example mining
由于SSD均匀采样,所以产生的正负例极其不平衡;此外,每个groundtruth至少与一个先验框anchor匹配,注意一个GT可以与多个先验框匹配,且匹配为正例的阈值为iou=0.5。但是这样正例相对于负例可能还是很少,所以为了尽量平衡,则采用OHEM技术,对负样本抽样时,按照预测出类别的执行度误差,从大到小排列,取误差大的topN个负样本作为负例。保证正负样本比例为1:3。具体代码如下
def hard_negative_mining(self, cls_loss, pos):
'''
:param cls_loss: (N*num_anc,) 经过cross_entropy_loss计算损失
:param pos: (N,num_anc,) 只有0,1值,
:return: neg indices, (N,num_anc)
'''
N,num_anc = pos.size()
cls_loss[pos.view(-1)] = 0 # set pos = 0将正例损失赋为0,不考虑正例
cls_loss = cls_loss.view(N,-1) # (N,num_anc)
# 注意这个地方有个编程技巧,能用索引的方式,求出topN的位置
# 先逆序,然后在对索引值进行正序,得到的二级索引值在小于N的位置就是topN
_, idx = cls_loss.sort(1,descending=True) # sort by neg
_, rank = idx.sort(1) # (N,num_anc)
num_pos = pos.long().sum(1) # (N,1)
num_neg = torch.clamp(3*num_pos,max=num_anc-1) #(N,1) 正例样本的3倍不得超过总anc数
# print('neg size:',num_neg.size(),rank.size())
neg = rank < num_neg.unsqueeze(1).expand_as(rank) # (N,num_anc_8732)
return neg # 只有0,1值
2) 数据增强
论文中的实验表明,数据增强对预测精度有很大的提升
主要采用水平旋转操作,随机采集块区域(获取小目标训练样本),还有随机裁剪加颜色扭曲(代码中未实现)。
3)损失计算
L(x,c,l,g)=1N(Lconf(x,c)+αLloc(x,l,g)
L
(
x
,
c
,
l
,
g
)
=
1
N
(
L
c
o
n
f
(
x
,
c
)
+
α
L
l
o
c
(
x
,
l
,
g
)
其中N为正样本个数。
- 位置损失计算: Lloc(x,l,g)=∑Ni∈Pos∑m∈cx,cy,w,hxkijsmoothL1(lmi−gmj) L l o c ( x , l , g ) = ∑ i ∈ P o s N ∑ m ∈ c x , c y , w , h x i j k s m o o t h L 1 ( l i m − g j m ) 这是采用Fast RCNN的位置计算方法;此处的trick有,训练时对cx,cy,w,h进行缩放,添加一个scales因子,加速训练。
- 类别损失计算:
Lconf(x,c)=−∑Ni∈Posxpijlog(Cpi)−∑i∈Neglog(Coi)whereCpi=exp(cpi)∑pexp(cpi) L c o n f ( x , c ) = − ∑ i ∈ P o s N x i j p l o g ( C i p ) − ∑ i ∈ N e g l o g ( C i o ) w h e r e C i p = e x p ( c i p ) ∑ p e x p ( c i p ) - 权重系数 α α 通过交叉验证设置为1.
五、总结
1)有些收获
- 在进行框预测时,要将box的值映射到(0,1)之间,这样在对不同的fmaps,进行逆转时,比较方便。
- 多尺度特征图,或者特征融合方式,更有利于识别出大小不一的物体
- 先验框的设置有利于物体检测,可以探究一下为什么,关于四个位置信息的计算方式(Fast RCNN方式)能否有更好的通用的方式。
2) 思想启发
- 根据SSD中的思想,能否根据物体大小(划分为一定梯度),然后不同大小物体选取的fmaps来源不同。
六 待补充
1)SSD 512的设计细节
2)SSD的一系列变形网络设计
参考文献
1.知乎博客: https://www.zhihu.com/people/xiaohuzc/posts?page=1
2. github SSD源码: https://github.com/kuangliu/pytorch-ssd
3. Wei Liu, et al. “SSD: Single Shot MultiBox Detector.” ECCV2016.