ChatGPT出现前的文本生成:手把手用Python实现n-gram古诗续写工具
从零构建唐诗生成器用Python揭秘n-gram的文本魔法记得第一次看到计算机生成古诗时那种震撼至今难忘——机器竟能模仿李白杜甫的笔触。这背后最基础的技术就是今天我们要探讨的n-gram模型。不同于现代庞大的神经网络n-gram用简单的统计规律就能产生令人惊喜的文本是理解自然语言生成的绝佳起点。1. 古诗生成器的技术基石n-gram的核心思想源于一个直观的观察人类语言具有强烈的局部依赖性。当我们说举头望明月时下一个词大概率是低头而非吃饭。这种连续性正是n-gram捕捉的关键。n-gram的数学本质是条件概率建模。给定前n-1个词预测第n个词的概率分布。例如在3-gram模型中P(词n | 词n-2, 词n-1) count(词n-2, 词n-1, 词n) / count(词n-2, 词n-1)这种统计方法虽然简单但在适当语料上效果惊人。我们来看《全唐诗》中的实际例子前序词后续词出现次数概率明月光420.38明月几230.21明月照190.17当模型看到明月时有38%的概率会选择光作为下一个词这正是床前明月光的经典搭配。2. 构建唐诗生成器的完整流程2.1 语料准备与清洗优质语料是n-gram模型成功的关键。我们使用《全唐诗》作为数据源首先需要进行预处理import re def clean_poem(text): # 去除标点符号 text re.sub(r[^\w\s], , text) # 统一转换为简体中文 text convert_to_simplified(text) # 去除空行和注释 lines [line.strip() for line in text.split(\n) if line.strip()] return lines清洗后的语料应该呈现这样的结构春眠不觉晓 处处闻啼鸟 夜来风雨声 花落知多少2.2 n-gram统计与概率计算接下来构建n-gram频率统计表。以3-gram为例from collections import defaultdict def build_ngram_model(corpus, n3): ngrams defaultdict(int) context defaultdict(int) for line in corpus: words list(line) for i in range(len(words)-n1): ngram tuple(words[i:in]) prefix tuple(words[i:in-1]) ngrams[ngram] 1 context[prefix] 1 # 计算条件概率 prob_dist {} for ngram, count in ngrams.items(): prefix ngram[:-1] prob_dist[ngram] count / context[prefix] return prob_dist生成的概率分布表会是这样{ (春,眠,不): 0.95, (眠,不,觉): 0.87, (不,觉,晓): 0.92, ... }2.3 文本生成策略有了概率分布我们需要选择生成策略。常见的有三种方法贪婪搜索每一步选择概率最高的词优点结果连贯性强缺点缺乏多样性随机采样按概率分布随机选择优点创造性更强缺点可能产生不合理组合束搜索(Beam Search)平衡连贯性与多样性保留top-k候选路径最终选择整体概率最高的序列实现Beam Search的核心代码def beam_search(prefix, ngram_model, k3, max_len20): beams [(list(prefix), 1.0)] # (序列, 累积概率) for _ in range(max_len - len(prefix)): new_beams [] for seq, prob in beams: last_words tuple(seq[-(n-1):]) if len(seq) n-1 else tuple(seq) candidates [(seq [next_word], prob * p) for (ctx, next_word), p in ngram_model.items() if ctx last_words] new_beams.extend(candidates) # 保留top-k beams sorted(new_beams, keylambda x: -x[1])[:k] return beams[0][0]3. n-gram的进阶技巧与调优3.1 平滑技术处理稀疏数据当遇到语料中未出现的n-gram组合时基础模型会失败。这时需要平滑技术加一平滑(Laplace)给所有可能组合加1次计数回退(Katz Backoff)当高阶n-gram不存在时使用低阶n-gram插值(Jelinek-Mercer)混合不同阶数的n-gram概率加一平滑的实现示例def laplace_smoothing(ngram, model, vocab_size, n3): prefix ngram[:-1] observed model.get(ngram, 0) prefix_count sum(1 for k in model if k[:-1] prefix) return (observed 1) / (prefix_count vocab_size)3.2 n值的选择艺术n的选择直接影响生成质量n值连贯性创造性内存需求2较低很高小3中等中等中等4高较低大在唐诗生成中3-gram通常是最佳平衡点。例如2-gram生成春风又绿江南岸明月何时照我还 → 春风又绿江南岸花开不见有人来3-gram生成更可能保持原句春风又绿江南岸明月何时照我还3.3 与现代模型的对比虽然n-gram看似简单但与现代Transformer相比仍有独特优势特性n-gramTransformer训练速度秒级小时/天级硬件需求CPU即可需要GPU可解释性完全透明黑盒小样本表现优秀需要大量数据长程依赖弱强创造性依赖语料可突破语料限制提示在小规模特定领域文本生成(如古诗)中n-gram的表现往往能媲美大模型且成本低几个数量级。4. 实战构建完整的唐诗生成系统4.1 系统架构设计完整的生成系统包含以下模块数据预处理层语料清洗分词/分字处理训练集/测试集划分核心模型层n-gram统计概率计算平滑处理生成策略层多种搜索算法温度参数控制长度控制评估与优化人工评估自动指标(如困惑度)A/B测试4.2 效果优化技巧混合n-gram同时使用2-gram和3-gram平衡创造性与连贯性主题控制通过筛选特定主题的诗句构建专属n-gram模型长度惩罚避免生成过长或过短的句子押韵处理在最后几个词约束押韵模式混合n-gram的实现示例def mixed_ngram_generate(prefix, model2, model3, weight0.7): for _ in range(max_len): # 获取2-gram和3-gram建议 suggestions2 get_suggestions(prefix, model2, n2) suggestions3 get_suggestions(prefix, model3, n3) # 混合概率 mixed {} for word, p in suggestions3.items(): mixed[word] weight * p for word, p in suggestions2.items(): mixed[word] mixed.get(word, 0) (1-weight) * p # 选择下一个词 next_word select_word(mixed) prefix.append(next_word) return prefix4.3 经典错误与调试在实际开发中常见问题包括无限循环生成因某些n-gram组合形成闭环修复设置最大生成长度或检测重复模式低质量输出语料噪声导致奇怪组合修复加强数据清洗或添加人工规则过滤内存不足高阶n-gram模型过大修复使用概率剪枝或磁盘存储调试时可以输出中间结果def debug_generate(prefix, model, n3): for i in range(10): last tuple(prefix[-(n-1):]) print(fStep {i}: Last words {last}) candidates [(k[-1],v) for k,v in model.items() if k[:-1]last] print(Candidates:, sorted(candidates, keylambda x:-x[1])[:5]) next_word max(candidates, keylambda x:x[1])[0] prefix.append(next_word) return prefix在构建这个唐诗生成器的过程中最让我惊讶的是简单如n-gram的技术在精心调优后竟能产生如此富有诗意的句子。有一次系统输出了孤舟蓑笠翁独钓寒江雪的完美续写——夜泊秦淮近酒家虽然这不是原诗的后句但意境衔接得天衣无缝。这种偶然迸发的灵感正是语言模型的魅力所在。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2490087.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!