+关注继续查看

# 1.ResNet

## 1.2. 为什么深度网络不仅仅是层数的堆叠？

### 1.2.1 梯度消失 or 爆炸

$$z_{i+1} = w_ia_i+b_i\\ a_{i+1} = \sigma(z_{i+1})$$

$$\frac{\partial y}{\partial a_1} = \frac{\partial y}{\partial a_4}\frac{\partial a_4}{\partial z_4}\frac{\partial z_4}{\partial a_3}\frac{\partial a_3}{\partial z_3}\frac{\partial z_3}{\partial a_2}\frac{\partial a_2}{\partial z_2}\frac{\partial z_2}{\partial a_1} \\ = \frac{\partial y}{\partial a_4}\sigma^{'}(z_4)w_3\sigma^{'}(z_3)w_2\sigma^{'}(z_2)w_1$$
Sigmoid 函数的导数 $\sigma^{'}(x)$ 如 图3 所示：

• 参考文献

# 2. ResNeXt（2017）

ResNeXt是由何凯明团队在2017年CVPR会议上提出来的新型图像分类网络。ResNeXt是ResNet的升级版，在ResNet的基础上，引入了cardinality的概念，类似于ResNet，ResNeXt也有ResNeXt-50，ResNeXt-101的版本。那么相较于ResNet，ResNeXt的创新点在哪里？既然是分类网络，那么在ImageNet数据集上的指标相较于ResNet有何变化？之后的ResNeXt_WSL又是什么东西？下面我和大家一起分享一下这些知识。

## 2.1 ResNeXt模型结构

ResNeXt正是借鉴了这种“分割-变换-聚合”的策略，但用相同的拓扑结构组建ResNeXt模块。每个结构都是相同的卷积核，保持了结构的简洁，使得模型在编程上更方便更容易，而InceptionNet则需要更为复杂的设计。

## 2.2 ResNeXt模型实现

class ConvBNLayer(nn.Layer):
def __init__(self, num_channels, num_filters, filter_size, stride=1,
groups=1, act=None, name=None, data_format="NCHW"
):
super(ConvBNLayer, self).__init__()
self._conv = Conv2D(
in_channels=num_channels, out_channels=num_filters,
kernel_size=filter_size, stride=stride,
padding=(filter_size - 1) // 2, groups=groups,
weight_attr=ParamAttr(name=name + "_weights"), bias_attr=False,
data_format=data_format
)
if name == "conv1":
bn_name = "bn_" + name
else:
bn_name = "bn" + name[3:]
self._batch_norm = BatchNorm(
num_filters, act=act, param_attr=ParamAttr(name=bn_name + '_scale'),
bias_attr=ParamAttr(bn_name + '_offset'), moving_mean_name=bn_name + '_mean',
moving_variance_name=bn_name + '_variance', data_layout=data_format
)

def forward(self, inputs):
y = self._conv(inputs)
y = self._batch_norm(y)
return y

class BottleneckBlock(nn.Layer):
def __init__(self, num_channels, num_filters, stride, cardinality, shortcut=True,
name=None, data_format="NCHW"
):
super(BottleneckBlock, self).__init__()
self.conv0 = ConvBNLayer(num_channels=num_channels, num_filters=num_filters,
filter_size=1, act='relu', name=name + "_branch2a",
data_format=data_format
)
self.conv1 = ConvBNLayer(
num_channels=num_filters, num_filters=num_filters,
filter_size=3, groups=cardinality,
stride=stride, act='relu', name=name + "_branch2b",
data_format=data_format
)

self.conv2 = ConvBNLayer(
num_channels=num_filters,
num_filters=num_filters * 2 if cardinality == 32 else num_filters,
filter_size=1, act=None,
name=name + "_branch2c",
data_format=data_format
)

if not shortcut:
self.short = ConvBNLayer(
num_channels=num_channels, num_filters=num_filters * 2
if cardinality == 32 else num_filters,
filter_size=1, stride=stride,
name=name + "_branch1", data_format=data_format
)

self.shortcut = shortcut

def forward(self, inputs):
y = self.conv0(inputs)
conv1 = self.conv1(y)
conv2 = self.conv2(conv1)

if self.shortcut:
short = inputs
else:
short = self.short(inputs)

y = F.relu(y)
return y


## 2.3 ResNeXt模型特点

1. ResNeXt通过控制cardinality的数量，使得ResNeXt的参数量和GFLOPs与ResNet几乎相同。
2. 通过cardinality的分支结构，为网络提供更多的非线性，从而获得更精确的分类效果。

## 2.4 ResNeXt模型指标

ResNeXt101_32×48d_WSL有8亿+的参数，是通过弱监督学习预训练的方法在Instagram数据集上训练，然后用ImageNet数据集做微调，Instagram有9.4亿张图片，没有经过特别的标注，只带着用户自己加的话题标签。
ResNeXt_WSL与ResNeXt是一样的结构，只是训练方式有所改变。下图是ResNeXt_WSL的训练效果。

ResNeXt

# 3.Res2Net（2020）

2020年，南开大学程明明组提出了一种面向目标检测任务的新模块Res2Net。并且其论文已被TPAMI2020录用。Res2Net和ResNeXt一样，是ResNet的变体形式，只不过Res2Net不止提高了分类任务的准确率，还提高了检测任务的精度。Res2Net的新模块可以和现有其他优秀模块轻松整合，在不增加计算负载量的情况下，在ImageNet、CIFAR-100等数据集上的测试性能超过了ResNet。因为模型的残差块里又有残差连接，所以取名为Res2Net。

## 3.2 Res2Net模型实现

class ConvBNLayer(nn.Layer):
def __init__(
self,
num_channels,
num_filters,
filter_size,
stride=1,
groups=1,
is_vd_mode=False,
act=None,
name=None, ):
super(ConvBNLayer, self).__init__()

self.is_vd_mode = is_vd_mode
self._pool2d_avg = AvgPool2D(
self._conv = Conv2D(
in_channels=num_channels,
out_channels=num_filters,
kernel_size=filter_size,
stride=stride,
groups=groups,
weight_attr=ParamAttr(name=name + "_weights"),
bias_attr=False)
if name == "conv1":
bn_name = "bn_" + name
else:
bn_name = "bn" + name[3:]
self._batch_norm = BatchNorm(
num_filters,
act=act,
param_attr=ParamAttr(name=bn_name + '_scale'),
bias_attr=ParamAttr(bn_name + '_offset'),
moving_mean_name=bn_name + '_mean',
moving_variance_name=bn_name + '_variance')

def forward(self, inputs):
if self.is_vd_mode:
inputs = self._pool2d_avg(inputs)
y = self._conv(inputs)
y = self._batch_norm(y)
return y

class BottleneckBlock(nn.Layer):
def __init__(self,
num_channels1,
num_channels2,
num_filters,
stride,
scales,
shortcut=True,
if_first=False,
name=None):
super(BottleneckBlock, self).__init__()
self.stride = stride
self.scales = scales
self.conv0 = ConvBNLayer(
num_channels=num_channels1,
num_filters=num_filters,
filter_size=1,
act='relu',
name=name + "_branch2a")
self.conv1_list = []
for s in range(scales - 1):
name + '_branch2b_' + str(s + 1),
ConvBNLayer(
num_channels=num_filters // scales,
num_filters=num_filters // scales,
filter_size=3,
stride=stride,
act='relu',
name=name + '_branch2b_' + str(s + 1)))
self.conv1_list.append(conv1)

self.conv2 = ConvBNLayer(
num_channels=num_filters,
num_filters=num_channels2,
filter_size=1,
act=None,
name=name + "_branch2c")

if not shortcut:
self.short = ConvBNLayer(
num_channels=num_channels1,
num_filters=num_channels2,
filter_size=1,
stride=1,
is_vd_mode=False if if_first else True,
name=name + "_branch1")

self.shortcut = shortcut

def forward(self, inputs):
y = self.conv0(inputs)
ys = []
for s, conv1 in enumerate(self.conv1_list):
if s == 0 or self.stride == 2:
ys.append(conv1(xs[s]))
else:
ys.append(conv1(xs[s] + ys[-1]))
if self.stride == 1:
ys.append(xs[-1])
else:
ys.append(self.pool2d_avg(xs[-1]))
conv2 = self.conv2(conv1)

if self.shortcut:
short = inputs
else:
short = self.short(inputs)
y = F.relu(y)
return y


## 3.3 模型特点

1. 可与其他结构整合，如SENEt， ResNeXt， DLA等，从而增加准确率。
2. 计算负载不增加，特征提取能力更强大。

## 3.4 模型指标

ImageNet分类效果如下图

Res2Net-50就是对标ResNet50的版本。

Res2Net-50-299指的是将输入图片裁剪到299×299进行预测的Res2Net-50，因为一般都是裁剪或者resize到224×224。

Res2NeXt-50为融合了ResNeXt的Res2Net-50。

Res2Net-DLA-60指的是融合了DLA-60的Res2Net-50。

Res2NeXt-DLA-60为融合了ResNeXt和DLA-60的Res2Net-50。

SE-Res2Net-50 为融合了SENet的Res2Net-50。

blRes2Net-50为融合了Big-Little Net的Res2Net-50。

Res2Net-v1b-50为采取和ResNet-vd-50一样的处理方法的Res2Net-50。

COCO数据集效果如下图

Res2Net-50的各种配置都比ResNet-50高。

ECSSD、PASCAL-S、DUT-OMRON、HKU-IS都是显著目标检测任务中现在最为常用的测试集，显著目标检测任务的目的就是分割出图片中的显著物体，并用白色像素点表示，其他背景用黑色像素点表示。从图中可以看出来，使用Res2Net作为骨干网络，效果比ResNet有了很大的提升。

# 4.Swin Trasnformer（2021）

Swin Transformer是由微软亚洲研究院在今年公布的一篇利用transformer架构处理计算机视觉任务的论文。Swin Transformer 在图像分类，图像分割，目标检测等各个领域已经屠榜，在论文中，作者分析表明，Transformer从NLP迁移到CV上没有大放异彩主要有两点原因：1. 两个领域涉及的scale不同，NLP的token是标准固定的大小，而CV的特征尺度变化范围非常大。2. CV比起NLP需要更大的分辨率，而且CV中使用Transformer的计算复杂度是图像尺度的平方，这会导致计算量过于庞大。为了解决这两个问题，Swin Transformer相比之前的ViT做了两个改进：1.引入CNN中常用的层次化构建方式构建层次化Transformer 2.引入locality思想，对无重合的window区域内进行self-attention计算。另外，Swin Transformer可以作为图像分类、目标检测和语义分割等任务的通用骨干网络，可以说，Swin Transformer可能是CNN的完美替代方案。

## 4.1 Swin Trasnformer模型结构

Linear embedding

class PatchEmbed(nn.Layer):
""" Image to Patch Embedding
Args:
img_size (int): Image size.  Default: 224.
patch_size (int): Patch token size. Default: 4.
in_chans (int): Number of input image channels. Default: 3.
embed_dim (int): Number of linear projection output channels. Default: 96.
norm_layer (nn.Layer, optional): Normalization layer. Default: None
"""

def __init__(self,
img_size=224,
patch_size=4,
in_chans=3,
embed_dim=96,
norm_layer=None):
super().__init__()
img_size = to_2tuple(img_size)
patch_size = to_2tuple(patch_size)
patches_resolution = [
img_size[0] // patch_size[0], img_size[1] // patch_size[1]
]
self.img_size = img_size
self.patch_size = patch_size
self.patches_resolution = patches_resolution
self.num_patches = patches_resolution[0] * patches_resolution[1] #patch个数

self.in_chans = in_chans
self.embed_dim = embed_dim

self.proj = nn.Conv2D(
in_chans, embed_dim, kernel_size=patch_size, stride=patch_size) #将stride和kernel_size设置为patch_size大小
if norm_layer is not None:
self.norm = norm_layer(embed_dim)
else:
self.norm = None

def forward(self, x):
B, C, H, W = x.shape

x = self.proj(x) # B, 96, H/4, W4

x = x.flatten(2).transpose([0, 2, 1])  # B Ph*Pw 96
if self.norm is not None:
x = self.norm(x)
return x


Patch Merging

class PatchMerging(nn.Layer):
r""" Patch Merging Layer.
Args:
input_resolution (tuple[int]): Resolution of input feature.
dim (int): Number of input channels.
norm_layer (nn.Layer, optional): Normalization layer.  Default: nn.LayerNorm
"""

def __init__(self, input_resolution, dim, norm_layer=nn.LayerNorm):
super().__init__()
self.input_resolution = input_resolution
self.dim = dim
self.reduction = nn.Linear(4 * dim, 2 * dim, bias_attr=False)
self.norm = norm_layer(4 * dim)

def forward(self, x):
"""
x: B, H*W, C
"""
H, W = self.input_resolution
B, L, C = x.shape
assert L == H * W, "input feature has wrong size"
assert H % 2 == 0 and W % 2 == 0, "x size ({}*{}) are not even.".format(
H, W)

x = x.reshape([B, H, W, C])
# 每次降采样是两倍，因此在行方向和列方向上，间隔2选取元素。
x0 = x[:, 0::2, 0::2, :]  # B H/2 W/2 C
x1 = x[:, 1::2, 0::2, :]  # B H/2 W/2 C
x2 = x[:, 0::2, 1::2, :]  # B H/2 W/2 C
x3 = x[:, 1::2, 1::2, :]  # B H/2 W/2 C
# 拼接在一起作为一整个张量，展开。通道维度会变成原先的4倍（因为H,W各缩小2倍）
x = paddle.concat([x0, x1, x2, x3], -1)  # B H/2 W/2 4*C
x = x.reshape([B, H * W // 4, 4 * C])  # B H/2*W/2 4*C

x = self.norm(x)
# 通过一个全连接层再调整通道维度为原来的两倍
x = self.reduction(x)

return x


Swin Transformer Block：

def window_partition(x, window_size):
"""
Args:
x: (B, H, W, C)
window_size (int): window size

Returns:
windows: (num_windows*B, window_size, window_size, C)
"""
B, H, W, C = x.shape
x = x.reshape([B, H // window_size, window_size, W // window_size, window_size, C])
windows = x.transpose([0, 1, 3, 2, 4, 5]).reshape([-1, window_size, window_size, C])
return windows

def window_reverse(windows, window_size, H, W):
"""
Args:
windows: (num_windows*B, window_size, window_size, C)
window_size (int): Window size
H (int): Height of image
W (int): Width of image

Returns:
x: (B, H, W, C)
"""
B = int(windows.shape[0] / (H * W / window_size / window_size))
x = windows.reshape([B, H // window_size, W // window_size, window_size, window_size, -1])
x = x.transpose([0, 1, 3, 2, 4, 5]).reshape([B, H, W, -1])
return x


Swin Transformer中重要的当然是Swin Transformer Block了，下面解释一下Swin Transformer Block的原理。

class Mlp(nn.Layer):
def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0.):
super().__init__()
out_features = out_features or in_features
hidden_features = hidden_features or in_features
self.fc1 = nn.Linear(in_features, hidden_features)
self.act = act_layer()
self.fc2 = nn.Linear(hidden_features, out_features)
self.drop = nn.Dropout(drop)

def forward(self, x):
x = self.fc1(x)
x = self.act(x)
x = self.drop(x)
x = self.fc2(x)
x = self.drop(x)
return x


class WindowAttention(nn.Layer):
""" Window based multi-head self attention (W-MSA) module with relative position bias.
It supports both of shifted and non-shifted window.

Args:
dim (int): Number of input channels.
window_size (tuple[int]): The height and width of the window.
qkv_bias (bool, optional):  If True, add a learnable bias to query, key, value. Default: True
qk_scale (float | None, optional): Override default qk scale of head_dim ** -0.5 if set
attn_drop (float, optional): Dropout ratio of attention weight. Default: 0.0
proj_drop (float, optional): Dropout ratio of output. Default: 0.0
"""

def __init__(self, dim, window_size, num_heads, qkv_bias=True, qk_scale=None, attn_drop=0., proj_drop=0.):

super().__init__()
self.dim = dim
self.window_size = window_size  # Wh, Ww
self.scale = qk_scale or head_dim ** -0.5

# define a parameter table of relative position bias
relative_position_bias_table = self.create_parameter(
shape=((2 * window_size[0] - 1) * (2 * window_size[1] - 1), num_heads), default_initializer=nn.initializer.Constant(value=0))  # 2*Wh-1 * 2*Ww-1, nH

# get pair-wise relative position index for each token inside the window
coords_flatten = paddle.flatten(coords, 1)                                     # 2, Wh*Ww
relative_coords = coords_flatten.unsqueeze(-1) - coords_flatten.unsqueeze(1)   # 2, Wh*Ww, Wh*Ww
relative_coords = relative_coords.transpose([1, 2, 0])                         # Wh*Ww, Wh*Ww, 2
relative_coords[:, :, 0] += self.window_size[0] - 1                            # shift to start from 0
relative_coords[:, :, 1] += self.window_size[1] - 1
relative_coords[:, :, 0] *= 2 * self.window_size[1] - 1
self.relative_position_index = relative_coords.sum(-1)                         # Wh*Ww, Wh*Ww
self.register_buffer("relative_position_index", self.relative_position_index)

self.qkv = nn.Linear(dim, dim * 3, bias_attr=qkv_bias)
self.attn_drop = nn.Dropout(attn_drop)
self.proj = nn.Linear(dim, dim)
self.proj_drop = nn.Dropout(proj_drop)

self.softmax = nn.Softmax(axis=-1)

"""
Args:
x: input features with shape of (num_windows*B, N, C)
"""
B_, N, C = x.shape
qkv = self.qkv(x).reshape([B_, N, 3, self.num_heads, C // self.num_heads]).transpose([2, 0, 3, 1, 4])
q, k, v = qkv[0], qkv[1], qkv[2]  # make torchscript happy (cannot use tensor as tuple)

q = q * self.scale
attn = q @ swapdim(k ,-2, -1)

self.relative_position_index.reshape((-1,)),axis=0).reshape((self.window_size[0] * self.window_size[1],self.window_size[0] * self.window_size[1], -1))

relative_position_bias = relative_position_bias.transpose([2, 0, 1])  # nH, Wh*Ww, Wh*Ww
attn = attn + relative_position_bias.unsqueeze(0)

attn = attn.reshape([-1, self.num_heads, N, N])
attn = self.softmax(attn)
else:
attn = self.softmax(attn)

attn = self.attn_drop(attn)

x = swapdim((attn @ v),1, 2).reshape([B_, N, C])
x = self.proj(x)
x = self.proj_drop(x)
return x


## 4.2 Swin Trasnformer模型实现

Swin Transformer涉及模型代码较多，所以建议完整的看Swin Transformer的代码，因此推荐一下桨的Swin Transformer实现。

## 4.3 Swin Trasnformer模型特点

1. 首次在cv领域的transformer模型中采用了分层结构。分层结构因为其不同大小的尺度，使不同层特征有了更加不同的意义，较浅层的特征具有大尺度和细节信息，较深层的特征具有小尺度和物体的整体轮廓信息，在图像分类领域，深层特征具有更加有用的作用，只需要根据这个信息判定物体的类别即可，但是在像素级的分割和检测任务中，则需要更为精细的细节信息，因此，分层结构的模型往往更适用于分割和检测这样的像素级要求的任务中。Swin Transformer 模仿ResNet采取了分层的结构，使其成为了cv领域的通用框架。

2. 引入locality思想，对无重合的window区域内进行self-attention计算。不仅减少了计算量，而且多了不同窗口之间的交互。

## 4.4 Swin Trasnformer模型效果

C就是上面提到的类似于通道数的值，layer numbers就是Swin Transformer Block的数量了。这两个都是值越大，效果越好。和ResNet十分相似。

# 5.ViT( Vision Transformer-2020)

• Transformer相较于CNN结构，缺少一定的平移不变性和局部感知性，因此在数据量不充分时，很难达到同等的效果。具体表现为使用中等规模的ImageNet训练的Transformer会比ResNet在精度上低几个百分点。
• 当有大量的训练样本时，结果则会发生改变。使用大规模数据集进行预训练后，再使用迁移学习的方式应用到其他数据集上，可以达到或超越当前的SOTA水平。

## 5.1 ViT模型结构与实现

ViT算法的整体结构如 图1 所示。

### 5.1.1. ViT图像分块嵌入

ViT中的具体实现方式为：将 $H \times W \times C$ 的图像，变为一个 $N \times (P^2 * C)$ 的序列。这个序列可以看作是一系列展平的图像块，也就是将图像切分成小块后，再将其展平。该序列中一共包含了 $N=HW/P^2$ 个图像块，每个图像块的维度则是 $(P^2*C)$。其中 $P$ 是图像块的大小，$C$ 是通道数量。经过如上变换，就可以将 $N$ 视为sequence的长度了。

#图像分块、Embedding
class PatchEmbed(nn.Layer):
def __init__(self, img_size=224, patch_size=16, in_chans=3, embed_dim=768):
super().__init__()
# 原始大小为int，转为tuple，即：img_size原始输入224，变换后为[224,224]
img_size = to_2tuple(img_size)
patch_size = to_2tuple(patch_size)
# 图像块的个数
num_patches = (img_size[1] // patch_size[1]) * \
(img_size[0] // patch_size[0])
self.img_size = img_size
self.patch_size = patch_size
self.num_patches = num_patches
# kernel_size=块大小，即每个块输出一个值，类似每个块展平后使用相同的全连接层进行处理
# 输入维度为3，输出维度为块向量长度
# 与原文中：分块、展平、全连接降维保持一致
# 输出为[B, C, H, W]
self.proj = nn.Conv2D(
in_chans, embed_dim, kernel_size=patch_size, stride=patch_size)

def forward(self, x):
B, C, H, W = x.shape
assert H == self.img_size[0] and W == self.img_size[1], \
"Input image size ({H}*{W}) doesn't match model ({self.img_size[0]}*{self.img_size[1]})."
# [B, C, H, W] -> [B, C, H*W] ->[B, H*W, C]
x = self.proj(x).flatten(2).transpose((0, 2, 1))
return x


### 5.1.2. ViT多头注意力

Transformer 结构中最重要的结构就是 Multi-head Attention，即多头注意力结构。具有2个head的 Multi-head Attention 结构如 图4 所示。输入 $a^i$ 经过转移矩阵，并切分生成 $q^{(i,1)}$、$q^{(i,2)}$、$k^{(i,1)}$、$k^{(i,2)}$、$v^{(i,1)}$、$v^{(i,2)}$，然后 $q^{(i,1)}$ 与 $k^{(i,1)}$ 做 attention，得到权重向量 $\alpha$，将 $\alpha$ 与 $v^{(i,1)}$ 进行加权求和，得到最终的 $b^{(i,1)}(i=1,2,…,N)$，同理可以得到 $b^{(i,2)}(i=1,2,…,N)$。接着将它们拼接起来，通过一个线性层进行处理，得到最终的结果。

$$\alpha_{(1,i)} = q^1 * k^i / \sqrt{d}$$

#Multi-head Attention
class Attention(nn.Layer):
def __init__(self,
dim,
qkv_bias=False,
qk_scale=None,
attn_drop=0.,
proj_drop=0.):
super().__init__()
# 计算 q,k,v 的转移矩阵
self.qkv = nn.Linear(dim, dim * 3, bias_attr=qkv_bias)
self.attn_drop = nn.Dropout(attn_drop)
# 最终的线性层
self.proj = nn.Linear(dim, dim)
self.proj_drop = nn.Dropout(proj_drop)

def forward(self, x):
N, C = x.shape[1:]
# 线性变换
qkv = self.qkv(x).reshape((-1, N, 3, self.num_heads, C //
# 分割 query key value
q, k, v = qkv[0], qkv[1], qkv[2]
# Scaled Dot-Product Attention
# Matmul + Scale
attn = (q.matmul(k.transpose((0, 1, 3, 2)))) * self.scale
# SoftMax
attn = nn.functional.softmax(attn, axis=-1)
attn = self.attn_drop(attn)
# Matmul
x = (attn.matmul(v)).transpose((0, 2, 1, 3)).reshape((-1, N, C))
# 线性变换
x = self.proj(x)
x = self.proj_drop(x)
return x


### 5.1.3. 多层感知机（MLP）

Transformer 结构中还有一个重要的结构就是 MLP，即多层感知机，如 图6 所示。

class Mlp(nn.Layer):
def __init__(self,
in_features,
hidden_features=None,
out_features=None,
act_layer=nn.GELU,
drop=0.):
super().__init__()
out_features = out_features or in_features
hidden_features = hidden_features or in_features
self.fc1 = nn.Linear(in_features, hidden_features)
self.act = act_layer()
self.fc2 = nn.Linear(hidden_features, out_features)
self.drop = nn.Dropout(drop)

def forward(self, x):
# 输入层：线性变换
x = self.fc1(x)
# 应用激活函数
x = self.act(x)
# Dropout
x = self.drop(x)
# 输出层：线性变换
x = self.fc2(x)
# Dropout
x = self.drop(x)
return x


### 5.1.4. DropPath

def drop_path(x, drop_prob=0., training=False):
if drop_prob == 0. or not training:
return x
shape = (paddle.shape(x)[0], ) + (1, ) * (x.ndim - 1)
random_tensor = keep_prob + paddle.rand(shape, dtype=x.dtype)
output = x.divide(keep_prob) * random_tensor
return output

class DropPath(nn.Layer):
def __init__(self, drop_prob=None):
super(DropPath, self).__init__()
self.drop_prob = drop_prob

def forward(self, x):
return drop_path(x, self.drop_prob, self.training)


### 5.1.5 基础模块

class Block(nn.Layer):
def __init__(self,
dim,
mlp_ratio=4.,
qkv_bias=False,
qk_scale=None,
drop=0.,
attn_drop=0.,
drop_path=0.,
act_layer=nn.GELU,
norm_layer='nn.LayerNorm',
epsilon=1e-5):
super().__init__()
self.norm1 = eval(norm_layer)(dim, epsilon=epsilon)
self.attn = Attention(
dim,
qkv_bias=qkv_bias,
qk_scale=qk_scale,
attn_drop=attn_drop,
proj_drop=drop)
# DropPath
self.drop_path = DropPath(drop_path) if drop_path > 0. else Identity()
self.norm2 = eval(norm_layer)(dim, epsilon=epsilon)
mlp_hidden_dim = int(dim * mlp_ratio)
self.mlp = Mlp(in_features=dim,
hidden_features=mlp_hidden_dim,
act_layer=act_layer,
drop=drop)

def forward(self, x):
x = x + self.drop_path(self.attn(self.norm1(x)))
x = x + self.drop_path(self.mlp(self.norm2(x)))
return x


### 5.1.6. 定义ViT网络

• Class Token

• Positional Encoding

#参数初始化配置
trunc_normal_ = nn.initializer.TruncatedNormal(std=.02)
zeros_ = nn.initializer.Constant(value=0.)
ones_ = nn.initializer.Constant(value=1.)

#将输入 x 由 int 类型转为 tuple 类型
def to_2tuple(x):
return tuple([x] * 2)

#定义一个什么操作都不进行的网络层
class Identity(nn.Layer):
def __init__(self):
super(Identity, self).__init__()

def forward(self, input):
return input


class VisionTransformer(nn.Layer):
def __init__(self,
img_size=224,
patch_size=16,
in_chans=3,
class_dim=1000,
embed_dim=768,
depth=12,
mlp_ratio=4,
qkv_bias=False,
qk_scale=None,
drop_rate=0.,
attn_drop_rate=0.,
drop_path_rate=0.,
norm_layer='nn.LayerNorm',
epsilon=1e-5,
**args):
super().__init__()
self.class_dim = class_dim

self.num_features = self.embed_dim = embed_dim
# 图片分块和降维，块大小为patch_size，最终块向量维度为768
self.patch_embed = PatchEmbed(
img_size=img_size,
patch_size=patch_size,
in_chans=in_chans,
embed_dim=embed_dim)
# 分块数量
num_patches = self.patch_embed.num_patches
# 可学习的位置编码
self.pos_embed = self.create_parameter(
shape=(1, num_patches + 1, embed_dim), default_initializer=zeros_)
# 人为追加class token，并使用该向量进行分类预测
self.cls_token = self.create_parameter(
shape=(1, 1, embed_dim), default_initializer=zeros_)
self.pos_drop = nn.Dropout(p=drop_rate)

dpr = np.linspace(0, drop_path_rate, depth)
# transformer
self.blocks = nn.LayerList([
Block(
dim=embed_dim,
mlp_ratio=mlp_ratio,
qkv_bias=qkv_bias,
qk_scale=qk_scale,
drop=drop_rate,
attn_drop=attn_drop_rate,
drop_path=dpr[i],
norm_layer=norm_layer,
epsilon=epsilon) for i in range(depth)
])

self.norm = eval(norm_layer)(embed_dim, epsilon=epsilon)

class_dim) if class_dim > 0 else Identity()

trunc_normal_(self.pos_embed)
trunc_normal_(self.cls_token)
self.apply(self._init_weights)
# 参数初始化
def _init_weights(self, m):
if isinstance(m, nn.Linear):
trunc_normal_(m.weight)
if isinstance(m, nn.Linear) and m.bias is not None:
zeros_(m.bias)
elif isinstance(m, nn.LayerNorm):
zeros_(m.bias)
ones_(m.weight)

def forward_features(self, x):
# 将图片分块，并调整每个块向量的维度
x = self.patch_embed(x)
# 将class token与前面的分块进行拼接
cls_tokens = self.cls_token.expand((B, -1, -1))
# 将编码向量中加入位置编码
x = x + self.pos_embed
x = self.pos_drop(x)
# 堆叠 transformer 结构
for blk in self.blocks:
x = blk(x)
# LayerNorm
x = self.norm(x)
# 提取分类 tokens 的输出
return x[:, 0]

def forward(self, x):
# 获取图像特征
x = self.forward_features(x)
# 图像分类
return x


## 5.3 ViT模型特点

• 作为CV领域最经典的 Transformer 算法之一，不同于传统的CNN算法，ViT尝试将标准的Transformer结构直接应用于图像，并对整个图像分类流程进行最少的修改。
• 为了满足 Transformer 输入结构的要求，将整幅图像拆分成小图像块，然后把这些小图像块的线性嵌入序列输入到网络。同时，使用了Class Token的方式进行分类预测。

• 参考文献

|
1月前
|

45 0
|
2月前
|

【计算机视觉】DINOv2的四种模型代码示范，以 28 * 28 的图像示例（含源代码）
DINOv2利用最大模型ViT-g的知识蒸馏，而不是从头开始训练，从而提高了性能。这个过程包括将知识从更大、更复杂的模型(教师)转移到更小的模型(学生)。学生模型被训练来模仿教师的输出，从而继承其优越的能力。这个过程提高了小型模型的性能，使它们更有效率。
87 0
|
2月前
|

【计算机视觉】DINOv2的四种模型代码示范（含源代码）
DINOv2利用最大模型ViT-g的知识蒸馏，而不是从头开始训练，从而提高了性能。这个过程包括将知识从更大、更复杂的模型(教师)转移到更小的模型(学生)。学生模型被训练来模仿教师的输出，从而继承其优越的能力。这个过程提高了小型模型的性能，使它们更有效率。
164 0
|
3月前
|

116 0
|
3月前
|

156 0
|
3月前
|

862 1
|
4月前
|

121 0
|
4月前
|

Vision Transformers (ViT) 在计算机视觉任务中取得了快速进展，在各种基准测试中取得了可喜的成果。然而，由于大量的参数和模型设计，例如注意力机制，基于 ViT 的模型通常比轻量级卷积网络慢几倍。因此，应用部署 ViT 具有很大的挑战性，尤其是在移动设备等资源受限的硬件上。
140 0
|
4月前
|

人体姿态估计是计算机视觉领域的基本研究问题之一，具有很广泛的实际应用，例如医疗健康领域的行为分析、元宇宙领域的AIGC内容生成等。但是，由于人体姿态丰富，人物衣着表观变化多样，光照和遮挡等因素，人体姿态估计任务非常具有挑战性。之前的深度学习方法主要研究了新的骨干网络设计、多尺度特征融合、更强的解码器结构和损失函数设计等。
136 0
|
4月前
|

253 0