近年来,Transformer 模型已成为自然语言处理(NLP)领域的主流方法。得益于其高效的自注意力机制(Self-Attention),Transformer 能够捕捉序列中远距离的依赖关系,极大地提升了文本生成、翻译和摘要等任务的性能。本文将详细介绍 Transformer 模型生成文本的基本原理,并结合示例代码逐步解析如何构建、训练以及利用 Transformer 生成文本。


一、Transformer 模型概述

Transformer 模型由 Vaswani 等人在 2017 年提出,其核心思想在于完全基于注意力机制,无需循环神经网络(RNN)结构即可处理序列数据。主要特点包括:

  • 自注意力机制(Self-Attention): 能够在编码阶段捕捉输入序列中任意两个位置之间的依赖关系,且计算效率高。
  • 位置编码(Positional Encoding): 因为模型本身不具备顺序信息,所以需要通过位置编码将序列中各个单词的位置信息引入模型中。
  • 编码器-解码器架构: 模型一般由编码器和解码器两部分构成,编码器将输入序列编码成隐状态表示,解码器根据这些隐状态逐步生成目标序列。

这种架构不仅提升了并行计算的能力,而且在处理长文本时也表现出色。


二、Transformer 在文本生成中的基本流程

利用 Transformer 生成文本主要包括以下几个步骤:

  1. 文本预处理与嵌入: 将输入文本转换为对应的词索引,然后通过嵌入层映射到连续向量空间。
  2. 位置编码: 由于 Transformer 不具备序列顺序感知能力,需要对嵌入后的向量加入位置编码,使模型能够区分不同位置的词语。
  3. Transformer 编码与解码:
    • 编码器部分: 将源文本信息编码成高维表示。
    • 解码器部分: 在生成过程中,每一步根据已生成的部分以及编码器输出预测下一个词。
  4. 输出层与采样策略: 通过全连接层将解码器输出映射回词汇表维度,再利用采样策略(如贪心采样、温度采样或束搜索)选择下一个生成的单词。

三、核心代码概览

下面是一段基于 PyTorch 的示例代码,展示了如何构建一个简单的 Transformer 文本生成模型。代码中包含了位置编码、Transformer 模块以及生成文本的过程。

分析说明
  • seq_length

    • 作用:定义训练时每个样本的词数。
    • 超参数建议
      • 较长的序列可以捕获更多上下文信息,但会占用更多内存;
      • 较短的序列训练速度快,但可能无法捕捉长距离依赖。
  • 数据构造
    利用滑动窗口方式生成样本,其中输入和目标序列均为连续词索引序列,目标序列相对于输入序列右移一位。


3. 模型构建

Transformer 模型主要包括词嵌入、位置编码以及 Transformer 编码器部分。下面我们结合代码逐步解析模型的实现过程。

3.1 位置编码(Positional Encoding)

由于 Transformer 结构不包含递归结构,因此需要通过位置编码来注入位置信息。

代码示例

python

代码解读

复制代码

作者:mguy_1
链接:https://juejin.cn/post/7476384398561378330
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

代码解析

  1. 位置编码(PositionalEncoding):
    为了弥补 Transformer 缺乏顺序信息的问题,位置编码层将预设好的正弦和余弦函数值与词向量相加,使模型能够感知单词在序列中的位置信息。

  2. 嵌入层(Embedding):
    将词汇表中每个单词的索引转换成固定维度的向量,并乘以缩放因子(一般为 dmodel\sqrt{d_{model}}dmodel​​),有助于稳定训练过程。

  3. Transformer 模块:
    利用 nn.Transformer 构建编码器-解码器结构,通过多层多头自注意力机制对输入进行编码,并利用解码器在生成阶段一步步预测下一个词。注意,由于 PyTorch 的 Transformer 模块要求输入形状为 [seq_len, batch_size, d_model],因此代码中对张量进行了转置处理。

  4. 生成函数(generate):
    在推理过程中,生成函数首先将源序列输入编码器,然后以起始标记初始化目标序列。接着在每一步中利用当前生成的部分进行预测,并使用贪心策略选取概率最高的词作为下一个输出。若遇到结束符号,则提前停止生成。


四、如何训练并应用该模型生成文本

在实际应用中,流程大致如下:

  1. 数据准备:
    对文本数据进行预处理,构建词汇表,并将文本转换为对应的词索引序列。通常需要对源文本与目标文本分别进行编码。

  2. 模型训练:
    使用交叉熵损失函数(CrossEntropy Loss)和适当的优化器(如 Adam),在大量的文本数据上训练 Transformer 模型。训练时,目标序列一般会进行右移操作(即教师强制技术)以对齐输入和输出。

  3. 推理与生成:
    训练完成后,通过 generate 函数输入源序列(例如给定一段开头或者特定提示),利用模型逐步生成后续文本。可以采用贪心采样或者其他更复杂的采样策略(例如束搜索、温度采样)来平衡生成文本的质量与多样性。

五、完整代码块解析与超参数调优

1. 数据预处理与词汇表构建

在文本生成任务中,首先需要对原始文本进行预处理,将文本转换为模型可以接受的数值化表示。下面代码展示了如何读取文本文件、进行分词、构建词汇表以及数值化处理。

代码示例
# 设置文本文件的路径
filepath = "xxx.txt"

# 读取文本文件内容
with open(filepath, 'r', encoding='utf-8') as f:
    text = f.read()

def tokenize_text(text):
    return text.split()
分析说明
  • 文件读取与分词
    代码中通过指定文件路径读取文本,然后调用 tokenize_text 函数对文本进行分词(这里采用简单的空格分割方式)。
    • 注意事项:在实际项目中,可能需要更复杂的分词器来处理标点符号和特殊字符。

接下来构建词汇表,并将每个词映射为对应的索引。

def build_vocab(tokens, min_freq=1):
    freq = {}
    for word in tokens:
        freq[word] = freq.get(word, 0) + 1
    # 初始化特殊标记
    vocab = {"<unk>": 0, "<pad>": 1, "<eos>": 2}
    for word, count in freq.items():
        if word not in vocab and count >= min_freq:
            vocab[word] = len(vocab)
    return vocab

def numericalize(tokens, vocab):
    """
    将文本中每个词转换成对应的索引。如果词不在词汇表中,则返回 <unk> 对应的索引
    """
    return [vocab.get(word, vocab["<unk>"]) for word in tokens]
超参数解析
  • min_freq

    • 作用:控制词汇表中词的最小出现频次。
    • 调整建议:当数据量较大且存在噪音时,可以适当提高 min_freq,过滤低频词,从而减小词汇表规模。
  • 特殊标记
    <unk>、<pad>、<eos> 分别用于表示未知词、填充符和句子结束符,保证后续模型训练时处理特殊情况。

2. 数据集构建

为了训练语言模型,需要构造一个数据集,每个样本包含输入序列和目标序列(目标序列为输入序列右移一位)。

代码示例
class LanguageModelDataset(Dataset):
    def __init__(self, token_ids, seq_length):
        """
        初始化数据集:
        - token_ids: 数值化后的词索引列表
        - seq_length: 每个训练样本的长度
        每个样本由输入序列 [w0, w1, ..., w_{L-1}] 和目标序列 [w1, w2, ..., w_L] 组成
        """
        self.token_ids = token_ids
        self.seq_length = seq_length

    def __len__(self):
        return len(self.token_ids) - self.seq_length

    def __getitem__(self, idx):
        x = self.token_ids[idx: idx + self.seq_length]
        y = self.token_ids[idx + 1: idx + self.seq_length + 1]
        return torch.tensor(x, dtype=torch.long), torch.tensor(y, dtype=torch.long)
分析说明
  • seq_length

    • 作用:定义训练时每个样本的词数。
    • 超参数建议
      • 较长的序列可以捕获更多上下文信息,但会占用更多内存;
      • 较短的序列训练速度快,但可能无法捕捉长距离依赖。
  • 数据构造
    利用滑动窗口方式生成样本,其中输入和目标序列均为连续词索引序列,目标序列相对于输入序列右移一位。


3. 模型构建

Transformer 模型主要包括词嵌入、位置编码以及 Transformer 编码器部分。下面我们结合代码逐步解析模型的实现过程。

3.1 位置编码(Positional Encoding)

由于 Transformer 结构不包含递归结构,因此需要通过位置编码来注入位置信息。

代码示例
class PositionalEncoding(nn.Module):
    def __init__(self, d_model, dropout=0.1, max_len=5000):
        super(PositionalEncoding, self).__init__()
        self.dropout = nn.Dropout(p=dropout)
        self.d_model = d_model
        self.max_len = max_len

        pe = torch.zeros(self.max_len, d_model)
        position = torch.arange(0, self.max_len, dtype=torch.float).unsqueeze(1)
        div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)
        pe = pe.unsqueeze(1)  # 形状 (max_len, 1, d_model)
        self.register_buffer('pe', pe)

    def forward(self, x):
        seq_len = x.size(0)
        if seq_len > self.max_len:
            pe = self.generate_positional_encodings(seq_len)
        else:
            pe = self.pe[:seq_len]
        x = x + pe
        return self.dropout(x)
分析说明
  • d_model

    • 作用:定义词嵌入及模型中各层的特征维度。
    • 超参数建议
      • 较小的 d_model(如 128 或 256)适合小规模任务,计算速度快;
      • 较大的 d_model(如 512 或 1024)可以捕捉更多细节,但资源消耗也更高。
  • dropout

    • 作用:防止过拟合。
    • 超参数建议:常设置在 0.1~0.3 之间,根据训练效果调整。
  • max_len

    • 作用:设置位置编码的最大序列长度。
    • 超参数建议:至少大于训练时的 seq_length;如有生成更长文本需求,可调大该值。
3.2 Transformer 语言模型

Transformer 语言模型基于编码器结构,主要包括嵌入层、位置编码、Transformer Encoder 层以及输出映射层。

代码示例
class TransformerLanguageModel(nn.Module):
    def __init__(self, vocab_size, d_model=256, nhead=8, num_layers=4,
                 dim_feedforward=512, dropout=0.1, max_seq_length=5000):
        """
        定义基于 Transformer Encoder 的语言模型
        参数说明:
        - vocab_size: 词汇表大小
        - d_model: 词嵌入及 Transformer 模型维度
        - nhead: 多头注意力的头数
        - num_layers: Transformer Encoder 层数
        - dim_feedforward: 前馈网络隐藏层维度
        - dropout: dropout 概率
        - max_seq_length: 最大序列长度(用于位置编码)
        """
        super(TransformerLanguageModel, self).__init__()
        self.d_model = d_model
        self.embedding = nn.Embedding(vocab_size, d_model)
        self.pos_encoder = PositionalEncoding(d_model, dropout, max_len=max_seq_length)
        encoder_layer = nn.TransformerEncoderLayer(d_model, nhead, dim_feedforward, dropout)
        self.transformer_encoder = nn.TransformerEncoder(encoder_layer, num_layers)
        self.fc_out = nn.Linear(d_model, vocab_size)

    def forward(self, src, src_mask):
        """
        src: 输入序列,形状 (seq_len, batch_size)
        src_mask: 掩码矩阵,形状 (seq_len, seq_len)
        """
        # 嵌入并缩放
        emb = self.embedding(src) * math.sqrt(self.d_model)
        emb = self.pos_encoder(emb)  # 使用位置编码
        output = self.transformer_encoder(emb, src_mask)  # 使用掩码
        logits = self.fc_out(output)
        return logits
分析说明
  • vocab_size

    • 决定嵌入层输出的词向量数量,同时也是输出层(全连接层)的输出维度。
  • nhead

    • 作用:多头注意力中头的数量,允许模型从多个子空间提取信息。
    • 要求d_model 必须能被 nhead 整除(例如 256 % 8 == 0)。
  • num_layers

    • 作用:Transformer Encoder 层数,决定模型的深度。
    • 超参数建议:层数越多,模型表达能力越强,但计算资源需求也更大。
  • dim_feedforward

    • 作用:前馈网络隐藏层维度。
    • 超参数建议:通常为 d_model 的 2~4 倍,提升模型的非线性表达能力。
  • dropoutmax_seq_length

    • 前面已介绍,这里确保生成时位置编码长度不小于训练序列长度。

4. 模型训练

训练阶段通过构造 DataLoader、定义优化器与损失函数,执行模型的前向传播、反向传播更新权重。

代码示例
# 设置训练参数
batch_size = 32
epochs = 500   # 可根据需求调整训练轮数
lr = 0.001

# 构造 DataLoader
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)

# 选择设备(GPU 优先)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)

# 定义优化器和交叉熵损失函数
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
criterion = nn.CrossEntropyLoss(ignore_index=vocab["<pad>"])

# 开始训练
model.train()
for epoch in range(1, epochs + 1):
    total_loss = 0.0
    for batch_idx, (data, targets) in enumerate(dataloader):
        # data, targets 的形状为 (batch_size, seq_length)
        # 转置为 (seq_length, batch_size) 符合 Transformer 的输入要求
        data = data.transpose(0, 1).to(device)
        targets = targets.transpose(0, 1).to(device)

        optimizer.zero_grad()
        seq_len = data.size(0)
        # 生成当前序列长度对应的掩码
        src_mask = generate_square_subsequent_mask(seq_len).to(device)
        output = model(data, src_mask)  # 输出形状为 (seq_length, batch_size, vocab_size)
        loss = criterion(output.view(-1, vocab_size), targets.reshape(-1))
        loss.backward()
        optimizer.step()
        total_loss += loss.item()

        if batch_idx % 200 == 0:
            print(f"Epoch {epoch}, Batch {batch_idx}, Loss: {loss.item():.4f}")
    avg_loss = total_loss / len(dataloader)
    print(f"Epoch {epoch} 完成,平均 Loss: {avg_loss:.4f}")
分析说明
  • batch_size

    • 作用:每个训练批次包含的样本数量。
    • 调整建议:较大的 batch_size 可加速梯度稳定,但需足够显存;若显存不足则调小。
  • epochs

    • 作用:整个数据集训练的轮次。
    • 调整建议:需结合验证集效果防止过拟合。
  • lr(学习率)

    • 作用:决定权重更新步长。
    • 调整建议:初始值 0.001 较为常用,可配合学习率调度器进一步优化。
  • 损失函数

    • CrossEntropyLoss:通过设置 ignore_index 忽略 <pad> 标记,确保损失计算正确。
  • 生成掩码(src_mask)

    • 通过 generate_square_subsequent_mask 函数构造防止模型看到未来信息的掩码,保证训练过程中自回归特性。

5. 文本生成过程

训练完成后,可以利用模型生成文本。生成过程通常是从给定的提示词开始,逐步预测下一个词,并将预测结果反馈到输入中。

代码示例
def generate_text(model, prompt, vocab, seq_length, gen_length=50):
    """
    根据提示词生成文本
    参数:
    - model: 已训练好的 Transformer 语言模型
    - prompt: 用户输入的提示词(字符串)
    - vocab: 词汇表(词->索引字典)
    - seq_length: 训练时使用的序列长度(用于生成掩码)
    - gen_length: 生成的新词数量
    """
    model.eval()
    # 构造索引到词的反向映射
    idx2word = {idx: word for word, idx in vocab.items()}

    # 对提示词进行分词和数值化
    prompt_tokens = prompt.split()
    prompt_ids = [vocab.get(word, vocab["<unk>"]) for word in prompt_tokens]

    # 将提示词转换为 tensor,形状 (seq_len, 1)
    input_seq = torch.tensor(prompt_ids, dtype=torch.long).unsqueeze(1).to(device)
    generated = prompt_tokens.copy()

    with torch.no_grad():
        for _ in range(gen_length):
            seq_len = input_seq.size(0)
            src_mask = generate_square_subsequent_mask(seq_len).to(device)
            output = model(input_seq, src_mask)
            # 取最后一个时间步的输出,预测下一个词
            last_logits = output[-1, 0, :]
            probs = F.softmax(last_logits, dim=0)
            next_token_id = torch.multinomial(probs, 1).item()
            next_word = idx2word.get(next_token_id, "<unk>")
            generated.append(next_word)
            # 将生成的词添加到输入序列中
            next_token = torch.tensor([[next_token_id]], dtype=torch.long).to(device)
            input_seq = torch.cat([input_seq, next_token], dim=0)
            # 如果生成了句子结束符,则终止生成
            if next_word == "<eos>":
                break
    return ' '.join(generated)
分析说明
  • 生成策略

    • 此处采用基于 softmax 概率分布的多项式采样,每一步从预测分布中抽样生成下一个词,保证一定的多样性。
    • 可根据需求改用贪心策略(argmax)或引入温度参数调整采样分布平滑程度。
  • 提示词与序列构建

    • 通过对用户输入提示词进行分词和数值化构造初始输入,再逐步扩展生成序列。

6. 超参数总结与调整建议

结合以上代码块与分析,我们可以总结出以下关键超参数及其调整策略:

  • 数据预处理阶段

    • min_freq:决定词汇表大小,过滤低频词以降低噪音。
  • 数据集构建

    • seq_length:训练样本长度,长短取决于任务需要与计算资源。
  • 模型构建阶段

    • d_model:词嵌入与模型特征维度,平衡表达能力与计算消耗;
    • nhead:多头注意力数量,确保 d_model 可整除;
    • num_layers:Transformer 层数,层数越多模型表达力更强但更耗资源;
    • dim_feedforward:前馈网络隐藏层维度,一般设为 d_model 的 2~4 倍;
    • dropout:防止过拟合,常取 0.1~0.3;
    • max_seq_length:位置编码最大序列长度,需大于训练时的 seq_length
  • 训练过程

    • batch_sizeepochslr:直接影响训练稳定性和速度,需根据硬件资源和数据规模调整。
  • 文本生成

    • gen_length:生成文本长度;
    • 采样策略:多项式采样、贪心采样或温度调整,均影响生成结果的多样性和连贯性。

总结

本文从数据预处理、数据集构建、模型设计、训练到文本生成,结合代码块详细解析了 Transformer 生成文本模型的实现过程和关键超参数的调优方法。通过对每个模块代码的深入分析,你可以清晰理解如何将原始文本数据转换为数值化表示,如何利用 Transformer Encoder 构建语言模型,以及如何在生成阶段不断改进采样策略,最终达到较好的文本生成效果。

 

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐