for i in range(len(in_channels)):
self.stems.append(
BaseConv(
in_channels=int(in_channels[i] * width),
out_channels=int(256 * width),
ksize=1,
stride=1,
act=act,
)
)
# Q: 三个尺度的backbone输出特征图的通道数不一样,为什么stem层的输出通道数都是256,它的作用是为了统一通道数吗?
# A: 是的,stem 层的作用是将 backbone 的输出特征图进行处理,使得其通道数为 256
# Q: 为什么要统一通道数呢?是因为后面的卷积层的输入通道数都是256吗?
# A: 是的,后面的卷积层的输入通道数都是256,所以需要将 backbone 的输出特征图进行处理,使得其通道数为 256
# Q: stems中的三个stem层并不是串联的吧
# A: 不是串联的,是并联的,因为后面的卷积层的输入通道数都是256,所以需要将 backbone 的输出特征图进行处理,使得其通道数为 256
self.cls_convs.append(
nn.Sequential(
*[
Conv(
in_channels=int(256 * width),
out_channels=int(256 * width),
ksize=3,
stride=1,
act=act,
),
Conv(
in_channels=int(256 * width),
out_channels=int(256 * width),
ksize=3,
stride=1,
act=act,
),
]
)
)
# cls_convs 用于分类,其结构为两个卷积层,每个卷积层的输入输出通道数都为 256
# Q: 既然输入输出通道数都是256,那么为什么不直接用一个卷积层呢?
# A: 为了增加网络的深度,增加网络的表达能力,增加网络的泛化能力,增加网络的鲁棒性,增加网络的稳定性
# Q: 256*width个通道数是怎么映射到num_classes个类别的呢?是后续的卷积层吗?它的名字叫什么?
# A: 是后续的卷积层,它的名字叫 cls_preds
self.reg_convs.append(
nn.Sequential(
*[
Conv(
in_channels=int(256 * width),
out_channels=int(256 * width),
ksize=3,
stride=1,
act=act,
),
Conv(
in_channels=int(256 * width),
out_channels=int(256 * width),
ksize=3,
stride=1,
act=act,
),
]
)
)
# reg_convs 用于回归,其结构为两个卷积层,每个卷积层的输入输出通道数都为 256
# Q: 这个与cls_convs的区别是什么呢?
# A: cls_convs 用于分类,其结构为两个卷积层,每个卷积层的输入输出通道数都为 256
# reg_convs 用于回归,其结构为两个卷积层,每个卷积层的输入输出通道数都为 256
# Q: 256*width个通道数是怎么映射到4个坐标的呢?是后续的卷积层吗?它的名字叫什么?
# A: 是后续的卷积层,它的名字叫 reg_preds
self.cls_preds.append(
nn.Conv2d(
in_channels=int(256 * width),
out_channels=self.num_classes,
kernel_size=1,
stride=1,
padding=0,
)
)
# cls_preds 用于分类,其输入通道数为 256,输出通道数为类别数
# Q: 经过cls_preds后,输出的通道数是num_classes个,特征图的尺寸是多少呢?
# A: 输出的通道数是num_classes个,特征图的尺寸是原始特征图的尺寸
# Q: 特征图里的每个像素点都对应一个num_classes维的向量,这个向量的每个元素代表这个像素点属于某个类别的概率吗?
# A: 是的,这个向量的每个元素代表这个像素点属于某个类别的概率
# Q: 如果(3,4)的像素点属于狗的概率是0.9,属于猫的概率是0.1,那么这个像素点对应的向量是[0.9,0.1]吗?
# A: 是的,这个像素点对应的向量是[0.9,0.1]
# Q: 为什么要将特征图的尺寸保持不变呢?
# A: 为了保证特征图的每个像素点都能对应到原始图像的某个像素点
# Q: 通过什么方式可以将特征图的每个像素点对应到原始图像的某个像素点呢?
# A: 通过特征图的每个像素点的坐标,以及原始图像的尺寸,就可以计算出原始图像的某个像素点的坐标
# Q: 回归出来的坐标是相对于原始图像的坐标吗?还是说要乘以stride才是相对于原始图像的坐标呢?
# A: 是相对于原始图像的坐标,不需要乘以stride
self.reg_preds.append(
nn.Conv2d(
in_channels=int(256 * width),
out_channels=4,
kernel_size=1,
stride=1,
padding=0,
)
)
# reg_preds 用于回归,其输入通道数为 256,输出通道数为 4
# Q: 经过reg_preds后,输出的通道数是4个,四个通道分别代表什么呢?
# A: 输出的通道数是4个,四个通道分别代表[中心点x坐标,中心点y坐标,宽度,高度]
# Q: 可以理解成回归的结果是一个BoundingBox,那为什么还要使用IOUloss呢?
# A: 因为回归的结果是一个BoundingBox,但是这个BoundingBox可能不是最优的,所以需要使用IOUloss来进行优化
# Q: 输出的BoundingBox与其他尺度的特征图的BoundingBox进行IOU吗?
# A: 是的,输出的BoundingBox与其他尺度的特征图的BoundingBox进行IOU
# Q: 同一尺度下的2个相邻的BoundingBox进行IOU吗?
# A: 是的,同一尺度下的2个相邻的BoundingBox进行IOU
self.obj_preds.append(
nn.Conv2d(
in_channels=int(256 * width),
out_channels=1,
kernel_size=1,
stride=1,
padding=0,
)
)
# obj_preds 用于预测目标,其输入通道数为 256,输出通道数为 1
# Q: 经过obj_preds后,输出的通道数是1个,这个通道代表什么呢?
# A: 输出的通道数是1个,这个通道代表[目标的置信度]
首先是一个循环,该循环的作用是构建多个 “stem” 模块,每个 “stem” 模块的作用是将 backbone 的输出特征图进行处理,使得其通道数为 256。在每个 “stem” 模块中,通过调用 BaseConv
类构造一个卷积层,将输入特征图的通道数从 in_channels[i]
转换为 256
。其中,in_channels
是一个列表,表示 backbone 输出的特征图的通道数。width
是一个缩放因子,用于控制模型的宽度。
接下来是三个列表,分别用于存储分类网络(cls_convs
)、回归网络(reg_convs
)和目标检测网络(obj_convs
)的构建。每个列表中包含若干个卷积层,用于从输入特征图中提取有用的特征。每个卷积层都是通过调用 Conv
类构造的,其中 in_channels
和 out_channels
表示输入和输出的通道数,ksize
表示卷积核大小,stride
表示步长,act
表示激活函数。
在分类网络和回归网络中,每个卷积层的输入和输出通道数都为 256,因为这是在 “stem” 模块中处理后的特征图的通道数。在目标检测网络中,每个卷积层的输入通道数也为 256,但输出通道数分别为类别数、4 和 1,分别表示分类、回归和目标检测的预测结果。其中,分类的预测结果需要预测每个目标属于哪一类,因此输出通道数为类别数;回归的预测结果需要预测每个目标的位置,因此输出通道数为 4;目标检测的预测结果需要预测每个目标是否存在,因此输出通道数为 1。
发表回复