Base LLM | 从 NLP 到 LLM 的算法全栈教程 第四天

Seq2Seq架构

任务类别与seq2seq

  • 多对一(Many-to-One):将整个序列信息压缩成一个特征向量,用于文本分类、情感分析等任务。
  • 多对多(Many-to-Many, Aligned):为输入序列的每一个词元(Token)都生成一个对应的输出,如词性标注、命名实体识别等。
  • 一对多(One-to-Many):从一个固定的输入(如一张图片、一个类别标签)生成一个可变长度的序列,例如图像描述生成、音乐生成等。
  • 多对多(Many-to-Many, Unaligned):输入序列和输出序列的长度可能不相等 ,且元素之间没有严格的对齐关系。最典型的例子就是机器翻译。

2014年,研究者们提出了序列到序列(Sequence-to-Sequence, Seq2Seq) 架构,它成功地将一种通用的编码器-解码器(Encoder-Decoder) 架构应用于序列转换任务。

模型同样被拆分为两个组件,其中编码器 扮演"阅读和理解"的角色,负责接收整个输入序列,并将其信息压缩成一个固定长度的上下文向量(Context Vector) C,这个向量即为输入序列的"语义概要"。解码器则扮演"组织语言并生成"的角色,它接收上下文向量 C 作为初始信息,然后逐个生成输出序列中的词元。

编码器就是常规的LSTM架构,上下文变量C可以由每一个时间步的隐藏状态h经过线性变换或者平均池化得到,也可以是最后一个神经步的ht和ct。encoder可以用双向RNN以获得更丰富的表征。解码器用于作为生成器,其初始化依赖于上下文向量C,然后进入自回归生成阶段,<SOS>(也有称<BOS>)和C作为开始,直到生成<EOS>结束。

编码器和解码器的embedding共享问题: 若源语言和目标语言的词汇表彼此独立(如未采用联合子词/合并词表的英译中),通常选择不共享,否则是可以共享embedding的。

**上下文变量C在解码器中的交互:**1.作为初始化参数传入,但是对于长序列容易遗忘;2.不改变零向量的初始化状态,但是在每一个时间步进行拼接,改变了特征维度。3.C和h进行逐向量相加。

目标函数: 通常是交叉熵损失 Cross-Entropy Loss )会计算这个预测概率分布 pt​ 与真实目标 yt 之间的差异,它的本质是取出 ptpt​ 中对应真实词元 ytyt​ 的概率值并取负对数,即 Losst=−log⁡pt(yt)。PyTorch 为例,通常需要调整维度(如将 (N, L, C) 展平或 permute(N, C, L))以适配 CrossEntropyLoss 接口,并且需要忽略pad位置的损失

训练与推理模式

**训练时使用教师强制 (Teacher Forcing),**在教师强制模式下,解码器在计算第 tt 步的输出时,它的输入不再是上一时刻的预测值 yt−1′,而是直接使用数据集中真实的标签值 yt−1​。通过这种方式,解码器的每个时间步都能接收到正确的历史信息,避免了误差累积,能够显著提升收敛稳定性与速度。

推理时采用自回归 :解码器会以 CC 和 <SOS> 为初始输入生成第一个词元 y1,随后将 y1​ 作为下一时间步的输入生成 y2​,不断将上一时刻的输出作为下一时刻的输入循环此过程,直到生成 <EOS> 标志或达到预设的最大输出长度时停止。实际上有可能采用束搜索。

代码实践

编码器代码

python 复制代码
class Encoder(nn.Module):
    def __init__(self, vocab_size, hidden_size, num_layers):
        super(Encoder, self).__init__()
        self.embedding = nn.Embedding(
            num_embeddings=vocab_size,
            embedding_dim=hidden_size
        )
        self.rnn = nn.LSTM(
            input_size=hidden_size,
            hidden_size=hidden_size,
            num_layers=num_layers,
            batch_first=True,
            bidirectional=False
        )

    def forward(self, x):
        # x shape: (batch_size, seq_length)
        embedded = self.embedding(x)
        # 返回最终的隐藏状态和细胞状态作为上下文
        _, (hidden, cell) = self.rnn(embedded)
        return hidden, cell

解码器代码

python 复制代码
class Decoder(nn.Module):
    def __init__(self, vocab_size, hidden_size, num_layers):
        super(Decoder, self).__init__()
        self.embedding = nn.Embedding(
            num_embeddings=vocab_size,
            embedding_dim=hidden_size
        )
        self.rnn = nn.LSTM(
            input_size=hidden_size,
            hidden_size=hidden_size,
            num_layers=num_layers,
            batch_first=True
        )
        self.fc = nn.Linear(in_features=hidden_size, out_features=vocab_size)

    def forward(self, x, hidden, cell):
        # x shape: (batch_size),只包含当前时间步的token
        x = x.unsqueeze(1) # -> (batch_size, 1)

        embedded = self.embedding(x)
        # 接收上一步的状态 (hidden, cell),计算当前步
        outputs, (hidden, cell) = self.rnn(embedded, (hidden, cell))

        predictions = self.fc(outputs.squeeze(1)) # -> (batch_size, vocab_size)
        return predictions, hidden, cell

seq2seq整合代码

python 复制代码
class Seq2Seq(nn.Module):
    def __init__(self, encoder, decoder, device):
        super(Seq2Seq, self).__init__()
        self.encoder = encoder
        self.decoder = decoder
        self.device = device

    def forward(self, src, trg, teacher_forcing_ratio=0.5):
        batch_size = src.shape[0]
        trg_len = trg.shape[1]
        trg_vocab_size = self.decoder.fc.out_features

        outputs = torch.zeros(batch_size, trg_len, trg_vocab_size).to(self.device)
        hidden, cell = self.encoder(src)

        # 第一个输入是 <SOS>
        input = trg[:, 0]

        for t in range(1, trg_len):
            output, hidden, cell = self.decoder(input, hidden, cell)
            outputs[:, t, :] = output

            # 决定是否使用 Teacher Forcing
            teacher_force = random.random() < teacher_forcing_ratio
            top1 = output.argmax(1)
            # 如果 teacher_force,下一个输入是真实值;否则是模型的预测值
            input = trg[:, t] if teacher_force else top1

        return outputs

seq2seq架构应用空间广阔:语音识别(Audio-to-Text),图像描述生成(Image-to-Text),文本到语音(Text-to-Speech, TTS),问答系统(QA)。

应用瓶颈:信息稀释(传入的C,信息会逐渐变弱);缺乏倾向性:每一个词都获取的全量C,没有特别关注某一部分。

注意力机制

编码器 Encoderforward 函数的返回值需要改变,除了最后一个时间步的隐藏状态 (hidden, cell),还需要返回所有时间步的输出 outputs

新增 Attention 模块 :创建一个独立的 nn.Module 类来实现注意力的计算逻辑。其 forward 方法接收解码器状态 (Query) 和编码器所有输出 (Keys/Values),返回计算得到的上下文向量。

解码器 Decoder:解码器的结构变化是最大的。它需要实例化一个 Attention 模块。解码器的 forward 函数通常以循环的方式逐个时间步解码,因为在第 tt 步计算注意力时需要依赖第 t−1步的解码器状态。需要强调的是,RNN 解码本身就是按时间步顺序计算,不能在时间维度并行,Attention 并未改变这一点。相较于"整序列一次性送入 RNN"的写法,逐步解码更便于在每步显式计算注意力并灵活插入教师强制等策略(按比例教师强制)。

注意力机制的类型:Soft Attention 所有 位置都计算一个注意力权重,这些权重是 0 到 1 之间的浮点数(经 Softmax 归一化),然后进行加权求和。开销极大,但是端到端可微。Hard Attention 在每一步只选择一个最相关的输入位置。可以看作是一种"非 0 即 1"的注意力分配,即选中的位置权重为 1,其他所有位置的权重均为 0。选择过程是离散的、不可微的,所以无法使用常规的反向传播算法进行训练,通常需要借助强化学习等更复杂的技巧。

Transformer理解

位置编码

add& Layer norm

相关推荐
柯西劝我别收敛9 分钟前
[AI-INFRA] HAMI VGPU 系列01:HAMI-Webhook
人工智能
实在智能RPA13 分钟前
Agent能适配不同行业的合规要求吗?——2026年企业级AI Agent合规技术架构与落地全解析
人工智能·ai·架构
柯西劝我别收敛16 分钟前
[AI-INFRA] HAMI VGPU 系列02:HAMI-Device-Plugin
人工智能
lizz66623 分钟前
Hermes-Agent 新手安装指南(言简意赅版)
人工智能·ai
Logintern0923 分钟前
【大语言模型应用】利用 DuckDuckGo 和 Tavily 搜索引擎
人工智能·搜索引擎·语言模型
chao_66666633 分钟前
2026定制AI玩具/AI硬件头部厂商全解析,定制需求选型参考
人工智能
gorgeous(๑>؂<๑)39 分钟前
【CVPR26-陶大程-南洋理工】启发式推理先验助力数据高效型指代目标检测
人工智能·目标检测·计算机视觉
Sharewinfo_BJ42 分钟前
香港Databricks AI DAYS参会笔记:Agentic Analytics离我们还有多远
大数据·人工智能·数据分析·powerbi
羽师1 小时前
RAG工作流程的执行时机
人工智能