从零开始手把手搭建,手写vision-transformer学习笔记

【论文阅读】Vision Transformer 模型详解 关于Vision Transformer模型的详解 ,末尾附原论文下载链接以及pytorch代码。 阅读详情

本文学习的是从零开始手把手搭建Vision Transformers(Pytorch版本)_从0搭建transfomer实现cv任务

在这里插入图片描述

首先,输入图像(a)先被切割成大小相等的patch 子图片,然后每个子图片均被放入到Linear Embedding 中,对每个图片向量做一个全连接操作,做transformer输入的前处理从Linear Embedding层出来后,加入Positonal encoding 将各个patch在图像中的相对位置信息考虑进去,后面就是transformer Encoder的过程,在之后加入MLP的分类head,输出图像的分类。

transformer开始是用于NLP这种序列化数据,因此第一步就是将图像序列化

步骤一:图像序列化

以一张图为例

在这里插入图片描述

将每个(1x28x28)的图像分成7x7块,每块大小是4x4(如果不能完全整除分块,需要对图像padding填充),这样我们能从单个图像中获得49个子图像。将原图重塑成:(N,49,16)即N个batch,49个子块,每块4*4。

请注意,虽然每个子图大小为 1x4x4 ,但我们将其展平为 16 维向量。此外,MNIST只有一个颜色通道。如果有多个颜色通道,它们也会被展平到矢量中。

代码如下:

class MyViT(nn.Module):
    def __init__(self, input_shape, n_patches=7):    
        # Super constructor        
        super(MyViT, self).__init__()
        
        # Input and patches sizes        
        self.input_shape = input_shape#输入尺寸这里是1,128,128        
        self.n_patches = n_patches#patch数这里是7        
        assert input_shape[1] % n_patches == 0, "Input shape not entirely divisible by number of patches"#若不能整除,报错        
        assert input_shape[2] % n_patches == 0, "Input shape not entirely divisible by number of patches"#若不能整除,报错

    def forward(self, images):    
        # Dividing images into patches#分割为子块        
        n, c, w, h = images.shape#获取输入图像尺寸,n为batch数,c为通道数,w为宽,h为高        
        patches = images.reshape(n, self.n_patches ** 2, self.input_d)#变为(n,49,16)        
        return patches #返回patch数目

在这里插入图片描述

我们就把一张图像变为49行16列的维度,每一行代表一个子图对其已经进行打平成为一维向量,然后对通过一个线性映射来改变维度,线性映射可以映射到任意向量大小,我们向类构造函数添加一个hidden_d参数,用于“隐藏维度”。这里,使用隐藏维度为8,这样我们将每个 16 维patch映射到一个 8 维patch。

class MyViT(nn.Module):
    def __init__(self, input_shape, n_patches=7, hidden_d=8):    
        # Super constructor        
        super(MyViT, self).__init__()
        
        # Input and patches sizes        
        self.input_shape = input_shape        
        self.n_patches = n_patches        
        assert input_shape[1] % n_patches == 0, "Input shape not entirely divisible by number of patches"        
        assert input_shape[2] % n_patches == 0, "Input shape not entirely divisible by number of patches"        
        self.hidden_d = hidden_d#这是隐藏层维度
        
        # 1) Linear mapper#线性映射        
        self.input_d = int(input_shape[0] * self.patch_size[0] * self.patch_size[1])#输入维度,这里就是1*4*4=16
#若是三维的即3*4*4,将三通道也打平为一维序列        
        self.linear_mapper = nn.Linear(self.input_d, self.hidden_d)#线性映射,可将维度进行调整,其本质为一个全连接层
        
    def forward(self, images):        
    # Dividing images into patches        
    n, c, w, h = images.shape        
    patches = images.reshape(n, self.n_patches ** 2, self.input_d)         
    # Running linear layer for tokenization #将        
    tokens = self.linear_mapper(patches)#进行线性变换维度变换为,(n,49,8)从开始16变换为8
    
    return tokens

在这里插入图片描述

这里分类v是在位置嵌入之后的,本人认为也应该是这样因为classification token为分类标记不需要添加位置信息因此应该先位置嵌入然后再添加classification token

在添加隐藏层后,为了完成分类任务,我们需要添加分类标记,现在可以向我们的模型添加一个参数将我们的 (N, 49, 8)张量转换为 (N, 50, 8) 张量(将特殊标记添加到每个序列)

class MyViT(nn.Module):
    def __init__(self, input_shape, n_patches=7, hidden_d=8, n_heads=2, out_d=10):   
        # Super constructor        
        super(MyViT, self).__init__()
        
        # Input and patches sizes        
        self.input_shape = input_shape        
        self.n_patches = n_patches        
        self.n_heads = n_heads        
        assert input_shape[1] % n_patches == 0, "Input shape not entirely divisible by number of patches"        
        assert input_shape[2] % n_patches == 0, "Input shape not entirely divisible by number of patches"        
        self.hidden_d = hidden_d
        
        # 1) Linear mapper        
        self.input_d = int(input_shape[0] * self.patch_size[0] * self.patch_size[1])        
        self.linear_mapper = nn.Linear(self.input_d, self.hidden_d)
        
        # 2) Classification token        
        self.class_token = nn.Parameter(torch.rand(1, self.hidden_d))#参数,(1,8)的
        
    def forward(self, images):    
        # Dividing images into patches        
        n, c, w, h = images.shape        
        patches = images.reshape(n, self.n_patches ** 2, self.input_d)       
         # Running linear layer for tokenization       
        tokens = self.linear_mapper(patches)        
        # Adding classification token to the tokens将分类标记放入token中        
        tokens = torch.stack([torch.vstack((self.class_token, tokens[i])) for i in range(len(tokens))])
'''
vstack 是一个用于垂直堆叠两个张量的操作。在这里,self.class_token 和 tokens[i] 被垂直堆叠在一起。
这意味着我们将 class_token 放在每个 tokens[i] 的最上面,形成一个新的张量,该张量的第一行是 class_token,
而后面的行是 tokens[i] 的元素。
现在token[0]为class_token
而后面的为每个子图的token
举一个例子哦
tokens = torch.tensor([
    [0.1, 0.2, 0.3],  # token 0    
    [0.4, 0.5, 0.6],  # token 1    
    [0.7, 0.8, 0.9]   # token 2
])
class_token = torch.tensor([1.0, 1.0, 1.0]) 
现在进行该步骤
[torch.vstack((self.class_token, tokens[i])) for i in range(len(tokens))]
就会变为
tokens = torch.tensor([
    [1.0, 1.0, 1.0],  # token 0    
    [0.1, 0.2, 0.3],  # token 1    
    [0.4, 0.5, 0.6],  # token 2    
    [0.7, 0.8, 0.9]   # token 3
])
'''
        return tokens

请注意,这里 (N,49,8) → (N,50,8) 实现方式可能不是最佳的。另外,请注意**分类标记需要放在每个序列的第一个标记位。**当我们完成最终 MLP 时,需要对应到对应的位置上。

添加位置编码

位置编码参见transformer模型中位置标明的输入,虽然理论上可以学习这种位置嵌入,但是这块也有人研究过,建议我们可以只添加正弦和余弦波。

def get_positional_embeddings(sequence_length, d):
    result = torch.ones(sequence_length, d)#sequence_length子图数量,d子图特征    
    for i in range(sequence_length):#遍历每张子图    
        for j in range(d):#遍历每个特征       
            result[i][j] = np.sin(i / (10000 ** (j / d))) if j % 2 == 0 else np.cos(i / (10000 ** ((j - 1) / d)))
#若j为偶数则用sin方法,若j为奇数则cos方法
    return resul#维度最后不变

在这里插入图片描述

在这里插入图片描述

从绘制的热图中,看到所有“水平线”都彼此不同,因此可以区分样本位置。

现在可以在线性映射和添加分类标记后将位置编码添加到模型中:

class MyViT(nn.Module):
    def __init__(self, input_shape, n_patches=7, hidden_d=8, n_heads=2, out_d=10):     
        # Super constructor        
        super(MyViT, self).__init__()
        
        # Input and patches sizes        
        self.input_shape = input_shape        
        self.n_patches = n_patches        
        self.n_heads = n_heads        
        assert input_shape[1] % n_patches == 0, "Input shape not entirely divisible by number of patches"        
        assert input_shape[2] % n_patches == 0, "Input shape not entirely divisible by number of patches"        
        self.patch_size = (input_shape[1] / n_patches, input_shape[2] / n_patches)        self.hidden_d = hidden_d
        
        # 1) Linear mapper        
        self.input_d = int(input_shape[0] * self.patch_size[0] * self.patch_size[1])        
        self.linear_mapper = nn.Linear(self.input_d, self.hidden_d)
        
        # 2) Classification token        
        self.class_token = nn.Parameter(torch.rand(1, self.hidden_d))
        
        # 3) Positional embedding        
        # (In forward method)
        


    def forward(self, images):    
        # Dividing images into patches        
        n, c, w, h = images.shape        
        patches = images.reshape(n, self.n_patches ** 2, self.input_d)
        
        # Running linear layer for tokenization        
        tokens = self.linear_mapper(patches)
        
        # Adding classification token to the tokens        
        tokens = torch.stack([torch.vstack((self.class_token, tokens[i])) for i in range(len(tokens))])
        
        # Adding positional embedding        
        tokens += get_positional_embeddings(self.n_patches ** 2 + 1, self.hidden_d).repeat(n, 1, 1)
#position_embedding维度为(50,8)因为有n个,因此要进行重复变为(n,50,8),
#有tokens维度(n,50,8)这样tokens和position_embedding进行对应位置元素相加,即位置嵌入,逐元素相加

        return tokens

img

img

img

LN, MSA和残差连接

这是最LN, MSA和残差连接复杂的一步。我们需要先对tokens做层归一化,然后应用多头注意力机制,最后添加一个残差连接(连接LN 之前的输入和多头注意力之后的输出)。

我们通常将LN应用于 (N, d) 输入,其中 d 是维度。直到自己实现 ViT,才发现nn.LayerNorm可以应用于多个维度。

img

通过 LN 运行 (N, 50, 8) 张量后,每个 50x8 矩阵的均值为 0 和标准差为 1,维度不变。

多头自注意力

img

img

我们现在需要实现架构图的子图c。这里就是多头注意力机制对于单个图像,我们希望每个patch都根据与其他patch的某种相似性度量来更新。通过将每个patch(示例中现在是一个 8 维向量)线性映射到 3 个不同的向量:q、k和v(查询、键、值)。然后,对于单个patch,我们将计算其q向量与所有k个向量之间的点积(即相似度),除以这些向量维度的平方根d,对计算结果softmax激活(获得相似度的概率),最后将计算结果与不同k向量相关联的v向量相乘,简单的理解也可以类似搜索引擎,你输入的问题就是Q,与索引键K进行匹配若比较类似则说明K对应的V是你所搜索的信息其权重应该高一点。整个计算公式如下。

img

通过这种方式,每个patch获得一个新值,该新值为与其他patch的相似性(在线性映射到q、k和v之后)。整个过程为单头,多头则重复多次整个过程。获得所有结果后,将它们通过线性层连接在一起。

因此为 MSA 创建一个新类:

class MyMSA(nn.Module):
    def __init__(self, d, n_heads=2):    
        super(MyMSA, self).__init__()        
        self.d = d#维度
#在多头自注意力中,输入的特征维度需要在每个头之间进行均匀分配
        self.n_heads = n_heads#自注意力头数
        
        assert d % n_heads == 0, f"Can't divide dimension {d} into {n_heads} heads"#若
        
        d_head = int(d / n_heads)#每个头的维度
#在自注意力机制中,我们需要为每个输入向量生成查询、键和值。每个头(head)都有自己独立的参数来生成这些向量,
#这样每个头能够学习到不同的特征。
        self.q_mappings = [nn.Linear(d_head, d_head) for _ in range(self.n_heads)]        
        self.k_mappings = [nn.Linear(d_head, d_head) for _ in range(self.n_heads)]        
        self.v_mappings = [nn.Linear(d_head, d_head) for _ in range(self.n_heads)]        
        self.d_head = d_head        
        self.softmax = nn.Softmax(dim=-1)
        
    def forward(self, sequences):    
        # Sequences has shape (N, seq_length, token_dim)        
        # We go into shape    (N, seq_length, n_heads, token_dim / n_heads)        
        # And come back to    (N, seq_length, item_dim)  (through concatenation)        
        result = []        
        for sequence in sequences:         
            seq_result = []            
            for head in range(self.n_heads):#遍历每个头           
                q_mapping = self.q_mappings[head]#每个头的q                
                k_mapping = self.k_mappings[head]#每个头的k                
                v_mapping = self.v_mappings[head]#每个头的v
                
                seq = sequence[:, head * self.d_head: (head + 1) * self.d_head]#按head进行头数进行分段                
                q, k, v = q_mapping(seq), k_mapping(seq), v_mapping(seq)
                
                attention = self.softmax(q @ k.T / (self.d_head ** 0.5))#求相似度                
                seq_result.append(attention @ v)#结果       
            result.append(torch.hstack(seq_result))
'''
torch.hstack() 是 PyTorch 中用于进行张量水平堆叠的函数。
它的作用是将多个相同形状的张量在水平方向上连接(拼接)在一起。
result.append(torch.hstack(seq_result)) 的核心作用在于将来自不同注意力头的加权输出合并成一个更大的张量。
这种拼接操作不仅方便后续的特征处理,还能够增强模型的表达能力,使模型能够从多个头中综合信息,提高模型的性能。
'''
        return torch.cat([torch.unsqueeze(r, dim=0) for r in result])#将多个注意力头提取的特征进行组合

由于输入是大小为 (N, 50, 8) 的序列,我们使用 2 个头,因此我们将在某些时候有 (N, 50, 2, 4) 张量,使用nn.Linear(4, 4 )模块,然后在连接后返回到 (N, 50, 8) 张量。另,使用循环并不是计算多头自注意力的最有效方法,但代码更清晰。

class MyViT(nn.Module):
    def __init__(self, input_shape, n_patches=7, hidden_d=8, n_heads=2, out_d=10):     
        # Super constructor        
        super(MyViT, self).__init__()
        
        # Input and patches sizes        
        self.input_shape = input_shape        
        self.n_patches = n_patches        
        self.n_heads = n_heads        
        assert input_shape[1] % n_patches == 0, "Input shape not entirely divisible by number of patches"        
        assert input_shape[2] % n_patches == 0, "Input shape not entirely divisible by number of patches"        self.patch_size = (input_shape[1] / n_patches, input_shape[2] / n_patches)        self.hidden_d = hidden_d
        
        # 1) Linear mapper        
        self.input_d = int(input_shape[0] * self.patch_size[0] * self.patch_size[1])        
        self.linear_mapper = nn.Linear(self.input_d, self.hidden_d)
        
        # 2) Classification token        
        self.class_token = nn.Parameter(torch.rand(1, self.hidden_d))
        
        # 3) Positional embedding        
        # (In forward method)
        
        # 4a) Layer normalization 1        
        self.ln1 = nn.LayerNorm((self.n_patches ** 2 + 1, self.hidden_d))
        
        # 4b) Multi-head Self Attention (MSA) and classification token        
        self.msa = MyMSA(self.hidden_d, n_heads)#多头注意力
        


    def forward(self, images):    
        # Dividing images into patches        
        n, c, w, h = images.shape        
        patches = images.reshape(n, self.n_patches ** 2, self.input_d)
        
        # Running linear layer for tokenization        
        tokens = self.linear_mapper(patches)
        
        # Adding classification token to the tokens        
        tokens = torch.stack([torch.vstack((self.class_token, tokens[i])) for i in range(len(tokens))])
        
        # Adding positional embedding        
        tokens += get_positional_embeddings(self.n_patches ** 2 + 1, self.hidden_d).repeat(n, 1, 1)
        
        # TRANSFORMER ENCODER BEGINS ###################################        
        # NOTICE: MULTIPLE ENCODER BLOCKS CAN BE STACKED TOGETHER ######        
        # Running Layer Normalization, MSA and residual connection        
        out = tokens + self.msa(self.ln1(tokens))#token通过线性层再进行多头注意力,最后与原来的token连接,进行残差连接
        
        return out

将添加一个残差连接,它将我们的原始 (N, 50, 8) 张量添加到在 LN 和 MSA 之后获得的 (N, 50, 8)。

img

再进行一次layernorm以及一次MLP

class MyViT(nn.Module):
    def __init__(self, input_shape, n_patches=7, hidden_d=8, n_heads=2, out_d=10):    
        # Super constructor        
        super(MyViT, self).__init__()
        
        # Input and patches sizes        
        self.input_shape = input_shape        
        self.n_patches = n_patches        
        self.n_heads = n_heads        
        assert input_shape[1] % n_patches == 0, "Input shape not entirely divisible by number of patches"        
        assert input_shape[2] % n_patches == 0, "Input shape not entirely divisible by number of patches"        
        self.patch_size = (input_shape[1] / n_patches, input_shape[2] / n_patches)        
        self.hidden_d = hidden_d
        
        # 1) Linear mapper        
        self.input_d = int(input_shape[0] * self.patch_size[0] * self.patch_size[1])        
        self.linear_mapper = nn.Linear(self.input_d, self.hidden_d)
        
        # 2) Classification token        
        self.class_token = nn.Parameter(torch.rand(1, self.hidden_d))
        
        # 3) Positional embedding        
        # (In forward method)
        
        # 4a) Layer normalization 1        
        self.ln1 = nn.LayerNorm((self.n_patches ** 2 + 1, self.hidden_d))
        
        # 4b) Multi-head Self Attention (MSA) and classification token        
        self.msa = MyMSA(self.hidden_d, n_heads)
        
        # 5a) Layer normalization 2          
        self.ln2 = nn.LayerNorm((self.n_patches ** 2 + 1, self.hidden_d))#第二层的Layer-normal
        
        # 5b) Encoder MLP 进行多层感知        
        self.enc_mlp = nn.Sequential(            
            nn.Linear(self.hidden_d, self.hidden_d),#线性层            
            nn.ReLU()#激活函数        
        )


    def forward(self, images):    
        # Dividing images into patches        
        n, c, w, h = images.shape        
        patches = images.reshape(n, self.n_patches ** 2, self.input_d)
        
        # Running linear layer for tokenization        
        tokens = self.linear_mapper(patches)
       
        # Adding classification token to the tokens        
        tokens = torch.stack([torch.vstack((self.class_token, tokens[i])) for i in 
        range(len(tokens))])
        
        # Adding positional embedding        
        tokens += get_positional_embeddings(self.n_patches ** 2 + 1, self.hidden_d).repeat(n, 1, 1)
        
        # TRANSFORMER ENCODER BEGINS ###################################        
        # NOTICE: MULTIPLE ENCODER BLOCKS CAN BE STACKED TOGETHER ######        
        # Running Layer Normalization, MSA and residual connection        
        out = tokens + self.msa(self.ln1(tokens))
        
        # Running Layer Normalization, MLP and residual connection        
        out = out + self.enc_mlp(self.ln2(out))        
        # TRANSFORMER ENCODER ENDS   ###################################
        
        return out

最后进行分类MLP

我们可以从 N 个序列中只提取分类标记(第一个标记),与添加分类标签的位置对应,并使用每个标记得到 N 个分类。由于我们决定每个标记是一个 8 维向量,并且由于我们有 10 个可能的数字,我们可以将分类 MLP 实现为一个简单的 8x10 矩阵,并使用 SoftMax 函数激活。即每张子图是8个特征的(本来是16个特征我们经过线性变换变为8个)每张子图生成类别的概率一共有10个数字可能,因此分类MLP首先为一个简单的8X10矩阵

class MyViT(nn.Module):
    def __init__(self, input_shape, n_patches=7, hidden_d=8, n_heads=2, out_d=10):    
        # Super constructor        
        super(MyViT, self).__init__()
        
        # Input and patches sizes        
        self.input_shape = input_shape        
        self.n_patches = n_patches        
        self.n_heads = n_heads        
        assert input_shape[1] % n_patches == 0, "Input shape not entirely divisible by number of patches"        
        assert input_shape[2] % n_patches == 0, "Input shape not entirely divisible by number of patches"        
        self.patch_size = (input_shape[1] / n_patches, input_shape[2] / n_patches)        
        self.hidden_d = hidden_d
        
        # 1) Linear mapper        
        self.input_d = int(input_shape[0] * self.patch_size[0] * self.patch_size[1])        
        self.linear_mapper = nn.Linear(self.input_d, self.hidden_d)
        
        # 2) Classification token        
        self.class_token = nn.Parameter(torch.rand(1, self.hidden_d))
        
        # 3) Positional embedding        
        # (In forward method)
        
        # 4a) Layer normalization 1        
        self.ln1 = nn.LayerNorm((self.n_patches ** 2 + 1, self.hidden_d))
        
        # 4b) Multi-head Self Attention (MSA) and classification token        
        self.msa = MyMSA(self.hidden_d, n_heads)
        
        # 5a) Layer normalization 2        
        self.ln2 = nn.LayerNorm((self.n_patches ** 2 + 1, self.hidden_d))
        
        # 5b) Encoder MLP 编码MLP        
        self.enc_mlp = nn.Sequential(            
            nn.Linear(self.hidden_d, self.hidden_d),            
            nn.ReLU()        
        )
        
        # 6) Classification MLP 分类MLP        
        self.mlp = nn.Sequential(            
            nn.Linear(self.hidden_d, out_d),#隐藏层8,输出层10.一个N是8个特征                   
            nn.Softmax(dim=-1)        
        )
        
    def forward(self, images):    
        # Dividing images into patches        
        n, c, w, h = images.shape        
        patches = images.reshape(n, self.n_patches ** 2, self.input_d)
        
        # Running linear layer for tokenization        
        tokens = self.linear_mapper(patches)
        
        # Adding classification token to the tokens        
        tokens = torch.stack([torch.vstack((self.class_token, tokens[i])) for i in range(len(tokens))])
        
        # Adding positional embedding        
        tokens += get_positional_embeddings(self.n_patches ** 2 + 1, self.hidden_d).repeat(n, 1, 1)
        
        # TRANSFORMER ENCODER BEGINS ###################################        
        # NOTICE: MULTIPLE ENCODER BLOCKS CAN BE STACKED TOGETHER ######        
        # Running Layer Normalization, MSA and residual connection        
        out = tokens + self.msa(self.ln1(tokens))
        
        # Running Layer Normalization, MLP and residual connection        
        out = out + self.enc_mlp(self.ln2(out))        
        # TRANSFORMER ENCODER ENDS   ###################################
        
        # Getting the classification token only        
        out = out[:, 0]#获得分类标记token
        
        return self.mlp(out)#进行分类

我们模型的输出现在是一个 (N, 10) 张量,一张图10种类别的可能性
完整测试脚本如下:

# import torch
#
# # 假设输入 tokens 的维度为 (N, 49, 8)
# N = 2  # 假设批量大小为 2
# num_tokens = 49  # 每个样本中 token 的数量
# feature_size = 8  # 所有 tokens 的特征维度
#
# # 创建随机输入 tokens,形状为 (N, num_tokens, feature_size)
# tokens = torch.rand(N, num_tokens, feature_size)
#
# # 定义 class token(只有一行,与 token 的特征维度一致)
# class_token = torch.ones(1, feature_size)  # 形状为 (1, 8)
#
# # 在每个 token 上方添加 class token
# # 使用列表推导式
# tokens_with_class_token = torch.stack(
#     [torch.vstack((class_token, tokens[0]))]
# )
#
# # 输出结果的形状
# print("Input tokens shape:", tokens.shape)  # (N, 49, 8)
# print("Class token shape:", class_token.shape)  # (1, 8)
# print("Result tokens with class token shape:", tokens_with_class_token.shape)  # (N, 49, 2, 8)
#
# # 验证是否在每个 token 上方添加了 class token
#
# for i in range(N):
#     print(f"Sample {i} tokens with class token:")
#     print(tokens_with_class_token[i])
# import torch
#
# def get_positional_embeddings(sequence_length, d):
#     result = torch.ones(sequence_length, d)  # 初始化全1的张量
#     for i in range(sequence_length):  # 遍历每个位置
#         for j in range(d):  # 遍历每个特征
#             if j % 2 == 0:
#                 result[i][j] = torch.sin(torch.tensor(i) / (10000 ** (j / d)))  # 偶数索引使用sin
#             else:
#                 result[i][j] = torch.cos(torch.tensor(i) / (10000 ** ((j - 1) / d)))  # 奇数索引使用cos
#     return result  # 返回嵌入矩阵
#
# # 示例调用
# sequence_length = 10  # 例如有10个子图
# d = 8  # 每个子图的特征维度
# positional_embeddings = get_positional_embeddings(sequence_length, d)
# print(positional_embeddings.shape)
import torch
import torch.nn as nn
import numpy as np


class MyMSA(nn.Module):
    def __init__(self, d, n_heads=2):    
        super(MyMSA, self).__init__()        
        self.d = d        
        self.n_heads = n_heads        
        assert d % n_heads == 0, f"Can't divide dimension {d} into {n_heads} heads"        
        d_head = int(d / n_heads)
        
        self.q_mappings = nn.ModuleList([nn.Linear(d_head, d_head) for _ in range(self.n_heads)])        
        self.k_mappings = nn.ModuleList([nn.Linear(d_head, d_head) for _ in range(self.n_heads)])        
        self.v_mappings = nn.ModuleList([nn.Linear(d_head, d_head) for _ in range(self.n_heads)])        
        self.d_head = d_head        
        self.softmax = nn.Softmax(dim=-1)
        
    def forward(self, sequences):    
        result = []        
        for sequence in sequences:        
            seq_result = []            
            for head in range(self.n_heads):            
                q_mapping = self.q_mappings[head]                
                k_mapping = self.k_mappings[head]                
                v_mapping = self.v_mappings[head]
                
                # Extract head-specific part                
                seq = sequence[:, head * self.d_head: (head + 1) * self.d_head]                
                q, k, v = q_mapping(seq), k_mapping(seq), v_mapping(seq)
                
                attention = self.softmax(q @ k.T / (self.d_head ** 0.5))                
                seq_result.append(attention @ v)            
           result.append(torch.hstack(seq_result))        
       return torch.cat([torch.unsqueeze(r, dim=0) for r in result])


def get_positional_embeddings(sequence_length, d):
    result = torch.ones(sequence_length, d)    
    for i in range(sequence_length):        
        for j in range(d):       
            result[i][j] = np.sin(i / (10000 ** (j / d))) if j % 2 == 0 else np.cos(i / (10000 ** ((j - 1) / d)))    
    return result
    

class MyViT(nn.Module):
    def __init__(self, input_shape, n_patches=7, hidden_d=8, n_heads=2, out_d=10):    
        super(MyViT, self).__init__()        
        self.input_shape = input_shape#(1,28,28)        
        self.n_patches = n_patches        
        self.n_heads = n_heads        
        print(input_shape)#为28,28        
        assert input_shape[1] % n_patches == 0, "Input shape not entirely divisible by number of patches"        
        assert input_shape[2] % n_patches == 0, "Input shape not entirely divisible by number of patches"        
        self.patch_size = (input_shape[1] // n_patches, input_shape[2] // n_patches)        
        self.hidden_d = hidden_d
        
        # 1) Linear mapper        
        self.input_d = int(input_shape[0] * self.patch_size[0] * self.patch_size[1])#这应该为16,通道数1*子图宽4*子图高4
        
        self.linear_mapper = nn.Linear(self.input_d, self.hidden_d)
        
        # 2) Classification token        
        self.class_token = nn.Parameter(torch.rand(1, self.hidden_d))
        
        # 4) Layer normalization 1        
        self.ln1 = nn.LayerNorm((self.n_patches ** 2 + 1, self.hidden_d))
        
        # 4b) Multi-head Self Attention (MSA)        
        self.msa = MyMSA(self.hidden_d, n_heads)
        
        # 5) Layer normalization 2        
        self.ln2 = nn.LayerNorm((self.n_patches ** 2 + 1, self.hidden_d))
        
        # 5b) Encoder MLP        
        self.enc_mlp = nn.Sequential(      
            nn.Linear(self.hidden_d, self.hidden_d),            
            nn.ReLU()        
        )
        
        # 6) Classification MLP        
        self.mlp = nn.Sequential(       
            nn.Linear(self.hidden_d, out_d),            
            nn.Softmax(dim=-1)        
        )
        
    def forward(self, images):    
        # Dividing images into patches        
        n, c, w, h = images.shape#1,1,28,28        
        patches = images.reshape(n, self.n_patches ** 2, self.input_d)#1,49,16        
        print(f"input_d:{self.input_d}")#16        
        print(f"Patches shape1: {patches.shape}")  # Output after patching
        
        # Running linear layer for tokenization 线性层        
        tokens = self.linear_mapper(patches)#(1,49,16)变为(1,49,8)        
        print(f"Tokens shape after linear mapping: {tokens.shape}")  # Output after linear mapping
        
        # Adding classification token to the tokens        
        tokens = torch.stack([torch.vstack((self.class_token, tokens[i])) for i in range(len(tokens))])#增加分类标记(1,50,8)        
        print(f"Tokens shape after adding classification token: {tokens.shape}")  # Output after adding classification token
        
        # Adding positional embedding        
        tokens += get_positional_embeddings(self.n_patches ** 2 + 1, self.hidden_d).repeat(n, 1, 1)#嵌入位置信息 (1,50,8)        
        print(f"Tokens shape after adding positional embedding: {tokens.shape}")  # Output after adding positional embeddings
        
        # TRANSFORMER ENCODER BEGINS ###################################        
        # Running Layer Normalization, MSA and residual connection        
        out = tokens + self.msa(self.ln1(tokens))#多头注意力(1,50,8)        
        print(f"Output shape after MSA: {out.shape}")  # Output after MSA
        
        # Running Layer Normalization, MLP and residual connection        
        out = out + self.enc_mlp(self.ln2(out))#经过Layer-norm MLP 残差 (1,50,8)        
        print(f"Output shape after MLP: {out.shape}")  # Output after MLP        
        # TRANSFORMER ENCODER ENDS   ###################################
        
        # Getting the classification token only        
        out = out[:, 0]#输出(n,8)一个图,8个特征标记,最后用mlp生成(n,10)一张图可能为10个数字可能        
        print(f"Final output shape (classification token): {out.shape}")  # Shape of the final output
        
        return self.mlp(out)
        

# Define input shape (batch size, channels, width, height)
input_shape = (1, 28, 28)
input_image_shape=(1,1,28,28)
# Create model
model = MyViT(input_shape=input_shape)

# Create random input tensor with shape (1, 1, 28, 28)
images = torch.rand(input_image_shape)

# Forward pass
output = model(images)
print(f"10类 output:{output.shape}")

可以运行一下看看维度变化

结果应该为:

img

如何学习大模型

下面这些都是我当初辛苦整理和花钱购买的资料,现在我已将重要的AI大模型资料包括市面上AI大模型各大白皮书、AGI大模型系统学习路线、AI大模型视频教程、实战学习,等录播视频免费分享出来,需要的小伙伴可以扫取。

现在社会上大模型越来越普及了,已经有很多人都想往这里面扎,但是却找不到适合的方法去学习。

作为一名资深码农,初入大模型时也吃了很多亏,踩了无数坑。现在我想把我的经验和知识分享给你们,帮助你们学习AI大模型,能够解决你们学习中的困难。

一、AGI大模型系统学习路线

很多人学习大模型的时候没有方向,东学一点西学一点,像只无头苍蝇乱撞,我下面分享的这个学习路线希望能够帮助到你们学习AI大模型。

在这里插入图片描述

二、AI大模型视频教程

在这里插入图片描述

三、AI大模型各大学习书籍!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

四、AI大模型各大场景实战案例

在这里插入图片描述

五、AI大模型面试题库

在这里插入图片描述

五、结束语

学习AI大模型是当前科技发展的趋势,它不仅能够为我们提供更多的机会和挑战,还能够让我们更好地理解和应用人工智能技术。通过学习AI大模型,我们可以深入了解深度学习、神经网络等核心概念,并将其应用于自然语言处理、计算机视觉、语音识别等领域。同时,掌握AI大模型还能够为我们的职业发展增添竞争力,成为未来技术领域的领导者。

再者,学习AI大模型也能为我们自己创造更多的价值,提供更多的岗位以及副业创收,让自己的生活更上一层楼。

因此,学习AI大模型是一项有前景且值得投入的时间和精力的重要选择。

Vision Transformer综述 总篇 Transformer首先应用于自然语言处理领域,是一种以自我注意机制为主的深度神经网络。由于其强大的表示能力,研究人员正在寻找将变压器应用于计算机视觉任务的方法。在各种视觉基准测试中,基于变压器的模型表现类似或优于其他类型的网络,如卷积和循环神经网络。由于其高性能和较少的视觉特异性感应偏倚需求,变压器正受到计算机视觉界越来越多的关注。在本文中,我们对这些视觉转换器模型进行了综述,并根据不同的任务对其进行了分类,分析了它们的优缺点。我们探讨的主要类别包括骨干网络、高/中级视觉、低级视觉和视频处理。我们还包括 阅读详情

相关推荐

Vision Transformer结构原理+实战项目+代码讲解

本文将详细讲解Vision Transformer的原理结构和代码,并通过实践项目帮助理解ViT的具体应用。

qq_68608661的博客 3411

浅谈Vision Transformer

Vision Transformer(ViT)的模型框架如下图:第一步: 使用ViT实现图片分类,首先需将图片分割成不同的块(Patches);第二步:向量化,将各个张量拉伸成向量。假如每个Patch对应的张量的形状为(3,16,16),对应向量的形状为(1,768),对应二维矩阵[num_token, token_dim]。原图被分割为n个Patch,就能得到向量x1~xn。第三步:在输入Transformer Encoder之前注意需要加上[class]token以及Position Embedding

weixin_47357629的博客 2613

【第30篇】Vision Transformer

另一个最近的相关模型是图像 GPT (iGPT) ,它在降低图像分辨率和色彩空间后将 Transformers 应用于图像像素。该模型作为生成模型以无监督方式进行训练,然后可以对生成的表示进行微调或线性探测以提高分类性能,在 ImageNet 上实现 72% 的最大准确度。我们的工作增加了越来越多的论文,这些论文探索了比标准 ImageNet 数据集更大规模的图像识别。使用额外的数据源可以在标准基准上实现最先进的结果。此外,Sun 等人研究了 CNN 的性能如何随数据集大小而变化,而 Kolesnikov

2401_83816970的博客 1045

详解视觉Transformers

从2017年在《Attention is All You Need》中首次提出以来,Transformer模型已经成为自然语言处理(NLP)领域的最新技术。在2021年,论文《An Image is Worth 16x16 Words》成功地将Transformer应用于计算机视觉任务。从那时起,基于Transformer的各种架构陆续被提出用于计算机视觉。Transformer是一种利用注意力机制作为主要学习机制的机器学习模型,它很快成为了序列到序列任务(如语言翻译)的最新技术。

u012374012的专栏 7868

Vision Transformer学习笔记(2020 ICLR)

本文介绍了一种名为Vision Transformer (ViT)的新模型,该模型直接将Transformer应用于图像识别任务。不同于以往在计算机视觉中使用自注意力机制时引入特定于图像的归纳偏差,ViT将图像视为一系列补丁,并通过标准Transformer编码器处理这些补丁。实验表明,当在大规模数据集上进行预训练时,ViT在多种图像分类基准上取得了与最先进的卷积神经网络相当甚至更好的结果,同时所需的计算资源更少。

朴实无华的研究生活 1222

【Vision Transformer】辅助理解笔记

再比如,图书馆(source)里有很多书(value),为了方便查找,我们给书做了编号(key)。当我们想要了解漫威(query)的时候,我们就可以看看那些动漫、电影、甚至二战(美国队长)相关的书籍。为了提高效率,并不是所有的书都会仔细看,针对漫威来说,动漫,电影相关的会看的仔细一些(权重高),但是二战的就只需要简单扫一下即可(权重低)。当我们全部看完后就对漫威有一个全面的了解了。

Akiiiira的博客 1291

Vision Transformer原理及代码实战

Vision Transformer原理及代码实战 背景 论文地址:https://arxiv.org/pdf/2010.11929.pdf 代码参考:https://github.com/BR-IDL/PaddleViT 在NLP领域,Transformer深度学习技术已经"统治"了该领域; 在CV领域,从2020年底开始,Vision Transformer(ViT)成为该方向的研究热点;基于Transformer的模型在多个视觉任务中已经超越CNN模型达到SOTA性能的程度; Transformer概

weixin_40620310的博客 5577

Transformer综述(A Survey on Vision Transformer) 阅读学习笔记(一)----transformer的发展,transformer的基本结构和原理

论文综述: 文章主旨:在本文中,我们回顾了这些视觉Transformer模型,将它们分为不同的任务,并分析了它们的优缺点。我们探讨的主要类别包括主干网络、高/中级视觉、低级视觉和视频处理。我们还包括有效的Transformer方法,用于将Transformer推进基于设备的实际应用。此外,我们还简要介绍了计算机视觉中的自我注意机制,因为它是Transformer的基本组成部分。在本文的最后,我们讨论了视觉Transformer面临的挑战,并提供了几个进一步的研究方向。 高/中级视觉处理 高级视觉处理:对图

sazass的博客 7906

Vision Transformer详解

论文名称: An Image Is Worth 16x16 Words: Transformers For Image Recognition At Scale 论文下载链接:https://arxiv.org/abs/2010.11929 原论文对应源码:https://github.com/google-research/vision_transformer Pytorch实现代码: pytorch_classification/vision_transformer 文章目录前言模型详解Vision

霹雳吧啦Wz 34万+

Transformer综述(A Survey on Vision Transformer) 阅读学习笔记(二)-- transformer在计算机视觉领域的发展和应用

论文综述: 文章主旨:在本文中,我们回顾了这些视觉Transformer模型,将它们分为不同的任务,并分析了它们的优缺点。我们探讨的主要类别包括主干网络、高/中级视觉、低级视觉和视频处理。我们还包括有效的Transformer方法,用于将Transformer推进基于设备的实际应用。此外,我们还简要介绍了计算机视觉中的自我注意机制,因为它是Transformer的基本组成部分。在本文的最后,我们讨论了视觉Transformer面临的挑战,并提供了几个进一步的研究方向。 A Survey on Vision

sazass的博客 8223

视觉Transformer实战 | Vision Transformer(ViT)详解与实现

在计算机视觉领域,卷积神经网络 (CNN) 长期以来一直是处理图像任务的主流架构。然而,随着 Transformer 在自然语言处理领域的巨大成功,研究人员开始探索将这种基于自注意力机制的架构应用于视觉任务。Vision Transformer (ViT) 是这一探索的重要里程碑,它首次证明了纯 Transformer 架构在图像分类任务上可以超越最先进的 CNN 模型。本文将详细介绍 ViT 的技术原理,并使用 PyTorch 从零开始构建 ViT 模型用于图像分类任务。

盼小辉丶的博客 4万+

【Transformer系列】深入浅出理解ViT(Vision Transformer)模型

【Transformer系列】深入浅出理解ViT(Vision Transformer)网络模型

IT菜鸟 6万+

从零开始VCS+Verdi 安装过程

从零开始VCS+Verdi 安装及破解过程 主要参考:VCS+Verdi 安装及破解过程(CentOS7)-----FPGA开发 1. 安装文件结构 –vcs-mx_vL-2016.06 –verdi_vL-2016.06-1 –scl_v11.9 #此程序统一管理license等破解文件 –SynopsysInstaller.rar #此程序用于安装以上3个程序 –sc...

Ztrans的博客 2万+

Vision Transformer学习笔记

由于在之前的文章中已经详细解读过Transformer的架构,ViT中的Transformer没有任何变动,因此重点讲述ViT对图像专门设计的方法。认为这个Token在不断的执行注意力计算的过程中,是可以学习到整个图像的全局信息的,并且只需要将这个Token送入后续的下游任务,即可取得十分出色的效果。我们再回到Transformer类中,在完成了Transformer Encoder的定义后,最后需要定义一个分类头,即一个简单的MLP即完成了对Transformer的定义过程。

weixin_45671402的博客 1260

从零开始:小鼠Bulk RNA-seq全流程实战指南

本文提供了一份详尽的小鼠Bulk RNA-seq全流程实战指南,涵盖从软件环境搭建、原始数据质控与清洗、序列比对、基因定量到下游差异表达分析与功能富集。内容面向生物信息学新手,通过清晰的代码示例和步骤解析,帮助读者系统掌握Bulk RNA-seq数据分析的核心技能,并产出可靠的生物学洞见。

lake5的博客 715

汇川PLC用户程序结构详解:如何像搭积木一样配置多任务?附EtherCAT最佳实践

本文深入解析汇川PLC用户程序结构,阐述如何像搭积木一样,通过POU、FB、FC等模块化组件高效配置多任务。文章重点结合EtherCAT通讯场景,分享了任务周期、优先级设置的最佳实践与关键避坑指南,帮助工程师构建稳定、高效的运动控制系统。

weixin_29184075的博客 541

Transformer学习笔记+Vision Transformer

Transformer学习笔记+Vision Transformer 参考链接: 详解Transformer (Attention Is All You Need) 【唐宇迪】transformer算法解读及其在CV领域应用 Transformer解读(论文 + PyTorch源码) Transfomer可以理解为将输入重新生成为集成了上下文所有相关信息的新的输出,即对输入的每一个向量做一个重构,使其不仅仅只代表自己,而是有一种全局的感觉。 self-attention(自注意力机制) 1.什么是self-

qq_51682716的博客 2480

迈创Cameralink采集卡与MIL软件集成开发实战指南

本文详细介绍了迈创Cameralink采集卡与MIL软件的集成开发实战指南,涵盖硬件安装、驱动配置、MIL软件深度设置及图像采集处理等关键步骤。特别适合工业视觉检测和医疗影像领域,通过优化配置和性能调优,实现高速稳定的图像处理。

weixin_29204205的博客 423

【Timm】搭建Vision Transformer系列实践,终于见面了,Timm库!

搭建Vision Transformer系列实践,终于见面了,Timm库!手把手搭建transformer框架理念和实践的资源!工具了解很重要!根本就不是完全自己搭建哇!有现成的工具!

MengYa_Dream的博客 5028

自动驾驶技术路线之争:纯视觉与多模态融合的成本与性能博弈

本文深入剖析了自动驾驶领域两大主流技术路线——纯视觉与多模态融合的本质差异。文章从成本结构、性能博弈与商业逻辑等多维度展开,揭示了纯视觉方案在硬件成本与数据算力投入上的权衡,以及多模态融合方案在安全冗余与感知稳定性方面的优势。这场技术路线之争,实则是车企在性能、成本与商业化落地之间寻求平衡的综合博弈。

weixin_29038303的博客 390

手教你如何使用Simulink和相关工具箱模拟和实现无人机的多机接力信号中继的建模

扩大通信范围:单架无人机的通信能力有限,通过多机接力的方式可以显著扩展通信覆盖范围。提高可靠性:当某架无人机出现故障或电量不足时,其他无人机可以接管其任务,确保通信不中断。适应复杂环境:在山区、森林等复杂地形中,多机接力可以绕过障碍物,建立稳定的通信链路。使用Communications Toolbox中的模块来模拟无线通信链路。例如,可以使用模块模拟噪声信道,或者使用模块计算信号衰减。matlab深色版本。

MHD0815的博客 767

Vision Transformer模型架构详解

2019年开始,自然语言处理(NLP)领域抛弃了循环神经网络(RNN)序列依赖的问题,开始采用Attention is All you need的Transformer结构[1],其中的Attention是一种可以让模型专注于重要的信息并能够充分学习和吸收的技术。在NLP领域中,伴随着各种语言Transformer模型的提出使得多项语言处理任务的精度和模型深度开始飞速提升。由于基于Transformer的预训练语言模型非常成功,研究者开始探索其在视觉领域的应用。

张小殊的博客 9729

保姆级教程:用Docker Compose一键部署WVP-PRO+ZLM+录像服务(含Nginx反代)

本文提供了一套完整的容器化GB28181视频平台部署方案,通过Docker Compose实现WVP-PRO、ZLMediaKit(ZLM)和录像服务的一键式部署。详细介绍了环境准备、架构设计、容器化部署实战以及高级配置优化,帮助用户快速搭建高性能视频监控管理平台,适用于安防行业和物联网设备管理场景。

weixin_30415113的博客 861

Vision Transformer之Pyramid Vision Transformer

Pyramid Vision Transformer: A Versatile Backbone for Dense Prediction without Convolutions 文章目录Pyramid Vision Transformer: A Versatile Backbone for Dense Prediction without Convolutions前言与ViT和CNN的比较下游任务分类检测分割其他的一些细节链接 原始文档:https://www.yuque.com/lart/paper

画心 4611

【DaVinci Developer工具实战】03 -导入xml文件

本文档描述了 DaVinci Developer 导入数据的的具体功能 ,它适用于导入AUTOSAR SWC 模板【AUTOSAR XML 文件】 文件。

1991

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

上一篇: 关于什么是Agent、如何构建Agent,最近最值得读的文章(没有之一)
下一篇: 吴恩达新书封神!吹爆这份最真实的AI求职指南《如何构建自己的AI职业》
我爱学大模型
博客等级 码龄3年 7883粉丝 687原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值