vit 中的 cls_token 与 position_embed 理解

1. cls_token()

Class Token
假设我们将原始图像切分成共9个小图像块,最终的输入序列长度却是10,也就是说我们这里人为的增加了一个向量进行输入,我们通常将人为增加的这个向量称为 Class Token。那么这个 Class Token 有什么作用呢?

我们可以想象,如果没有这个向量,也就是将9个向量(1~9)输入 Transformer 结构中进行编码,我们最终会得到9个编码向量,可对于图像分类任务而言,我们应该选择哪个输出向量进行后续分类呢?

因此,ViT算法提出了一个可学习的嵌入向量 Class Token( 向量0),将它与9个向量一起输入到 Transformer 结构中,输出10个编码向量,然后用这个 Class Token 进行分类预测即可。

在这里插入图片描述

1.1 原理

类似于BERT中的[class] token,ViT引入了class token机制,其目的:因为transformer输入为一系列的patch embedding,输出也是同样长的序列patch feature,但是最后要总结为一个类别的判断,简单方法可以用avg pool,把所有的patch feature都考虑算出image feature。

但是作者没有用这种方式,而是引入一个类似flag的class token,其输出特征加上一个线性分类器就可以实现分类。

其中训练的时候,class token的embedding被随机初始化并与pos embedding相加,因此从图可以看到输入transformer的时候, 0 处补上一个新embedding,最终输入长度N+1.

# 随机初始化
self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim))
# Classifier head
self.head = nn.Linear(self.num_features, num_classes) if num_classes > 0 else nn.Identity()
# 具体forward过程
B = x.shape[0]
x = self.patch_embed(x)
cls_tokens = self.cls_token.expand(B, -1, -1)  # stole cls_tokens impl from Phil Wang, thanks
x = torch.cat((cls_tokens, x), dim=1)
x = x + self.pos_embed


一些问题:
ViT做分类时取出第n+1个token作为分类的特征,这样做的原理在哪里?有人说这样是为了避免对输入的某一个token有偏向性,那么我将前n个token做平均作为要分类的特征是否可行呢?

首先不存在n+1这个意思奥,论文里面是class token是放在首位,也就是第0个位置,
答案:

题主所说的第n+1个token(class embedding)的主要特点是:
(1)不基于图像内容;
(2)位置编码固定。

这样做有以下好处:
1、该token随机初始化,并随着网络的训练不断更新,它能够编码整个数据集的统计特性;

2、该token对所有其他token上的信息做汇聚(全局特征聚合),并且由于它本身不基于图像内容,因此可以避免对sequence中某个特定token的偏向性;

3、对该token使用固定的位置编码能够避免输出受到位置编码的干扰。ViT中作者将class embedding视为sequence的头部而非尾部,即位置为0。

这样即使sequence的长度n发生变化,class embedding的位置编码依然是固定的,因此,更准确的来说class embedding应该是第0个而非第n+1个token。

另外题主说的“将前n个token做平均作为要分类的特征是否可行呢”,这也是一种全局特征聚合的方式,但它相较于采用attention机制来做全局特征聚合而言表达能力较弱。

因为采用attention机制来做特征聚合,能够根据query和key之间的关系来自适应地调整特征聚合的权重,而采用求平均的方式则是对所有的key给了相同的权重,这限制了模型的表达能力。

2. Positional Encoding

按照 Transformer 结构中的位置编码习惯,这个工作也使用了位置编码。不同的是,ViT 中的位置编码没有采用原版 Transformer 中的 s i n c o s sincos sincos 编码,而是直接设置为可学习的 Positional Encoding。对训练好的 Positional Encoding 进行可视化,如 图9 所示。我们可以看到,位置越接近,往往具有更相似的位置编码。此外,出现了行列结构,同一行/列中的 patch 具有相似的位置编码。

### 如何在 Vision Transformer (ViT) 中实现和使用 Dropout Dropout 是一种有效的正则化方法,在训练神经网络过程中随机丢弃一部分神经元,从而防止过拟合。对于 ViT 模型而言,可以在多个位置引入 Dropout 来增强泛化能力。 #### 1. 输入嵌入层后的 Dropout 当图像被分割成 patches 后并转换为 tokens 形式的向量表示之后,通常会紧接着添加一个可学习的位置编码。在此之后可以加入一层 Dropout: ```python class PatchEmbedding(nn.Module): def __init__(self, img_size=224, patch_size=16, embed_dim=768, dropout_rate=0.1): super().__init__() self.patch_embed = nn.Conv2d(3, embed_dim, kernel_size=patch_size, stride=patch_size) num_patches = (img_size // patch_size)**2 # 添加位置编码 self.position_embeddings = nn.Parameter(torch.zeros(1, num_patches + 1, embed_dim)) # 定义dropout层 self.dropout = nn.Dropout(dropout_rate) def forward(self, x): B, C, H, W = x.shape x = self.patch_embed(x).flatten(2).transpose(1, 2) cls_token = torch.zeros(B, 1, x.shape[-1]).cuda() x = torch.cat((cls_token, x), dim=1) embeddings = x + self.position_embeddings[:, :x.size(1)] return self.dropout(embeddings)[^1] ``` #### 2. 编码器中的多头自注意力机制前/后应用 Dropout 为了进一步提升模型性能,还可以考虑在每个 Transformer 编码器模块内部设置两处不同的 Dropout 层——分别位于 Multi-head Self-Attention 和 Feed Forward Network 的前后端: ```python import torch.nn as nn class Block(nn.Module): def __init__(self, dim, num_heads, mlp_ratio=4., qkv_bias=False, drop_path=0., norm_layer=nn.LayerNorm, act_layer=nn.GELU, attn_drop=0., proj_drop=0.): ... self.attn_dropout = nn.Dropout(attn_drop) self.proj_dropout = nn.Dropout(proj_drop) ... def forward(self, x): shortcut = x x = self.norm1(x) x = self.attn(x) x = self.attn_dropout(x) x = self.proj(x) x = self.proj_dropout(x) x += shortcut ... return x ``` 通过上述方式,在构建 ViT 架构时合理配置 Dropout 可以有效减少过拟合并提高最终预测效果[^2]。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值