自回归(Autoregressive)模型详解

自回归(Autoregressive, AR)是序列生成任务中的核心范式,广泛应用于自然语言处理(如GPT)、语音合成、时间序列预测等领域。

其核心思想是:序列中每个位置的生成仅依赖于之前已生成的部分,即通过逐步预测下一个元素的方式构建完整序列。


1. 自回归的核心原理


2. 自回归模型的典型结构

(1) 基于RNN的结构(如LSTM、GRU)
  • 原理
    通过循环单元维护隐状态(Hidden State),逐步传递历史信息。

    • 隐状态 ht=f(ht−1,xt),其中 xt​ 是当前输入(通常是上一步的输出 yt−1​)。

  • 缺点
    串行计算导致训练和推理速度慢,长距离依赖易丢失。

(2) 基于Transformer的结构(如GPT、T5)
  • 原理
    使用**掩码自注意力(Masked Self-Attention)**实现并行训练,同时保证自回归特性。

    • 训练时,通过下三角掩码矩阵限制每个位置只能关注其左侧的上下文(如图1)。

    • 推理时,逐步生成序列,每一步将新生成的词追加到输入中。

  • 优点

    • 并行计算加速训练;

      扫描二维码关注公众号,回复: 17598898 查看本文章
    • 长距离依赖捕捉能力强。


3. 自回归 vs 非自回归

特性 自回归(AR) 非自回归(NAR)
生成方式 逐位置生成,依赖历史信息 并行生成所有位置
速度 慢(串行生成) 快(一步或少量步骤生成)
生成质量 高(上下文连贯性强) 可能较低(依赖后处理或迭代优化)
典型模型 GPT、Transformer解码器 NAT(Non-Autoregressive Translation)
适用场景 文本生成、对话系统、高精度合成任务 实时翻译、语音合成等对速度敏感的任务

4. 自回归的关键技术

(1) 掩码机制(Masking)
  • 目标:确保训练时模型无法“偷看”未来信息。

  • 实现

(2) 输入偏移(Shifted Inputs)
  • 目标:对齐训练与推理时的输入分布。

  • 实现

    • 训练时,将目标序列右移一位,并添加起始符 <sos>

    • :目标序列 [A, B, C] → 输入 [<sos>, A, B],输出 [A, B, C]

(3) 解码策略
  • 贪婪搜索(Greedy Search):每一步选择概率最高的词,速度快但可能陷入局部最优。

  • 束搜索(Beam Search):保留多个候选序列,平衡质量与计算成本。

  • 采样(Sampling):按概率分布随机选择,增加多样性(可调节温度系数)。


5. 自回归模型的优缺点

优点
  • 生成质量高:依赖完整历史信息,输出逻辑连贯;

  • 灵活性:适用于变长序列生成;

  • 可解释性:生成过程透明,便于调试。

缺点
  • 速度慢:无法并行生成,长序列推理延迟高;

  • 误差累积:早期生成错误会影响后续步骤;

  • 固定顺序依赖:生成顺序可能影响结果(如从左到右 vs 从右到左)。


6. 自回归模型的应用场景

  1. 文本生成:故事创作、对话系统(如ChatGPT)、代码生成。

  2. 语音合成:将文本逐帧转换为语音信号(如Tacotron)。

  3. 时间序列预测:股票价格预测、天气预测。

  4. 图像生成:按像素或块逐步生成图像(如PixelRNN)。


7. 优化自回归生成效率的方法

  • 缓存机制(KV Cache):在Transformer推理时缓存已计算的键值对(Key-Value),避免重复计算。

  • 分块生成(Chunkwise Generation):将长序列分为多个块,逐块生成(如Transformer-XL)。

  • 模型蒸馏:将大型自回归模型的知识迁移到小型非自回归模型中。

8. 代码示例(PyTorch实现掩码自注意力)

import torch
import torch.nn as nn
import torch.nn.functional as F

class MaskedSelfAttention(nn.Module):
    def __init__(self, embed_size, heads):
        super().__init__()
        self.embed_size = embed_size
        self.heads = heads
        self.head_dim = embed_size // heads

        self.query = nn.Linear(embed_size, embed_size)
        self.key = nn.Linear(embed_size, embed_size)
        self.value = nn.Linear(embed_size, embed_size)
        self.fc_out = nn.Linear(embed_size, embed_size)

    def forward(self, x, mask):
        # x: [batch_size, seq_len, embed_size]
        batch_size, seq_len, _ = x.shape
        
        Q = self.query(x)  # [batch_size, seq_len, embed_size]
        K = self.key(x)    # [batch_size, seq_len, embed_size]
        V = self.value(x)  # [batch_size, seq_len, embed_size]
        
        # 分割多头
        Q = Q.view(batch_size, seq_len, self.heads, self.head_dim).permute(0, 2, 1, 3)
        K = K.view(batch_size, seq_len, self.heads, self.head_dim).permute(0, 2, 1, 3)
        V = V.view(batch_size, seq_len, self.heads, self.head_dim).permute(0, 2, 1, 3)
        
        # 计算注意力分数
        energy = torch.matmul(Q, K.permute(0, 1, 3, 2)) / (self.head_dim ** 0.5)
        
        # 应用掩码
        if mask is not None:
            energy = energy.masked_fill(mask == 0, float("-1e20"))
        
        # Softmax + 注意力权重
        attention = F.softmax(energy, dim=-1)
        
        # 加权求和
        out = torch.matmul(attention, V)
        out = out.permute(0, 2, 1, 3).contiguous().view(batch_size, seq_len, self.embed_size)
        out = self.fc_out(out)
        return out

# 使用示例
embed_size = 512
heads = 8
seq_len = 10
batch_size = 4

model = MaskedSelfAttention(embed_size, heads)
x = torch.randn(batch_size, seq_len, embed_size)
mask = torch.tril(torch.ones(seq_len, seq_len)).unsqueeze(0).unsqueeze(0)  # 下三角掩码
output = model(x, mask)

猜你喜欢

转载自blog.csdn.net/m0_63855028/article/details/146239674
今日推荐