Detect-and-Track论文:3D Mask R-CNN Caffe2源代码解析——2.ResNet18_3D解析

2023-05-16

在上一篇博文中,我们对Detect-and-Track论文源码中模型构建部分进行了代码梳理,此篇博文我们对其采用的主干网络ResNet18进行详细分析。

参考内容链接如下:

Detect-and-Track论文:【网页链接】

Detect-and-Track源代码:【网页链接】


目录

 

一、ResNet简单回顾

二、3D Mask R-CNN代码实现(/lib/modeling/ResNet3D.py)

三、一些疑问


一、ResNet简单回顾

直观来讲,ResNet实现了深层神经网络,就代码应用而言,我们首先要掌握两幅图。首先是ResNet的两种block:building block和“bottleneck” building block:

左侧的block:64通道的feature map经过3*3卷积后,通道依然是64,再通过Relu函数。再进行一次64—>64的3*3卷积。此时加上64个通道的“Shortcut”,再过Relu函数。

右侧的bottleneck:256通道的feature map经过1*1卷积后,通道数变为64(不明白具体操作步骤的建议看此篇博文:【彻底掌握卷积计算】),再过Relu函数。进行64—>64的3*3卷积,过Relu函数。再通过1*1卷积由64通道变为256通道。与Shortcut求和后通过Relu函数。

可以看到,bottleneck的通道数发生了:256—64—64—256的变化,其目的是降低网络的参数。掌握了这两个block,我们可以观察ResNet论文中搭出来的几个ImageNet框架:

以18-layer为例:

Conv1:用(7*7,64通道,2步长)对图片进行卷积,得到112*112*64的输出。

Conv2_x:先进行3*3最大池化。其中的x指的是block过程,在这里是2,意思是有两个block过程:Conv2_1和Conv2_2。这两个过程内容相同,进行3*3的64通道卷积。

Conv3_x、Conv4_x、Conv5_x:与第二阶段相同,用简单的buliding bock搭建两阶段网络。可以观察到feature map越来越小,通道数越来越多(特征图越来越厚)。

Fc:经过平均池化后,由全连接层映射到1000维,再由softmax转化为概率,进行分类。

而后三列与前三列相似,不过是将block换为了bottleneck,还增加了bottleneck数量。以50-layer中的Conv2_x为例,其中包含3个bottleneck,每个bottleneck内部都进行了256—64—64—256的通道变化。

最后一行展现了FLOPs(每秒浮点运算次数)。可以发现由于bottleneck的存在,50层和34层的参数相差无几。


二、3D Mask R-CNN代码实现(/lib/modeling/ResNet3D.py)

代码中,ResNet18里所有的buliding bock被替换为了bottleneck bock,这点与ResNet论文中原结构不同。同时扩展为了3D版本:ResNet3D。

在 1.模型构建梳理 中的build_generic_fast_rcnn_model()第28行,调用了ResNet函数:

blob_conv, dim_conv, spatial_scale_conv = add_conv_body_func(model)	
# ResNet3D.add_ResNet18_conv4_body

现在开始分析ResNet构建过程,首先看ResNet18主体函数:

def add_ResNet18_conv4_body(model):
	cfg.RESNETS.TRANS_FUNC = 'basic_transformation'	# 基础变换
	return add_ResNet_convX_body(
	model, 	
	block_counts=(2, 2, 2), 	# bottleneck block个数的分配(与ResNet论文中18层构建参数一致)
	freeze_at=2, 	# 在第二层进行梯度停传(为啥要弄这个?)
	feat_dims=(64, 64, 128, 256)	# feature map的通道数
	)

定位到add_ResNet_convX_body函数,其实这个conv后面的X可以选为4和5,即最后一个阶段conv5可以省去。这里用的conv4。

def add_ResNet_convX_body(model, block_counts, freeze_at=2, feat_dims=(64, 256, 512, 1024, 2048)):
	# (model, block_counts=(2, 2, 2), freeze_at=2, feat_dims=(64, 64, 128, 256))
	"""
	Adds a ResNet body from input data up through the res5 (aka conv5) stage.
	The final res5/conv5 stage may be optionally excluded (hence convX, whereX = 4 or 5).
	"""
	assert freeze_at in [0, 2, 3, 4, 5]
	
	# No striding or temporal convolutions at layer 1
	# 在第一层没有跨步或时序的变换(卷积、仿射变换、激活函数、最大池化)
	p = model.ConvNd('data', 'conv1', 3, feat_dims[0], [1, 7, 7], pads=2 * [0, 3, 3], strides=[1, 2, 2], no_bias=1)
	# 标准的2D卷积:p = model.Conv('data', 'conv1', 3, feat_dims[0], 7, pad=3, stride=2, no_bias=1)
	# 可以看到,卷积变为了N维、输入通道数是3,输出通道是64,没有偏移
	# 不同的是卷积核、填充方法、步长
	
	p = model.AffineChannelNd(blob_in=p, blob_out='res_conv1_bn', dim_out=feat_dims[0], inplace=True)	# 仿射变换
	p = model.Relu(p, p)
	p = model.MaxPool(p, 'pool1', kernels=[1, 3, 3],pads=2 * [0, 1, 1], strides=[1, 2, 2])
	dim_in = feat_dims[0]	# 喂入数据的第一个维度 64
	dim_bottleneck = cfg.RESNETS.NUM_GROUPS * cfg.RESNETS.WIDTH_PER_GROUP	# 维度瓶颈1*64
	(n1, n2, n3) = block_counts[:3]	# 每个Stage含有多少个block;这里都是两个。
	
	# 添加4个Stage,每个Stage中有2个sub_stage,每个sub_stage含有一个瓶颈块
	s, dim_in = add_stage(1, model, prefix='res2', p, n1, dim_in, feat_dims[1], dim_bottleneck, 1,time_kernel_dim=1, time_stride_on=False)
	
	if freeze_at == 2:	# 在第二个Conv层停止梯度传播,为什么要有这个步骤????
		model.StopGradient(s, s)
	s, dim_in = add_stage(2, model, prefix='res3', s, n2, dim_in, feat_dims[2], dim_bottleneck * 2, 1,time_kernel_dim=cfg.VIDEO.TIME_KERNEL_DIM.BODY,time_stride_on=cfg.VIDEO.TIME_STRIDE_ON)
	
	if freeze_at == 3:
		model.StopGradient(s, s)
	s, dim_in = add_stage(3, model, prefix='res4', s, n3, dim_in, feat_dims[3], dim_bottleneck * 4, 1,time_kernel_dim=cfg.VIDEO.TIME_KERNEL_DIM.BODY,time_stride_on=cfg.VIDEO.TIME_STRIDE_ON)
	
	if freeze_at == 4:
		model.StopGradient(s, s)
	
	if len(block_counts) == 4:	# block_counts=(2,2,2),所以没有这个阶段
		n4 = block_counts[3]
		s, dim_in = add_stage(4, model, 'res5', s, n4, dim_in, feat_dims[4], dim_bottleneck * 8,cfg.MODEL.DILATION,time_kernel_dim=cfg.VIDEO.TIME_KERNEL_DIM.BODY,time_stride_on=cfg.VIDEO.TIME_STRIDE_ON)
		if freeze_at == 5:
			model.StopGradient(s, s)
		return s, dim_in, 1. / 32. * cfg.MODEL.DILATION	# 这个是1
		
	else:
		return s, dim_in, 1. / 16.

在第二个阶段停止了梯度传播,至今不知道为什么……。此函数中最重要的就是add_stage函数了,这个函数直接为网络添加bottleneck block。再定位到add_stage函数,此函数用了一个循环来创建block,循环次数是用n来决定的,n都是2。

def add_stage(stage_id, model, prefix, blob_in, n, dim_in, dim_out, dim_inner, dilation, stride_init=2, time_kernel_dim=1, time_stride_on=False):
	# add_stage(1, model, 'res2', p, n1, dim_in, feat_dims[1], dim_bottleneck, 1,time_kernel_dim=1, time_stride_on=False)
	
	"""Adds a single ResNet stage by stacking n bottleneck blocks."""
	"""通过添加n个瓶颈块来构建ResNet的阶段"""
	# e.g., prefix = res2
	for i in range(n):
		blob_in = add_bottleneck_block(
			stage_id,
			model, 
			'{}_{}'.format(prefix, i),	# prefix = res<stage>_<sub_stage>, e.g., res2_3
			blob_in, dim_in, dim_out, dim_inner,
			dilation, stride_init,
			# Not using inplace for the last block;
			# it may be fetched externally or used by FPN
			inplace_sum=i < n - 1,
			time_kernel_dim=time_kernel_dim,
			time_stride_on=time_stride_on)
		dim_in = dim_out
	return blob_in, dim_in

add_stage 函数创建了每一个Stage,每个Stage里包含了两个block。具体的block是通过add_bottleneck_block()构建的。

def add_bottleneck_block(
		stage_id,
		model, prefix, blob_in, dim_in, dim_out, dim_inner, dilation,
		stride_init=2, inplace_sum=False,
		time_kernel_dim=1, time_stride_on=False):
	"""添加一个”瓶颈“模块,旨在减少参数"""

	# prefix = res<stage>_<sub_stage>, e.g., res2_3

	# Max pooling is performed prior to the first stage (which is uniquely
	# distinguished by dim_in = 64), thus we keep stride = 1 for the first stage
	# stride = stride_init if (
	#     dim_in != dim_out and dim_in != 64 and dilation == 1) else 1
	# rgirdhar: The above didn't work for R-18/34 case where the first two
	# stages have 64-D outputs. So, using an explicit stage_id instead.
	stride = stride_init if (
		dim_in != dim_out and stage_id != 1 and dilation == 1) else 1

	# transformation blob
	tr = globals()[cfg.RESNETS.TRANS_FUNC](		# b'bottleneck_transformation'
		model, blob_in, dim_in, dim_out, stride, prefix, dim_inner,
		group=cfg.RESNETS.NUM_GROUPS, dilation=dilation,
		time_kernel_dim=time_kernel_dim, time_stride_on=time_stride_on)

	# sum -> ReLU
	# 在这里进行了shortcut求和
	sc = add_shortcut(model, prefix, blob_in, dim_in, dim_out, stride, time_stride_on=time_stride_on)
	if inplace_sum:
		s = model.net.Sum([tr, sc], tr)
	else:
		s = model.net.Sum([tr, sc], prefix + '_sum')

	return model.Relu(s, s)

此函数使用globals()的方法调用了bottleneck_transformation()函数来构建一个block,然后将block的卷积路径结果与shortcut结果求和。定位到bottleneck_transformation():

def bottleneck_transformation(
		model, blob_in, dim_in, dim_out,
		stride, prefix, dim_inner, dilation=1, group=1,
		time_kernel_dim=1, time_stride_on=False):
	"""
	In original resnet, stride=2 is on 1x1.
	In fb.torch resnet, stride=2 is on 3x3.
	"""
	(str1x1, str3x3) = (stride, 1) if cfg.RESNETS.STRIDE_1X1 else (1, stride)

	# conv 1x1 -> BN -> ReLU
	cur = model.ConvAffineNd(
		blob_in, prefix + '_branch2a', dim_in, dim_inner, kernels=[1, 1, 1],
		strides=[
			str1x1 if time_stride_on else 1, str1x1, str1x1],
		pads=2 * [0, 0, 0], inplace=True)
	cur = model.Relu(cur, cur)

	# conv 3x3 -> BN -> ReLU
	cur = model.ConvAffineNd(
		cur, prefix + '_branch2b', dim_inner, dim_inner,
		kernels=[time_kernel_dim, 3, 3],
		strides=[
			str3x3 if time_stride_on else 1, str3x3, str3x3],
		pads=2 * [time_kernel_dim // 2, dilation, dilation],
		dilations=[1, dilation, dilation], group=group, inplace=True)
	cur = model.Relu(cur, cur)

	# conv 1x1 -> BN (no ReLU)
	# NB: for now this AffineChannel op cannot be in-place due to a bug in C2
	# gradient computation for graphs like this
	cur = model.ConvAffineNd(
		cur, prefix + '_branch2c', dim_inner, dim_out, kernels=[1, 1, 1],
		strides=[1, 1, 1], pads=2 * [0, 0, 0], inplace=False)
	return cur

哈哈哈,我们终于找到了最最核心、最最基本的bottleneck核心。通过它自己的注释我们就可以发现:conv 1x1、conv 3x3、conv 1x1这样的基本操作。

当然还有一个shortcut的函数:

def add_shortcut(model, prefix, blob_in, dim_in, dim_out, stride, time_stride_on):
	"""添加直通路径"""
	if dim_in == dim_out:	# 如果输入、输出维度相同,则直接输出blob
		return blob_in
	c = model.ConvNd(	# 如果不同,则需要进行卷积变化
		blob_in, 
		prefix + '_branch1',
		dim_in, dim_out,
		[1, 1, 1],
		strides=[stride if time_stride_on else 1, stride, stride],
		pads=2 * [0, 0, 0],
		no_bias=1)
	return model.AffineChannelNd(c, prefix + '_branch1_bn', dim_out=dim_out)

至此,3D版本的ResNet18顺藤摸瓜完毕,整个构建过程一环套一环,思路总算清晰了。


三、一些疑问

1. 为什么要将第二阶段的梯度停掉?

2. 3D卷积部分的张量是怎么样变换的?

欢迎讨论!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Detect-and-Track论文:3D Mask R-CNN Caffe2源代码解析——2.ResNet18_3D解析 的相关文章

  • Tensorflow笔记3:Variable

    调用Variable类即可向Graph中添加变量 Variable在创建之后需要给定初始值 xff0c 可以是任意type shape的Tensor 一旦使用初始值完成了初始化 xff0c type和shape都固定 xff0c 除非使用a

随机推荐

  • TensorFlow-Slim API 官方教程

    https blog csdn net u014061630 article details 80632736 TF Slim 模块是 TensorFlow 中最好用的 API 之一 尤其是里面引入的 arg scope model var
  • Tensorflow笔记4:Saver

    Saver类位于tf train中 xff0c 属于训练过程中要用到的方法 xff0c 主要作用就是保存和加载save amp restore ckpt 最简单的保存应用举例 xff1a saver span class token pun
  • Tensorflow笔记4:学习率衰减策略tf.train.cosine_decay_restarts

    TF在learning rate decay py中提供了几种非常骚气的学习率下降方法 xff0c 今天就来玩一玩 只需要简单的参数设定 xff0c 就能够产生神奇的lr衰减效果 首先简介lr的一般使用方法 xff1a lr span cl
  • ffmpeg使用笔记

    视频压缩 ffmpeg i lt input gt vcodec libx264 crf 25 lt output gt 分辨率调整 ffmpeg i 123 mp4 s 960x540 1 mp4 视频截取 截取 xff08 t1 43
  • Pytorch 入门

    1 加载模型 seg model 61 torchvision models detection maskrcnn resnet50 fpn pretrained 61 True seg model 61 seg model cuda 1
  • 小白之通俗易懂的贝叶斯定理

    原文链接 xff1a https zhuanlan zhihu com p 37768413 概率论与数理统计 xff0c 在生活中实在是太有用了 xff0c 但由于大学课堂理解不够深入 xff0c 不能很好地将这些理论具象化并应用到实际生
  • tf.variable_scope中的reuse

    一 两种scope xff1a variable与name tf variable scope 是对变量进行命名管理 xff0c 而tf name scope 是对算子 op 进行命名管理 xff0c 二者相互不影响 见下例 xff1a i
  • HTML+CSS 简易搜索框

    搜索框是页面中很常见的一种 xff0c 下面分享一段简单搜索框案例 xff0c 直接上代码 HTML部分 lt DOCTYPE html gt lt html gt lt head gt lt meta charset 61 34 UTF
  • frp内网穿刺/反向代理教程

    文章目录 前言一 明确基本概念二 frp下载与使用1 云服务器做为Server端2 GPU服务器做为Client端3 远程访问 三 云服务器防火墙端口开启 前言 frp 是一个高性能的反向代理应用 xff0c 可以帮助开发者轻松地进行内网穿
  • Keras模型基本流程

    文章目录 前言一 Keras流程二 Keras中的模型定义1 基于Sequential 类2 基于函数式API 前言 目前打算看tensorflow probability模块 xff0c 但是发现整个都是适配Keras的 xff0c 很多
  • pytorch自动混合精度训练

    from torch cuda amp import autocast GradScaler Init Step 1 Create Model model device start epoch 61 create model opt if
  • Linux系统查看CPU个数&超线程&线程数

    小命令 xff1a Linux查看CPU详细信息 简书 jianshu com Intel CPU产品规范 xff1a 英特尔 产品 xff1a 处理器 xff0c 英特尔 NUC 迷你电脑 xff0c 内存和存储 xff0c 芯片组 in
  • Tensorflow学习笔记(1)——Tensorflow-CPU版安装躺坑实录

    从今天开始就要出品自己的Tensorflow系列啦 xff01 由于是第一篇关于Python的博文 xff0c 所以先把接触Python以来的感想和心得都放了进来 xff0c 想看Tensorflow的博友请直接跳到标题三 一 Python
  • Tensorflow学习笔记(3)——图、会话基本语法

    前言 xff1a Tensorflow xff0c 与我们之前熟悉的C C 43 43 Python的确不太一样 xff0c 建议初学者当作一门新的语言来学 如果看的过程中忽然有一种 的确不太一样 的感觉 xff0c 并且明白了哪里不一样
  • Tensorflow学习笔记(2)——Tensorflow-GPU版安装总结

    经历了N多次的失败 xff0c TF GPU的环境终于搭建好了 xff0c 全程连着弄了三天 现在把安装中的注意事项总结一下 xff0c 希望大家少走弯路 xff01 先把自己的配置列表说一下 xff1a Windows7 x64 VS20
  • 深度学习基础——彻底掌握卷积层的计算

    机器学习也是刚刚入门 xff0c 虽然对卷积 池化等过程很熟悉 xff0c 但是一直不太清楚具体的计算 xff0c 幸好在博文上看到了讲解过程 xff0c 看完以后受益匪浅 xff0c 然后自己又重新写了一遍 有这个表格 xff0c 再也不
  • Mask R-CNN 简介与论文笔记

    本篇文章是面向Mask R CNN的初学者 xff0c 旨在梳理大体思路 xff0c 认识框架基础概念 由于自己现在大四 xff0c 也是刚刚入足深度学习领域 xff0c 所以文章中免不了有错误之处 xff0c 欢迎各位博友批评指正 本博文
  • Detect-and-Track论文:3D Mask R-CNN Caffe2源代码解析——1.模型构建梳理

    这篇博文的内容是对Detect and Track的源代码进行解析 xff0c 由于是第一篇 xff0c 所以展示的是代码的脉络 xff0c 以及如何寻找到3D Mask R CNN的构建过程 博文的目录是按照文件来的 xff1a 参考内容
  • INRIA数据集

    INRIA数据集 HOG 43 SVM是经典的行人检测方法 xff0c 论文作者同时搞了一个INRIA数据集 INRIA数据集官方页面 xff1a http pascal inrialpes fr data human xff0c 不过说真
  • Detect-and-Track论文:3D Mask R-CNN Caffe2源代码解析——2.ResNet18_3D解析

    在上一篇博文中 xff0c 我们对Detect and Track论文源码中模型构建部分进行了代码梳理 xff0c 此篇博文我们对其采用的主干网络ResNet18进行详细分析 参考内容链接如下 xff1a Detect and Track论