ViT模型技术学习

前言

最近多模态模型特别火,模型也越来越小,MiniCPM-2.6只有8B,里面采用的图片编码器是SigLipViT模型,一起从头学习ViT和Transformer!本文记录一下学习过程,所以是自上而下的写,从ViT拆到Transformer。

用Transformer来做图像分类?!

  1. Vision Transformer (ViT)出自ICLR 2021的论文《An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale》,使用之前做文本任务的Transformer来做图片分类任务
  2. ViT模型的构成主要包含图像切片、图像映射、Transformer模块和分类头
    在这里插入图片描述

ViT整体工作流程

假设输入图片尺寸image_size是 224 × 224 224 \times 224 224×224,子图大小(patch_size)为16,图片编码维度(hidden_dim)为768,
当1张224*224的图输入ViT后(批大小batch_size=1)会经历:

  1. 图片切片 – 图片首先被分割为 16 × 16 16 \times 16 16×16大小的子图,总共 ( 224 / / 16 ) × ( 224 / / 16 ) = 14 × 14 = 196 (224//16) \times (224//16)=14 \times 14=196 (224//16)×(224//16)=14×14=196
  2. 图片映射 – 子图被分别送到Linear Projection这个模块进行映射,得到大小为[1,768,196]的向量
  3. 变换一下维度便于输入Transformer,所有子图拼成的图片隐向量维度为[1,196,768];
  4. 分类token – 在输入Transformer前,为了与bert架构统一,也使用一个类似[CLS]的标记,在图片隐向量前面插入一个class_token,最终输入Transformer的向量大小为[1,197,768]
  5. 位置编码 – 随机初始化的pos_embedding大小也是[1,197,768],加到图片向量上
  6. 输入Transformer,编码器输入输出维度一致,输出的维度是[1,197,768]
  7. 输出分类结果 – 取class_token对应的输出向量输入分类头

*需要注意的是:分类任务不一定要取class_token对应的向量,也可在最后一个Transformer块的输出接一个global average pooling层再接MLP分类层,特定学习率参数情况下效果类似;ViT是为了和bert架构统一所以加入了class_token

ViT源代码拆解

1. VisionTransformer类的forward()

在torchvision代码中可以找到ViT的torch官方实现

def forward(self, x: torch.Tensor):
    # 图片切片、图片编码并把图片向量调整为transformer能接受的维度
    x = self._process_input(x)
    n = x.shape[0] # n是batch size
    # 给这个batch的n个图片向量最前面,都加入一个class_token,类似[CLS]
    batch_class_token = self.class_token.expand(n, -1, -1)
    x = torch.cat([batch_class_token, x], dim=1)
                   
    # 图片向量用Transformer的block进行处理
    x = self.encoder(x)
    # 取class_token对应的向量,x是[1,197,768],x[:,0]表示x[:,0,:]
    x = x[:, 0]
    # 输入分类头进行分类任务
    x = self.heads(x)
    return x

2.图片切片与编码——VisionTransformer类的_process_input()

  • ViT框架图里面的Linear Projection模块实际上是用一个nn.Con2d隐式实现的
  • nn.Con2d起到的作用和单独把一个个子图放到Linear层编码是一样的
  • 所以实际上图片编码后的维度为[1,768,14,14]–> [1,196,768]
    ........
    self.conv_proj = nn.Conv2d(in_channels=3, out_channels=hidden_dim, kernel_size=patch_size, stride=patch_size)
def _process_input(self, x: torch.Tensor) -> torch.Tensor:    n, c, h, w = x.shape 
    # 图片维度为(n, c, h, w),n是batchsize,c是图像通道数一般为3,h/w是图像高宽
    p = self.patch_size  # 图片切片大小,例如为16,子图大小为patch_size*patch_size 
    n_h = h // p         # 图片切片,高度维度切的片数
    n_w = w // p         # 图片切片,高度维度切的片数
    # (n, c, h, w) -> (n, hidden_dim, n_h, n_w)
    x = self.conv_proj(x)
    # (n, hidden_dim, n_h, n_w) -> (n, hidden_dim, (n_h * n_w)),进行展平操作
    x = x.reshape(n, self.hidden_dim, n_h * n_w)    
    # Transformer期望的输入维度是(N,S,E),N是batchsize,S是序列长度,E是文本编码隐向量维度
    # 所以把维度变换一下,permute(0,2,1)表示把第0维放最前面,第2维放中间,第1维放后面
    x = x.permute(0, 2, 1) # 得到(n, (n_h * n_w), hidden_dim), n_h * n_w是子图数,类似文本序列长度
    return x

其中,对于卷积操作而言

self.conv_proj = nn.Conv2d(in_channels=3, out_channels=hidden_dim, kernel_size=patch_size, stride=patch_size)
  • 默认hidden_dim=768,patch_size=16,卷积核个数也就是输出的特征图通道数为768
  • 卷积核大小为16,步长也是16,可以保证卷积扫描的时候每次正好对一个子图做运算,子图互相之间不重叠,一个卷积核卷积运算的次数为(224//16)* (224//16)正好是14*14,每个运算值对应一个子图
  • 有768个卷积核,所以输出的大小为(n,768,14,14),对RGB图像而言卷积核也是个[3,16,16]的矩阵
  • RGB图像的卷积如下,RGB分别计算后相加,这只是1/768个卷积核的计算结果,所有结果拼接为矩阵
    在这里插入图片描述

3. Transformer的Encoder

3.1 Encoder的forward()

ViT使用的是Encoder for sequence to sequence translation

    ......
    super().__init__()
    # Note that batch_size is on the first dim because
    # we have batch_first=True in nn.MultiAttention() by default
    self.pos_embedding = nn.Parameter(torch.empty(1, seq_length, hidden_dim).normal_(std=0.02))  # from BERT
    self.dropout = nn.Dropout(dropout)
    layers: OrderedDict[str, nn.Module] = OrderedDict()
    for i in range(num_layers):
        layers[f"encoder_layer_{i}"] = EncoderBlock(
        	num_heads, 
        	hidden_dim, 
        	mlp_dim, 
        	dropout,
        	attention_dropout,  
        	norm_layer,)
    self.layers = nn.Sequential(layers)
    self.ln = norm_layer(hidden_dim)
def forward(self, input: torch.Tensor):
    torch._assert(input.dim() == 3, f"Expected (batch_size, seq_length, hidden_dim) got {input.shape}")
    input = input + self.pos_embedding
    return self.ln(self.layers(self.dropout(input)))

3.2 Transformer的Encoder Block

主要包含self_attention结构,在self-attention中每个patch和patch之间计算相似度,学习patch间的关系

        ......
        super().__init__()
        self.num_heads = num_heads

        # Attention block
        self.ln_1 = norm_layer(hidden_dim)  # 层归一化,是对单个样本在其特征维度(最后一个维度)上进行的归一化
        self.self_attention = nn.MultiheadAttention(
            hidden_dim, num_heads, dropout=attention_dropout, batch_first=True
        )
        self.dropout = nn.Dropout(dropout)

        # MLP block
        self.ln_2 = norm_layer(hidden_dim)
        self.mlp = MLPBlock(hidden_dim, mlp_dim, dropout)

    def forward(self, input: torch.Tensor):
        torch._assert(
            input.dim() == 3,
            f"Expected (batch_size, seq_length, hidden_dim) got {input.shape}"
        )
        x = self.ln_1(input)
        x, _ = self.self_attention(x, x, x, need_weights=False)
        x = self.dropout(x)
        x = x + input  # 残差连接

        y = self.ln_2(x)
        y = self.mlp(y)
        return x + y

3.3 Transformer的Encoder Block的MultiheadAttention

关于代码:

  • 多头注意力模块nn.MultiHeadAttention,forward方法在torch.nn.functional中,在这里之前ViT代码中已经统一把向量变换为(L,N,E)的形状

  • q(L,N,E) k(S,N,E) v(S,N,E) output(L,N,E)

  • L is the target
    length, S is the sequence length, H is the number of attention heads,
    N is the batch size, and E is the embedding dimension

  • nn.MultiHeadAttention的attention有一版注释的代码也在源文件中,搜索” multihead attention”往下翻

  • *torchtext.nn.modules.multiheadattention的多头注意力模块代码更简洁一些

下面是torchtext.nn.modules.multiheadattention的多头注意力模块代码:

# 假设这是在一个类的方法中定义的
    ......
    if self.batch_first:  # 如果是batch_first的先从(N, L, E)变为(L, N, E)形式
        query, key, value = query.transpose(-3, -2), key.transpose(-3, -2), value.transpose(-3, -2)

    # 获取维度信息
    tgt_len, src_len, bsz, embed_dim = (
        query.size(-3),
        key.size(-3),
        query.size(-2),
        query.size(-1)
    )

    # 分别乘qkv矩阵得到qkv
    q, k, v = self.in_proj_container(query, key, value)

    # 确保query的embed_dim可以被head数整除
    assert q.size(-1) % self.nhead == 0, "query's embed_dim must be divisible by the number of heads"
    head_dim = q.size(-1) // self.nhead
    q = q.reshape(tgt_len, bsz * self.nhead, head_dim)

    # 确保key的embed_dim可以被head数整除
    assert k.size(-1) % self.nhead == 0, "key's embed_dim must be divisible by the number of heads"
    head_dim = k.size(-1) // self.nhead
    k = k.reshape(src_len, bsz * self.nhead, head_dim)

    # 确保value的embed_dim可以被head数整除
    assert v.size(-1) % self.nhead == 0, "value's embed_dim must be divisible by the number of heads"
    head_dim = v.size(-1) // self.nhead
    v = v.reshape(src_len, bsz * self.nhead, head_dim)

    # 计算注意力输出和权重
    attn_output, attn_output_weights = self.attention_layer(
        q, k, v,
        attn_mask=attn_mask,
        bias_k=bias_k,
        bias_v=bias_v
    )

    # 将输出重新调整为原始形状
    attn_output = attn_output.reshape(tgt_len, bsz, embed_dim)
    attn_output = self.out_proj(attn_output)

    # 如果是batch_first从(L, N, E)变回去(N, L, E),编码器输入输出形状保持一致
    if self.batch_first:
        attn_output = attn_output.transpose(-3, -2)

    return attn_output, attn_output_weights

3.4 Transformer的Encoder Block的ScaledDotProduct

  • torchtext.nn.modules.multiheadattention的self.attention_layer是ScaledDotProduct
  • query: (L, N * H, E / H) , key: (S, N * H, E / H),self-attantion中L=E
  • 计算注意力权重 : matmul(query,key)
  • 权重归一化:对 attn_output_weights 进行 softmax 归一化时,希望确保每个查询位置(L)对所有键位置(S)的注意力权重之和为 1。因此,我们需要沿着最后一个维度 S 进行 softmax 归一化,即 dim=-1
  • 加权求和:matmul(att_output_weights, value)
# Scale query
# 变成(N*H,L,E/H)
query, key, value = query.transpose(-2, -3), key.transpose(-2, -3), value.transpose(-2, -3)
query = query * (float(head_dim) ** -0.5)
# Dot product of q, k
#(N*H,L,E/H) ×  (N*H, E/H, S),matmul计算最后2维,也就是[N*H,:,:]×[N*H,:,:],得到[N*H,L,S]
attn_output_weights = torch.matmul(query, key.transpose(-2, -1))
attn_output_weights = torch.nn.functional.softmax(attn_output_weights, dim=-1) # (N*H, L, S)
attn_output_weights = torch.nn.functional.dropout(attn_output_weights, p=self.dropout, training=self.training)
attn_output = torch.matmul(attn_output_weights, value) # (N*H, L, E/H)

self-attention的直观解释-b站视频

Attention的解释有一个b站上搬运的视频非常直观,attention可以关注到全局上信息的关联,卷积只能关注到局部的信息
在这里插入图片描述

  • 假设图片有4个像素,RGB三个通道,表示起来x是[4,3]的矩阵

  • 如果隐空间维度hidden_dim=2,输入x乘以[3,2]的Wq/Wk/Wv矩阵可以得到[4,2]的Q/K/V向量
    在这里插入图片描述
    在这里插入图片描述

  • 计算相似性度量 𝑄 ∙ 𝐾 𝑇 𝑄∙𝐾^𝑇 QKT
    在这里插入图片描述

  • 注意到每次是向量的点积运算,例如Q的第4行表示q4,K的第4列表示k4,计算的实际上是向量相似度,得到的 𝑄 ∙ 𝐾 𝑇 𝑄∙𝐾^𝑇 QKT是每个像素间的相似度矩阵

  • 在self-attention的计算中涉及到除以√𝑑放缩,否则维度越大雅可比矩阵接近零矩阵梯度消失,详细原理可以在文末知乎专栏中找到

  • 计算softmax进行归一化,因为每一行是一个像素和其它像素的相似度,所以预期是每行概率值相加为1,对列做softmax:
    在这里插入图片描述

  • 最后乘以V矩阵完成注意力计算:
    在这里插入图片描述

  • 左边的相似度矩阵可以理解为权重,乘以V矩阵类似加权平均

  • 例如0.23表示第1个像素关注第1个像素的程度,0.33表示第1个像素关注第2个像素的程度

参考链接

  1. b站attention视频讲解:https://www.bilibili.com/video/BV1Ke411X7t7
  2. 知乎解释为什么attention需要除以√𝑑放缩:https://zhuanlan.zhihu.com/p/503321685

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

cmd设置文件夹共享和清除磁盘的只读属性

背景:备份vm虚拟机到新上架的IBM交换机服务器 备份方法:设置服务器D:\盘为共享,再在其他机器通过IP地址共享路径访问服务器D:\盘,进行复制备份 交换机服务器操作系统:Microsoft hyper-v server 2016英文版&#xff0…

k3s安装指定版本以及离线安装(docker)

首先下载你所需要版本的k3s安装包,目录结构如下所示,我这里是v1.19.15k3s2。 1.首先赋予可执行权限后进行安装。 # k3s 需要赋予可执行权限 sudo chmod x k3s sudo chmod x k3s-install.sh2.然后将k3s的二进制文件复制到/usr/local/bin/ cp k3s /us…

【测试用例设计】一个登录界面的测试用例设计

文章目录 1. 登录页面的测试用例设计 1. 登录页面的测试用例设计

2024 好玩有趣的nc(netcat)瑞士军刀,可以玩的对话工具哦!超级简单,包会,图文讲解,不讲虚话

一、nc是什么? 在Linux系统中,nc(即netcat)是一个非常强大的网络工具,常被昵称为“瑞士军刀”。它能够通过TCP或UDP协议读写网络连接,被广泛应用于网络调试和检测。 二、nc具体怎么进行通讯呢?&…

通信工程学习:什么是RIP路由信息协议

RIP:路由信息协议 RIP(Routing Information Protocol)路由信息协议是一种基于距离矢量算法的内部网关协议(IGP),主要用于在自治系统(AS)内部进行路由信息的交换和传播。以下是关于RI…

【机器学习】随机森林算法(看我以弱博强)

目录 算法引入: 算法介绍: 1. 集成学习: 2. 训练过程: 3. 分类和回归: 算法优点: 算法缺点: 算法实现: 1. 数据准备 2. 划分数据集 3. 创建随机森林模型 4. 训练模型 5…

Python和C++的差异在哪里

1.编程应用领域 C:广泛应用于系统级开发、嵌入式系统、游戏开发等领域。C的底层控制和高性能使其成为这些领域的理想选择。 Python:广泛应用于数据科学、Web开发、人工智能等领域。Python的简洁语法和强大库支持使其成为这些领域的首选语言。 2.语法风…

代码随想录 (三)—— 哈希表部分刷题

当我们想使用哈希法来解决问题的时候,我们一般会选择如下三种数据结构。 数组set (集合)map(映射) 在java中有就是,hashmap, LinkedHashMap, TreeMap ,HashTable 等 总结一下,当我们遇到了要快速判断一个…

vue-scrollto实现页面组件锚点定位

文章目录 前言背景操作指南安装及配置步骤vue组件中使用 参考文章 前言 博主介绍:✌目前全网粉丝3W,csdn博客专家、Java领域优质创作者,博客之星、阿里云平台优质作者、专注于Java后端技术领域。 涵盖技术内容:Java后端、大数据…

处理 Vue3 中隐藏元素刷新闪烁问题

一、问题说明 页面刷新,原本隐藏的元素会一闪而过。 效果展示: 页面的导航栏通过路由跳转中携带的 meta 参数控制导航栏的 显示/隐藏,但在实践过程中发现,虽然元素隐藏了,但是刷新页面会出现闪烁的问题。 项目源码&…

现代 C++ 模板教程 学习笔记

现代C模板教程 看了这个教程,自己记录一些内容,不一定靠谱。 为什么需要模板?简单来说就是为了少写点重复的代码,让编译器自动帮我们生成。为了少写点函数,就有了函数模板,为了少写点类,就有了…

探索未来:揭秘pymqtt,AI与物联网的新桥梁

文章目录 探索未来:揭秘pymqtt,AI与物联网的新桥梁背景:为什么选择pymqtt?什么是pymqtt?如何安装pymqtt?简单的库函数使用方法1. 配置MQTT连接2. 创建Mqtt对象3. 发布消息4. 订阅主题5. 运行MQTT客户端 场景…

transformer中的attention机制之旅

本文为学习笔记,若有相关交流或者需要补充的评论区欢迎 attention在深度学习之中最初是作为模型中的一个组件 用于提升模型精度,其原理是为了通过一定的计算方式获取信息中强化关注的特征。 attention相关的基础介绍可以参考其他博客中的内容: 一文看懂 …

C++ | Leetcode C++题解之第463题岛屿的周长

题目&#xff1a; 题解&#xff1a; class Solution {constexpr static int dx[4] {0, 1, 0, -1};constexpr static int dy[4] {1, 0, -1, 0}; public:int dfs(int x, int y, vector<vector<int>> &grid, int n, int m) {if (x < 0 || x > n || y <…

P1169 [ZJOI2007] 棋盘制作

P1169 [ZJOI2007] 棋盘制作 left[i][j]:代表从(i,j)(i,j)能到达的最左位置 right[i][j]:代表从(i,j)(i,j)能到达的最右位置 up[i][j]:代表从(i,j)(i,j)向上扩展最长长度. 悬线法&#xff1a; 假设求没有黑色格子的最大矩形&#xff0c;那么我们可以考虑对每个点计算&#x…

二、安装vmtools

1、 介绍 vmtools 安装后&#xff0c;可以让我们在 windows 下更好的管理 vm 虚拟机。可以设置 windows 和 centos 的共享文件夹 当时当我们发现安装虚拟机工具位置是灰色的 右击打开终端 在终端输入命令 yum list installed | grep open-vm-*yum list installed 命令会列出…

04:(寄存器开发)使用外部中断按键控制LED

寄存器开发 1、选择外部引脚配置2、上升沿触发/下降沿触发3、NVIC的配置4、完整代码 关于外部中断的AFIO&#xff0c;NVIC的基础知识请参考《stm32标准库入门教程》 链接: link 1、选择外部引脚配置 如上图所示&#xff1a;外部中断配置寄存器AFIO_EXTICR(1~4)中选择EXTI(0 ~ …

Bootstrap 5 练习 - 显示工具提示

文章目录 引言准备工作创建HTML文件导入Bootstrap 5框架编写页面代码编写JavaScript脚本浏览网页注意事项结束语 引言 大家好&#xff0c;今天我们将一起学习如何在Bootstrap 5中创建一个简单的工具提示&#xff08;Tooltip&#xff09;。工具提示是一个非常实用的用户界面元素…

通过观测云 DataKit Extension 接入 AWS Lambda 最佳实践

前言 AWS Lambda 是一项计算服务&#xff0c;使用时无需预配置或管理服务器即可运行代码。AWS Lambda 只在需要时执行代码并自动缩放。借助 AWS Lambda&#xff0c;几乎可以为任何类型的应用程序或后端服务运行代码&#xff0c;而且无需执行任何管理。 Lambda Layer 是一个包…

Java面试宝典-Java集合02

目录 Java面试宝典-Java集合02 21、TreeMap 和 TreeSet 在排序时如何比较元素&#xff1f; 22、ArrayList 和 LinkedList 的区别是什么&#xff1f; 23、ArrayList 和 Vector 的区别&#xff1f; 24、队列和栈是什么&#xff1f;有什么区别&#xff1f; 25、Queue和Deque的区别…