Vision Transformer在CV领域的理解

最近比较火,找了好多资料和源码研究了下,写下这篇读后感

在CV领域是怎么用呢?

先说注意力机制,注意力机制对人类来说其实很好理解,假如给定一张图片,我们会自动聚焦到一些关键信息位置,而不需要逐行扫描全图,此处的attention,其本质是对输入的自适应加权,senet模型中的se模块就是典型例子

9bb0bafdb352c04fc2a51d89e2b7d238.png

se模块最终学出来是一个1X1xC的向量,然后逐通道乘以原始输入,即通道注意力。

其实都可以抽像成

a3498b0355b1890f568bad1182db3300.png

# 假设q是(1,N,512),N就是最大标签化后的list长度,k是(1,M,512),M可以等于N,也可以不相等
# (1,N,512) x (1,512,M)-->(1,N,M)
attn = torch.matmul(q, k.transpose(2, 3))
# softmax转化为概率,输出(1,N,M),表示q中每个n和每个m的相关性
attn=F.softmax(attn, dim=-1)
# (1,N,M) x (1,M,512)-->(1,N,512),V和k的shape相同
output = torch.matmul(attn, v)

作者:深度眸
链接:https://zhuanlan.zhihu.com/p/308301901
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

1  CNN backbone 处理的是 [B,3,H,W] 图像,经过层层提取特征,输出是[B,C,H,W],backbone 只做这一件事。

2 Encoder部分:

      先用1X1 卷积,将通道数从C 压缩到D,得到新的特征图[B,D,H,W]

      将[B,D,H,W]的空间维度(高和宽)压缩成一个维度,得到序列化数据【B,D,HW】

      在得到了【B,D,H,W】特征图之后,正式输入Encoder之前,需要进行位置编码,因为自注意力需要有表示位置的信息,得到positional encoding 需要以下几步:

   1 在自然语言中得到PE向量公式是

         f438b678379ba85b52964233859a1935.png

     这个式子中d,就是序列化数据中的D,pos属于【1,HW】范围,表示token在序列中的位置,i取值范围【0,.,.,d/2】,所以pos=1,式子可以写成:

      c70bb580e0d1ddda97f05243a3c03a94.png 

    所以 PE的输出张量是 hw行,d列,reshape成【B,d,H,W】,表示这个特征图的任意一点(h,w)都有个位置编码。然后得到位置编码和输入加权

 

率先使用Transformer模型是Vit,因为输入必须是要序列,比如说输入是【3,256,256】,打算分成64patch,每个patch 是32x32,也就是最终输入变成【64,3072】,输入长度是64个图像序列,每个序列采用3072编码

使用的代码就是

x = rearrange(img, 'b c (h p1) (w p2) -> b (h w) (p1 p2 c)', p1=p, p2=p)

考虑到3072有点大,可以先降维

# 将3072变成dim,假设是1024
self.patch_to_embedding = nn.Linear(patch_dim, dim)
x = self.patch_to_embedding(x)

现在输入确定了,然后是位置编码也必不可少,这里比较简单,没有采用sincos编码,而是这只成可学习,效果差不多

# num_patches=64,dim=1024,+1是因为多了一个cls开启解码标志
self.pos_embedding = nn.Parameter(torch.randn(1, num_patches + 1, dim))

 

将输入向量和位置编码相加输入进编码器,编码器的前向过程:

self.transformer = Transformer(dim, depth, heads, mlp_dim, dropout)

输入是【b,64,1024】,输出也是一样的维度。

然后编码器后接分类器

 

self.mlp_head = nn.Sequential(
            nn.LayerNorm(dim),
            nn.Linear(dim, mlp_dim),
            nn.GELU(),
            nn.Dropout(dropout),
            nn.Linear(mlp_dim, num_classes)
        )

# 65个输出里面只需要第0个输出进行后续分类即可
self.mlp_head(x[:, 0])

整体的是



class ViT(nn.Module):
    def __init__(self, *, image_size, patch_size, num_classes, dim, depth, heads, mlp_dim, channels=3, dropout=0.,emb_dropout=0.):
        super().__init__()
        # image_size输入图片大小 256
        # patch_size 每个patch的大小 32
        num_patches = (image_size // patch_size) ** 2  # 一共有多少个patch 8x8=64
        patch_dim = channels * patch_size ** 2  # 3x32x32=3072
        self.patch_size = patch_size  # 32
        # 1,64+1,1024,+1是因为token,可学习变量,不是固定编码
        self.pos_embedding = nn.Parameter(torch.randn(1, num_patches + 1, dim))
        # 图片维度太大了,需要先降维
        self.patch_to_embedding = nn.Linear(patch_dim, dim)
        # 分类输出位置标志,否则分类输出不知道应该取哪个位置
        self.cls_token = nn.Parameter(torch.randn(1, 1, dim))
        self.dropout = nn.Dropout(emb_dropout)
        # 编码器
        self.transformer = Transformer(dim, depth, heads, mlp_dim, dropout)
        # 输出头
        self.mlp_head = nn.Sequential(
            nn.LayerNorm(dim),
            nn.Linear(dim, mlp_dim),
            nn.GELU(),
            nn.Dropout(dropout),
            nn.Linear(mlp_dim, num_classes)
        )

    def forward(self, img, mask=None):
        p = self.patch_size

        # 先把图片变成64个patch,输出shape=b,64,3072
        x = rearrange(img, 'b c (h p1) (w p2) -> b (h w) (p1 p2 c)', p1=p, p2=p)
        # 输出 b,64,1024
        x = self.patch_to_embedding(x)
        b, n, _ = x.shape
        # 输出 b,1,1024
        cls_tokens = repeat(self.cls_token, '() n d -> b n d', b=b)
        # 额外追加token,变成b,65,1024
        x = torch.cat((cls_tokens, x), dim=1)
        # 加上位置编码1,64+1,1024
        x += self.pos_embedding[:, :(n + 1)]
        x = self.dropout(x)

        x = self.transformer(x, mask)
        # 分类head,只需要x[0]即可
        # x = self.to_cls_token(x[:, 0])
        x = x[:, 0]
        return self.mlp_head(x)

 

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值