[论文阅读]Vit(Vision Transformer)及代码

TRM

VIT 只用了encoder部分,就是把编码端用于图像

如何把图片放到encoder中:朴素思路,一个一个像素点输入

不直接输入的原因:224*224 参数数量太多,远大于nlp中的参数量

VIT

2部分中处理用的方法:1.把一个整体的patch拉成一个向量,再对其长度转变为embedding-size(拉长,减小) 2.Conv成768的channel数也行

3部分处理方法:

3.1 生成CLS符号(分类标签)的TokenEmbedding(*)【粉色的叫tokenEMB】

3.2 生成所有序列的位置编码(紫色的)【自注意力机制不含位置信息】

3.3 TokenEMB + 位置编码(紫色加粉色)

VIT模型

整体流程:

输入encoder中的数据格式 n(patch+1) * 768

最后5的步骤是,把第一个CLS拿出来,做一个多分类的任务,然后就能知道这张图片是属于哪个类的(可以认为CLS这一条向量,储存了类别信息)

也有不用CLS的做法,对每个token输出,并做一个GAP就能得到

一个 token 应该是一个形状为 (1, 768) 的向量(也就是一个 768 维的向量),而不是 (n, 768) 的张量。每个 token 代表了一个单独的特征向量,而不是多个 token 组成的张量。

下面是更正的步骤:

  1. 汇总 Token:首先,您需要计算这些单个 token 的平均值,以获得一个形状为 (1, 768) 的全局特征向量。这可以通过在维度 0 上取平均值来实现。

pythonCopy code
global_feature = tokens.mean(dim=0)  # 计算全局平均特征向量
  1. 添加分类头:接下来,您可以将全局特征向量传递给一个分类头,这个头通常是一个全连接层(Linear)层,输出维度为 1000(对应于 1000 个类别)。您可以使用 softmax 激活函数将输出转换为分类概率。

pythonCopy code
classifier_head = nn.Linear(768, 1000)  # 创建分类头,输入维度为 768,输出维度为 1000
  1. 前向传播:在前向传播过程中,将全局特征向量传递给分类头,然后应用 softmax 函数以获取每个类别的概率分布。

pythonCopy codelogits = classifier_head(global_feature)  # 通过分类头获取 logits
probs = nn.functional.softmax(logits, dim=0)  # 使用 softmax 转换为概率分布

现在,probs 包含了每个类别的分类概率分布。可以使用这些概率来进行分类预测,通常选择概率最高的类别作为最终预测结果。

这就是如何使用 Global Average Pooling (GAP) 和分类头来进行 1000 类的分类预测。请确保在训练时使用适当的损失函数,如交叉熵损失,来训练模型以进行分类任务。

# 导入所需的库和模块
import torch
from torch import nn
from einops import rearrange, repeat
from einops.layers.torch import Rearrange

# 辅助函数,如果输入不是元组,则将其转换为元组
def pair(t):
    return t if isinstance(t, tuple) else (t, t)

# 定义 PreNorm 类,对输入进行预归一化,然后应用函数
class PreNorm(nn.Module):
    def __init__(self, dim, fn):
        super().__init__()
        self.norm = nn.LayerNorm(dim)  # 使用 Layer Normalization 对输入进行归一化
        self.fn = fn

    def forward(self, x, **kwargs):
        return self.fn(self.norm(x), **kwargs)  # 应用函数到归一化后的输入

# 定义 FeedForward 类,实现前馈神经网络
class FeedForward(nn.Module):
    def __init__(self, dim, hidden_dim, dropout=0.):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(dim, hidden_dim),  # 全连接层
            nn.GELU(),  # GELU激活函数
            nn.Dropout(dropout),  # Dropout层
            nn.Linear(hidden_dim, dim),  # 全连接层
            nn.Dropout(dropout)  # Dropout层
        )

    def forward(self, x):
        return self.net(x)  # 应用前馈神经网络到输入

# 定义 Attention 类,包括自注意力计算
class Attention(nn.Module):
    def __init__(self, dim, heads=8, dim_head=64, dropout=0.):
        super().__init__()
        inner_dim = dim_head * heads  # 内部维度计算 (聚合头特征)
        '''
        具体来说,dim_head 表示每个头的输出维度。假设输入序列的维度是 dim,
        并且自注意力机制被分为 num_heads 个头,那么每个头的输出维度就是 dim_head。
        因此,总的输出维度将是 num_heads * dim_head = inner_dim。
        一头Wk Wq Wv 变换成的维度为dim_head == 64
        '''

        project_out = not (heads == 1 and dim_head == dim)  # 是否需要投影输出

        self.heads = heads  # 多头注意力的头数
        self.scale = dim_head ** -0.5  # 缩放因子

        self.attend = nn.Softmax(dim=-1)  # 注意力权重计算使用Softmax
        self.to_qkv = nn.Linear(dim, inner_dim * 3, bias=False)  # 生成查询、键、值的线性映射 *3主要是保证QKV的并行计算

        self.to_out = nn.Sequential(
            nn.Linear(inner_dim, dim),  # 输出的全连接层
            nn.Dropout(dropout)  # 输出层的Dropout
        ) if project_out else nn.Identity()  # 如果不需要投影输出,使用恒等映射

    def forward(self, x):
        qkv = self.to_qkv(x).chunk(3, dim=-1)  # 对输入进行查询、键、值分割 x:1 196+1 1024 -> 1 197 512*3   qkv是一个元组 q k v 1 197 512
        q, k, v = map(lambda t: rearrange(t, 'b n (h d) -> b h n d', h=self.heads), qkv)  # 重排分割后的查询、键、值
        #batch 序列长 (head*head_dims)-> batch head 序列长 head_dims(经过头后输出的维度)1 197 64*8 -> 1 8 197 64
        dots = torch.matmul(q, k.transpose(-1, -2)) * self.scale  # 计算点积注意力权重

        attn = self.attend(dots)  # 计算注意力分数 1 8 197 197 8个头,8个不同的注意力分数图(197*197,代表1与197个序列的相似度)

        out = torch.matmul(attn, v)  # 用注意力权重聚合值 1 8 197 64(1个头中, 每个位置融合了197个value后的新值)
        out = rearrange(out, 'b h n d -> b n (h d)')  # 重排输出1 197 8*64(融合为大的values)
        #得到聚合头的特征排列
        return self.to_out(out)  # 返回经过投影的输出

# 定义 Transformer 类,包括多层的自注意力和前馈神经网络
class Transformer(nn.Module):
    def __init__(self, dim, depth, heads, dim_head, mlp_dim, dropout=0.):
        super().__init__()
        self.layers = nn.ModuleList([])  # 存储Transformer层的列表
        for _ in range(depth): #堆叠trm  结合图看
            self.layers.append(nn.ModuleList([
                PreNorm(dim, Attention(dim, heads=heads, dim_head=dim_head, dropout=dropout)),  # 自注意力层
                PreNorm(dim, FeedForward(dim, mlp_dim, dropout=dropout))  # 前馈神经网络层mlp_dim是hidden_dim,出来的还是1024
            ]))

    def forward(self, x):
        for attn, ff in self.layers:  # 遍历每个Transformer层
            x = attn(x) + x  # 自注意力层和残差连接
            x = ff(x) + x  # 前馈神经网络层和残差连接
            print(x.shape)
        return x  # 返回Transformer层的输出

# 定义 ViT 类,包括块嵌入、位置嵌入、Transformer层、池化和多层感知机(MLP)
class ViT(nn.Module):
    def __init__(self, image_size, patch_size, num_classes, dim, depth, heads, mlp_dim, pool='cls', channels=3, dim_head=64, dropout=0., emb_dropout=0.):
        super().__init__()

        # 提取图像和块的尺寸信息
        image_height, image_width = pair(image_size)  #224*224
        patch_height, patch_width = pair(patch_size) #16*16

        assert image_height % patch_height == 0 and image_width % patch_width == 0, 'Image dimensions must be divisible by the patch size.'

        num_patches = (image_height // patch_height) * (image_width // patch_width)  # 计算块的数量
        patch_dim = channels * patch_height * patch_width  # 计算拉平向量的维度

        assert pool in {'cls', 'mean'}, 'pool type must be either cls (cls token) or mean (mean pooling)'

        #patch-embedding操作
        self.to_patch_embedding = nn.Sequential(
            Rearrange('b c (h p1) (w p2) -> b (h w) (p1 p2 c)', p1=patch_height, p2=patch_width),  # 通过重排操作将图像块flatten为向量 主要目的是将图像分割成许多小的块,然后将每个块中的像素值排列成一行,以便它们可以被输入到全连接层或其他神经网络层中
            nn.Linear(patch_dim, dim)  # 维度的变换   (h w)就是 h*w的意思 14*14 = 196(p1 p2 c)就是 p1 * p2 *c 的意思 
        )

        # 定义位置嵌入和类别标志
        self.pos_embedding = nn.Parameter(torch.randn(1, num_patches + 1, dim))  # 初始化位置嵌入参数 (生成所有patches+cls的位置编码)【num-patch+1, dim】
        self.cls_token = nn.Parameter(torch.randn(1, 1, dim))  # 初始化类别标志参数 【1,dim】
        self.dropout = nn.Dropout(emb_dropout)  # 嵌入的Dropout层

        # 定义Transformer层
        self.transformer = Transformer(dim, depth, heads, dim_head, mlp_dim, dropout)

        # 池化方式和输出层
        self.pool = pool
        self.to_latent = nn.Identity()  # 恒等映射层

        self.mlp_head = nn.Sequential(
            nn.LayerNorm(dim),  # 使用Layer Normalization进行归一化
            nn.Linear(dim, num_classes)  # 输出层的多层感知机(MLP) 做多分类的任务
        )

    def forward(self, img):
        #想象成立方体来理解好理解一些
        # 对图像应用块嵌入
        x = self.to_patch_embedding(img)  # 1 3 224 224-> 1 14*14 1024 
        b, n, _ = x.shape  # 获取输入张量的形状

        # 重复类别标志并与块嵌入相连接
        cls_tokens = repeat(self.cls_token, '() n d -> b n d', b=b)  # 重复类别标志以匹配批次大小,每一个样本,都要加一个CLS
        print(cls_tokens.shape,'cls_token')
        x = torch.cat((cls_tokens, x), dim=1)  # 连接类别标志和块嵌入 在序列长度维度concat

        # 添加位置嵌入并应用dropout
        x += self.pos_embedding[:, :(n + 1)]  # 每个(197个)token【1,1024】加上位置嵌入
        x = self.dropout(x)  # 应用嵌入的Dropout层   batch 197 1024
        #print(x.shape)   #结果:batch 197 1024

        # 使用Transformer层进行特征提取
        x = self.transformer(x)

        # 池化层(平均池化或CLS池化)
        x = x.mean(dim=1) if self.pool == 'mean' else x[:, 0] #切片第0个元素
        print(x.shape)
        # 应用输出层的多层感知机(MLP)
        x = self.to_latent(x)
        return self.mlp_head(x)

# 创建ViT模型的实例,并设置参数
v = ViT(
    image_size=224,
    patch_size=16,
    num_classes=1000,  #CLS需要做多少类别的分类
    dim=1024, #维度
    depth=6, #encoder堆叠多少个
    heads=16, #多头
    mlp_dim=2048,
    dropout=0.1,
    emb_dropout=0.1
)

# 创建一个形状为(1, 3, 224, 224)的随机图像张量
img = torch.randn(2, 3, 224, 224)

# 使用ViT模型进行前向传播,得到分类预测
preds = v(img)  # 形状为(1, 1000)的分类分数,过一个softmax就是分类预测
print(preds.shape)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值