Vision Transformer(vit)原理分析以及特征可视化

目录

Vit简介

Vit model结构图

vit输入处理

图像分块

class token与position的添加

特征提取

vit代码


Vit简介

Vision Transformer(ViT)是一种基于Transformer架构的深度学习模型,用于图像识别和计算机视觉任务。与传统的卷积神经网络(CNN)不同,ViT直接将图像视为一个序列化的输入,并利用自注意力机制来处理图像中的像素关系。

ViT通过将图像分成一系列的图块(patches),并将每个图块转换为向量表示作为输入序列。然后,这些向量将通过多层的Transformer编码器进行处理,其中包含了自注意力机制和前馈神经网络层。这样可以捕捉到图像中不同位置的上下文依赖关系。最后,通过对Transformer编码器输出进行分类或回归,可以完成特定的视觉任务。

vit代码参考:神经网络学习小记录67——Pytorch版 Vision Transformer(VIT)模型的复现详解_vit复现_Bubbliiiing的博客-CSDN博客

为什么不能直接将transformer直接应用于图像处理中呢?这是因为transformer本身是用来处理序列任务的(比如NLP),但图像是二维或三维的,像素之间存在一定的结构关系,如果单纯的将transformer之间应用于图像中,像素和像素之间需要一定的关联性,那么这个计算量是相当大的。因此vit就诞生了。


Vit model结构图

Vit的模型结构如下图所示。vit是将图像块应用于transformer。CNN是以滑窗的思想用卷积核在图像上进行卷积得到特征图。为了可以使图像仿照NLP的输入序列,我们可以先将图像分成块(patch),再将这些图像块进行平铺后输入到网络中(这样就变成了图像序列),然后通过transformer进行特征提取,最后再通过MLP对这些特征进行分类【其实就可以理解为在以往的CNN分类任务中,将backbone替换为transformer】。

Figure 1: Model overview. We split an image into fixed-size patches, linearly embed each of them, add position embeddings, and feed the resulting sequence of vectors to a standard Transformer encoder. In order to perform classification, we use the standard approach of adding an extra learnable “classification token” to the sequence. The illustration of the Transformer encoder was inspired by Vaswani et al. (2017).

vit输入处理

图像分块

图像分块就是上述vit图中的patch,position Embedding就是位置嵌入(可以得出图像块的位置信息)。那么如何对图像进行分块呢最简单的就是可以通过卷积来实现,我们可以通过设置卷积核大小以及步长来控制图像块的分辨率以及分多少块。

在代码中是如何来实现的呢?可以看下面的代码。

class PatchEmbed(nn.Module):def __init__(self, input_shape = [224,224], patch_size = 16, in_channels = 3, num_features = 768, norm_layer = None, flatten = True):super().__init__()self.num_patch = (input_shape[0] // patch_size) * (input_shape[1] // patch_size)  # 14*14的patch = 196self.flatten = flattenself.proj = nn.Conv2d(in_channels, num_features, kernel_size=patch_size, stride=patch_size)self.norm = norm_layer(num_features) if norm_layer else nn.Identity()def forward(self, x):x = self.proj(x)  # 先进行卷积 [1,3,224,224] ->[1,768,14,14]if self.flatten:x = x.flatten(2).transpose(1, 2)  # x.flatten.transpose(1, 2) shape:[1,768,196]x = self.norm(x)return x

上述代码中,num_patch就是可以划分多少个图像块。proj就是对输入图像进行卷积分块,进行特征映射,假设输入大小为1,3,224,224,通过该卷积操作后得到1,768,14,14【表明通过卷积分成了768个分辨率大小为14×14的图像块】。

每个图像块都是经过一次特征提取的,可以对其中的一个图像块可视化一下看看:

输入图像
输入图像
其中一个图像块

然后再进行flatten平铺操作。就会变成【1,768,196】,最后再经过一个layernorm层得到最终的输出。

对输入序列可视化

class token与position的添加

通过上述的操作,我们可以得到平铺后的特征序列(shape 为1,768,196)。接着会在该序列上添加class token,这个token会与前面的特征序列一起送入网络中进行特征提取。该class token就是图中的0*,因此原本长度为196的序列就会变成长度为197的序列。

然后会在添加Position embedding,可以为所有的特征序列添加位置信息。通过生成一个[197,768]的矩阵加到原来的特征序列中。至此,网络输入的预处理patch+position embedding就完成了。

# class token的定义
self.cls_token      = nn.Parameter(torch.zeros(1, 1, num_features))

# position embedding定义
self.pos_embed = nn.Parameter(torch.zeros(1, num_patches + 1, num_features))

代码:

class VisionTransformer(nn.Module):def __init__(self, input_shape=[224, 224], patch_size=16, in_chans=3, num_classes=1000, num_features=768,depth=12, num_heads=12, mlp_ratio=4., qkv_bias=True, drop_rate=0.1, attn_drop_rate=0.1, drop_path_rate=0.1,norm_layer=partial(nn.LayerNorm, eps=1e-6), act_layer=GELU):"""输入大小为224,以16*16的卷积核分块14*14:param input_shape: 网络输入大小:param patch_size:  分块大小:param in_chans:  输入通道:param num_classes:  类别数量:param num_features: 特征图维度:param num_heads:  多头注意力机制head的数量:param mlp_ratio: MLP ratio:param qkv_bias: qkv的bias:param drop_rate: dropout rate:param norm_layer: layernorm:param act_layer: 激活函数"""super().__init__()#-----------------------------------------------##   224, 224, 3 -> 196, 768#-----------------------------------------------#self.patch_embed    = PatchEmbed(input_shape=input_shape, patch_size=patch_size, in_channels=in_chans, num_features=num_features)num_patches         = (224 // patch_size) * (224 // patch_size)self.num_features   = num_featuresself.new_feature_shape = [int(input_shape[0] // patch_size), int(input_shape[1] // patch_size)]self.old_feature_shape = [int(224 // patch_size), int(224 // patch_size)]self.cls_token = nn.Parameter(torch.zeros(1, 1, num_features))  # shape [1,1,768]self.pos_embed = nn.Parameter(torch.zeros(1, num_patches + 1, num_features))  # shape [1, 197, 768]def forward_features(self, x):x = self.patch_embed(x)  # 先分块 [1,196, 768]cls_token = self.cls_token.expand(x.shape[0], -1, -1)  # [1,1,768]x = torch.cat((cls_token, x), dim=1)  # [1,197,768]cls_token_pe = self.pos_embed[:, 0:1, :]  # 获取class token pos_embed 【类位置信息】img_token_pe = self.pos_embed[:, 1:, :]  # 后196维度是图像特征的位置信息img_token_pe = img_token_pe.view(1, *self.old_feature_shape, -1).permute(0, 3, 1, 2)  # [1,768,14,14]img_token_pe = F.interpolate(img_token_pe, size=self.new_feature_shape, mode='bicubic', align_corners=False)img_token_pe = img_token_pe.permute(0, 2, 3, 1).flatten(1, 2)  # [1,196,768]pos_embed = torch.cat([cls_token_pe, img_token_pe], dim=1)  # [1,197,768]x = self.pos_drop(x + pos_embed)

特征提取

与CNN网络一样,在做特征提取的时候也需要一个backbone做特征提取。而在vit中是利用transformer encoder进行特征提取。

我们的输入是一个[197,768]的序列,这里的197包含了class token【可学习的】,图像序列以及pos_embed【可学习的】。将这个序列输入到我们的编码器中进行特征提取,而transformer中特征提取的重要组件就是multi-head attention。

上面的图中,可以看到输入图像先经过了Norm层,然后分成了三份,这三份就是q,k,v,再同时输入多头注意力机制中,这也就是自注意力机制。然后在和残差边的输入进行相加,再经过Norm和MLP进行输出即可。

q就是我们的查询序列,q和k的相乘就是让q中的每个查询向量和k中的特征向量求相关性,或者说是重要度。然后我们再和原始输入向量v相乘,得到每个序列的贡献度【其实和通道注意力机制是有些像的】。

通过搭建很多个self-attention去提取特征。如果和CNN相比,transformer的基本组成单元是self-attention,CNN的基本组成单元是卷积核。

自注意力机制代码:

代码中的qkv:

# 几何意义:q,k,v分布在num_heads个head中(每个head中均有qkv),每个head上还有197*64的特征序列
class Attention(nn.Module):def __init__(self, dim, num_heads=8, qkv_bias=False, attn_drop=0., proj_drop=0.):super().__init__()self.num_heads  = num_headsself.scale      = (dim // num_heads) ** -0.5self.qkv        = nn.Linear(dim, dim * 3, bias=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):B, N, C     = x.shapeqkv         = self.qkv(x).reshape(B, N, 3, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)q, k, v     = qkv[0], qkv[1], qkv[2]attn = (q @ k.transpose(-2, -1)) * self.scaleattn = attn.softmax(dim=-1)attn = self.attn_drop(attn)x = (attn @ v).transpose(1, 2).reshape(B, N, C)x = self.proj(x)x = self.proj_drop(x)return x

同理,我们可以qkv进行特征可视化看看q,k,v里到底是什么。我们的q,k,v的shape是一样的,shape为[batch_size, num_heads, 197, 768//num_heads].我们对q的第一个head的输入进行可视化(像这种图有12个heads,每个head上提取了64种特征)。

第一个head上的q特征向量

然后再来看下第一个head上的k的特征。

第一个head上的k的特征向量

然后再通过q和k矩阵乘积获得注意力权重。 

通过q和k的矩阵乘后得到注意力特征图如下,我们还是仅对第一个head可视化【一共有12个head,每个head的注意力特征图是不同的】:

然后再通过sofmax去计算所有head上的注意力分数。 

第一个head的注意力分数为:

tensor([[9.9350e-01, 2.5650e-05, 2.6444e-05,  ..., 3.7445e-05, 3.3614e-05,
         2.7365e-05],
        [3.7948e-01, 2.3743e-01, 8.7877e-02,  ..., 2.2976e-05, 1.2177e-04,
         6.6991e-04],
        [3.7756e-01, 1.2583e-01, 1.4249e-01,  ..., 1.0860e-05, 3.4743e-05,
         1.1384e-04],
        ...,
        [4.1151e-01, 3.6945e-05, 9.8513e-06,  ..., 1.5886e-01, 1.1042e-01,
         4.4855e-02],
        [4.0967e-01, 1.7754e-04, 2.8480e-05,  ..., 1.0884e-01, 1.4333e-01,
         1.2111e-01],
        [4.1888e-01, 6.8779e-04, 6.7465e-05,  ..., 3.5659e-02, 9.4098e-02,
         2.2174e-01]], device='cuda:0')

再将得到的注意力分数与v相乘,得到每个通道的贡献度。 

然后是添加MLP层,最后可以得到我们的Transformer Block。 

class Mlp(nn.Module):""" MLP as used in Vision Transformer, MLP-Mixer and related networks"""def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=GELU, drop=0.):super().__init__()out_features = out_features or in_featureshidden_features = hidden_features or in_featuresdrop_probs = (drop, drop)self.fc1 = nn.Linear(in_features, hidden_features)self.act = act_layer()self.drop1 = nn.Dropout(drop_probs[0])self.fc2 = nn.Linear(hidden_features, out_features)self.drop2 = nn.Dropout(drop_probs[1])def forward(self, x):x = self.fc1(x)x = self.act(x)x = self.drop1(x)x = self.fc2(x)x = self.drop2(x)return xclass Block(nn.Module):def __init__(self, dim, num_heads, mlp_ratio=4., qkv_bias=False, drop=0., attn_drop=0.,drop_path=0., act_layer=GELU, norm_layer=nn.LayerNorm):super().__init__()self.norm1 = norm_layer(dim)self.attn = Attention(dim, num_heads=num_heads, qkv_bias=qkv_bias, attn_drop=attn_drop, proj_drop=drop)self.norm2 = norm_layer(dim)self.mlp = Mlp(in_features=dim, hidden_features=int(dim * mlp_ratio), act_layer=act_layer, drop=drop)self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()def forward(self, x):def forward(self, x):''':param x: 输入序列x --> layer_norm --> mulit head attention --> + --> x --> layer_norm --> mlp --> +-->x|____________________________________________|     |_____________________________|'''x = x + self.drop_path(self.attn(self.norm1(x)))x = x + self.drop_path(self.mlp(self.norm2(x)))return xx = x + self.drop_path(self.attn(self.norm1(x)))x = x + self.drop_path(self.mlp(self.norm2(x)))return x

vit代码

class VisionTransformer(nn.Module):def __init__(self, input_shape=[224, 224], patch_size=16, in_chans=3, num_classes=1000, num_features=768,depth=12, num_heads=12, mlp_ratio=4., qkv_bias=True, drop_rate=0.1, attn_drop_rate=0.1, drop_path_rate=0.1,norm_layer=partial(nn.LayerNorm, eps=1e-6), act_layer=GELU):"""输入大小为224,以16*16的卷积核分块14*14:param input_shape: 网络输入大小:param patch_size:  分块大小:param in_chans:  输入通道:param num_classes:  类别数量:param num_features: 特征图维度:param num_heads:  多头注意力机制head的数量:param mlp_ratio: MLP ratio:param qkv_bias: qkv的bias:param drop_rate: dropout rate:param norm_layer: layernorm:param act_layer: 激活函数"""super().__init__()#-----------------------------------------------##   224, 224, 3 -> 196, 768#-----------------------------------------------#self.patch_embed    = PatchEmbed(input_shape=input_shape, patch_size=patch_size, in_channels=in_chans, num_features=num_features)num_patches         = (224 // patch_size) * (224 // patch_size)self.num_features   = num_featuresself.new_feature_shape = [int(input_shape[0] // patch_size), int(input_shape[1] // patch_size)]self.old_feature_shape = [int(224 // patch_size), int(224 // patch_size)]self.cls_token = nn.Parameter(torch.zeros(1, 1, num_features))  # shape [1,1,768]self.pos_embed = nn.Parameter(torch.zeros(1, num_patches + 1, num_features))  # shape [1, 197, 768]# -----------------------------------------------##   197, 768 -> 197, 768  12次# -----------------------------------------------#dpr = [x.item() for x in torch.linspace(0, drop_path_rate, depth)]self.blocks = nn.Sequential(*[Block(dim=num_features,num_heads=num_heads,mlp_ratio=mlp_ratio,qkv_bias=qkv_bias,drop=drop_rate,attn_drop=attn_drop_rate,drop_path=dpr[i],norm_layer=norm_layer,act_layer=act_layer) for i in range(depth)])self.norm = norm_layer(num_features)self.head = nn.Linear(num_features, num_classes) if num_classes > 0 else nn.Identity()def forward_features(self, x):x = self.patch_embed(x)  # 先分块 [1,196, 768]cls_token = self.cls_token.expand(x.shape[0], -1, -1)  # [1,1,768]x = torch.cat((cls_token, x), dim=1)  # [1,197,768]cls_token_pe = self.pos_embed[:, 0:1, :]  # 获取class token pos_embed 【类位置信息】img_token_pe = self.pos_embed[:, 1:, :]  # 后196维度是图像特征的位置信息img_token_pe = img_token_pe.view(1, *self.old_feature_shape, -1).permute(0, 3, 1, 2)  # [1,768,14,14]img_token_pe = F.interpolate(img_token_pe, size=self.new_feature_shape, mode='bicubic', align_corners=False)img_token_pe = img_token_pe.permute(0, 2, 3, 1).flatten(1, 2)  # [1,196,768]pos_embed = torch.cat([cls_token_pe, img_token_pe], dim=1)  # [1,197,768] 获得最终的位置信息x = self.pos_drop(x + pos_embed)  # 将位置信息和图像序列相加x = self.blocks(x)  # 特征提取x = self.norm(x)return x[:, 0]def forward(self, x):x = self.forward_features(x)x = self.head(x)return xdef freeze_backbone(self):backbone = [self.patch_embed, self.cls_token, self.pos_embed, self.pos_drop, self.blocks[:8]]for module in backbone:try:for param in module.parameters():param.requires_grad = Falseexcept:module.requires_grad = Falsedef Unfreeze_backbone(self):backbone = [self.patch_embed, self.cls_token, self.pos_embed, self.pos_drop, self.blocks[:8]]for module in backbone:try:for param in module.parameters():param.requires_grad = Trueexcept:module.requires_grad = True

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/58635.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WebGL模型矩阵

前言:依赖矩阵库 WebGL矩阵变换库_山楂树の的博客-CSDN博客 先平移,后旋转的模型变换: 1.将三角形沿着X轴平移一段距离。 2.在此基础上,旋转三角形。 先写下第1条(平移操作)中的坐标方程式。 等式1&am…

如何将 PDF 转换为 Word:前 5 个应用程序

必须将 PDF 转换为 Word 才能对其进行编辑和自定义。所以这里有 5 种很棒的方法 PDF 文件被广泛使用,因为它非常稳定且难以更改。这在处理法律合同、财务文件和推荐信等重要文件时尤其重要。但是,有时您可能需要编辑 PDF 文件。最好的方法是使用应用程序…

openGauss学习笔记-54 openGauss 高级特性-MOT

文章目录 openGauss学习笔记-54 openGauss 高级特性-MOT54.1 MOT特性及价值54.2 MOT关键技术54.3 MOT应用场景54.4 不支持的数据类型54.5 使用MOT54.6 将磁盘表转换为MOT openGauss学习笔记-54 openGauss 高级特性-MOT openGauss引入了MOT(Memory-Optimized Table&…

读书笔记——《万物有灵》

前言 上一本书是《走出荒野》,太平洋步道女王提到了这本书《万物有灵》,她同样是看一点撕一点的阅读。我想,在她穿越山河森林,听见鸟鸣溪流的旅行过程中,是不是看这本描写动物有如何聪明的书——《万物有灵》&#xf…

vue中解决ajax跨域问题(no “access-control-allow-origin”)

文章目录 跨域报错信息产生原因举例解决方法方式一优缺点方式二优缺点 跨域报错信息 产生原因 跨域是是因为浏览器的同源策略限制,是浏览器的一种安全机制,服务端之间是不存在跨域的。 所谓同源指的是两个页面具有相同的协议、主机和端口,三…

R语言空气污染数据的地理空间可视化和分析:颗粒物2.5(PM2.5)和空气质量指数(AQI)...

原文链接:http://tecdat.cn/?p23800 由于空气污染对公众健康的不利影响,人们一直非常关注。世界各国的环境部门都通过各种方法(例如地面观测网络)来监测和评估空气污染问题(点击文末“阅读原文”获取完整代码数据&…

可拖动表格

支持行拖动&#xff0c;列拖动 插件&#xff1a;sortablejs UI: elementUI <template><div><hr style"margin: 30px 0;"><div><!-- 数据里面要有主键id&#xff0c; 否则拖拽异常 --><h2 style"margin-bottom: 30px&qu…

打开谷歌浏览器远程调试功能

谷歌浏览器远程调试功能 首先我们来启动Chrome的远程调试端口。你需要找到Chrome的安装位置&#xff0c;在Chrome的地址栏输入chrome://version就能找到Chrome的安装路径 开启远程控制命令 文件路径/chrome.exe --remote-debugging-port9222开启后的样子(注意要关闭其他谷歌浏…

Qt快捷键

#include //注意&#xff0c;头文件一定要添加 QT提供了一个很有用的调试方式&#xff1a;断点调试。这使用户可以轻易地看到自己某个部分的调试结果。下面是使用方法&#xff1a; 按下F5或者左侧的在这里插入图片描述进入调试模式&#xff0c;然后在代码的左侧设置断点 一:断…

MFC网络编程简单例程

目录 一、关于网络的部分概念1 URL(网址)及URL的解析2 URL的解析3 域名及域名解析3 IP及子网掩码4 什么是Web服务器5 HTTP的基本概念6 Socket库概念7 协议栈8 Socket库收发数据基本步骤 二、基于TCP的网络应用程序三、基于UDP的网络应用程序 一、关于网络的部分概念 1 URL(网址…

安防视频监控/视频集中存储/云存储平台EasyCVR平台无法播放HLS协议该如何解决?

视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。音视频流媒体视频平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视频录像、…

基于 OV5640 摄像头理论知识讲解-成像和采样原理

基于OV2640/ OV5640 的图像采集显示系统系列文章目录&#xff1a; &#xff08;1&#xff09;基于 OV5640 摄像头理论知识讲解-成像和采样原理 &#xff08;2&#xff09;基于 OV5640 摄像头理论知识讲解-数字接口和控制接口 &#xff08;3&#xff09;基于 OV5640 摄像头理论知…

springboot第37集:kafka,mqtt,Netty,nginx,CentOS,Webpack

image.png binzookeeper-server-start.shconfigzookeeper.properties.png image.png image.png 消费 image.png image.png image.png image.png image.png image.png image.png image.png image.png Netty的优点有很多&#xff1a; API使用简单&#xff0c;学习成本低。功能强大…

Pillow:Python的图像处理库(安装与使用教程)

在Python中&#xff0c;Pillow库是一个非常强大的图像处理库。它提供了广泛的图像处理功能&#xff0c;让我们可以轻松地操作图像&#xff0c;实现图像的转换、裁剪、缩放、旋转等操作。此外&#xff0c;Pillow还支持多种图像格式的读取和保存&#xff0c;包括JPEG、PNG、BMP、…

db2迁移至oracle

1.思路 &#xff08;1&#xff09;用java连接数据库&#xff08;2&#xff09;把DB2数据导出为通用的格式如csv&#xff0c;json等&#xff08;3&#xff09;导入其他数据库&#xff0c;比如oracle&#xff0c;mongodb。这个方法自由发挥的空间比较大。朋友说他会用springboot…

BananaPi BPI-6202工业控制板全志科技A40i、24V DC输入、RS485接口

Banana Pi BPI-6202“嵌入式单板计算机”采用工业级全志A40i四核Cortex-A7处理器&#xff0c;工业温度范围和长生命周期&#xff0c;2GB DDR3&#xff0c;8GB eMMC闪存&#xff0c;M.2 SATA插槽等。 这是自 Banana Pi去年推出Banana Pi BPI-M2 Ultra SBC 和BPI-M2 Berry以来&am…

算法通关村第8关【黄金】| 寻找祖先问题

思路&#xff1a;递归三部曲 第一步&#xff1a;确定参数和返回值 题目要求找到指定的结点&#xff0c;就需要返回结点。 题目又涉及到p,q就需要传入p,q&#xff0c;需要遍历传入root 第二步&#xff1a;确定终止条件 当遍历到结点为空说明到底没找到返回空 或者遍历到p,…

华为云新生代开发者招募

开发者您好&#xff0c;我们是华为2012UCD的研究团队 为了解年轻开发者的开发现状和趋势 正在邀请各位先锋开发者&#xff0c;与我们进行2小时的线上交流&#xff08;江浙沪附近可线下交流&#xff09; 聊聊您日常开发工作中的产品使用需求 成功参与访谈者将获得至少300元京…

Xshell7和Xftp7的下载、安装及连接服务器的教程

1.下载 1.官网地址&#xff1a; XSHELL - NetSarang Website 选择学校免费版下载 2.将XSHELL和XFTP全都下载下来 2.安装 安装过程就是选择默认选项&#xff0c;然后无脑下一步 3.连接服务器 1.打开Xshell7&#xff0c;然后新建会话 2.填写相关信息 出现Connection establ…

【算法】双指针求解盛最多水的容器

Problem: 11. 盛最多水的容器 文章目录 题目解析算法原理讲解复杂度Code 题目解析 首先我们来解析一下本题 题目中说到&#xff0c;要找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 那我们现在来看最外侧的两根&#xff0c;一个高度为8&#…