别再死记硬背N-Gram了!用Python从零实现一个能‘打分’的句子生成器

发布时间:2026/5/27 6:19:09

别再死记硬背N-Gram了!用Python从零实现一个能‘打分’的句子生成器 别再死记硬背N-Gram了用Python从零实现一个能‘打分’的句子生成器你是否曾经好奇为什么有些AI生成的句子读起来流畅自然而有些却显得生硬怪异这背后隐藏着一个强大的语言模型——N-Gram。今天我们不谈枯燥的数学公式而是通过Python实战带你亲手打造一个能评估句子合理性的打分器并让它指导我们生成更自然的文本。1. 为什么需要N-Gram从AI的语无伦次说起当AI生成今天天气很好所以我要去图书馆吃三明治这样的句子时虽然每个词都认识组合起来却显得怪异。这就是缺乏语言模型评估的结果。N-Gram的核心思想很简单通过统计大量文本中词语的共现频率来判断一个新句子像不像人话。**Bigram二元模型**是最实用的入门选择它只关注相邻两个词的关系。比如喝咖啡在语料中频繁出现 → 高概率喝石头几乎从未出现 → 低概率我们即将构建的系统工作流程如下训练阶段分析语料库统计词对频率评分阶段计算新句子中所有词对的联合概率生成阶段用评分指导文本生成2. 实战准备语料处理与概率计算2.1 构建基础语料库我们从简单的中文短句开始创建一个corpus.txt文件今天天气真好 我要去公园散步 下午喝咖啡看书 明天可能下雨 记得带伞出门2.2 概率计算核心原理Bigram概率公式为P(w2|w1) C(w1w2) / C(w1)其中C(w1w2)是词对出现的次数C(w1)是前词单独出现的次数例如计算P(天气|今天)今天天气出现1次今天出现1次因此P1/113. Python实现从统计到评分3.1 构建频率词典首先实现文本预处理和统计功能import re from collections import defaultdict class BigramModel: def __init__(self): self.unigram_counts defaultdict(int) self.bigram_counts defaultdict(int) def preprocess(self, text): # 移除标点添加句子边界符 text re.sub(r[^\w\s], , text) return [s] text.split() [/s] def train(self, corpus): for sentence in corpus: tokens self.preprocess(sentence) for i in range(len(tokens)-1): self.unigram_counts[tokens[i]] 1 self.bigram_counts[(tokens[i], tokens[i1])] 13.2 实现句子评分添加概率计算和评分方法def score_sentence(self, sentence): tokens self.preprocess(sentence) score 1.0 for i in range(len(tokens)-1): w1, w2 tokens[i], tokens[i1] bigram_count self.bigram_counts.get((w1, w2), 0) unigram_count self.unigram_counts.get(w1, 0) # 使用加一平滑处理零概率问题 probability (bigram_count 1) / (unigram_count len(self.unigram_counts)) score * probability return score注意实际应用中需要考虑数据稀疏问题这里使用最简单的加一平滑(Laplace Smoothing)4. 可视化对比什么样的句子更合理训练模型后我们可以对比不同句子的得分model BigramModel() model.train(corpus) sentences [ 今天天气真好, 今天咖啡真好, 下午我要去公园, 下午我要吃公园 ] for sent in sentences: print(f{sent}: {model.score_sentence(sent):.2e})典型输出结果今天天气真好: 3.25e-05 今天咖啡真好: 1.12e-06 下午我要去公园: 2.87e-05 下午我要吃公园: 4.33e-07从数据可见真实存在的搭配得分更高语义不合理的组合(吃公园)得分显著降低出现频率高的搭配(去公园)优于低频搭配5. 进阶应用指导文本生成将评分器与马尔可夫链生成器结合def generate_text(self, seed_words, max_len10): result [seed_word] for _ in range(max_len): possible_next [] for (w1, w2), count in self.bigram_counts.items(): if w1 result[-1]: possible_next.append((w2, count)) if not possible_next: break # 按概率选择下一个词 words, counts zip(*possible_next) probs [c/sum(counts) for c in counts] next_word random.choices(words, weightsprobs)[0] if next_word /s: break result.append(next_word) return .join(result[1:]) # 去除起始符生成示例print(model.generate_text(seed_word今天)) # 输出可能天气真好 或 下午喝咖啡6. 优化与陷阱规避实际应用中会遇到几个关键问题6.1 数据稀疏问题当语料库较小时很多合理搭配可能得零分。解决方案包括平滑技术除了加一平滑还有Good-Turing、Kneser-Ney等高级方法回退策略当Bigram不存在时回退到Unigram概率6.2 长句子得分衰减由于概率连乘长句子得分会指数级下降。解决方法使用对数概率相加替代连乘对长度进行归一化6.3 上下文窗口限制Bigram只考虑相邻词可能忽略长距离依赖。可以尝试Trigram或更高阶模型与神经网络模型结合# 对数概率改进示例 def log_score_sentence(self, sentence): tokens self.preprocess(sentence) log_score 0.0 for i in range(len(tokens)-1): # ...计算概率同上... log_score math.log(probability) return log_score / len(tokens) # 长度归一化7. 从玩具系统到工业级应用虽然我们的实现只有不到100行代码但已经包含了N-Gram的核心思想。在实际NLP系统中语料预处理会更复杂包括词性标注命名实体识别停用词处理混合模型常被采用最终概率 λ1*Bigram λ2*Trigram λ3*Unigram其中λ是各模型的权重系数评估指标更专业困惑度(Perplexity)BLEU分数人工评估在项目后期你可以尝试用新闻语料训练专业领域模型实现一个自动写诗系统构建聊天机器人的回复评估模块这个简单的评分器已经能帮你识别出今天我要喝图书馆这样的怪异句子。当我在第一次实现时最惊讶的是发现即使如此简单的统计模型也能捕捉到许多语言直觉。

相关新闻