DataWhale 11月 Happy-LLM T5:搭建一个 Transformer

在深入理解了注意力机制和Encoder-Decoder架构后,来到了最激动人心的环节——亲手搭建一个完整的Transformer模型。深入理解Embedding层和位置编码的工作原理,分享实践的心得体会。

一、Embedding层:

1.1 Embedding的核心作用

技术定义:将离散的token索引映射为连续的向量表示

工作流程

  • 输入:(batch_size, seq_len)的索引矩阵

  • 输出:(batch_size, seq_len, embedding_dim)的向量矩阵

代码实现

self.tok_embeddings = nn.Embedding(args.vocab_size, args.dim)

实例说明

# 假设词表大小为4,输入"我喜欢你"
输入索引: [[0, 1, 2]]  # 对应["我", "喜欢", "你"]
输出向量: 
[
  [0.1, 0.2, 0.3, 0.4],  # "我"的向量表示
  [0.2, 0.3, 0.4, 0.5],  # "喜欢"的向量表示  
  [0.3, 0.4, 0.5, 0.6]   # "你"的向量表示
]

个人理解:Embedding层就像本"词典",每个词对应一个向量"定义",让模型能理解词汇的语义。

1.2 Embedding的技术细节

学习机制

  • Embedding矩阵是可训练参数

  • 通过反向传播学习词汇的语义关系

  • 语义相似的词在向量空间中距离相近

心得:预训练的Embedding往往能显著提升模型性能,特别是在数据量不足的情况下。

二、位置编码:

2.1 为什么需要位置编码?

核心问题:自注意力机制是位置无关的

  • "我喜欢你"和"你喜欢我"在模型看来完全相同

  • 但实际语义截然不同

解决方案:位置编码为每个位置生成独特的"位置指纹"

2.2 正弦余弦位置编码

数学公式

PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))

代码实现

class PositionalEncoding(nn.Module):
    def __init__(self, args):
        super().__init__()
        # 创建位置编码矩阵
        pe = torch.zeros(args.block_size, args.n_embd)
        position = torch.arange(0, args.block_size).unsqueeze(1)
        
        # 计算频率项
        div_term = torch.exp(
            torch.arange(0, args.n_embd, 2) * -(math.log(10000.0) / args.n_embd)
        )
        
        # 分别计算正弦和余弦
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)
        pe = pe.unsqueeze(0)
        self.register_buffer("pe", pe)  # 注册为缓冲区,不参与训练

    def forward(self, x):
        return x + self.pe[:, :x.size(1)].requires_grad_(False)
2.3 位置编码的数学原理

相对位置关系

# 关键性质:PE(pos + k) 可以用 PE(pos) 线性表示
# 这使模型能够学习相对位置关系
  • 通过复数运算证明相对位置的可表示性

  • 正弦余弦函数的周期性支持长序列

  • 不同频率的组合提供丰富的位置信息

心得:位置编码的设计体现了深度学习中的"归纳偏置"——通过先验知识帮助模型更好地学习。

三、完整Transformer实现

3.1 模型架构总览
class Transformer(nn.Module):
    def __init__(self, args):
        super().__init__()
        self.args = args
        self.transformer = nn.ModuleDict(dict(
            wte = nn.Embedding(args.vocab_size, args.n_embd),  # Token Embedding
            wpe = PositionalEncoding(args),                    # 位置编码
            drop = nn.Dropout(args.dropout),                   # Dropout层
            encoder = Encoder(args),                           # 编码器
            decoder = Decoder(args),                           # 解码器
        ))
        self.lm_head = nn.Linear(args.n_embd, args.vocab_size, bias=False)  # 输出层
3.2 前向传播流程
def forward(self, idx, targets=None):
    # 1. Token Embedding
    tok_emb = self.transformer.wte(idx)        # (batch, seq_len) → (batch, seq_len, dim)
    
    # 2. 位置编码
    pos_emb = self.transformer.wpe(tok_emb)    # 加入位置信息
    
    # 3. Dropout
    x = self.transformer.drop(pos_emb)
    
    # 4. 编码器
    enc_out = self.transformer.encoder(x)      # 编码输入序列
    
    # 5. 解码器  
    x = self.transformer.decoder(x, enc_out)   # 解码生成输出
    
    # 6. 输出层
    if targets is not None:
        logits = self.lm_head(x)               # 训练模式:计算所有位置
        loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1))
    else:
        logits = self.lm_head(x[:, [-1], :])   # 推理模式:只计算最后一个位置
        loss = None
    
    return logits, loss

3.3 关键实现细节

参数初始化

def _init_weights(self, module):
    # 线性层和Embedding层使用正态分布初始化
    if isinstance(module, nn.Linear):
        torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
    elif isinstance(module, nn.Embedding):
        torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)

参数统计

def get_num_params(self, non_embedding=False):
    n_params = sum(p.numel() for p in self.parameters())
    if non_embedding:
        n_params -= self.transformer.wte.weight.numel()  # 排除Embedding参数
    return n_params

四、Pre-Norm vs Post-Norm

4.1 架构差异

原始论文(Post-Norm)

输出 = LayerNorm(输入 + 子层(输入))

现代实现(Pre-Norm)

4.2 选择Pre-Norm的原因

训练稳定性

  1. Pre-Norm让梯度流动更顺畅

  2. 减少梯度消失/爆炸问题

  3. 更适合深层网络

现代大语言模型(如LLaMA、GPT)普遍采用Pre-Norm结构

五、个人心得

Transformer架构的精髓在于它完美模拟了人类理解语言的思维方式。当我们阅读一篇文章时,我们不会机械地逐字阅读,而是会快速扫视全文,同时捕捉关键词、理清逻辑关系、理解上下文语境——这正是Transformer自注意力机制的核心思想。它的Embedding层如同为每个词语赋予了独特的"人格特征",位置编码则像给词语标注了精确的"坐标",而多头注意力机制就如同多个专业分析师同时从不同角度解读文本:一个关注语法结构,一个分析情感色彩,一个梳理逻辑关系。这种设计让模型摆脱了传统RNN必须"逐字咀嚼"的束缚,实现了"纵观全局"的突破性进化。更重要的是,这种并行处理的思想不仅大幅提升了计算效率,更关键的是让模型建立了真正的语义理解能力——它能瞬间把握"它"指代的是前文的哪个名词,能理解"虽然...但是..."之间的转折关系,能捕捉字里行间的言外之意。这正是为什么Transformer能够成为大语言模型基石的根本原因:它不是在机械地处理文字,而是在构建一个立体的、关联的、深层次的语义网络,这让我们向真正的机器理解人类语言迈出了最关键的一步。

 资料来源:Happy-LLM

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值