关闭网站弹窗代码,蜀icp备 网站建设中企动力成都,做微商进哪个网站安全吗,网站建设创意公司3.4 模型结构
本文来自开源组织 DataWhale #x1f433; CV小组创作的目标检测入门教程。
对应开源项目 《动手学CV-Pytorch》 的第3章的内容#xff0c;教程中涉及的代码也可以在项目中找到#xff0c;后续会持续更新更多的优质内容#xff0c;欢迎⭐️。
如果使用我们…3.4 模型结构
本文来自开源组织 DataWhale CV小组创作的目标检测入门教程。
对应开源项目 《动手学CV-Pytorch》 的第3章的内容教程中涉及的代码也可以在项目中找到后续会持续更新更多的优质内容欢迎⭐️。
如果使用我们教程的内容或图片请在文章醒目位置注明我们的github主页链接https://github.com/datawhalechina/dive-into-cv-pytorch
本章教程所介绍的网络后面我们称其为Tiny_Detector是为了本教程特意设计的网络而并不是某个经典的目标检测网络。如果一定要溯源的话由于代码是由一个外国的开源SSD教程改编而来因此很多细节上也更接近SSD网络可以认为是一个简化后的版本目的是帮助大家更好的入门。
那么下面我们就开始介绍Tiny_Detector的模型结构
3.4.1 VGG16作为backbone
为了使结构简单易懂我们使用VGG16作为backbone即完全采用vgg16的结构作为特征提取模块只是去掉fc6和fc7两个全连接层。如图3-17所示 图3-17 Tiny-Detector的backbone对于网络的输入尺寸的确定由于vgg16的ImageNet预训练模型是使用224x224尺寸训练的因此我们的网络输入也固定为224x224和预训练模型尺度保持一致可以更好的发挥其作用。通常来说这样的网络输入大小对于检测网络来说还是偏小在完整的进行完本章的学习后不妨尝试下将输入尺度扩大看看会不会带来更好的效果。
特征提取模块对应代码模块在model.py中的VGGBase类进行了定义
class VGGBase(nn.Module): VGG base convolutions to produce feature maps.完全采用vgg16的结构作为特征提取模块丢掉fc6和fc7两个全连接层。因为vgg16的ImageNet预训练模型是使用224×224尺寸训练的因此我们的网络输入也固定为224×224def __init__(self):super(VGGBase, self).__init__()# Standard convolutional layers in VGG16self.conv1_1 nn.Conv2d(3, 64, kernel_size3, padding1) # stride 1, by defaultself.conv1_2 nn.Conv2d(64, 64, kernel_size3, padding1)self.pool1 nn.MaxPool2d(kernel_size2, stride2) # 224-112self.conv2_1 nn.Conv2d(64, 128, kernel_size3, padding1)self.conv2_2 nn.Conv2d(128, 128, kernel_size3, padding1)self.pool2 nn.MaxPool2d(kernel_size2, stride2) # 112-56self.conv3_1 nn.Conv2d(128, 256, kernel_size3, padding1)self.conv3_2 nn.Conv2d(256, 256, kernel_size3, padding1)self.conv3_3 nn.Conv2d(256, 256, kernel_size3, padding1)self.pool3 nn.MaxPool2d(kernel_size2, stride2) # 56-28self.conv4_1 nn.Conv2d(256, 512, kernel_size3, padding1)self.conv4_2 nn.Conv2d(512, 512, kernel_size3, padding1)self.conv4_3 nn.Conv2d(512, 512, kernel_size3, padding1)self.pool4 nn.MaxPool2d(kernel_size2, stride2) # 28-14self.conv5_1 nn.Conv2d(512, 512, kernel_size3, padding1)self.conv5_2 nn.Conv2d(512, 512, kernel_size3, padding1)self.conv5_3 nn.Conv2d(512, 512, kernel_size3, padding1)self.pool5 nn.MaxPool2d(kernel_size2, stride2) # 14-7# Load pretrained weights on ImageNetself.load_pretrained_layers()def forward(self, image):Forward propagation.:param image: images, a tensor of dimensions (N, 3, 224, 224):return: feature maps pool5out F.relu(self.conv1_1(image)) # (N, 64, 224, 224)out F.relu(self.conv1_2(out)) # (N, 64, 224, 224)out self.pool1(out) # (N, 64, 112, 112)out F.relu(self.conv2_1(out)) # (N, 128, 112, 112)out F.relu(self.conv2_2(out)) # (N, 128, 112, 112)out self.pool2(out) # (N, 128, 56, 56)out F.relu(self.conv3_1(out)) # (N, 256, 56, 56)out F.relu(self.conv3_2(out)) # (N, 256, 56, 56)out F.relu(self.conv3_3(out)) # (N, 256, 56, 56)out self.pool3(out) # (N, 256, 28, 28)out F.relu(self.conv4_1(out)) # (N, 512, 28, 28)out F.relu(self.conv4_2(out)) # (N, 512, 28, 28)out F.relu(self.conv4_3(out)) # (N, 512, 28, 28)out self.pool4(out) # (N, 512, 14, 14)out F.relu(self.conv5_1(out)) # (N, 512, 14, 14)out F.relu(self.conv5_2(out)) # (N, 512, 14, 14)out F.relu(self.conv5_3(out)) # (N, 512, 14, 14)out self.pool5(out) # (N, 512, 7, 7)# return 7*7 feature map return outdef load_pretrained_layers(self):we use a VGG-16 pretrained on the ImageNet task as the base network.Theres one available in PyTorch, see https://pytorch.org/docs/stable/torchvision/models.html#torchvision.models.vgg16We copy these parameters into our network. Its straightforward for conv1 to conv5.# Current state of basestate_dict self.state_dict()param_names list(state_dict.keys())# Pretrained VGG basepretrained_state_dict torchvision.models.vgg16(pretrainedTrue).state_dict()pretrained_param_names list(pretrained_state_dict.keys())# Transfer conv. parameters from pretrained model to current modelfor i, param in enumerate(param_names): state_dict[param] pretrained_state_dict[pretrained_param_names[i]]self.load_state_dict(state_dict)print(\nLoaded base model.\n)因此我们的Tiny_Detector特征提取层输出的是7x7的feature map下面我们要在feature_map上设置对应的先验框或者说anchor。
关于先验框的概念上节已经做了介绍在本实验中anchor的配置如下
将原图均匀分成7x7个cell设置3种不同的尺度0.2, 0.4, 0.6设置3种不同的长宽比1:1, 1:2, 2:1
因此我们对这 7x7 的 feature map 设置了对应的7x7x9个anchor框其中每一个cell有9个anchor框如图3-18所示 图3-18(a)原图(b)中的9种颜色框代表的是9个anchor框对于每个anchor我们需要预测两类信息一个是这个anchor的类别信息一个是物体的边界框信息。如图3-19
在我们的实验中类别信息由21类别的得分组成VOC数据集的20个类别 一个背景类模型最终会选择预测得分最高的类作为边界框对象的类别。
而边界框信息是指我们大致知道了当前anchor中包含一个物体的情况下如何对anchor进行微调使得最终能够准确预测出物体的bbox。 图3-19 Tiny-Detector的输出示例这两种预测我们分别称为分类头和回归头那么分类头预测和回归头预测是怎么得到的
其实我们只需在7x7的feature map后接上两个3x3的卷积层即可分别完成分类和回归的预测。
下面我们就对分类头和回归头的更多细节进行介绍。
3.4.2 分类头和回归头
3.4.2.1 边界框的编解码
Tiny_Detector并不是直接预测目标框而是回归对于anchor要进行多大程度的调整才能更准确的预测出边界框的位置。那么我们的目标就是需要找一种方法来量化计算这个偏差。
对于一只狗的目标边界框和先验框的示例如下图3-21所示 图3-21 目标框和预测框示例我们的模型要预测anchor与目标框的偏移并且这个偏移会进行某种形式的归一化这个过程我们称为边界框的编码。
这里我们使用的是与SSD完全一致的编码方法具体公示表达如下
gcxcx−c^xw^g_{cx}\frac{c_x-\hat{c}_x}{\hat{w}}gcxw^cx−c^x
gcycy−c^yh^g_{cy}\frac{c_y-\hat{c}_y}{\hat{h}}gcyh^cy−c^y
gwlog(ww^)g_wlog(\frac{w}{\hat{w}})gwlog(w^w)
ghlog(hh^)g_hlog(\frac{h}{\hat{h}})ghlog(h^h)
模型预测并输出的是这个编码后的偏移量(gcx,gcy,gw,ghg_{cx},g_{cy},g_w,g_hgcx,gcy,gw,gh)最终只要再依照公式反向进行解码就可以得到预测的目标框的信息。
目标框编码与解码的实现位于utils.py中代码如下
def cxcy_to_gcxgcy(cxcy, priors_cxcy): Encode bounding boxes (that are in center-size form) w.r.t. the corresponding prior boxes (that are in center-size form).For the center coordinates, find the offset with respect to the prior box, and scale by the size of the prior box.For the size coordinates, scale by the size of the prior box, and convert to the log-space.In the model, we are predicting bounding box coordinates in this encoded form.:param cxcy: bounding boxes in center-size coordinates, a tensor of size (n_priors, 4):param priors_cxcy: prior boxes with respect to which the encoding must be performed, a tensor of size (n_priors, 4):return: encoded bounding boxes, a tensor of size (n_priors, 4)# The 10 and 5 below are referred to as variances in the original SSD Caffe repo, completely empirical# They are for some sort of numerical conditioning, for scaling the localization gradient# See https://github.com/weiliu89/caffe/issues/155return torch.cat([(cxcy[:, :2] - priors_cxcy[:, :2]) / (priors_cxcy[:, 2:] / 10), # g_c_x, g_c_ytorch.log(cxcy[:, 2:] / priors_cxcy[:, 2:]) * 5], 1) # g_w, g_hdef gcxgcy_to_cxcy(gcxgcy, priors_cxcy): Decode bounding box coordinates predicted by the model, since they are encoded in the form mentioned above.They are decoded into center-size coordinates.This is the inverse of the function above.:param gcxgcy: encoded bounding boxes, i.e. output of the model, a tensor of size (n_priors, 4):param priors_cxcy: prior boxes with respect to which the encoding is defined, a tensor of size (n_priors, 4):return: decoded bounding boxes in center-size form, a tensor of size (n_priors, 4)return torch.cat([gcxgcy[:, :2] * priors_cxcy[:, 2:] / 10 priors_cxcy[:, :2], # c_x, c_ytorch.exp(gcxgcy[:, 2:] / 5) * priors_cxcy[:, 2:]], 1) # w, h3.4.2.2 分类头与回归头预测
按照前面的介绍对于输出7x7的feature map上的每个先验框我们想预测
1边界框的一组21类分数其中包括VOC的20类和一个背景类。
2边界框编码后的偏移量(gcx,gcy,gw,ghg_{cx},g_{cy},g_w,g_hgcx,gcy,gw,gh)。
为了得到我们想预测的类别和偏移量我们需要在feature map后分别接上两个卷积层
1一个分类预测的卷积层采用3x3卷积核padding和stride都为1每个anchor需要分配21个卷积核每个位置有9个anchor因此需要21x9个卷积核。
2一个定位预测卷积层每个位置使用3x3卷积核padding和stride都为1每个anchor需要分配4个卷积核因此需要4x9个卷积核。
我们直观的看看这些卷积上的输出见下图3-22 图 3-22 Tiny-Detector输出示例这个回归头和分类头的输出分别用蓝色和黄色表示。其feature map的大小7x7保持不变。我们真正关心的是第三维度通道数把其具体的展开可以看到如下图3-23所示 图3-23 每个cell中9个anchor预测编码偏移量也就是说最终回归头的输出有36个通道其中每4个值就对应了一个anchor的编码后偏移量的预测这样的4个值的预测共有9组因此通道数是36。
分类头可以用同样的方式理解如下图3-24所示: 图3-24 每个cell中9个anchor预测分类得分分类头和回归头结构的定义由 model.py 中的 PredictionConvolutions 类实现代码如下
class PredictionConvolutions(nn.Module): Convolutions to predict class scores and bounding boxes using feature maps.The bounding boxes (locations) are predicted as encoded offsets w.r.t each of the 441 prior (default) boxes.See cxcy_to_gcxgcy in utils.py for the encoding definition.这里预测坐标的编码方式完全遵循的SSD的定义The class scores represent the scores of each object class in each of the 441 bounding boxes located.A high score for background no object.def __init__(self, n_classes): :param n_classes: number of different types of objectssuper(PredictionConvolutions, self).__init__()self.n_classes n_classes# Number of prior-boxes we are considering per position in the feature map# 9 prior-boxes implies we use 9 different aspect ratios, etc.n_boxes 9 # Localization prediction convolutions (predict offsets w.r.t prior-boxes)self.loc_conv nn.Conv2d(512, n_boxes * 4, kernel_size3, padding1)# Class prediction convolutions (predict classes in localization boxes)self.cl_conv nn.Conv2d(512, n_boxes * n_classes, kernel_size3, padding1)# Initialize convolutions parametersself.init_conv2d()def init_conv2d(self):Initialize convolution parameters.for c in self.children():if isinstance(c, nn.Conv2d):nn.init.xavier_uniform_(c.weight)nn.init.constant_(c.bias, 0.)def forward(self, pool5_feats):Forward propagation.:param pool5_feats: conv4_3 feature map, a tensor of dimensions (N, 512, 7, 7):return: 441 locations and class scores (i.e. w.r.t each prior box) for each imagebatch_size pool5_feats.size(0)# Predict localization boxes bounds (as offsets w.r.t prior-boxes)l_conv self.loc_conv(pool5_feats) # (N, n_boxes * 4, 7, 7)l_conv l_conv.permute(0, 2, 3, 1).contiguous() # (N, 7, 7, n_boxes * 4), to match prior-box order (after .view())# (.contiguous() ensures it is stored in a contiguous chunk of memory, needed for .view() below)locs l_conv.view(batch_size, -1, 4) # (N, 441, 4), there are a total 441 boxes on this feature map# Predict classes in localization boxesc_conv self.cl_conv(pool5_feats) # (N, n_boxes * n_classes, 7, 7)c_conv c_conv.permute(0, 2, 3, 1).contiguous() # (N, 7, 7, n_boxes * n_classes), to match prior-box order (after .view())classes_scores c_conv.view(batch_size, -1, self.n_classes) # (N, 441, n_classes), there are a total 441 boxes on this feature mapreturn locs, classes_scores按照上面的介绍我们的模型输出的shape应该为
分类头 batch_size x 7 x 7 x 189回归头 batch_size x 7 x 7 x 36
但是为了方便后面的处理我们肯定更希望每个anchor的预测独自成一维也就是
分类头 batch_size x 441 x 21回归头 batch_size x 441 x 4
441是因为我们的模型定义了总共4417x7x9个先验框这个转换对应了这两行代码:
locs l_conv.view(batch_size, -1, 4)
classes_scores c_conv.view(batch_size, -1, self.n_classes)
这个过程的可视化如图3-25所示。 图3-25 pool5层输出分类和回归结果3.4.3 小结
到此模型前向推理相关的内容就已经都预测完毕了。我们已经了解了模型的结构以及模型最终会输出什么结果。
下一小节我们将会学习和模型训练相关的内容看看如何通过定义损失函数和一些相关的训练技巧来让模型向着正确的方向学习从而预测出我们想要的结果。