🔥 手撕 Transformer 架构:从零逐层代码实现,彻底搞懂 Attention 机制

前言:作为一名正在入门大模型的大一学生,我在学完 Transformer 架构后一直有种"似懂非懂"的感觉——知道 Self-Attention、知道 Encoder-Decoder、知道位置编码,但把这些组件串起来时脑子就容易乱,因为不同的编码层解码层前缀前连接层等都是调用之前自己所写的输入层等,各种层的类和各种调用函数搞得我头晕脑转,以及里面各种各样的输入输出数据都是多维度的,很多时候分不清不同维度的具体作用和含义。于是我决定跟着课程,逐层手写一遍 Transformer 的 PyTorch 实现,从输入层、编码层、解码层到输出层,一个模块一个模块地啃下来。这篇文章既是我的学习笔记,也希望帮助同样在学习 Transformer 的同学建立完整的知识体系。(对比机器学习和深度学习我真的觉得这个transformer好难好难)



1. Transformer 整体架构概览

在深入代码之前,先用一张"文字版架构图"建立全局视角:

┌─────────────────────────────────────────────────────┐
│                    Transformer                       │
│                                                      │
│  输入(source)               输入(target)              │
│     │                          │                     │
│  ┌──▼──────────────────────┐  ┌─▼──────────────────┐ │
│  │ 词嵌入 + 位置编码        │  │ 词嵌入 + 位置编码    │ │
│  └──┬──────────────────────┘  └─┬──────────────────┘ │
│     │                           │                    │
│  ┌──▼──────────────────────┐    │                    │
│  │    Encoder × N(6)       │    │                    │
│  │  ┌───────────────────┐  │    │                    │
│  │  │ Multi-Head Attn    │  │    │                    │
│  │  │   + Add & Norm     │  │    │                    │
│  │  │ Feed Forward       │  │    │                    │
│  │  │   + Add & Norm     │  │    │                    │
│  │  └───────────────────┘  │    │                    │
│  └──┬──────────────────────┘    │                    │
│     │                           │                    │
│     │    ┌──────────────────────▼──────────────────┐ │
│     │    │         Decoder × N(6)                   │ │
│     │    │  ┌───────────────────────────────────┐  │ │
│     │    │  │ Masked Multi-Head Attn + Add&Norm │  │ │
│     │    │  │ Cross Multi-Head Attn + Add&Norm  │  │ │
│     │    │  │ Feed Forward + Add&Norm           │  │ │
│     │    │  └───────────────────────────────────┘  │ │
│     │    └──────────────────────┬──────────────────┘ │
│     │                           │                    │
│     │                    ┌──────▼──────────┐         │
│     │                    │  Linear + Softmax│         │
│     │                    └──────┬──────────┘         │
│     │                           │                    │
│                             输出概率分布               │
└─────────────────────────────────────────────────────┘

核心数据流

  • 训练时:源序列 → Encoder → 编码器输出(K, V) → Decoder(结合目标序列) → Linear → Softmax → 预测概率
  • 推理时:源序列 → Encoder → 逐词生成(每次将已生成的词作为 Decoder 输入)

下面的代码实现会严格按照 由底向上 的顺序展开。


2. 输入层:词嵌入 + 位置编码

一定要知道词嵌入和位置编码不同维度的含义,才能理解,有个好开头

Transformer 的输入部分由 词嵌入层(Word Embedding)位置编码(Positional Encoding) 两部分组成。

2.1 词嵌入层(Embeddings)

将离散的单词索引映射为连续的稠密向量:

class Embeddings(nn.Module):
    def __init__(self, vocab_size, d_model):
        super().__init__()
        self.vocab_size = vocab_size    # 词汇表大小(去重后单词总数)
        self.d_model = d_model          # 词嵌入维度,如 512
        self.embed = nn.Embedding(vocab_size, d_model)

    def forward(self, x):
        # 映射后乘以 √d_model 进行缩放,平衡梯度,避免梯度消失或爆炸
        return self.embed(x) * math.sqrt(self.d_model)

关键点:为什么要乘以 d m o d e l \sqrt{d_{model}} dmodel
因为 Embedding 层的权重初始化通常是较小值(如均值为 0、方差为 1),而 Positional Encoding 使用的是 sin/cos 函数(值域 [-1, 1])。如果不缩放,词嵌入的数值量级会远小于位置编码,加上位置编码后词向量信息几乎被"淹没"。乘以 d m o d e l \sqrt{d_{model}} dmodel 可以让两者的数值量级保持在相近范围内。

测试示例:输入形状 [2, 4](2 个句子,每个句子 4 个词),经过 Embedding 后变为 [2, 4, 512]

2.2 位置编码(Positional Encoding)

因为 Transformer 没有 RNN 那样的时序结构,所以需要显式地注入位置信息:

class PositionEncoding(nn.Module):
    def __init__(self, d_model, dropout, max_len=60):
        super().__init__()
        self.dropout = nn.Dropout(p=dropout)

        # 初始化位置编码矩阵 [max_len, d_model]
        pe = torch.zeros(max_len, d_model)

        # 位置列向量 [max_len, 1]
        position = torch.arange(0, max_len).unsqueeze(1)

        # 计算 1 / 10000^(2i/d_model)  →  e^(2i * -ln(10000) / d_model)
        div_term = torch.exp(
            torch.arange(0, d_model, 2) * (-math.log(10000.0) / d_model)
        )

        # 偶数维度用 sin,奇数维度用 cos
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)

        # 升维增加 batch 维度 [1, max_len, d_model]
        pe = pe.unsqueeze(0)

        # register_buffer:作为模型一部分保存/加载,但不参与梯度更新
        self.register_buffer('pe', pe)

    def forward(self, x):
        # 词向量 + 位置编码(按句子实际长度截取)
        x = x + self.pe[:, :x.size(1)]
        return self.dropout(x)

核心理解

  • 公式: P E ( p o s , 2 i ) = sin ⁡ ( p o s / 10000 2 i / d m o d e l ) PE_{(pos, 2i)} = \sin(pos / 10000^{2i/d_{model}}) PE(pos,2i)=sin(pos/100002i/dmodel) P E ( p o s , 2 i + 1 ) = cos ⁡ ( p o s / 10000 2 i / d m o d e l ) PE_{(pos, 2i+1)} = \cos(pos / 10000^{2i/d_{model}}) PE(pos,2i+1)=cos(pos/100002i/dmodel)
  • 为什么用 sin/cos?因为三角函数有性质: sin ⁡ ( α + β ) = sin ⁡ α cos ⁡ β + cos ⁡ α sin ⁡ β \sin(\alpha+\beta) = \sin\alpha\cos\beta + \cos\alpha\sin\beta sin(α+β)=sinαcosβ+cosαsinβ,这意味着位置 p o s + k pos+k pos+k 的编码可以由位置 p o s pos pos 的编码线性表示,模型可以学习到相对位置关系
  • register_buffer:位置编码是固定规则产生的,不需要训练,但需要在模型保存/加载时一起持久化

可视化位置编码(代码中 plot_position() 函数):当把位置编码矩阵画出来时,不同维度呈现出不同频率的正弦波,低维度变化慢(捕捉全局位置),高维度变化快(捕捉局部位置),这让模型能在不同粒度上感知位置。


3. 编码器核心组件:注意力机制与辅助模块

我觉得不用死抠注意力的计算公式,只要做到了解即可,甚至attention模块的代码都只需要了解,因为以后应用时肯定是有底层封装好的attention方法供你使用(但还是一定要知道是如何实现的注意力机制,毕竟是transformer的核心,是之前GRU,RNN等都没有的),多头注意力层和前缀前连接层可能需要大家详细理解一下(有助更好理解transformer)

3.1 缩放点积注意力(Scaled Dot-Product Attention)

这是整个 Transformer 的灵魂,公式为:

Attention ( Q , K , V ) = softmax ( Q K T d k ) V \text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V Attention(Q,K,V)=softmax(dk QKT)V

def attention(query, key, value, mask=None, dropout=None):
    d_k = query.size()[-1]  # 词向量维度

    # Q @ K^T / sqrt(d_k)  → 得到注意力分数矩阵
    scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k)

    # 掩码处理:将不需要关注的位置设为极小值(-1e9),softmax 后趋近于 0
    if mask is not None:
        scores = scores.masked_fill(mask == 0, -1e9)

    # softmax 归一化 → 注意力权重
    p_attn = F.softmax(scores, dim=-1)

    if dropout is not None:
        p_attn = dropout(p_attn)

    # 注意力权重 加权求和 V → 最终输出
    return torch.matmul(p_attn, value), p_attn

逐行解读

步骤 操作 输入形状 输出形状 含义
Q @ K^T [B, L, d] @ [B, d, L] [B, L, L] 计算每对词之间的"亲疏关系"
÷ √d_k [B, L, L] [B, L, L] 缩放,防止内积过大导致 softmax 梯度消失
masked_fill [B, L, L] [B, L, L] 把需要遮蔽的位置置为 -∞
softmax(dim=-1) [B, L, L] [B, L, L] 将分数转为概率分布(每行和为 1)
p_attn @ V [B, L, L] @ [B, L, d] [B, L, d] 加权聚合:每个词按注意力权重"吸收"其他词的信息

3.2 多头注意力(Multi-Head Attention)

单头注意力只能从一个角度"看"句子,多头则并行地从多个子空间计算注意力:

class MultiHeadAttention(nn.Module):
    def __init__(self, embed_dim, head, dropout_p=0.1):
        super().__init__()
        assert embed_dim % head == 0
        self.d_k = embed_dim // head      # 每个头的维度,如 512/8 = 64
        self.head = head                   # 头数,论文用 8
        # 4 个线性层:前 3 个分别投影 Q/K/V,最后 1 个投影多头合并后的结果
        self.linears = clones(nn.Linear(embed_dim, embed_dim), 4)
        self.dropout = nn.Dropout(dropout_p)

    def forward(self, query, key, value, mask=None):
        if mask is not None:
            mask = mask.unsqueeze(0)       # 增加头维度

        batch = query.size(0)

        # 核心:线性变换 → 拆分为多头 → 并行计算注意力
        # [B, L, 512] → [B, L, 8, 64] → [B, 8, L, 64]
        query, key, value = [
            model(x).view(batch, -1, self.head, self.d_k).transpose(1, 2)
            for model, x in zip(self.linears, (query, key, value))
        ]

        # 计算缩放点积注意力(每个头独立计算)
        x, self.atten = attention(query, key, value, mask, self.dropout)

        # 合并多头:[B, 8, L, 64] → [B, L, 8, 64] → [B, L, 512]
        x = x.transpose(1, 2).contiguous().view(batch, -1, self.head * self.d_k)

        # 最后再过一个线性层
        return self.linears[-1](x)

维度变化全流程(假设 batch=2, seq_len=4, d_model=512, head=8):

输入 x:           [2, 4, 512]
    ↓ Linear
投影后:           [2, 4, 512]
    ↓ view + transpose
分头后:           [2, 8, 4, 64]     ← 8个头,每个头处理64维
    ↓ attention
注意力输出:        [2, 8, 4, 64]
    ↓ transpose + view
合并:             [2, 4, 512]
    ↓ Linear
最终输出:          [2, 4, 512]

为什么多头有效? 类比 CNN 中的多个卷积核——不同头关注不同类型的关系(语法关系、语义关系、指代关系等)。

3.3 位置前馈网络(Feed Forward)

两层全连接,中间用 ReLU 激活:

class FeedForward(nn.Module):
    def __init__(self, d_model, d_ff, dropout_p=0.1):
        super().__init__()
        self.linear1 = nn.Linear(d_model, d_ff)     # 512 → 2048 扩展
        self.linear2 = nn.Linear(d_ff, d_model)      # 2048 → 512 还原
        self.dropout = nn.Dropout(dropout_p)

    def forward(self, x):
        # FFN(x) = ReLU(xW1 + b1)W2 + b2
        return self.linear2(self.dropout(F.relu(self.linear1(x))))

设计思路:注意力层负责"交流"(词与词之间交换信息),FFN 负责"思考"(对每个词的特征独立做非线性变换)。先升维再降维(512 → 2048 → 512)给模型更大的特征空间来学习。

3.4 层规范化(Layer Normalization)

class LayerNorm(nn.Module):
    def __init__(self, features, eps=1e-6):
        super().__init__()
        self.a = nn.Parameter(torch.ones(features))     # 缩放
        self.b = nn.Parameter(torch.zeros(features))    # 平移
        self.eps = eps

    def forward(self, x):
        mean = x.mean(-1, keepdim=True)   # 对最后一维求均值
        std = x.std(-1, keepdim=True)     # 对最后一维求标准差
        return self.a * (x - mean) / (std + self.eps) + self.b

BN vs LN 面试常考题

对比维度 Batch Normalization Layer Normalization
归一化维度 对 batch 维度(跨样本) 对 feature 维度(单样本内部)
受 batch size 影响 大,小 batch 时不稳定 无影响
NLP 适用性 差(序列长度不一致)
Transformer 使用

3.5 掩码张量

# 产生下三角矩阵(用于 Decoder 的自注意力掩码)
def dm02_test_triu(size):
    temp = np.triu(m=np.ones((1, size, size)), k=0).astype('uint8')
    return torch.from_numpy(1 - temp)   # 上三角 → 下三角

掩码的核心作用:

  • Encoder 中的 Padding Mask:句子的填充位置不参与注意力计算
  • Decoder 中的 Sequence Mask(下三角掩码):防止当前位置"看到"未来的词

4. 编码器子层连接:残差 + 层规范化

每个多头注意力层和前缀前连接层的输出都会经过残差和层规范化处理(Add&Norm),残差连接是为了解决梯度爆炸和梯度消失的(说实话我对残差连接也是一知半解的),层规范化是提高模型效率和准确性(对每个隐藏状态标准化)

每个子层(Multi-Head Attention 或 Feed Forward)的输出都会经过 残差连接 + 层规范化

class SublayerConnection(nn.Module):
    def __init__(self, d_model, dropout=0.1):
        super().__init__()
        self.norm = LayerNorm(d_model)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x, sublayer):
        # 公式:LayerNorm(x + Dropout(Sublayer(x)))
        return self.dropout(self.norm(sublayer(x))) + x

残差连接的价值

  • 让梯度可以"绕过高山直达平原",有效缓解深层网络的梯度消失问题
  • 这解释了为什么 Transformer 可以堆叠 6 层甚至更多层而依然容易训练

这里有一个实现细节值得注意:原论文用的是 LayerNorm(x + Sublayer(x))(Post-LN),而现在的很多实现(包括本代码)将 LayerNorm 放在子层前面(Pre-LN),训练更稳定。


5. 编码器层与完整编码器

编码器是由N个编码器层组成的,论文中N为6

5.1 单个编码器层(EncoderLayer)

具体编码器层架构

将两个子层串联起来:

class EncoderLayer(nn.Module):
    def __init__(self, d_model, self_attn, feed_forward, dropout=0.1):
        super().__init__()
        self.self_attn = self_attn          # 多头自注意力
        self.feed_forward = feed_forward    # 前馈网络
        # 克隆两个子层连接结构
        self.sublayer = clones(SublayerConnection(d_model, dropout), 2)

    def forward(self, x, mask):
        # 子层 1:多头自注意力 + 残差 + LN
        x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, mask))
        # 子层 2:前馈网络 + 残差 + LN
        x = self.sublayer[1](x, lambda x: self.feed_forward(x))
        return x

5.2 完整编码器(Encoder)(由N个编码器层组成,论文中N为6)

具体编码器架构

将 N 个编码器层堆叠起来:

class Encoder(nn.Module):
    def __init__(self, layer, N):
        super().__init__()
        self.layers = clones(layer, N)            # 克隆 N 层
        self.norm = LayerNorm(layer.d_model)       # 最终统一规范化

    def forward(self, x, mask):
        for layer in self.layers:
            x = layer(x, mask)                     # 逐层传递
        return self.norm(x)                        # 最后一层 LN

数据流输入 [2, 4, 512] → 编码器层1 → 编码器层2 → ... → 编码器层N → LayerNorm → 输出 [2, 4, 512]

每一层的输入和输出形状完全相同,这也是残差结构带来的好处——不同层之间的输出可以直接相加,因此可以堆叠任意层数。


6. 解码器层与完整解码器

解码器层多了一个多头注意力机制(也叫掩码多头注意力机制)

6.1 解码器层(DecoderLayer)

解码器比编码器多一个子层,共有 三个子层

class DecoderLayer(nn.Module):
    def __init__(self, d_model, self_attn, src_attn, feed_forward, dropout=0.1):
        super().__init__()
        self.self_attn = self_attn        # ① 带掩码的自注意力(关注目标序列内部)
        self.src_attn = src_attn           # ② 交叉注意力(关注编码器输出)
        self.feed_forward = feed_forward   # ③ 前馈网络
        self.layers = clones(SublayerConnection(d_model, dropout), 3)

    def forward(self, x, encoder_output, source_mask, target_mask):
        # 子层 1:Masked Multi-Head Self-Attention
        x = self.layers[0](x, lambda x: self.self_attn(x, x, x, target_mask))
        # 子层 2:Cross Multi-Head Attention(Q 来自解码器,K,V 来自编码器)
        x = self.layers[1](x, lambda x: self.src_attn(x, encoder_output, encoder_output, source_mask))
        # 子层 3:Feed Forward
        x = self.layers[2](x, lambda x: self.feed_forward(x))
        return x

三个子层的作用

子层 类型 Q 来源 K, V 来源 作用
Masked Self-Attention 目标序列 目标序列 理解已生成词之间的关系(带掩码防作弊)
Cross-Attention 目标序列 编码器输出 让解码器"看到"源序列信息
Feed Forward - - 非线性特征变换

交叉注意力是最精妙的设计:Q 来自解码器当前的表示(“我想知道什么”),K 和 V 来自编码器的输出(“源序列有什么”),这样解码器每一步生成时都能动态地"查阅"源序列中最相关的部分。

6.2 完整解码器(Decoder)

与编码器类似,也堆叠 N 层:

class Decoder(nn.Module):
    def __init__(self, layer, N):
        super().__init__()
        self.layers = clones(layer, N)
        self.norm = LayerNorm(layer.d_model)

    def forward(self, x, encoder_output, source_mask, target_mask):
        for layer in self.layers:
            x = layer(x, encoder_output, source_mask, target_mask)
        return self.norm(x)

7. 输出层:从特征到概率分布

输出层由线性全连接层和softmax激活函数实现,将词向量维度转化成词的个数,每个词的概率分布,最终结果就是概率最大的
在这里插入图片描述

解码器输出的是 [batch, seq_len, d_model] 的特征张量,需要映射为词汇表上的概率分布:

class Generator(nn.Module):
    def __init__(self, d_model, vocab_size):
        super().__init__()
        self.linear = nn.Linear(d_model, vocab_size)   # 512 → vocab_size

    def forward(self, x):
        # 线性映射 + log_softmax → 对数概率分布
        # dim=-1 表示对最后一个维度(词汇表维度)做 softmax
        return F.log_softmax(self.linear(x), dim=-1)

为什么用 log_softmax 而非 softmax?(我觉得用softmax配合CrossEntropyLoss应该效果差不多)

  • 数值稳定性更好(避免概率连乘导致下溢)
  • 配合 NLL Loss(负对数似然损失)直接计算交叉熵,在 PyTorch 中 NLLLoss + log_softmax 等价于 CrossEntropyLoss(在我看来用CrossEntropyLoss编写起来会更简单方便)
  • 对数空间中乘法变加法,计算更高效

输出形状[2, 4, 1000]——每个位置的 1000 维向量表示该位置预测为词汇表中每个词的对数概率。


8. 完整 Transformer 模型组装

最后的步骤就和拼高达一样了,把之前写的一个个组件组成到一起,形成完整transformer框架

最后将所有组件拼装成完整的 Transformer:

class EncoderDecoder(nn.Module):
    def __init__(self, source_embed, encoder, target_embed, decoder, generator):
        super().__init__()
        self.source_embed = source_embed    # 编码器输入处理(Embedding + Position)
        self.encoder = encoder              # 编码器
        self.target_embed = target_embed    # 解码器输入处理(Embedding + Position)
        self.decoder = decoder              # 解码器
        self.generator = generator          # 输出层

    def forward(self, source_x, target_y, source_mask, target_mask):
        # 1. 编码阶段
        encoder_result = self.encode(source_x, source_mask)
        # 2. 解码阶段
        decoder_result = self.decode(target_y, encoder_result, source_mask, target_mask)
        # 3. 生成概率分布
        return self.generator(decoder_result)

    def encode(self, source_x, source_mask):
        embed_x = self.source_embed(source_x)
        return self.encoder(embed_x, source_mask)

    def decode(self, target_y, encoder_output, source_mask, target_mask):
        embed_y = self.target_embed(target_y)
        return self.decoder(embed_y, encoder_output, source_mask, target_mask)

组装过程make_model() 函数):

def make_model():
    c = copy.deepcopy

    # === 编码器部分 ===
    source_embed = Embeddings(vocab_size=1000, d_model=512)
    source_position = PositionEncoding(d_model=512, dropout=0.1)
    self_attn = MultiHeadAttention(embed_dim=512, head=8)
    ff = FeedForward(d_model=512, d_ff=2048)
    encoder_layer = EncoderLayer(512, self_attn, ff, dropout_p=0.2)
    encoder = Encoder(encoder_layer, 6)       # 堆叠 6 层

    # === 解码器部分 ===
    target_embed = c(source_embed)            # 深拷贝复用结构
    target_position = c(source_position)
    decoder_layer = DecoderLayer(512, c(self_attn), c(self_attn), c(ff))
    decoder = Decoder(decoder_layer, 6)       # 堆叠 6 层

    # === 输出层 ===
    generator = Generator(512, 1000)

    # === 组装 ===
    model = EncoderDecoder(
        nn.Sequential(source_embed, source_position),
        encoder,
        nn.Sequential(target_embed, target_position),
        decoder,
        generator
    )
    return model

关键设计点

  1. copy.deepcopy 确保编码器和解码器的每一层有独立的参数,不会被共享
  2. 编码器输入和位置编码用 nn.Sequential 打包,形成完整的输入处理链
  3. 编码器输出 → \rightarrow 解码器交叉注意力层(作为 K, V) → \rightarrow 输出层,整条管线是端到端可微

9. 学习心得与总结

9.1 我走过的弯路

  1. 不要只看图不动手:Transformer 的架构图看了很多遍,一到代码还是懵。写完一遍 PyTorch 实现后,对整个数据流的感觉完全不同了。
  2. 维度变化是关键:Transformer 的大部分 bug 来自维度对不上。建议在纸上或注释里把每一步的 shape 变化都标注清楚,最好的办法还是print大法,走一步print一步,这样是最直观的。
  3. 理解 Attention 的本质:Attention 本质就是一个"加权求和"操作——给重要的信息分配更高的权重。Q、K、V 三个矩阵只是这个操作的三种变体,Q 负责"提问",K 负责"应答",V 负责"提供内容",“K"可以说是"V"的关键信息,比如一张报纸,具体内容是"V”,而报纸的标题就是"K"。

9.2 核心组件一句话总结

组件 一句话
词嵌入 把词 ID 变成稠密向量(把机器看不懂的文字转化成看得懂的数字)
位置编码 给无时序的模型注入位置信息(维度和词嵌入层一样)
自注意力 句子中的词互相"看",动态调整关注度(简单说就是每个词都有自己的权重)
多头注意力 从多个角度同时做注意力(并行执行)
前馈网络 每个词独立做非线性变换(先升维再降维,让模型学到更多内容)
残差连接 让梯度直通底层,解决深层网络的训练难题(直接将每层输入和输出相加)
LayerNorm 将每个样本内部标准化,不受 batch size 影响(批量归一化)
交叉注意力 解码器"查阅"编码器输出的桥梁(其输入是编码器的输出)
Mask 防止信息泄露(训练时不能让解码器看到未来词,不让作弊)

9.3 给同样在学的同学的建议

  • 先理解 Encoder 再啃 Decoder:Decoder 多了 Mask 和 Cross-Attention,复杂度翻倍。先把 Encoder 的 6 层跑通,再整体串联。
  • 适当回看论文:代码写完后重读《Attention Is All You Need》,你会发现论文里的每句话都有了具体的"图像"。
  • 尝试修改超参数:把 head 数从 8 改到 4,把层数从 6 改到 2,观察 shape 变化和训练效果,理解每个超参数影响的是什么。
  • 多进行打印操作:transformer架构中的各种数据都是多维度的,只通过人脑思考的话很容易搞混不同维度的含义,最好的办法就是多打印,看不同数据的shape,才能有助你更好的理解。
  • 借助ai工具助你了解:你可以把transformer的具体代码扔给ai,让他阅读理解之后给你讲解,这是一个很好的办法助你更好的了解代码(最好告诉他你具体哪里没懂)
  • 阅读书籍文献:阅读书籍文献也是很好的办法,我就是借助了书籍和论文来理解的

参考

  • 《Attention Is All You Need》(Vaswani et al., 2017)
  • 课程代码:黑马 AI 大模型开发
  • 参考书籍:《自然语言处理与大语言模型原理详解:从NLP模型到Transformer架构》
    写在最后:这篇文章是我学习 Transformer 过程中的笔记整理,代码按由底向上、逐层递进的方式组织。如果文中有任何理解不到位的地方,欢迎在评论区交流指正。一起加油 🔥
Logo

脑启社区是一个专注类脑智能领域的开发者社区。欢迎加入社区,共建类脑智能生态。社区为开发者提供了丰富的开源类脑工具软件、类脑算法模型及数据集、类脑知识库、类脑技术培训课程以及类脑应用案例等资源。

更多推荐