动手学深度学习13.7. 单发多框检测(SSD)-笔记练习(PyTorch)
以下内容为结合李沐老师的课程和教材补充的学习笔记,以及对课后练习的一些思考,自留回顾,也供同学之人交流参考。
本节课程地址:45 SSD实现【动手学深度学习v2】_哔哩哔哩_bilibili
本节教材地址:13.7. 单发多框检测(SSD) — 动手学深度学习 2.0.0 documentation
本节开源代码:…>d2l-zh>pytorch>chapter_optimization>ssd.ipynb
单发多框检测(SSD)
在 13.3节 — 13.6节 中,我们分别介绍了边界框、锚框、多尺度目标检测和用于目标检测的数据集。现在我们已经准备好使用这样的背景知识来设计一个目标检测模型:单发多框检测(SSD) (Liu et al., 2016)。该模型简单、快速且被广泛使用。尽管这只是其中一种目标检测模型,但本节中的一些设计原则和实现细节也适用于其他模型。
模型
图13.7.1 描述了单发多框检测模型的设计。此模型主要由基础网络组成,其后是几个多尺度特征块。基本网络用于从输入图像中提取特征,因此它可以使用深度卷积神经网络。单发多框检测论文中选用了在分类层之前截断的VGG (Liu et al., 2016),现在也常用ResNet替代。我们可以设计基础网络,使它输出的高和宽较大。这样一来,基于该特征图生成的锚框数量较多,可以用来检测尺寸较小的目标。接下来的每个多尺度特征块将上一层提供的特征图的高和宽缩小(如减半),并使特征图中每个单元在输入图像上的感受野变得更广阔。
回想一下在 13.5节 中,通过深度神经网络分层表示图像的多尺度目标检测的设计。由于接近 图13.7.1 顶部的多尺度特征图较小,但具有较大的感受野,它们适合检测较少但较大的物体。简而言之,通过多尺度特征块,单发多框检测生成不同大小的锚框,并通过预测边界框的类别和偏移量来检测大小不同的目标,因此这是一个多尺度目标检测模型。
在下面,我们将介绍 图13.7.1 中不同块的实施细节。首先,我们将讨论如何实施类别和边界框预测。
[类别预测层]
设目标类别的数量为 q q q。这样一来,锚框有 q + 1 q+1 q+1个类别,其中0类是背景。在某个尺度下,设特征图的高和宽分别为 h h h和 w w w。如果以其中每个单元为中心生成 a a a个锚框,那么我们需要对 h w a hwa hwa个锚框进行分类。如果使用全连接层作为输出,很容易导致模型参数过多。回忆 7.3节 一节介绍的使用卷积层的通道来输出类别预测的方法,单发多框检测采用同样的方法来降低模型复杂度。
具体来说,类别预测层使用一个保持输入高和宽的卷积层。这样一来,输出和输入在特征图宽和高上的空间坐标一一对应。考虑输出和输入同一空间坐标( x x x、 y y y):输出特征图上( x x x、 y y y)坐标的通道里包含了以输入特征图( x x x、 y y y)坐标为中心生成的所有锚框的类别预测。因此输出通道数为 a ( q + 1 ) a(q+1) a(q+1),其中索引为 i ( q + 1 ) + j i(q+1) + j i(q+1)+j( 0 ≤ j ≤ q 0 \leq j \leq q 0≤j≤q)的通道代表了索引为 i i i的锚框有关类别索引为 j j j的预测。
补充-对于SSD降低模型复杂度的解释:
- 使用全连接层存在的问题
目标检测需要在特征图上的每个单元生成 a a a个锚框。对于每个锚框,都需要预测其属于哪个类别(共有 q + 1 q+1 q+1个类别,包括背景)。
假设特征图大小为 h × w h \times w h×w,那么总共需要预测的锚框数量是 h × w × a h \times w \times a h×w×a。
如果使用全连接层来为每个锚框预测类别的话,那么:
- 输入需要展平成一个巨大的向量,长度为 h × w × C i n h \times w \times C_{in} h×w×Cin,其中 C i n C_{in} Cin是输入特征图的通道数。
- 输出需要一个同样巨大的向量,长度为 h × w × a × ( q + 1 ) h \times w \times a \times (q+1) h×w×a×(q+1),包含所有锚框的所有类别预测。
- 那么,全连接层的参数量将有 ( h w C i n ) × ( h w a ( q + 1 ) ) (h w C_{in}) \times (h w a (q+1)) (hwCin)×(hwa(q+1))。并且参数量会随着特征图尺寸 h h h和 w w w增大而呈二次方增长,随锚框数 a a a和类别数 q + 1 q+1 q+1的增多而线性增长,因此,全连接层的使用会导致模型过于复杂,难以训练和部署。
- SSD的解决方法:空间位置保持的卷积层
SSD的类别预测层(代码在下面)使用了一个卷积核为3x3、填充为1的卷积层,这种设置保证了输入特征图 ( h × w × C i n h \times w \times C_{in} h×w×Cin) 和输出特征图 ( h × w × C o u t h \times w \times C_{out} h×w×Cout)在高度和宽度上完全一致(空间坐标一一对应)。
这一设置同时还可以利用通道维度编码预测信息:
- 卷积层输出通道数被设为 C o u t = a × ( q + 1 ) C_out = a \times (q+1) Cout=a×(q+1),输出的每个空间位置( x x x、 y y y)的通道向量,包含了以输入特征图上对应位置( x x x、 y y y)为中心生成的所有 a a a个锚框的类别预测信息。
- 输出特征图在( x x x、 y y y)位置的第 k k k个通道的值,代表:
- 锚框索引 i = k / / ( q + 1 ) i = k // (q+1) i=k//(q+1)(整数除法,向下取整,决定了是 a a a个锚框中的哪一个);
- 类别索引 j = k j = k % (q+1) j=k(取余运算,决定了是哪个类别,包括背景);
- 也即: k = i ∗ ( q + 1 ) + j k = i * (q+1) + j k=i∗(q+1)+j。
如此一来,类别预测层的卷积核在整个特征图的 h × w h \times w h×w个空间位置上是共享参数的。同一个卷积核负责处理所有空间位置上相同 i , j i, j i,j索引组合的预测。而且,每个输出的预测值(某个位置( x x x、 y y y)的某个通道 k k k)只依赖于输入特征图上( x x x、 y y y)位置及其邻域的值,而不是像全连接层那样依赖于整张图的输入。
从参数量上,卷积层的参数量为 C i n × C o u t × k e r n e l h e i g h t ∗ k e r n e l w i d t h = C i n × ( a ( q + 1 ) ) × 3 × 3 C_{in} \times C_{out} \times kernel_{height} * kernel_{width} = C_{in} \times (a (q+1)) \times 3 \times 3 Cin×Cout×kernelheight∗kernelwidth=Cin×(a(q+1))×3×3,只取决于卷积核的大小和输入/输出通道数呈线性相关,而与特征图尺寸完全无关,相比于全连接层,卷积层节省了大量的参数数量,极大地降低了模型的复杂度,使其更易于训练和部署。
在下面,我们定义了这样一个类别预测层,通过参数num_anchors
和num_classes
分别指定了 a a a和 q q q。该图层使用填充为1的 3 × 3 3\times3 3×3的卷积层。此卷积层的输入和输出的宽度和高度保持不变。
%matplotlib inline
import torch
import torchvision
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l
import numpy as np
from torchvision import transforms
from PIL import Image# 类别预测层
def cls_predictor(num_inputs, num_anchors, num_classes):return nn.Conv2d(num_inputs, num_anchors * (num_classes + 1), # +1是加背景类别kernel_size=3, padding=1)
(边界框预测层)
边界框预测层的设计与类别预测层的设计类似。唯一不同的是,这里需要为每个锚框预测4个偏移量,而不是 q + 1 q+1 q+1个类别。
def bbox_predictor(num_inputs, num_anchors):return nn.Conv2d(num_inputs, num_anchors * 4, kernel_size=3, padding=1)
[连结多尺度的预测]
正如我们所提到的,单发多框检测使用多尺度特征图来生成锚框并预测其类别和偏移量。在不同的尺度下,特征图的形状或以同一单元为中心的锚框的数量可能会有所不同。因此,不同尺度下预测输出的形状可能会有所不同。
在以下示例中,我们为同一个小批量构建两个不同比例(Y1
和Y2
)的特征图,其中Y2
的高度和宽度是Y1
的一半。以类别预测为例,假设Y1
和Y2
的每个单元分别生成了 5 5