基于LSTM的AI诗歌生成:从数据预处理到模型调优全解析

发布时间:2026/5/30 16:39:58

基于LSTM的AI诗歌生成:从数据预处理到模型调优全解析 1. 项目概述当AI遇见十四行诗“写诗”这件事长久以来被认为是人类情感与灵感的专属领域是算法难以触及的感性高地。但如果你告诉我现在有一台机器不仅能写出语法通顺的句子还能模仿莎士比亚的十四行诗或者生成一首充满意境的现代朦胧诗你会不会觉得有点意思这正是“用深度学习写诗”这个项目吸引我的地方。它不是一个简单的文字游戏而是一个融合了自然语言处理、序列建模和创造性生成的前沿探索。简单来说这个项目的核心就是教会计算机理解诗歌的“形式”与“神韵”并让它能够自主生成新的诗篇。这听起来很玄乎但拆解开来它解决的是一个非常具体的问题如何将一个庞大、非结构化的文本数据集比如成千上万首古诗或现代诗转化为一个能够捕捉诗歌内在规律包括格律、押韵、意象、情感基调的数学模型并利用这个模型进行“创作”。它适合谁呢如果你是NLP自然语言处理的初学者想找一个有趣又有挑战性的项目来练手如果你是文学或创意写作领域的工作者对技术如何介入创作过程感到好奇或者你只是一个诗歌爱好者想看看AI眼中的世界是什么样子——这个项目都能为你打开一扇新的大门。它不要求你成为诗歌大师或算法专家但需要你带着一点耐心和探索精神去理解数据、模型和创意之间微妙的平衡。2. 核心思路与模型选型为什么是RNN家族拿到“用深度学习写诗”这个标题第一反应可能是市面上模型那么多我该选哪个是直接用现成的GPT去生成还是从头搭建一个这里面的核心思路其实围绕着诗歌的两个关键特性序列性和上下文依赖。一首诗是一个字词按特定顺序排列的序列。“床前明月光”之所以是“床前明月光”而不是“光月明前床”是因为字词的顺序承载了语义。同时诗歌中后面的词往往高度依赖于前面的词所营造的语境和情感。这种特性天然契合**循环神经网络RNN**及其变体家族。RNN的设计就是为了处理序列数据它有一个“记忆单元”能够将前面步骤的信息传递到后面从而捕捉上下文关系。然而基础RNN存在“梯度消失”的问题难以学习长距离依赖。这对于篇幅较长的诗歌来说是致命的因为诗的第一句可能为最后一句的意境埋下伏笔。因此在实际项目中我们几乎都会选择RNN的增强版长短期记忆网络LSTM或门控循环单元GRU。它们通过精巧的“门”结构输入门、遗忘门、输出门有选择地记住重要信息、忘记无关信息非常适合处理诗歌这种需要“记忆”前期意象和情感基调的文本。注意虽然Transformer如GPT在长文本生成上现在表现更优但对于入门和教学项目LSTM/GRU模型更轻量、结构更清晰更容易让我们理解诗歌生成背后的“序列建模”本质。从LSTM入手打好基础再探索Transformer是一个更稳妥的学习路径。那么为什么不直接用规则比如随机从词库选词并强制押韵因为那样生成的“诗”没有灵魂只是词汇的机械拼凑。深度学习的价值在于它通过海量数据的学习能够隐式地掌握押韵规律、词语搭配习惯如“皎洁”常配“月光”“磅礴”常配“大海”、甚至某种风格下的常用意象群婉约派 vs 豪放派。模型学到的是一个概率分布给定前面若干个字下一个字最可能是哪一个。这个概率分布里就蕴含了它从训练数据中学到的所有“诗感”。3. 数据准备诗歌的“食材”处理模型再好没有高质量的数据也是巧妇难为无米之炊。数据准备是整个项目的基石也是最耗费心力的环节之一。你的诗歌数据集决定了模型最终能写出什么风格的诗。3.1 数据来源与收集数据来源可以很广泛古典诗词可以从《全唐诗》、《宋词三百首》等公开的电子文本中获取。这类数据格律严谨用字精炼是训练模型学习中文古典韵律美的绝佳材料。现代诗歌收集现当代著名诗人的作品需注意版权。现代诗形式自由意象丰富能训练出更具现代感和创新性的模型。特定风格/诗人数据集如果你只想模仿某一位诗人如李白、海子就需要专门收集他们的作品集。这样训练出的模型风格会更纯粹。一个实用的技巧是初期可以使用一些开源的中文诗歌数据集例如在GitHub上能找到的“Chinese-Poetry”数据集它包含了大量的唐诗宋词已经做了初步的整理可以大大降低起步门槛。3.2 数据清洗与预处理原始文本数据通常是“脏”的直接喂给模型效果会很差。预处理的核心目标是得到干净、格式统一的序列数据。关键步骤包括去除杂质删除文本中的非诗歌内容如标题、作者、注释、页码标记、乱码等。正则表达式是你的好帮手。统一格式确保每首诗以独立的形式存在。例如可以将每首诗处理成一行用特殊符号如start和end标记诗的开头和结尾。这对于模型学习“开始一首诗”和“结束一首诗”至关重要。分词对于中文诗歌需要分词。与普通文本不同诗歌分词可能需要更精细。例如“明月光”是一个词还是“明月”和“光”两个词不同的分词策略会影响模型学习到的语言单元。一个折中的方案是使用混合粒度或者直接按字进行分割将每个汉字作为一个token这对于古诗词尤其有效因为古诗常以字为基本单位营造意境。构建词表将所有的字或词映射成数字ID。例如“床”- 1“前”- 2“明”- 3……。模型只认识数字不认识汉字。创建训练序列这是准备阶段最关键的一步。我们需要将整首诗的ID序列切割成多个固定长度的、重叠的短序列作为模型的输入-输出对。示例假设有一首诗“床前明月光疑是地上霜。”分词/分字后得到ID序列[1,2,3,4,5,6,7,8,9,10]假设逗号句号也作为token。如果我们设定序列长度seq_length为5那么我们可以创建以下训练样本输入:[1,2,3,4,5]- 目标输出下一个字:6输入:[2,3,4,5,6]- 目标输出:7输入:[3,4,5,6,7]- 目标输出:8……这样模型的任务就变成了给定前5个字的序列预测第6个字是什么。通过海量这样的样本训练模型就能学会诗歌中的字词接续规律。实操心得数据预处理的质量直接决定模型的天花板。花80%的时间处理好数据模型训练可能只占20%。一个常见的坑是数据量不足。诗歌本身数据量就不像新闻语料那么大如果清洗后再损失一部分可能导致模型严重过拟合只能“背诵”训练集中的诗而无法生成新诗。建议至少准备数万行诗歌数据作为起点。4. 模型构建与训练实战有了干净的数据我们就可以搭建模型了。这里以使用PyTorch框架构建一个基于LSTM的诗歌生成模型为例拆解关键步骤。4.1 模型架构设计一个典型的诗歌生成模型包含以下层嵌入层将输入的字词ID整数转换为密集向量词向量。这个层是可学习的模型在训练过程中会不断调整每个字词的向量表示使得语义相近的字在向量空间中也靠近。LSTM层这是模型的核心。它接收嵌入层输出的向量序列并处理其中的时序信息。我们通常会使用多层LSTM堆叠并设置dropout来防止过拟合。LSTM的隐藏状态hidden state承载了到当前时刻为止的上下文记忆。全连接层将LSTM层最后一个时间步输出的隐藏状态或者所有时间步输出的集合映射到一个维度等于词表大小的向量上。Softmax层将全连接层的输出转换为一个概率分布。这个分布的长度等于词表大小每个位置的值代表对应字词作为下一个输出字的概率。4.2 关键代码解析import torch import torch.nn as nn class PoetryModel(nn.Module): def __init__(self, vocab_size, embedding_dim, hidden_dim, num_layers, dropout0.5): super(PoetryModel, self).__init__() self.hidden_dim hidden_dim self.num_layers num_layers # 1. 嵌入层 self.embedding nn.Embedding(vocab_size, embedding_dim) # 2. LSTM层 self.lstm nn.LSTM(embedding_dim, hidden_dim, num_layers, batch_firstTrue, dropoutdropout if num_layers1 else 0) # 3. 全连接层 self.fc nn.Linear(hidden_dim, vocab_size) # 4. Dropout (用于全连接层前) self.dropout nn.Dropout(dropout) def forward(self, x, hidden): # x: [batch_size, seq_length] embedded self.embedding(x) # [batch_size, seq_length, embedding_dim] lstm_out, hidden self.lstm(embedded, hidden) # lstm_out: [batch_size, seq_length, hidden_dim] # 我们通常取最后一个时间步的输出用于预测下一个字 lstm_out self.dropout(lstm_out[:, -1, :]) # [batch_size, hidden_dim] output self.fc(lstm_out) # [batch_size, vocab_size] return output, hidden def init_hidden(self, batch_size): # 初始化LSTM的隐藏状态和细胞状态 weight next(self.parameters()).data return (weight.new(self.num_layers, batch_size, self.hidden_dim).zero_(), weight.new(self.num_layers, batch_size, self.hidden_dim).zero_())4.3 训练过程与参数选择训练过程就是不断调整模型参数让模型预测的下一个字越来越接近真实的下一个字。我们使用交叉熵损失函数来衡量预测概率分布与真实one-hot编码只有真实字的位置为1其余为0之间的差距。优化器Adam优化器是首选它自适应调整学习率收敛速度快且稳定。学习率这是一个需要仔细调试的超参数。可以从3e-4或1e-3开始尝试。学习率太大会导致训练不稳定损失值震荡太小则收敛缓慢。批次大小根据你的GPU内存决定常见的有32、64、128。更大的批次通常能使训练更稳定但可能会降低模型泛化能力。序列长度即seq_length决定了模型能看到的上下文长度。对于五言绝句20字设置10-15可能就够了对于长诗可能需要设置到50甚至100。这需要根据你的数据特点进行实验。训练轮数需要观察训练损失和验证损失。当验证损失不再下降甚至开始上升时就说明模型可能过拟合了应该停止训练。一个重要的技巧是使用教师强制在训练时我们将真实的上一句作为输入来预测下一句这能加速模型收敛。但在生成推理时模型只能用自己预测出来的字作为下一步的输入任何错误都会累积。这种训练与推理的不匹配被称为“曝光偏差”。为了缓解这个问题可以在训练后期引入计划采样即有一定概率使用模型自己预测的字而不是真实标签作为下一步的输入让模型提前适应推理时的环境。5. 诗歌生成策略从“随机”到“可控”模型训练好后我们如何让它“写”出一首诗这不仅仅是运行一次前向传播那么简单生成策略决定了诗歌的“创造性”和“连贯性”。5.1 基础生成贪婪搜索与随机采样最简单的生成方式是贪婪搜索在每一步都选择模型输出概率最高的那个字作为下一个字。这种方式生成的诗通常很安全、语法正确但往往缺乏新意容易陷入重复的、平庸的套路。为了增加多样性我们引入随机采样根据模型输出的概率分布随机挑选下一个字。概率高的字被选中的机会大但概率低的字也有机会。这能带来意想不到的创意组合但也可能导致语句不通顺。5.2 进阶策略核采样与温度参数在实际应用中更常用的是结合两者优点的策略温度参数在应用Softmax之前将模型的原始输出logits除以一个温度值T。T 1保持原分布。T 1概率分布变得更平缓低概率字被选中的机会增加生成结果更随机、更有创意。0 T 1概率分布变得更尖锐高概率字被选中的机会更大生成结果更确定、更保守。通常设置T在0.7到0.9之间能在创造性和通顺性之间取得不错平衡。核采样这是一种更聪明的采样方式。它只从概率最高的前k个候选字例如top-50中进行随机采样而完全忽略那些概率极低的字。这既保证了生成质量避免出现完全离谱的字又保留了随机性。k值是一个超参数需要调试。5.3 生成流程示例def generate_poetry(model, start_words, ix2word, word2ix, max_gen_len100, temperature0.8, top_k50): model.eval() # 切换到评估模式 with torch.no_grad(): # 将起始词转换为ID inputs [word2ix[start]] [word2ix[word] for word in start_words] inputs torch.tensor(inputs).unsqueeze(0) # [1, seq_len] hidden model.init_hidden(1) generated list(start_words) for _ in range(max_gen_len): output, hidden model(inputs, hidden) # output: [1, vocab_size] # 应用温度参数 output output / temperature # 应用核采样 (top-k sampling) probs torch.softmax(output, dim-1).squeeze() # [vocab_size] top_k_probs, top_k_indices torch.topk(probs, top_k) # 从top-k中随机选择一个索引 next_idx top_k_indices[torch.multinomial(top_k_probs, 1)].item() # 如果生成了结束符则停止 if next_idx word2ix[end]: break next_word ix2word[next_idx] generated.append(next_word) # 将预测的字作为下一步输入的一部分 inputs torch.tensor([[next_idx]]) return .join(generated)6. 效果评估与调优什么样的诗才算“好”评估AI生成的诗歌是最大的挑战之一因为它没有标准答案。我们不能像图像分类那样用准确率来衡量。常用的评估维度包括流畅性与语法正确性这是最基本的要求。生成的句子是否通顺是否符合基本语法人可以直观判断。新颖性生成的诗歌是否只是简单拼接训练集中的句子我们可以计算生成文本与训练集的n-gram重复率重复率越低新颖性可能越高。意境与连贯性整首诗是否围绕一个大致统一的主题或意象展开前后句之间是否有逻辑或情感上的关联这需要人工阅读评判。符合特定形式如果训练数据有形式要求例如生成的古诗是否基本符合五言/七言的句式是否在偶数句末尾有押韵的趋势注意让AI严格押韵需要在数据预处理和生成策略上做更多工作例如引入韵律约束。调优是一个持续的过程如果诗歌不通顺检查数据清洗是否彻底是否存在大量噪音尝试降低温度参数T或减小top-k值增加训练数据量检查模型是否过拟合训练损失很低但生成效果差。如果诗歌过于平庸、重复尝试提高温度参数T增大top-k值在训练中引入dropout或者尝试使用更复杂的模型结构如注意力机制。如果想控制诗歌风格或主题这是更高级的方向。可以在输入时加入一个代表风格或主题的控制编码让模型在生成过程中始终“记住”这个条件。这需要修改模型结构将条件信息融入到LSTM的初始状态或每一步的计算中。7. 常见问题与避坑指南在实际操作中你一定会遇到各种各样的问题。下面是我踩过的一些坑和对应的解决方案7.1 模型只生成重复或无意义的字症状生成的诗全是“的的的的”或者“啊啊啊啊”或者不断重复同一句话。可能原因与解决梯度爆炸这是最常见的原因之一。在训练初期损失值突然变成NaN或巨大无比。解决使用梯度裁剪。在PyTorch中可以在每次loss.backward()之后调用torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm5)将梯度范数限制在一个阈值内。数据问题数据中可能存在大量重复或无意义的片段。解决重新检查并清洗数据。学习率过高导致训练不稳定。解决降低学习率例如从1e-3降到3e-4。模型初始化问题解决确保LSTM的隐藏状态被正确初始化通常为零向量。7.2 生成的诗总是很短达不到指定长度症状模型很快远早于max_gen_len就生成了结束符end。可能原因与解决训练数据中的诗普遍较短模型学到了“诗应该短”的模式。解决在数据中混合一些长诗或者在生成时强制忽略结束符直到达到最小长度。温度参数过低导致模型过于“自信”地预测结束符。解决适当提高温度参数T增加随机性。7.3 训练损失下降很慢或者震荡厉害症状训练了很多轮损失值居高不下或者像心电图一样上下波动。可能原因与解决学习率不合适可能是最根本的原因。解决使用学习率调度器如ReduceLROnPlateau当验证损失停滞时自动降低学习率。批次大小不合适批次太小可能导致梯度估计噪声大。解决在硬件允许的情况下增大批次大小。模型结构或数据有问题解决先用一个极小的数据集比如100首诗和简单的模型过拟合它如果连小数据都学不好那肯定是模型或代码有bug。7.4 如何让AI写的诗“更像诗”这是一个开放性问题没有标准答案但有一些进阶思路引入外部知识在词向量中融入平仄、韵律信息。例如可以为押韵的字赋予某种关联向量。使用更强大的模型迁移到基于Transformer的模型如GPT-2或GPT-3的轻量版。它们的注意力机制能更好地捕捉长距离依赖和全局结构。后处理与人工筛选目前最实用的方法。让模型生成大量候选诗歌然后由人从中挑选出意境、语言俱佳的“佳作”。AI负责“海选”人负责“精选”这是一种有效的人机协作创作模式。这个项目从数据爬取、清洗到模型构建、训练再到策略调优、问题排查几乎涵盖了深度学习NLP项目的大部分核心环节。它像是一个微缩的实验室让你能亲手触摸到语言模型的“温度”。最终当你看到屏幕上流淌出第一句由你亲手训练的模型写出的、虽显稚嫩但已有几分诗意的句子时那种感觉或许本身就是一首诗。

相关新闻