拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Python古诗生成器实战:从数据清洗到前端集成

简介这是一份基于Python的古诗生成器完整项目源码面向文学爱好者、编程学习者与AI技术入门开发者帮助读者在体验古诗创作乐趣的同时理解自然语言处理与前后端协作的落地方式。压缩包共43个文件约10.85MB以7个Python脚本为核心涵盖数据加载、模型定义、训练与诗词评估等模块另有5个XML配置、5个CSS样式、5个JavaScript脚本及HTML页面构成前端交互界面并辅以字体、图片、文本说明与项目配置文件目录结构清晰。项目将后端算法与前端设计融合读者可据此学习模型训练流程、前后端数据对接与界面布局思路也便于二次开发与功能扩展。目前已有324人学习下载适合希望以趣味项目切入AI创作与Web集成的实践者参考。1. 从零搭一个古诗生成器Python 模型、数据管道与前端集成的完整落地路径很多人第一次听到「古诗生成器」脑子里浮现的是拿一份现成源码跑个 demo输入「春天」就吐出四句押韵的七言。真到自己动手问题立刻变成训练数据从哪来模型用 LSTM 还是 Transformer生成的诗怎么保证平仄和韵脚前端又怎么接我做过几版不同规模的方案踩过的坑比写过的代码多。这篇笔记把「基于 Python 的古诗生成器实现与前端集成」拆成一条能复现的路径从语料清洗、模型选型、训练调参到推理接口封装、前端调用与部署。适合有 Python 基础、想完整跑通一个文本生成项目的开发者也适合已经跑过 demo 但卡在「生成质量差」或「前后端接不上」的人。全程只讲我实际用过的做法参数给具体值坑标出来。2. 语料准备与数据管道古诗生成器的原料决定上限2.1 为什么数据清洗比模型选型更影响生成质量古诗生成器的输出质量七成取决于语料。我见过太多人直接拿网上爬的「全唐诗」丢进模型结果生成的诗里混着标点、注释、作者小传甚至现代白话。常见做法是先用公开的古诗数据集比如整理好的 JSON 或 TXT 格式每行一首但拿到手必须做三件事统一标点、过滤非诗文本、按字数切分。统一标点是指把全角逗号、句号、问号统一成中文标点去掉括号、引号、书名号。过滤非诗文本要剔除含「作者」「朝代」「注释」等字段的行。按字数切分是因为古诗以五言、七言为主长度固定模型学起来更稳。我一般只保留五言和七言绝句、律诗长度在 20 到 56 字之间。import re import json def clean_poem(line): # 去掉作者、朝代等非诗句内容 line re.sub(r[(].*?[)], , line) # 只保留中文和中文标点 line re.sub(r[^\u4e00-\u9fa5。、], , line) # 统一标点 line line.replace(,, ).replace(., 。) return line.strip() def build_corpus(raw_path, out_path): poems [] with open(raw_path, r, encodingutf-8) as f: for line in f: line clean_poem(line) # 只保留五言或七言长度 20-56 字 if 20 len(line) 56 and line.count() line.count(。) 2: poems.append(line) with open(out_path, w, encodingutf-8) as f: for p in poems: f.write(p \n) print(f清洗后保留 {len(poems)} 首) build_corpus(raw_poems.txt, clean_poems.txt)这段代码的关键在clean_poem里的正则[^\u4e00-\u9fa5。、]把非中文和非中文标点的字符全部删掉包括英文、数字、空格。20 len(line) 56这个范围覆盖了五言绝句20 字到七言律诗56 字。参数可以按需调整比如只做五言就改成20 len(line) 28。清洗后建议人工抽看 20 首确认没有混入奇怪内容。2.2 字符级词表的构建与序列填充古诗生成器通常用字符级建模因为汉字总量可控常用 6000 字左右词表小、训练快。构建词表就是统计所有出现过的字符按频率排序给每个字一个 ID。特殊 token 需要三个PAD填充、START序列起始、END序列结束。from collections import Counter def build_vocab(corpus_path, vocab_path, min_freq2): counter Counter() with open(corpus_path, r, encodingutf-8) as f: for line in f: counter.update(line.strip()) # 过滤低频字 chars [c for c, n in counter.most_common() if n min_freq] vocab {PAD: 0, START: 1, END: 2} for i, ch in enumerate(chars, start3): vocab[ch] i with open(vocab_path, w, encodingutf-8) as f: json.dump(vocab, f, ensure_asciiFalse) print(f词表大小: {len(vocab)}) return vocab build_vocab(clean_poems.txt, vocab.json)min_freq2表示出现少于 2 次的字直接丢弃这样词表能压到 5000 左右减少模型参数量。PAD固定为 0 是为了后续 padding 方便。序列填充时把所有诗句统一到最大长度比如 56不足的补PAD这样批量训练时张量形状一致。注意START和END要加在每首诗的首尾让模型学会「从哪开始、到哪结束」。3. 模型选型与训练LSTM 和 Transformer 到底选哪个3.1 两种主流结构的适用场景对比古诗生成器常见的两种结构LSTM 和 Transformer。LSTM 训练快、显存占用低适合数据量小几千到几万首、想快速出结果的场景。Transformer 生成质量更高、长距离依赖更好但需要更多数据和算力。我一般这样选如果语料少于 2 万首先用 LSTM 跑通如果超过 5 万首且有 GPU上 Transformer。维度LSTMTransformer训练速度快CPU 也能跑慢建议 GPU显存占用低高生成质量中等易重复较高更连贯数据需求几千首即可建议 2 万首以上调参难度低中高我实际用 LSTM 做过一个 1.5 万首的版本生成的诗能押韵但偶尔跑题换成 4 层 Transformer 后同样数据下连贯性明显提升但训练时间从 20 分钟涨到 2 小时。所以选型要看你的时间和硬件不要盲目追新。3.2 用 PyTorch 搭一个可训练的 LSTM 生成模型下面是一个两层 LSTM 的模型定义嵌入维度 256隐藏层 512。这个配置在我 8GB 显存的机器上 batch_size 可以开到 64。import torch import torch.nn as nn class PoemLSTM(nn.Module): def __init__(self, vocab_size, embed_dim256, hidden_dim512, num_layers2): super().__init__() self.embed nn.Embedding(vocab_size, embed_dim, padding_idx0) self.lstm nn.LSTM(embed_dim, hidden_dim, num_layers, batch_firstTrue, dropout0.3) self.fc nn.Linear(hidden_dim, vocab_size) def forward(self, x, hiddenNone): # x: (batch, seq_len) emb self.embed(x) out, hidden self.lstm(emb, hidden) logits self.fc(out) return logits, hiddenpadding_idx0让PAD不参与梯度计算。dropout0.3是防止过拟合的关键古诗数据量不大时尤其重要。num_layers2是平衡表达能力和训练速度的选择加到 3 层以上容易过拟合。训练时用交叉熵损失注意要把PAD的位置忽略掉。def train_step(model, batch, optimizer, criterion): inputs batch[:, :-1] targets batch[:, 1:] logits, _ model(inputs) loss criterion(logits.reshape(-1, logits.size(-1)), targets.reshape(-1)) optimizer.zero_grad() loss.backward() # 梯度裁剪防止梯度爆炸 torch.nn.utils.clip_grad_norm_(model.parameters(), 5.0) optimizer.step() return loss.item()clip_grad_norm_的 5.0 是经验值LSTM 训练时梯度容易爆炸不加这个损失会变成 NaN。学习率我一般设 0.001用 Adam 优化器。训练 30 到 50 个 epoch看验证集损失不再下降就停。3.3 生成阶段的温度参数与重复惩罚训练完模型生成时有两个参数直接决定输出质量温度temperature和重复惩罚repetition penalty。温度控制随机性低于 0.8 输出保守但可能呆板高于 1.2 容易胡言乱语。我一般用 0.9。重复惩罚用来压制重复字设 1.2 左右。def generate(model, vocab, start_textSTART, max_len56, temperature0.9, rep_penalty1.2): model.eval() idx2char {v: k for k, v in vocab.items()} input_ids [vocab.get(c, vocab[PAD]) for c in start_text] input_tensor torch.tensor([input_ids]) generated [] hidden None with torch.no_grad(): for _ in range(max_len): logits, hidden model(input_tensor, hidden) logits logits[0, -1, :] / temperature # 重复惩罚 for token in set(generated): logits[vocab[token]] / rep_penalty probs torch.softmax(logits, dim-1) next_id torch.multinomial(probs, 1).item() char idx2char[next_id] if char END: break generated.append(char) input_tensor torch.tensor([[next_id]]) return .join(generated)torch.multinomial按概率采样比 argmax 更有多样性。重复惩罚那段遍历已生成的字把对应 logits 除以惩罚系数简单有效。注意生成时每次只输入上一个字hidden 状态要传递否则模型记不住上下文。4. 前端集成把生成接口接到网页上4.1 用 Flask 封装推理接口的最小实现模型跑在 Python 里前端要调用就得有个 HTTP 接口。Flask 是最轻的选择几十行就能跑起来。核心是把模型加载一次常驻内存请求来了直接推理不要每次重新加载。from flask import Flask, request, jsonify import torch, json app Flask(__name__) vocab json.load(open(vocab.json, encodingutf-8)) model PoemLSTM(len(vocab)) model.load_state_dict(torch.load(poem_lstm.pth, map_locationcpu)) model.eval() app.route(/generate, methods[POST]) def generate_api(): data request.get_json() start data.get(start, START) temperature float(data.get(temperature, 0.9)) poem generate(model, vocab, start_textstart, temperaturetemperature) return jsonify({poem: poem}) if __name__ __main__: app.run(host0.0.0.0, port5000)load_state_dict加载训练好的权重map_locationcpu保证没有 GPU 也能跑。接口接收 JSON返回 JSON前端用 fetch 调用即可。注意host0.0.0.0让局域网内其他设备也能访问本地调试用127.0.0.1更安全。4.2 前端页面调用与跨域处理前端最简单就是一个 HTML 页面输入起始字点按钮请求接口把返回的诗显示出来。跨域问题用 Flask-CORS 解决或者在前端用代理。我一般直接加 CORS 头。!DOCTYPE html html headmeta charsetutf-8title古诗生成器/title/head body input idstart placeholder输入起始字如春 button onclickgen()生成/button pre idresult/pre script async function gen() { const start document.getElementById(start).value || START; const res await fetch(http://127.0.0.1:5000/generate, { method: POST, headers: {Content-Type: application/json}, body: JSON.stringify({start: start, temperature: 0.9}) }); const data await res.json(); document.getElementById(result).textContent data.poem; } /script /body /htmlfetch的Content-Type必须是application/json否则 Flask 的get_json()拿不到数据。如果前端和后端不在同一个端口浏览器会拦跨域请求需要在 Flask 里加from flask_cors import CORS; CORS(app)。这个页面没有样式但功能完整先跑通再美化。5. 避坑与排查古诗生成器最常见的 5 个翻车现场5.1 生成结果全是重复字或乱码现象输出像「春春春春春春春春」或者一堆不相关的字。原因通常是训练不充分或温度参数不对。先检查训练损失有没有降到 2.0 以下如果还在 4.0 以上说明模型没学会。解决增加 epoch 到 50或者把学习率降到 0.0005。如果损失正常但输出重复把重复惩罚从 1.2 调到 1.5温度从 0.9 降到 0.7。5.2 前端请求返回 500 错误现象点按钮没反应控制台显示 500。原因多半是后端加载模型失败或输入格式不对。先看 Flask 终端的报错如果是KeyError说明起始字不在词表里。解决在generate_api里加 try-except把未知字符替换成START。另外确认vocab.json和模型权重的路径是绝对路径相对路径在 Flask 启动目录变化时会找不到。5.3 训练时损失变成 NaN现象训练几个 batch 后 loss 显示 nan。原因是梯度爆炸LSTM 常见问题。解决确认clip_grad_norm_已经加上阈值设 5.0 或更低。如果还不行把学习率从 0.001 降到 0.0001。另外检查数据里有没有空行或超长序列空行会导致除零。5.4 生成的诗不押韵现象句子通顺但韵脚不对。原因是模型只学了字符共现没学韵律规则。解决在语料里只保留押韵的诗或者在生成后用规则过滤。我一般会在生成后检查最后一个字的韵母不押韵就重新生成最多重试 5 次。这个做法简单但有效比改模型结构快得多。5.5 部署后接口响应慢现象本地测试很快部署到服务器后每次请求要好几秒。原因是模型在 CPU 上推理或者每次请求都重新加载模型。解决确认模型是全局加载一次不是写在generate_api里面。如果还是慢把max_len从 56 降到 28只生成绝句。CPU 推理 LSTM 生成 28 个字大约 0.5 秒可以接受。6. 进阶技巧用束搜索和韵脚约束把生成质量再提一档温度采样生成快但质量不稳定。如果想让输出更靠谱可以换成束搜索beam search。束搜索每步保留概率最高的 k 个候选最后选整体概率最高的序列。k 一般取 3 到 5太大反而会生成保守重复的内容。def beam_search(model, vocab, start_textSTART, max_len28, beam_width3): model.eval() idx2char {v: k for k, v in vocab.items()} input_ids [vocab.get(c, vocab[START]) for c in start_text] beams [(input_ids, 0.0, None)] # (序列, 累计log概率, hidden) for _ in range(max_len): new_beams [] for seq, score, hidden in beams: input_tensor torch.tensor([seq[-1:]]) with torch.no_grad(): logits, new_hidden model(input_tensor, hidden) log_probs torch.log_softmax(logits[0, -1, :], dim-1) topk torch.topk(log_probs, beam_width) for prob, idx in zip(topk.values, topk.indices): new_seq seq [idx.item()] new_beams.append((new_seq, score prob.item(), new_hidden)) # 保留概率最高的 beam_width 个 beams sorted(new_beams, keylambda x: x[1], reverseTrue)[:beam_width] best_seq beams[0][0] return .join(idx2char[i] for i in best_seq if i 3)log_softmax把 logits 转成对数概率累加避免浮点下溢。beam_width3是速度和质量的平衡点设 5 会慢一倍但提升有限。注意 hidden 状态要跟着每个 beam 走否则上下文会乱。韵脚约束更直接生成前先确定韵脚字比如「春」的韵母是「un」生成时只从韵母相同的字里采样。实现方式是维护一个韵母到字的映射表最后一句的最后一个字强制从这个表里选。这个做法需要额外准备韵表但效果立竿见影。我一般用pypinyin库提取韵母几行代码就能建好映射。from pypinyin import pinyin, Style def build_rhyme_dict(vocab): rhyme_dict {} for ch in vocab: if len(ch) ! 1 or not \u4e00 ch \u9fa5: continue finals pinyin(ch, styleStyle.FINALS) if finals and finals[0]: r finals[0][0] rhyme_dict.setdefault(r, []).append(ch) return rhyme_dictStyle.FINALS只取韵母忽略声母。rhyme_dict建好后生成最后一句时把候选字限制在同一个韵母组里。这个技巧对七言绝句特别有效因为绝句的韵脚规律很强。最后说个我自己的习惯每次改完模型或参数先固定随机种子生成 10 首人工看一遍再决定要不要继续调。生成质量这种事指标只能参考眼睛不会骗你。希望帮到你。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门