关于SSD训练时默认框输出层输出通道num_output的计算,

本文详细解析SSD训练过程中num_output参数的计算方法,包括位置预测与置信度输出通道的确定,以及默认框数量对计算的影响。特别关注小目标检测场景,通过实例解释如何避免常见错误。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于SSD训练时默认框输出层输出通道num_output的计算

申明,本博文是为解决以下两个问题而撰写。

Check failed: num_priors_ * num_loc_classes_ * 4 == bottom[0]->channels() (264348 vs. 88116) Number of priors must match number of location predictions.

Check failed: num_priors_ * num_classes_ == bottom[1]->channels() (132174 vs. 99406) Number of priors must match number of confidence predictions.

我们针对小目标检测时受到此方面的困扰,在阅读源码后解决。特写下此博客,以供参考。具体源码,请仔细阅读:src/caffe/layers/multibox_loss_layer.cpp

输出通道主要涉及默认框产生层的置信输出mbox_conf和位置输出mbox_conf。对应prototxt文件中的应该是以下部分,我们以conv4_3为例讲解。

layer {
  name: "conv4_3_norm_mbox_loc"
  type: "Convolution"
  bottom: "conv4_3_norm"
  top: "conv4_3_norm_mbox_loc"
  param {
    lr_mult: 1
    decay_mult: 1
  }
  param {
    lr_mult: 2
    decay_mult: 0
  }
  convolution_param {
    num_output: 16
    pad: 1
    kernel_size: 3
    stride: 1
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
      value: 0
    }
  }
}
以上就是conv4_3层的在train.prototxt中的内容。我们主要讲解num_output=16是怎么计算来的,在原始SSD中conv4_3产生的每个特征图的中心点产生4个默认框,具体默认框的产生数量及方式请参看博主博文: 点击打开链接

这四个默认仍框分别对应x1,y1,x2,y2四个点,所以呢在产生位置损失时就会产生四个loc损失,所以一个中心点的所产生的4个默认框就有4*4=16个位置信息需要输出,这就是16的来源。具体在multibox_loss_layer.cpp中的定义是:

template <typename Dtype>
void MultiBoxLossLayer<Dtype>::Reshape(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top) {
  LossLayer<Dtype>::Reshape(bottom, top);
  num_ = bottom[0]->num();
  num_priors_ = bottom[2]->height() / 4;
  num_gt_ = bottom[3]->height();
  CHECK_EQ(bottom[0]->num(), bottom[1]->num());
  CHECK_EQ(num_priors_ * loc_classes_ * 4, bottom[0]->channels())    //num_priors_就是指每个中心点产生的默认框个数,位置个数计算。
      << "Number of priors must match number of location predictions.";
  CHECK_EQ(num_priors_ * num_classes_, bottom[1]->channels())    //置信个数计算。
      << "Number of priors must match number of confidence predictions.";
}
其他5个默认框提取特征层同样的计算方法。

对于置信输出通道,其在train.prototxt中的内容为:

layer {
  name: "conv4_3_norm_mbox_conf"
  type: "Convolution"
  bottom: "conv4_3_norm"
  top: "conv4_3_norm_mbox_conf"
  param {
    lr_mult: 1
    decay_mult: 1
  }
  param {
    lr_mult: 2
    decay_mult: 0
  }
  convolution_param {
    num_output: 64
    pad: 1
    kernel_size: 3
    stride: 1
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
      value: 0
    }
  }
}
置信conf的输出计算方法不同与位置loc,一个中心点会产生一个默认框,但是这个默认框到底是正样本还是负样本,这就涉及到正负的两个置信,如果是负的,那它就是背景。所以这需要看你的类别,在原始SSD检测VOC时,类别数为21,所以这里的num_output应该=(你的类别数+1)*中心点产生的默认框的个数,这里为21*4=64。它在multibox_loss_layer.cpp中的定义也在上面我们复制出来的代码中。

其他5个提取层也是如此计算。

注意的一点是以上是针对conv4_3的计算,而fc7、conv6_2、conv7_2,原始的SSD框架中这三层的每个中心点产生了6个默认框,例如fc7,他在train.prototxt中的内容是:

layer {
  name: "fc7_mbox_loc"
  type: "Convolution"
  bottom: "fc7"
  top: "fc7_mbox_loc"
  param {
    lr_mult: 1
    decay_mult: 1
  }
  param {
    lr_mult: 2
    decay_mult: 0
  }
  convolution_param {
    num_output: 24
    pad: 1
    kernel_size: 3
    stride: 1
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
      value: 0
    }
  }
}

这里我们只列举了位置loc,计算为4*6=24。置信输出就应该是21*6=126了,大家应该知道怎么计算了吧。

另外我们着重强调一点,关于flip参数的设置,这在我的博文关于ssd_pascal.py解读中有讲解:点击打开链接

他是设置翻转的,我们在关于默认框的产生一文中讲到,ssd默认产生一大一小两个正方形默认框,另外,每设置一个aspect_ratio则增加两个长方形默认框,然而在prior_box_layer.cpp文件中只有产生一个长方形默认框的计算公式如下:

        // rest of priors
        for (int r = 0; r < aspect_ratios_.size(); ++r) {
          float ar = aspect_ratios_[r];
          if (fabs(ar - 1.) < 1e-6) {
            continue;
          }
          box_width = min_size_ * sqrt(ar);
          box_height = min_size_ / sqrt(ar);
          // xmin
          top_data[idx++] = (center_x - box_width / 2.) / img_width;
          // ymin
          top_data[idx++] = (center_y - box_height / 2.) / img_height;
          // xmax
          top_data[idx++] = (center_x + box_width / 2.) / img_width;
          // ymax
          top_data[idx++] = (center_y + box_height / 2.) / img_height;
        }
比如我们在conv4_3计算出的min_size=30,max_size=60,而该层的aspect_ratio=2,那么产生四个默认框,两个正方形,两个长方形。这里只计算了一个长方形框的大小,最后计算的纵横比为1:2,那么2:1的纵横比长方形哪里去了呢?就是靠我们的flip来计算了,当我们设置flip=True时一个aspect_ratio才会产生两个默认框,如果不设置或者为Flase那么就只产生一个长方形默认框。比如conv4_3产生的默认框在train.prototxt中的内容如下:

layer {
  name: "conv4_3_norm_mbox_priorbox"   //注意在卷积层conv4_3后有一个norm层做归一化。
  type: "PriorBox"
  bottom: "conv4_3_norm"
  bottom: "data"
  top: "conv4_3_norm_mbox_priorbox"
  prior_box_param {
    min_size: 32
    aspect_ratio: 2
    flip: true   //注意,如果没有flip参数,则aspect_ratio=2只能产生一个纵横比为1:2的默认框。
    clip: false
    variance: 0.1
    variance: 0.1
    variance: 0.2
    variance: 0.2
    step: 8
    offset: 0.5
  }
}
当然,这里是我们项目中的设置,我们这里只设置了min_size,因为我们只需要一个较小的正方形边框就可以了,并不需要较大的正方形边框,所以我们没有设置max_size参数,故每个默认框产生特征层只生成一个边长为min_size的正方形默认框,剔除边长为sqrt(min_size*max_size)的默认框。这里我们设置了一个aspect_ratio=2,所以每个中心点产生3个默认框。这里flip我吃了很大的亏。一直在报错。


最后以上面的讲解延伸到我们最近研究的小人脸检测建构SFD,他最低层的默认框提取层是conv3_3,而并不是conv4_4,所以其在conv3_3后面也做了norm操作,另外他在conv3_3后面还添加了一个slice层,注意只在conv3_3后面添加,它在train.prototxt中的内容为:

layer {
  name: "conv3_3_norm_mbox_conf"
  type: "Convolution"
  bottom: "conv3_3_norm"
  top: "conv3_3_norm_mbox_conf"
  param {
    lr_mult: 1
    decay_mult: 1
  }
  param {
    lr_mult: 2
    decay_mult: 0
  }
  convolution_param {
    num_output: 8  //这里=类别数*该层每个中心点产生的默认框个数+2,这个2是由于以下添加的slice层的作用导致的。其他的如4_3、5_3、fc7、6_2、7_2这几层没有加slice层的则不需要+2。
    pad: 1
    kernel_size: 3
    stride: 1
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
      value: 0
    }
  }
}
layer {
  name: "conv3_3_norm_mbox_conf_slice"
  type: "Slice"
  bottom: "conv3_3_norm_mbox_conf"
  top: "conv3_3_norm_mbox_conf1"
  top: "conv3_3_norm_mbox_conf2"
  top: "conv3_3_norm_mbox_conf3"
  top: "conv3_3_norm_mbox_conf4"
  slice_param {
    axis: 1
    slice_point: 1
    slice_point: 2
    slice_point: 3
  }
}
layer {
  name: "conv3_3_norm_mbox_conf_maxout"
  type: "Eltwise"
  bottom: "conv3_3_norm_mbox_conf1"
  bottom: "conv3_3_norm_mbox_conf2"
  bottom: "conv3_3_norm_mbox_conf3"
  top: "conv3_3_norm_mbox_conf_maxout"
  eltwise_param {
    operation: MAX
  }
}
layer {
  name: "conv3_3_norm_mbox_conf_out"
  type: "Concat"
  bottom: "conv3_3_norm_mbox_conf_maxout"
  bottom: "conv3_3_norm_mbox_conf4"
  top: "conv3_3_norm_mbox_conf_out"
  concat_param {
    axis: 1
  }
}
该层的具体作用,博主还没有做仔细的解读,但是其中slice层导致了conv3_3的置信conf输出发生了变化,要在我们前面讲的基础上加上2,即我们在以上代码中所注释的部分。

最后给大家附上有置信conf和位置loc输出通道设置错误引起的error:

1.由置信引起的:

Check failed: num_priors_ * num_classes_ == bottom[1]->channels() (132174 vs. 99406) Number of priors must match number of confidence predictions.

其中括号里的数字不必去纠结,这和你的数据有关。

2.由位置引起的:

Check failed: num_priors_ * num_loc_classes_ * 4 == bottom[0]->channels() (264348 vs. 88116) Number of priors must match number of location predictions.

博主水平有限,如有错误请多多指正,转载请注明地址。谢谢!

### 如何训练SSD深度学习模型 #### 训练SSD模型的基础概念 SSD(Single Shot MultiBox Detector)是一种用于目标检测的高效算法,它通过单次前向传播完成边界预测和类别分类的任务。为了实现这一过程,通常会利用卷积神经网络提取特征,并结合锚机制来定位物体。 在TensorFlow或PyTorch中训练SSD模型的过程可以分为以下几个方面: #### 数据准备 数据集的选择对于模型性能至关重要。常用的目标检测数据集包括COCO、VOC等。这些数据集中包含了标注好的图像及其对应的边界位置和类别标签。加载数据需将其转换为适合输入到SSD模型的形式。例如,在PyTorch中可以通过自定义Dataset类来读取图片文件并返回相应的张量形式的数据[^1]。 #### 模型构建 ##### TensorFlow中的实现方式 在TensorFlow中,可以借助Keras API快速搭建起SSD结构。首先定义骨干网络(Backbone Network),比如ResNet或者MobileNet作为特征提取器;接着添加额外以生成不同尺度上的候选区域;最后连接回归分支与分类分支完成最终输出的设计[^2]。 以下是使用TensorFlow Keras创建简单版本SSD的一个片段示例: ```python from tensorflow.keras import layers, models def create_ssd_model(input_shape=(300, 300, 3)): base_model = tf.keras.applications.MobileNetV2(include_top=False, weights='imagenet', input_tensor=None, pooling=max) x = base_model.output # Add additional convolutional layers here... model = models.Model(inputs=base_model.input, outputs=x) return model ``` ##### PyTorch中的实现方法 而在PyTorch里,则更倾向于手动编写每一部分的具体操作流程。同样地,也需要指定好基础网络之后再依次堆叠其他组件直至形成完整的体系架构。 下面给出一段关于如何初始化一个基本版SSD实例的部分代码展示: ```python import torch.nn as nn class SSD(nn.Module): def __init__(self, num_classes): super(SSD, self).__init__() self.vgg_base = VGGBase() ... def forward(self, images): features_maps = [] locs_preds = [] cls_scores = [] # Pass through the network and collect predictions from each feature map level. ... model = SSD(num_classes=len(classes)) if use_cuda: model.cuda() ``` #### 损失函数设计 考虑到目标检测任务既涉及坐标估计又包含种类判断两部分内容,所以一般采用复合损失函数组合而成。具体来说就是将Smooth L1 Loss作用于位置偏移项之上再加上Cross Entropy Loss衡量置信度得分情况得到总的结果表达式[^3]。 #### 开始训练 当上述准备工作全部完成后就可以正式进入优化阶段啦!这里建议选用SGD(随机梯度下降法)配合momentum参数调节速度方向变化规律从而加快收敛进程同防止陷入局部最优解陷阱之中[^4]。 另外值得注意的是,在实际工程实践中往往还需要考虑很多细节问题,像是否启用混合精度加速技术?要不要做分布式计算提高效率呢等等因素都会影响到最后的效果表现哦! ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值