Sparse R-CNN细节剖析

一:2D目标检测——问题探讨

当前目标检测已经有许多经典框架,大致可以分为三大类:Dense method、Dense-to-Sparse method和Sparse method,在Sparse R-CNN提出前,其实没有任何一个模型是真正的Sparse method,下面我来进行简单利弊分析:

  • Dense method
    到底什么是Dense,其实就是大量的prior candidates。对于yolo系列,设定了Dense个prior anchors。对于FCOS ,设定了Dense个prior reference points。这些one-stage模型以大量的先验candidates为基础,进行后面的offsets和cls_pred学习。优点很明显——快,但缺点同样很多,具体如下:

      一:大量hand-designed object candidates
      对于yolo,需要事先设定anchor的个数、大小、长宽比,这些超参数的人为设定不仅麻烦,更使得网络对这些超参数敏感,	
      不利于网络的检测效果。
      
      二:many-to-one label assignment
      同样用yolo举例,在网络训练过程中,需要对大量的anchor进行label分配,不仅麻烦,而且使得网络 
      sensitive to heuristic assign rules,换句话说就是靠直观感受去分配样本标签, 比如依靠iou阈值设定,
      似乎理所当然,但实际上无形中破坏了end-to-end的涵义。这些对网络都是不利的。
      
      三:依赖post-processing
      这种pipelines往往会预测出不少冗余、重叠、相似的检测框,所以需要进行NMS等后处理,大大影响效率和检测效果。
    
  • Dense-to-Sparse method
    比较典型的网络就是Faster R-CNN等two-stage网络了,它们利用SS、RPN等方法从Dense candidates中选择出a sparse set of candidates,虽然提高了准确度,但是上诉的几个问题依然存在,并且训练策略麻烦、推理速度慢。

  • Sparse method
    其实在Sparse R-CNN之前,传统DETR率先使用的Sparse思想,用a sparse set of queries,来访问global( Dense ) keys,也因此严格意义上,传统DETR更似Dense-to-Sparse method。后面Deformable DETR的提出,算是真正的Sparse method。那么Sparse method到底强在哪儿呢?如下:

      一:真正的end-to-end
      只需要随即初始化N个candidates(DETR中是query,Sparse R-CNN是N对proposal box和proposal feature),
      在训练中进行学习。而不再需要人为设定scale、ratio等超参数,意味着实现了真正“自由”的检测,这才是end-to-end
      的精髓所在。不仅摆脱了头疼的hand-designed过程,更是大大提高了检测效果。
      
      二:不再需要post-processing
      因为candidates少而精,而且由于算法本身的精妙(后面会讲),预测结果不会存在冗余、相近的情况,自然不需要
      NMS这种后处理操作,大大提高了检测效率。
    

二:Sparse细节探讨

在我看来,Sparse R-CNN的核心思路和DETR有异曲同工之妙,这是由Sparse思想决定的。那Sparse method的核心思想到底是什么呢?N set of candidates是如何学习更新的呢?

前方核能:

这里我就拿DETR进行讲解,首先query_embed其实就是coarse positions,等于N个query先在src上框出N个大致范围。接着通过tgt(起初是0,代表的是许多latent信息,在传统的DETR中代表的是每个query负责的内容、它们负责的内容和附近的一些feature的关系以及boxes的位置信息,很混乱)+ query_pos(在传统DETR中是query和query间的一个彼此位置信息,其实也可以理解为boxes的初始基准位置,是query_embed决定的,需要先给每个query一个大致的boxes位置,这样才方便后面学习到每个query负责的大致内容、基准boxes需要调节的模糊方向)查询feature map上的keys,来更新牛*的tgt。

传统的DETR在cross-attention时,没有考虑到q_content和q_pos应该分开查询,所以后面就有了conditional DETR的提出,将解耦后的q_content和q_pos拼接而不是直接相加,这样tgt就只包含content信息,而拼接的query_sine_embed并依据output微调后,就代表的boxes信息。两者解耦开始查询,大大提高了收敛速度和准确度。

在DAB-DETR后,就开始初始化reference_points(x, y, w, h),而不是初始化query_embed,更加光明正大的显式体现了DETR实质上就是基于初始化boxes进行的一系列操作。 每个query基于初始化的boxes,来生成query彼此的q_pos关系,并生成query_sine_embed(就是boxes的信息),后面将三者融合后,生成q来查询key,这样得到包含大量内容信息的output,利用它再更新reference_points,再重新生成全新的query_pos和query_sine_embed。可见,一切始于boxes!!!

三:Sparse R-CNN讲解

在这里插入图片描述

Sparse R-CNN中的核心就是先初始化了N组4维proposal boxes和256维proposal features。其实它们分别对应了DETR中的reference_points和tgt即q_content。

两者都是随即初始化的,源码中对proposal boxes的初始化是正好覆盖全部的src,当然论文中提到就算换成其他初始化方法,也有一定的鲁棒性,性能没什么影响。

重点是Dynamic Head,传入该模块前先对proposal features进行了self.attention。对N组proposal boxes分配到对应的feature level上,然后ROIAligen,得到7✖7的256维向量,也就是(B,100, 49,256)。然后利用(B, 100, 256)的proposal feature生成两个params矩阵(B, 100, 256, 64)(B, 100, 64, 256),对proposal features依次乘上,生成全新的(B, 100, 49, 256),然后flatten(1)并输入进Linear层转化为256维度,也就是(B, 100, 256)

其实有种cross-attention的感觉,或者更像是SAM-DETR或者Deformable DETR中的采样思想。但是不同的是,这里没有简单的将boxes内采样特征随便分配个weights就融合,而是经过了上面的两个filter,这样做可以对特征进行筛选。比较类似于AdaMixer中的ACM和ADM的融合特征方式,当然,还是有不太相同的,只是着力点一样,都是对已采样特征的更好的融合策略。

有了全新的(B, 100, 256)后,做一些residual、FFN等常规操作后,生成全新的proposal features,里面融合了大量latent信息。

全新的proposal features再经过cls层和bbox层,生成bboxes_deltas和class_logits,将bboxes_deltas加到原boxes上,生成全新的boxes输入进下一轮,并将它们存入intermediate列表中。

最后就是匈牙利匹配,对匹配上的query的预测结果进行set_loss计算,反向传递完成参数更新。

总体而言,和DETR的思路大同小异,只不过是CNN方式的实现。但是有个不同点,就是作者测试,Dynamic Head用query的方式,需要加入query_pos信息,否则效果很差。但是CNN的方式不需要以来pos信息 ,效果也很好。

时间有限,留个-----------------------------------------------------源码坑-------------------------------------------------------------------,需要源码讲解的小伙伴,可以评论区留言~


  至此我对Sparse R-CNN中重点流程与细节进行了深度讲解,同时对当下主流2D检测模型进行了利弊分析。希望对大家有所帮助,有不懂的地方或者建议,欢迎大家在下方留言评论。

我是努力在CV泥潭中摸爬滚打的江南咸鱼,我们一起努力,不留遗憾!

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LRP-Gamma是一种用于对卷积神经网络(CNN)进行可解释性分析的方法。下面是基于TensorFlow实现LRP-Gamma的流程和代码: 1. 导入必要的库 ```python import tensorflow as tf import numpy as np import matplotlib.pyplot as plt ``` 2. 定义模型 ```python # 定义模型 def cnn_model(x): # 第1个卷积层 conv1 = tf.layers.conv2d(x, filters=32, kernel_size=(3, 3), padding='same', activation=tf.nn.relu) # 第1个池化层 pool1 = tf.layers.max_pooling2d(conv1, pool_size=(2, 2), strides=(2, 2)) # 第2个卷积层 conv2 = tf.layers.conv2d(pool1, filters=64, kernel_size=(3, 3), padding='same', activation=tf.nn.relu) # 第2个池化层 pool2 = tf.layers.max_pooling2d(conv2, pool_size=(2, 2), strides=(2, 2)) # 将卷积层的输出转化为一维向量 flatten = tf.layers.flatten(pool2) # 第1个全连接层 fc1 = tf.layers.dense(flatten, units=128, activation=tf.nn.relu) # 第2个全连接层 fc2 = tf.layers.dense(fc1, units=10) return fc2 ``` 3. 加载数据集 ```python # 加载MNIST数据集 (x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data() # 将数据集转化为浮点数类型 x_train, x_test = x_train.astype(np.float32), x_test.astype(np.float32) # 将数据集归一化到0-1之间 x_train, x_test = x_train / 255.0, x_test / 255.0 ``` 4. 定义计算图 ```python # 定义计算图 tf.reset_default_graph() # 定义输入占位符 x = tf.placeholder(tf.float32, shape=(None, 28, 28, 1)) y = tf.placeholder(tf.int64, shape=(None,)) # 获得模型的输出 logits = cnn_model(x) # 计算损失函数 loss = tf.losses.sparse_softmax_cross_entropy(labels=y, logits=logits) # 定义优化器 optimizer = tf.train.AdamOptimizer(learning_rate=0.001) # 定义训练操作 train_op = optimizer.minimize(loss) # 定义准确率 accuracy = tf.reduce_mean(tf.cast(tf.equal(tf.argmax(logits, axis=1), y), tf.float32)) ``` 5. 训练模型 ```python # 定义训练参数 batch_size = 128 num_epochs = 5 num_batches = int(len(x_train) / batch_size) # 创建Session sess = tf.Session() sess.run(tf.global_variables_initializer()) # 开始训练模型 for epoch in range(num_epochs): for batch in range(num_batches): # 获得一个batch的数据 batch_x = x_train[batch * batch_size:(batch + 1) * batch_size] batch_y = y_train[batch * batch_size:(batch + 1) * batch_size] # 训练模型 _, loss_val, acc_val = sess.run([train_op, loss, accuracy], feed_dict={x: batch_x.reshape(-1, 28, 28, 1), y: batch_y}) print('Epoch: %d, Batch: %d/%d, Loss: %f, Accuracy: %f' % (epoch + 1, batch + 1, num_batches, loss_val, acc_val)) ``` 6. 进行LRP-Gamma分析 ```python # 选择一个样本 sample_idx = 100 sample_x = x_test[sample_idx].reshape(-1, 28, 28, 1) sample_y = y_test[sample_idx] # 获得模型的输出和预测结果 logits_val, pred_val = sess.run([logits, tf.argmax(logits, axis=1)], feed_dict={x: sample_x}) print('True Label:', sample_y) print('Predicted Label:', pred_val[0]) # 定义LRP-Gamma函数 def lrp_gamma(z, r): alpha = 1 eps = 1e-12 z_p = tf.where(z >= 0, z, tf.zeros_like(z)) s_p = tf.where(r > 0, tf.ones_like(r), alpha * tf.ones_like(r)) c_p = (z_p + eps) / (r + eps) return c_p * s_p * r # 定义输出层的LRP-Gamma值 R = tf.one_hot(pred_val, depth=10, on_value=1., off_value=0., dtype=tf.float32) * logits # 定义最后一层的LRP-Gamma值 R = lrp_gamma(logits, R) # 定义中间层的LRP-Gamma值 for layer_name in ['dense', 'conv2d']: layer = tf.get_default_graph().get_tensor_by_name('dense/BiasAdd:0') R = lrp_gamma(layer, R) # 定义输入层的LRP-Gamma值 R = tf.reshape(R, shape=(28, 28, -1)) R = tf.reduce_sum(R, axis=2) # 绘制LRP-Gamma图像 plt.imshow(sample_x.reshape(28, 28)) plt.imshow(R.eval(session=sess), cmap='jet', alpha=0.5) plt.colorbar() plt.show() ``` 以上是基于TensorFlow实现LRP-Gamma的流程和代码。请注意,实现LRP-Gamma需要根据具体的模型架构进行修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值