使用 Transformer 生成文本介绍及调参
Transformer 模型由 Vaswani 等人在 2017 年提出,其核心思想在于完全基于注意力机制,无需循环神经网络(RNN)结构即可处理序列数据。能够在编码阶段捕捉输入序列中任意两个位置之间的依赖关系,且计算效率高。因为模型本身不具备顺序信息,所以需要通过位置编码将序列中各个单词的位置信息引入模型中。模型一般由编码器和解码器两部分构成,编码器将输入序列编码成隐状态表示,解码器根据这些隐状
近年来,Transformer 模型已成为自然语言处理(NLP)领域的主流方法。得益于其高效的自注意力机制(Self-Attention),Transformer 能够捕捉序列中远距离的依赖关系,极大地提升了文本生成、翻译和摘要等任务的性能。本文将详细介绍 Transformer 模型生成文本的基本原理,并结合示例代码逐步解析如何构建、训练以及利用 Transformer 生成文本。
一、Transformer 模型概述
Transformer 模型由 Vaswani 等人在 2017 年提出,其核心思想在于完全基于注意力机制,无需循环神经网络(RNN)结构即可处理序列数据。主要特点包括:
- 自注意力机制(Self-Attention): 能够在编码阶段捕捉输入序列中任意两个位置之间的依赖关系,且计算效率高。
- 位置编码(Positional Encoding): 因为模型本身不具备顺序信息,所以需要通过位置编码将序列中各个单词的位置信息引入模型中。
- 编码器-解码器架构: 模型一般由编码器和解码器两部分构成,编码器将输入序列编码成隐状态表示,解码器根据这些隐状态逐步生成目标序列。
这种架构不仅提升了并行计算的能力,而且在处理长文本时也表现出色。
二、Transformer 在文本生成中的基本流程
利用 Transformer 生成文本主要包括以下几个步骤:
- 文本预处理与嵌入: 将输入文本转换为对应的词索引,然后通过嵌入层映射到连续向量空间。
- 位置编码: 由于 Transformer 不具备序列顺序感知能力,需要对嵌入后的向量加入位置编码,使模型能够区分不同位置的词语。
- Transformer 编码与解码:
- 编码器部分: 将源文本信息编码成高维表示。
- 解码器部分: 在生成过程中,每一步根据已生成的部分以及编码器输出预测下一个词。
- 输出层与采样策略: 通过全连接层将解码器输出映射回词汇表维度,再利用采样策略(如贪心采样、温度采样或束搜索)选择下一个生成的单词。
三、核心代码概览
下面是一段基于 PyTorch 的示例代码,展示了如何构建一个简单的 Transformer 文本生成模型。代码中包含了位置编码、Transformer 模块以及生成文本的过程。
分析说明
-
seq_length
- 作用:定义训练时每个样本的词数。
- 超参数建议:
- 较长的序列可以捕获更多上下文信息,但会占用更多内存;
- 较短的序列训练速度快,但可能无法捕捉长距离依赖。
-
数据构造
利用滑动窗口方式生成样本,其中输入和目标序列均为连续词索引序列,目标序列相对于输入序列右移一位。
3. 模型构建
Transformer 模型主要包括词嵌入、位置编码以及 Transformer 编码器部分。下面我们结合代码逐步解析模型的实现过程。
3.1 位置编码(Positional Encoding)
由于 Transformer 结构不包含递归结构,因此需要通过位置编码来注入位置信息。
代码示例
python
代码解读
复制代码
作者:mguy_1
链接:https://juejin.cn/post/7476384398561378330
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
代码解析
-
位置编码(PositionalEncoding):
为了弥补 Transformer 缺乏顺序信息的问题,位置编码层将预设好的正弦和余弦函数值与词向量相加,使模型能够感知单词在序列中的位置信息。 -
嵌入层(Embedding):
将词汇表中每个单词的索引转换成固定维度的向量,并乘以缩放因子(一般为 dmodel\sqrt{d_{model}}dmodel),有助于稳定训练过程。 -
Transformer 模块:
利用nn.Transformer
构建编码器-解码器结构,通过多层多头自注意力机制对输入进行编码,并利用解码器在生成阶段一步步预测下一个词。注意,由于 PyTorch 的 Transformer 模块要求输入形状为[seq_len, batch_size, d_model]
,因此代码中对张量进行了转置处理。 -
生成函数(generate):
在推理过程中,生成函数首先将源序列输入编码器,然后以起始标记初始化目标序列。接着在每一步中利用当前生成的部分进行预测,并使用贪心策略选取概率最高的词作为下一个输出。若遇到结束符号,则提前停止生成。
四、如何训练并应用该模型生成文本
在实际应用中,流程大致如下:
-
数据准备:
对文本数据进行预处理,构建词汇表,并将文本转换为对应的词索引序列。通常需要对源文本与目标文本分别进行编码。 -
模型训练:
使用交叉熵损失函数(CrossEntropy Loss)和适当的优化器(如 Adam),在大量的文本数据上训练 Transformer 模型。训练时,目标序列一般会进行右移操作(即教师强制技术)以对齐输入和输出。 -
推理与生成:
训练完成后,通过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 倍,提升模型的非线性表达能力。
-
dropout 与 max_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>
标记,确保损失计算正确。
- CrossEntropyLoss:通过设置
-
生成掩码(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_size
、epochs
与lr
:直接影响训练稳定性和速度,需根据硬件资源和数据规模调整。
-
文本生成
gen_length
:生成文本长度;- 采样策略:多项式采样、贪心采样或温度调整,均影响生成结果的多样性和连贯性。
总结
本文从数据预处理、数据集构建、模型设计、训练到文本生成,结合代码块详细解析了 Transformer 生成文本模型的实现过程和关键超参数的调优方法。通过对每个模块代码的深入分析,你可以清晰理解如何将原始文本数据转换为数值化表示,如何利用 Transformer Encoder 构建语言模型,以及如何在生成阶段不断改进采样策略,最终达到较好的文本生成效果。
更多推荐
所有评论(0)