SSD属于one-stage的物体检测器
主要以R-CNN系列算法,主要是通过启发式方法Selective search或者RPN产生较为准确的候选框,然后再对这些候选框进行分类与回归。有利用多级方式,则预测精度更加准确。因而RefineDet 论文就是利用两个SSD级联,一个相当于RPN对anchor修正,第二个SSD对第一个的修正anchor后的输出进行预测,取得更好的结果。
该技术来自于ParseNet,conv4_3作为第一个输出预测的特征图,其大小为38x38,该层比较靠前,norm较大,因而使用L2Norm对特征图进行归一化,保证其与后面高语义层的差异不是很大。
- 注意L2Norm与BatchNorm(BN)的区别,BN是在batch_size,width,height三个维度上进行归一化,;而L2Norm仅仅在channel每个像素的维度上进行归一化,归一化后一般设置一个可训练的放缩变量gamma。
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)
这个技术来自于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 博文
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 ]
由于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值
论文中的实验表明,数据增强对预测精度有很大的提升
主要采用水平旋转操作,随机采集块区域(获取小目标训练样本),还有随机裁剪加颜色扭曲(代码中未实现)。
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为正样本个数。
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.