基于LSTM的AI诗歌生成:从数据预处理到模型调优全解析
2026/5/30 16:39:51 网站建设 项目流程

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 数据清洗与预处理

原始文本数据通常是“脏”的,直接喂给模型效果会很差。预处理的核心目标是得到干净、格式统一的序列数据。关键步骤包括:

  1. 去除杂质:删除文本中的非诗歌内容,如标题、作者、注释、页码标记、乱码等。正则表达式是你的好帮手。
  2. 统一格式:确保每首诗以独立的形式存在。例如,可以将每首诗处理成一行,用特殊符号(如<start><end>)标记诗的开头和结尾。这对于模型学习“开始一首诗”和“结束一首诗”至关重要。
  3. 分词:对于中文诗歌,需要分词。与普通文本不同,诗歌分词可能需要更精细。例如,“明月光”是一个词还是“明月”和“光”两个词?不同的分词策略会影响模型学习到的语言单元。一个折中的方案是使用混合粒度,或者直接按字进行分割(将每个汉字作为一个token),这对于古诗词尤其有效,因为古诗常以字为基本单位营造意境。
  4. 构建词表:将所有的字(或词)映射成数字ID。例如,“床”-> 1,“前”-> 2,“明”-> 3……。模型只认识数字,不认识汉字。
  5. 创建训练序列:这是准备阶段最关键的一步。我们需要将整首诗的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 模型架构设计

一个典型的诗歌生成模型包含以下层:

  1. 嵌入层:将输入的字词ID(整数)转换为密集向量(词向量)。这个层是可学习的,模型在训练过程中会不断调整每个字词的向量表示,使得语义相近的字在向量空间中也靠近。
  2. LSTM层:这是模型的核心。它接收嵌入层输出的向量序列,并处理其中的时序信息。我们通常会使用多层LSTM堆叠,并设置dropout来防止过拟合。LSTM的隐藏状态(hidden state)承载了到当前时刻为止的上下文记忆。
  3. 全连接层:将LSTM层最后一个时间步输出的隐藏状态(或者所有时间步输出的集合)映射到一个维度等于词表大小的向量上。
  4. 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, dropout=0.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_first=True, dropout=dropout if num_layers>1 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-41e-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_len=100, temperature=0.8, top_k=50): 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生成的诗歌是最大的挑战之一,因为它没有标准答案。我们不能像图像分类那样用准确率来衡量。常用的评估维度包括:

  1. 流畅性与语法正确性:这是最基本的要求。生成的句子是否通顺,是否符合基本语法?人可以直观判断。
  2. 新颖性:生成的诗歌是否只是简单拼接训练集中的句子?我们可以计算生成文本与训练集的n-gram重复率,重复率越低,新颖性可能越高。
  3. 意境与连贯性:整首诗是否围绕一个大致统一的主题或意象展开?前后句之间是否有逻辑或情感上的关联?这需要人工阅读评判。
  4. 符合特定形式(如果训练数据有形式要求):例如,生成的古诗是否基本符合五言/七言的句式?是否在偶数句末尾有押韵的趋势?(注意:让AI严格押韵需要在数据预处理和生成策略上做更多工作,例如引入韵律约束)。

调优是一个持续的过程

  • 如果诗歌不通顺:检查数据清洗是否彻底,是否存在大量噪音;尝试降低温度参数T或减小top-k值;增加训练数据量;检查模型是否过拟合(训练损失很低但生成效果差)。
  • 如果诗歌过于平庸、重复:尝试提高温度参数T;增大top-k值;在训练中引入dropout;或者尝试使用更复杂的模型结构,如注意力机制。
  • 如果想控制诗歌风格或主题:这是更高级的方向。可以在输入时加入一个代表风格或主题的控制编码,让模型在生成过程中始终“记住”这个条件。这需要修改模型结构,将条件信息融入到LSTM的初始状态或每一步的计算中。

7. 常见问题与避坑指南

在实际操作中,你一定会遇到各种各样的问题。下面是我踩过的一些坑和对应的解决方案:

7.1 模型只生成重复或无意义的字

  • 症状:生成的诗全是“的的的的”或者“啊啊啊啊”,或者不断重复同一句话。
  • 可能原因与解决
    1. 梯度爆炸:这是最常见的原因之一。在训练初期,损失值突然变成NaN或巨大无比。解决:使用梯度裁剪。在PyTorch中,可以在每次loss.backward()之后,调用torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=5),将梯度范数限制在一个阈值内。
    2. 数据问题:数据中可能存在大量重复或无意义的片段。解决:重新检查并清洗数据。
    3. 学习率过高:导致训练不稳定。解决:降低学习率,例如从1e-3降到3e-4
    4. 模型初始化问题解决:确保LSTM的隐藏状态被正确初始化(通常为零向量)。

7.2 生成的诗总是很短,达不到指定长度

  • 症状:模型很快(远早于max_gen_len)就生成了结束符<end>
  • 可能原因与解决
    1. 训练数据中的诗普遍较短:模型学到了“诗应该短”的模式。解决:在数据中混合一些长诗,或者在生成时强制忽略结束符直到达到最小长度。
    2. 温度参数过低:导致模型过于“自信”地预测结束符。解决:适当提高温度参数T,增加随机性。

7.3 训练损失下降很慢,或者震荡厉害

  • 症状:训练了很多轮,损失值居高不下,或者像心电图一样上下波动。
  • 可能原因与解决
    1. 学习率不合适:可能是最根本的原因。解决:使用学习率调度器,如ReduceLROnPlateau,当验证损失停滞时自动降低学习率。
    2. 批次大小不合适:批次太小可能导致梯度估计噪声大。解决:在硬件允许的情况下增大批次大小。
    3. 模型结构或数据有问题解决:先用一个极小的数据集(比如100首诗)和简单的模型过拟合它,如果连小数据都学不好,那肯定是模型或代码有bug。

7.4 如何让AI写的诗“更像诗”?

这是一个开放性问题,没有标准答案,但有一些进阶思路:

  • 引入外部知识:在词向量中融入平仄、韵律信息。例如,可以为押韵的字赋予某种关联向量。
  • 使用更强大的模型:迁移到基于Transformer的模型,如GPT-2或GPT-3的轻量版。它们的注意力机制能更好地捕捉长距离依赖和全局结构。
  • 后处理与人工筛选:目前最实用的方法。让模型生成大量候选诗歌,然后由人从中挑选出意境、语言俱佳的“佳作”。AI负责“海选”,人负责“精选”,这是一种有效的人机协作创作模式。

这个项目从数据爬取、清洗到模型构建、训练,再到策略调优、问题排查,几乎涵盖了深度学习NLP项目的大部分核心环节。它像是一个微缩的实验室,让你能亲手触摸到语言模型的“温度”。最终,当你看到屏幕上流淌出第一句由你亲手训练的模型写出的、虽显稚嫩但已有几分诗意的句子时,那种感觉,或许本身就是一首诗。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询