Keras实现RNN+LSTM自动写古诗:从零跑通文本生成源码
发布时间:2026/10/18 12:35:00 锦皓数字建站

简介这份资源面向具备一定深度学习基础、希望动手实践序列生成模型的开发者与学习者核心是用Keras框架搭建RNN与LSTM网络实现自动编写古诗的完整流程帮助读者理解循环神经网络在文本生成任务中的建模思路与训练细节。压缩包共8个文件约3.86MB以4个Python脚本为主体分别承担模型定义、数据预处理与配置管理等职责另含诗词语料文本、说明文档、开源协议及忽略配置等辅助文件结构清晰便于直接运行与二次修改。目前已有602人学习下载说明该案例在入门序列生成方向具有一定参考价值。读者可从中获得可运行的完整代码、古诗数据集、模型构建与训练脚本以及作者在学习过程中整理的关键问题记录适合作为RNN与LSTM实战练手项目也可在此基础上调整网络结构或更换语料探索更丰富的文本生成效果。1. 用 Keras 搭一个会写古诗的 RNNLSTM这份源码能让你从零跑通文本生成有个挺有意思的现象很多开发者学完 RNN 和 LSTM 的理论梯度消失、门控机制、时间步展开都能讲得头头是道但一到“给我一个能跑起来的序列生成项目”就卡住了。这份《用 Keras 实现 RNNLSTM 的模型自动编写古诗》源码包解决的就是这个断层——它把“古诗文本 → 字符级序列建模 → 训练 → 按首句续写”整条链路完整落到了一个可运行的工程里。技术栈是 Python KerasTensorFlow 后端核心是字符级 LSTM 语言模型适合刚学完深度学习基础、想找一个完整 NLP 小项目练手的开发者也适合想拿它当模板改造成歌词生成、对联生成、代码补全等场景的熟手。下面我按“它是什么 → 怎么跑 → 怎么改”的顺序把这份源码拆开讲。2. 项目架构与数据流从古诗文本到字符级序列样本2.1 整体目录结构与模块职责拿到源码包解压后常见的目录组织大致是这样不同版本可能略有差异但核心文件不会少poetry_lstm/ ├── data/ │ └── poems.txt # 古诗语料每行一首 ├── model/ │ └── poetry_model.h5 # 训练后保存的权重 ├── config.py # 超参数集中配置 ├── data_loader.py # 语料读取与字符映射表构建 ├── model.py # 网络结构定义 ├── train.py # 训练入口 ├── generate.py # 生成/续写入口 └── requirements.txt # 依赖清单这个划分是典型的“配置、数据、模型、训练、推理”五分离。我一般拿到这类项目第一件事不是急着跑train.py而是先看config.py因为所有能调的东西都集中在那里改起来不用满项目翻。2.2 数据预处理字符级建模的关键一步古诗生成属于字符级语言模型不是词级。原因很直接古诗用词凝练、生僻字多分词反而会破坏韵律结构而字符级建模能让模型学到“平仄”“对仗”这种位置规律。核心预处理逻辑如下# data_loader.py import numpy as np def load_poems(path): with open(path, r, encodingutf-8) as f: poems [line.strip() for line in f if line.strip()] return poems def build_vocab(poems): # 把所有诗拼成一个长字符串统计出现过的字符 text .join(poems) chars sorted(set(text)) char2idx {c: i for i, c in enumerate(chars)} idx2char {i: c for c, i in char2idx.items()} return chars, char2idx, idx2char def make_sequences(poems, char2idx, seq_len20): X, y [], [] for poem in poems: # 每首诗转成索引序列 idxs [char2idx[c] for c in poem if c in char2idx] # 滑动窗口切样本前 seq_len 个字符预测第 seq_len1 个 for i in range(len(idxs) - seq_len): X.append(idxs[i:i seq_len]) y.append(idxs[i seq_len]) return np.array(X), np.array(y)逻辑说明build_vocab构建字符到整数的双向映射这是所有文本生成任务的地基。make_sequences用滑动窗口把长诗切成“输入 20 个字符、预测下一个字符”的监督样本。参数seq_len决定模型一次能“看到”多长的上下文默认 20 对五言/七言绝句够用改大能捕捉更长依赖但显存和训练时间会涨。注意语料里如果有标点、空格、英文要么在预处理阶段过滤掉要么让它们进词表否则char2idx[c]会直接抛 KeyError这是新手最常见的翻车点。2.3 模型结构Embedding LSTM Softmax 三段式网络本身不复杂但每一层的参数都值得说清楚# model.py from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding, LSTM, Dense def build_model(vocab_size, seq_len, embedding_dim128, lstm_units256): model Sequential([ # 把字符索引映射成稠密向量 Embedding(input_dimvocab_size, output_dimembedding_dim, input_lengthseq_len), # 单层 LSTM返回最后一个时间步的输出 LSTM(lstm_units, return_sequencesFalse), # 输出层vocab_size 个字符的概率分布 Dense(vocab_size, activationsoftmax) ]) model.compile(losscategorical_crossentropy, optimizeradam, metrics[accuracy]) return model逻辑说明Embedding把离散字符索引变成embedding_dim维向量让模型能学字符间的语义相似性。LSTM是核心return_sequencesFalse表示只取最后一个时间步的隐状态因为我们要预测的是“下一个字符”这一个输出。Dense softmax输出整个词表上的概率分布。参数上embedding_dim128、lstm_units256是这类小语料的稳妥起点如果语料只有几千首诗层数再堆深容易过拟合单层 LSTM 反而更稳。3. 训练与生成把模型跑起来并让它写出第一句诗3.1 训练脚本与回调配置训练入口通常长这样重点在回调# train.py from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping from data_loader import load_poems, build_vocab, make_sequences from model import build_model import config poems load_poems(config.DATA_PATH) chars, char2idx, idx2char build_vocab(poems) X, y make_sequences(poems, char2idx, seq_lenconfig.SEQ_LEN) model build_model(len(chars), config.SEQ_LEN) model.summary() callbacks [ # 只保存验证损失最低的权重避免最后一轮反而变差 ModelCheckpoint(config.MODEL_PATH, save_best_onlyTrue), # 连续 3 轮验证损失不降就停省时间 EarlyStopping(patience3, restore_best_weightsTrue) ] model.fit(X, y, batch_sizeconfig.BATCH_SIZE, epochsconfig.EPOCHS, validation_split0.1, callbackscallbacks)逻辑说明ModelCheckpoint配合save_best_onlyTrue是血泪经验——文本生成模型训练后期很容易过拟合最后一轮的权重未必最好保存最优权重能省掉手动回滚的麻烦。EarlyStopping的patience3表示容忍 3 轮不进步。validation_split0.1从训练集里切 10% 做验证语料小的时候这个比例够用。3.2 生成逻辑温度参数与逐字符采样生成不是一次性输出而是“预测一个字符 → 拼回输入 → 再预测下一个”的循环# generate.py import numpy as np def sample(preds, temperature1.0): # 温度越低越保守越高越随机 preds np.asarray(preds).astype(float64) preds np.log(preds 1e-8) / temperature exp_preds np.exp(preds) preds exp_preds / np.sum(exp_preds) return np.argmax(np.random.multinomial(1, preds, 1)) def generate_poem(model, start_text, char2idx, idx2char, seq_len20, length50, temperature0.8): generated start_text for _ in range(length): # 取最后 seq_len 个字符作为输入 seq generated[-seq_len:] x np.zeros((1, seq_len)) for i, c in enumerate(seq): x[0, i] char2idx.get(c, 0) preds model.predict(x, verbose0)[0] next_idx sample(preds, temperature) generated idx2char[next_idx] return generated逻辑说明sample函数里的温度参数是控制生成质量的关键。temperature0.5时输出保守、重复多但通顺temperature1.2时用词新奇但容易跑偏。generate_poem每次只预测一个字符然后把它追加到已生成文本末尾再截取最后seq_len个字符作为下一轮输入。参数length控制生成总长度start_text是给定的首句。提示如果生成结果全是重复字先别怀疑模型八成是温度太低或训练轮数不够。把temperature调到 0.8~1.0 之间再试。3.3 运行步骤与依赖安装完整跑通流程如下# 1. 建虚拟环境推荐避免依赖冲突 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 2. 装依赖核心就两个 pip install tensorflow keras numpy # 3. 训练首次跑建议先把 EPOCHS 调小验证流程 python train.py # 4. 生成 python generate.py --start 床前明月光 --length 40依赖上TensorFlow 2.x 已经内置 Keras不需要单独装keras包但有些老代码会import keras这时装一个兼容版本即可。训练时间取决于语料规模几千首诗在普通 CPU 上跑几十轮大概几十分钟到几小时有 GPU 会快很多。4. 避坑与常见问题排查4.1 报错 KeyError字符不在词表里现象生成时输入首句程序直接抛KeyError。原因首句里出现了训练语料中没出现过的字符char2idx里查不到。解决在generate_poem里用char2idx.get(c, 0)兜底或者干脆在生成前过滤掉生僻字。我一般会在预处理阶段就把非汉字字符全部剔除从源头避免。4.2 生成结果全是同一个字现象不管输入什么首句输出都是“啊啊啊啊”或“春春春春”。原因模型没训练充分或者学习率过高导致输出分布坍缩到单个字符。解决先确认训练轮数和损失是否正常下降再把temperature调高到 1.0 以上增加随机性如果还不行检查make_sequences的滑动窗口是否切错了导致标签和输入错位。4.3 显存/内存爆掉现象训练到一半OOM被杀。原因seq_len或batch_size设太大或者make_sequences一次性把所有样本堆进内存。解决把batch_size降到 64 或 32语料特别大时改用生成器逐批喂数据而不是np.array全量加载。4.4 模型保存后加载失败现象model.load_weights报维度不匹配。原因加载时的vocab_size或seq_len和训练时不一致。解决把词表和超参数一起保存加载时先读配置再建模型。常见做法是把char2idx用pickle存下来生成脚本直接读别每次重新构建。4.5 训练损失不下降现象loss 卡在某个值不动。原因学习率太大导致震荡或者数据没做归一化字符级建模一般不需要但索引越界会出问题。解决换用Adam默认学习率通常够用检查X里的索引是否都小于vocab_size越界会让 Embedding 层输出异常。5. 二次开发把古诗模型改成你自己的生成器这份源码最大的价值不是“会写古诗”而是它提供了一个字符级序列生成的通用骨架。想改成歌词生成、对联生成、甚至代码补全改动集中在三个地方。第一换语料。把data/poems.txt换成你的目标文本每行一条。歌词就一行一句对联就一行上联一行下联。注意保持“每行一个独立样本”的格式load_poems不用动。第二调seq_len。古诗五言七言短20 够用歌词句子长可能要 40~60代码补全的上下文更长可以到 100 以上。但seq_len越大LSTM 要记的依赖越长训练越慢建议从 30 起步逐步加。第三改生成策略。默认是逐字符贪心/采样如果你想让生成更可控可以加“首句约束”或“关键词引导”。比如对联生成可以固定上联只让模型生成下联# 固定前半段只生成后半段 def generate_with_prefix(model, prefix, suffix_len, char2idx, idx2char): generated prefix for _ in range(suffix_len): seq generated[-config.SEQ_LEN:] x np.zeros((1, config.SEQ_LEN)) for i, c in enumerate(seq): x[0, i] char2idx.get(c, 0) preds model.predict(x, verbose0)[0] generated idx2char[sample(preds, temperature0.7)] return generated验证改造是否成功我一般用两个指标一是生成结果里目标领域的词占比二是人工读几句看通顺度。别只看 lossloss 低不代表生成好看这是文本生成任务最容易踩的坑。改造时还有个实用技巧把config.py里的超参数全部参数化用命令行传入这样同一份代码能跑多组实验不用每次改文件。我习惯在train.py里加argparse把seq_len、lstm_units、epochs都做成可选参数。从那以后我每次拿到这类生成项目都强制先跑一遍小规模训练把EPOCHS设成 2、语料只取 100 首确认整条链路通了再上全量数据——这样能省掉大量“训练几小时才发现数据格式错了”的时间。希望这份拆解能帮你顺利跑起来并改成你自己想要的样子。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。