尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Python NLP诗歌接龙源码解析:字级n-gram与押韵束搜索
简介这份资源面向想用Python入门自然语言处理、并希望完成一个可运行小项目的学习者核心是一个围绕诗词文本展开的接龙程序从诗句中提取尾字、借助pypinyin做拼音与韵脚匹配再结合jieba分词与规则或统计策略筛选可接续的诗句可用于课程设计、NLP练手与文本处理入门。压缩包共17个文件、约5.86MB以py源码和xml配置为主兼有pk、dat模型与数据文件、exe与pyc可直接运行或对照的成品、docx说明文档及txt记录源码、数据与文档层次分明便于一边读实现一边跑效果。目前已有499人学习下载。文档中对接龙流程、模块划分与使用方式有完整交代读者可据此理解音韵匹配思路、复用抓取与清洗脚本并自行替换词库或扩展押韵规则获得一份结构清晰的实战参考。1. 从一句“春眠不觉晓”往下接这份 Python NLP 诗歌接龙源码包到底在做什么先把结论放这儿这份 Python NLP 诗歌接龙源码包不是让你背诗也不是简单查“下一句”它把古诗拆成字级语言模型让程序根据上一句续写下一句真正难的是押韵约束、重复控制和长度对齐。很多人第一次跑这类项目会发现模型能出字却总在韵脚上翻车或者五言接成六言这也是它值得拆开看的原因。它适合两类人一类是刚接触 NLP 的 Python 开发者想找一个比情感分类更有意思的练手项目另一类是做内容 demo 的工程师需要一套能本地跑、能改参数、能接前端接口的诗歌接龙基线。源码包里的思路通常围绕语料清洗、n-gram 或轻量 LSTM、束搜索生成这三块展开。不适合把它当成“开箱即用的作诗神器”。古诗的平仄、对仗、意象连贯性靠一个几万字的语料和字级模型只能做到形似但作为教学项目、课程设计、NLP 流程演示它足够完整也足够让你踩到真实工程里的坑。2. 语料与韵脚表把古诗拆成字级 n-gram 和可查询的韵库拿到压缩包后先别急着运行生成脚本。诗歌接龙的上限很大程度由语料清洗和韵脚表决定清洗错了模型学到的是标题、作者名和标点韵脚表粗了生成句的尾字会“看着像押韵读起来别扭”。这一章按数据进入模型的顺序拆先按字切分再建韵部最后落成多阶 n-gram 索引。2.1 语料清洗先按字切不按词切古诗和现代文不一样很多“词”在古诗里是两个字各自表意直接用 jieba 分词会把“明月”“故乡”这类高频意象切碎字级模型反而更稳。我一般会先做三件事去掉行首的标题标记、去掉括号注释、只保留汉字。下面这段脚本适合“一行一句”的预处理文本如果你的语料是一行一首先按标点切成句再喂进来。import re from pathlib import Path HANZI re.compile(r[\u4e00-\u9fff]) def normalize_line(line: str) - list[str]: line line.strip() # 去掉行首《》【】包裹的标题片段避免标题字进入正文统计 line re.sub(r^[《【].*?[》】], , line) # 去掉括号内的注释或异文标记 line re.sub(r[(].*?[)], , line) # 只保留汉字标点和空白全部丢弃 return HANZI.findall(line) def load_corpus(path: str, min_len: int 5) - list[list[str]]: sentences [] for raw in Path(path).read_text(encodingutf-8).splitlines(): chars normalize_line(raw) if len(chars) min_len: continue sentences.append(chars) return sentences这段代码的逻辑很直白normalize_line负责把一行文本压成汉字列表load_corpus负责过滤过短的行。min_len5是按五言诗的下限设的如果你只做七言可以改成 7如果语料里混了词牌和长调建议先按标点切句再按 5 或 7 过滤否则长句会把 n-gram 的上下文拉得很散。编码参数固定utf-8Windows 下如果遇到gbk文件先转码再跑不要在代码里做编码猜测后面排查会很痛苦。清洗完别直接全量训练。抽 100 行打印出来看重点看三件事标题字有没有混进去、异体字有没有统一、一行里是不是塞了两句。古诗语料最常见的翻车不是模型太弱而是“春眠不觉晓处处闻啼鸟”被当成一句长度控制直接失效。这个检查花两分钟能省后面两小时。2.2 韵脚表用普通话韵母近似不硬套平水韵诗歌接龙最容易被低估的是韵脚。严格按平水韵做当然好但工程上更常见的做法是先用普通话韵母做近似韵部把ang/iang/uang归到一类把an/ian/uan/üan归到一类先让接龙能跑起来再按需要细化。pypinyin的Style.FINALS能直接取韵母多音字用heteronymTrue展开建表时保留所有读音生成时再取交集。from pypinyin import pinyin, Style RHYME_MAP { ang: ang, iang: ang, uang: ang, an: an, ian: an, uan: an, üan: an, ao: ao, iao: ao, ai: ai, uai: ai, en: en, in: in, un: un, ün: un, eng: eng, ing: ing, ong: ong, iong: ong, i: i, u: u, ü: ü, er: er, } def rhyme_keys(ch: str) - set[str]: result set() for item in pinyin(ch, styleStyle.FINALS, heteronymTrue, errorsignore): for final in item: if final: result.add(RHYME_MAP.get(final, final)) return result逻辑说明pinyin返回的是嵌套列表heteronymTrue时一个字可能有多个读音每个读音对应一个韵母errorsignore让非汉字直接跳过避免生成阶段因为标点或生僻符号抛异常。参数上RHYME_MAP是近似归并表不是韵书ang组和an组不要混在一起否则“山”和“光”会被判成同韵读起来很怪。多音字返回set是为了后面做交集判断比如上一句尾字有两个韵部下一句尾字命中任意一个就算押韵。原始韵母归并到说明iang / uangang江阳辙近似适合七言ian / uan / üanan寒山辙近似注意与 ang 分开iaoao遥条辙近似ing / engeng庚青辙可合并但输出会更宽松这张表只解决“能不能押”的第一层问题。真要做到读起来顺还要看声调普通话一、二声算平三、四声算仄生成时把平仄当软约束加一点分不要硬卡。硬卡的结果是候选字被筛空束搜索直接返回None新手很容易在这里卡住。2.3 多阶 n-gram把s和/s写进索引字级 n-gram 是这套项目最稳的基线。n3 表示用前两个字预测下一个字n2 表示只看前一个字n1 就是全局字频。为什么不只建 trigram因为生成时一旦遇到没见过的二字组合trigram 查不到必须回退到 bigram 和 unigram否则接龙会在第二个字就断掉。下面这段代码把 1 到 3 阶一起建出来键是历史元组值是Counter。from collections import defaultdict, Counter BOS, EOS s, /s def build_ngram(sentences: list[list[str]], max_n: int 3): models {n: defaultdict(Counter) for n in range(1, max_n 1)} for sent in sentences: for n in range(1, max_n 1): tokens [BOS] * (n - 1) sent [EOS] for i in range(n - 1, len(tokens)): history tuple(tokens[i - n 1:i]) if n 1 else tuple() models[n][history][tokens[i]] 1 return models逻辑说明对每个阶数 n先在句首补n-1个s句尾补/s然后滑窗统计。history是前n-1个字models[3][(春,眠)][不]表示“春眠”后面接“不”的次数。参数max_n3是起步值n2 生成更稳但容易重复n4 更通顺但很多上下文查不到语料不到十万句时建议先停在 3。BOS和EOS不要用普通汉字代替否则会和正文里的“开”“终”混在一起后面做长度控制时无法区分句尾。内存上要有预期unigram 很小bigram 是字表平方量级trigram 会随语料线性增长。如果跑起来发现内存吃紧先把max_n降到 2或者把低频字统一映射到unk再建索引。别一上来就上 5-gram诗歌语料稀疏高阶模型看起来“更懂上下文”实际生成时大量回退效果不会比 3-gram 好多少。3. 接龙生成器从概率表到带押韵约束的束搜索有了多阶 n-gram下一步不是直接most_common(1)那样每句都会接成最保守的高频组合读三句就腻。生成器要做三件事历史查不到时按阶回退、候选打分时把押韵和重复惩罚加进去、用束搜索保留多条路径。这一章把这三块拆开写参数都能直接改。3.1 回退候选trigram 不够就退到 bigram 和 unigram回退策略不是“随便找一个字补上”而是按阶数从高到低查。当前历史有两个字先查 3-gram查不到就把历史砍到最后一个字查 2-gram再查不到就用 1-gram 全局字频。下面这段函数返回(字, 概率)列表概率按当前阶的总频次归一化方便后面和押韵分数相加。def backoff_candidates(models, history, k20): max_n max(models.keys()) for n in range(min(max_n, len(history) 1), 0, -1): hist tuple(history[-(n - 1):]) if n 1 else tuple() counter models.get(n, {}).get(hist) if counter: total sum(counter.values()) return [(ch, cnt / total) for ch, cnt in counter.most_common(k)] return []逻辑说明history传的是最近两个字组成的元组长度最多 2n从min(max_n, len(history)1)开始往下退正好对应 3-gram、2-gram、1-gram。k20是每阶取的候选数太小会让押韵筛选没有余地太大则束搜索变慢。参数上k在生成五言时建议 30 到 50七言可以放到 50 到 80如果候选里大量是s和/s说明句首句尾统计没洗干净回到 2.1 检查语料。这里有个细节回退到 unigram 时概率是全局字频高频字会占据候选榜生成句容易变成“之乎者也”堆叠。我的做法是给低阶回退乘一个折扣比如 2-gram 乘 0.7、1-gram 乘 0.4再和押韵分相加。这个折扣不是玄学是为了让高阶上下文优先避免模型一查不到就摆烂。3.2 打分函数押韵权重、平仄软约束和重复惩罚候选字不能只看语言模型概率。接龙的目标是“下一句尾字押上一句的韵”所以尾字要加押韵奖励句内重复字要扣分平仄只做轻量加分不做硬过滤。下面这个打分函数把三项揉在一起权重量级是经验值你可以按语料风格调。import math def tone_of(ch: str): py pinyin(ch, styleStyle.TONE3, errorsignore) if not py or not py[0]: return None tone py[0][-1] return ping if tone in 12 else ze if tone in 34 else None def score_char(ch, prob, used, target_rhymeNone, tone_targetNone): score math.log(max(prob, 1e-12)) if target_rhyme and target_rhyme in rhyme_keys(ch): score 1.2 if tone_target and tone_of(ch) tone_target: score 0.15 score - 0.8 * used[ch] return score逻辑说明math.log把概率转成对数分避免连乘下溢used是当前句已出现字的Counter每重复一次扣 0.8押韵命中加 1.2这个权重明显高于平仄的 0.15是因为接龙场景里“尾字押韵”比“句内平仄完美”更容易被读者感知。参数上如果生成句重复严重把 0.8 提到 1.2如果押韵太生硬、为了押韵牺牲语义把 1.2 降到 0.6 到 0.8让语言模型概率重新占主导。平仄这里只对尾字或关键位置做软约束不要每个字都卡。古诗的平仄格式有固定套路但字级模型没有学过整句格式硬卡会把候选筛空。常见做法是五言看第 2、4 字和尾字七言看第 2、4、6 字和尾字其他位置放开。这个项目如果只是课程设计平仄可以先不做先把押韵和重复率压下去效果提升更明显。3.3 束搜索最后一位才强制押韵别每一步都卡束搜索的关键是“保留多条候选路径”但诗歌接龙有一个特殊性押韵只约束尾字句内其他位置不应该被韵脚绑死。下面这段生成函数在最后一个位置才强制target_rhyme前面位置只做打分同时用prune_beams按最后两个字去重防止同质路径挤占 beam。def prune_beams(beams, max_beam): seen {} for chars, score, hist in beams: key tuple(chars[-2:]) if key not in seen or score seen[key][1]: seen[key] (chars, score, hist) return sorted(seen.values(), keylambda x: x[1], reverseTrue)[:max_beam] def generate_line(models, prev_line, length5, target_rhymeNone, beam_size8, topk40): seed [BOS] list(prev_line) beams [([], 0.0, tuple(seed[-2:]))] for pos in range(length): new_beams [] for chars, score, hist in beams: for ch, prob in backoff_candidates(models, hist, ktopk): if ch in (BOS, EOS): continue if pos length - 1 and target_rhyme and target_rhyme not in rhyme_keys(ch): continue used Counter(chars) s score score_char(ch, prob, used, target_rhyme if pos length - 1 else None) new_hist tuple((list(hist) [ch])[-2:]) new_beams.append((chars [ch], s, new_hist)) if not new_beams: return None beams prune_beams(new_beams, beam_size) if not beams: return None return .join(max(beams, keylambda x: x[1])[0])逻辑说明seed把上一句拼进历史保证接龙和上文有字面衔接beams里每个元素是“已生成字列表、累计分数、最近两字历史”。最后一个位置用target_rhyme not in rhyme_keys(ch)直接跳过不押韵的字这一步是硬约束前面位置不传target_rhyme避免整句被韵脚拖偏。prune_beams用最后两字做状态键是因为 n-gram 生成下一字只依赖最近两字同状态保留高分路径就够了。参数上length5是五言七言改 7beam_size8适合本地 CPU调到 32 会明显变慢但句子更稳topk40是每步候选数语料小可以降到 20。注意generate_line返回None不是异常而是“当前韵脚下没有候选”生产环境要接兜底逻辑别直接join(None)。下一章会讲 n-gram 和 LSTM 怎么共用接口以及怎么在超时的时候降级。4. 模型训练与推理n-gram 基线到轻量 LSTM 的切换n-gram 的好处是训练快、可解释、随时能查表坏处是生成多样性有限长上下文记不住。源码包里如果有 LSTM 版本通常是为了对比“统计模型”和“神经模型”的差异。我的建议是先把 n-gram 跑通再用 LSTM 做增强不要让神经模型一上来就把数据管道和评测拖住。这一章按“体检指标、数据管道、训练与降级”三块写。4.1 n-gram 的困惑度只当体检不当验收困惑度能告诉你模型在验证集上“有多意外”但它和“接龙好不好读”不是一回事。下面这段计算按 3-gram 走查不到历史时给一个极小概率正式评测应该接回退概率这里保留1e-6是为了让脚本能跑完并暴露稀疏程度。def perplexity(models, sentences, n3): total_logp 0.0 total_tokens 0 for sent in sentences: tokens [BOS] * (n - 1) sent [EOS] for i in range(n - 1, len(tokens)): hist tuple(tokens[i - n 1:i]) counter models[n].get(hist) if not counter: p 1e-6 else: p counter.get(tokens[i], 0) / sum(counter.values()) total_logp math.log(max(p, 1e-12)) total_tokens 1 return math.exp(-total_logp / max(total_tokens, 1))逻辑说明对每个 token 取条件概率取对数后累加最后除以 token 数再取指数。n3要和训练时一致验证集不能混进训练集否则困惑度会虚低。参数上1e-6是回退占位不是平滑如果你加了加一平滑困惑度会变不同平滑方式之间不要直接比数值。更实用的做法是同时记录“回退次数占比”回退超过 30% 说明语料覆盖不够先补数据或降阶。困惑度低但接龙难读通常是因为高频虚词拉低了平均意外度。我的验收习惯是困惑度只看趋势真正上线前跑 200 条批量生成统计长度合规率、押韵命中率和句内重复率再抽 50 条人工读。指标口径在第 6 章给表。4.2 LSTM 数据管道字表、BOS/EOS 和定长上下文LSTM 版本的第一道坎不是模型结构是数据管道。字级模型需要把每个字映射成 idpad固定为 0unk接生僻字s和/s标句界。下面先建字表再把每句拆成“前ctx_len个字预测下一个字”的样本左侧补pad保证 batch 能堆叠。from collections import Counter import torch from torch.utils.data import Dataset def build_vocab(lines, min_freq1): counter Counter(ch for line in lines for ch in line) chars [ch for ch, n in counter.items() if n min_freq] stoi {pad: 0, unk: 1, s: 2, /s: 3} for ch in chars: stoi.setdefault(ch, len(stoi)) return stoi class NextCharDataset(Dataset): def __init__(self, lines, stoi, ctx_len12): self.items [] unk stoi[unk] for line in lines: ids [stoi[s]] [stoi.get(ch, unk) for ch in line] [stoi[/s]] for i in range(1, len(ids)): ctx ids[max(0, i - ctx_len):i] ctx [stoi[pad]] * (ctx_len - len(ctx)) ctx self.items.append((ctx, ids[i])) def __len__(self): return len(self.items) def __getitem__(self, idx): ctx, target self.items[idx] return torch.tensor(ctx), torch.tensor(target)逻辑说明build_vocab先统计字频min_freq1表示生僻字也保留诗歌语料小设 2 或 3 会把很多意象字打成unk反而伤生成如果语料很大可以设 2。NextCharDataset对每句生成多个训练样本ctx_len12是上下文窗口五言七言接龙不需要太长12 到 20 够用。左侧补pad而不是右侧补是为了让out[:, -1, :]永远取到最后一个真实上下文位置。参数上ctx_len越大模型能记住的上文越多但训练样本的 padding 也越多batch_size在 CPU 上本文还有配套的精品资源点击获取
RELATED

相关推荐

国庆7天AI扫盲路线图:从大模型原理到Agent工程实践

国庆7天AI扫盲路线图:从大模型原理到Agent工程实践

国庆7天,外面人山人海,不如在家把AI这摊事彻底弄明白。这篇文章的定位不是给你堆概念,而是围绕“AI概念大全”这个主题,帮技术人(以及准备转AI方向的产品、测试、前端后端同学)把听到耳朵起茧的AI术语、AI …

📅 2026/11/2 8:28:18
2025年AI降噪技术解析与旗舰产品横评

2025年AI降噪技术解析与旗舰产品横评

1. 2025届AI降噪助手市场概览在数字内容创作爆发的2025年,音频处理已成为刚需。根据AudioTech研究院最新数据,全球每日产生的语音内容超过80亿分钟,其中67%需要降噪处理。传统降噪工具处理后的语音平均失真率达到12%,而新一代AI降…

📅 2026/11/2 8:23:17
STBC空时分组码原理、线性合并译码与BER仿真实践

STBC空时分组码原理、线性合并译码与BER仿真实践

简介:面向无线通信入门学习者与课程实验需求,这套空时分组码(STBC)编码译码实现代码围绕发射端空时编码与接收端译码过程展开,并给出误码率(BER)曲线用于直观验证算法效果。资源包共3个文件&…

📅 2026/11/2 8:23:17
MORE NEWS

更多资讯

📰

雷达原始数据在多模态融合中的创新应用与优化

1. 项目概述:雷达原始数据在多模态融合中的突破性应用这篇论文《Echoes Beyond Points: Unleashing the Power of Raw Radar Data in Multi-modality Fusion》提出了一个颠覆性的观点:在自动驾驶和环境感知领域,我们应该超越传统的点云处理方…

📰

SPA应用中window.location.href跳转不刷新的解决方案

1. 问题现象与背景分析最近在开发一个SPA应用时遇到了一个典型问题:使用window.location.href进行页面跳转后,目标页面内容没有自动刷新。这个问题在传统多页应用中很少出现,但在现代前端框架(Vue/React/Angular)构建的单页应用中却相当普遍。…

📰

鸿蒙开发中测试覆盖率净化实践与优化

1. 为什么鸿蒙开发者需要关注测试覆盖率净化在鸿蒙应用开发中,我们经常遇到一个尴尬的局面:单元测试报告显示覆盖率高达90%,但实际业务逻辑的测试却漏洞百出。这种"虚假繁荣"的现象主要源于LCOV报告将以下三类非业务代码纳入了统计…

📰

从Linux到AIOps大模型:云原生智能运维体系实战避坑指南

1. 从零搭建一套能打的云原生智能运维体系,到底要啃哪些硬骨头干了七八年运维,从最早的手工敲命令、写Shell脚本,到后来玩Ansible、Docker、Kubernetes,再到这两年大家都在聊的AIOps和大模型,我最大的感受就是&#xf…

📰

Vitis HLS新手入门:从C++到FPGA硬件加速的实战与避坑

简介:面向具备C/C基础、希望以高级语言完成FPGA硬件加速的工程师、研究生与自学者,这份中文版《Vitis高层次综合用户指南》(UG1399 v2022.2,2022年12月7日版)PDF文档适合从入门到进阶系统查用。全文按七个部分组织&…

📰

Jev 热词解析:类型安全 SDK 如何接入 Claude Code 与本地模型

1. 从热搜词里挖出的真实需求最近一段时间,后台和评论区被同一个词刷屏了:Jev。有人问“Jev 模型官网到底在哪”,有人问“Jev 密钥怎么申请”,还有人直接甩过来一句“Jev 在 Codex 里怎么用”。我一开始也懵,因为这个词…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬