从 1970 年以后人们意识到不能基于规则,得基于统计这个时间节点开始,就出现了很多语言模型。
基本介绍
N-Gram模型诞生于 1950s–1960s,最早由香农(Claude Shannon)在信息论中提出,用于语言的概率建模,香农在 1951 年的论文中提出,使用 1-gram、2-gram 等方法估计英文文本的概率。前面的 N 是一个数字,表示你每次要看几个词,例如:
- 1-Gram(Unigram):只看一个词(不看前面的词)。
- 2-Gram(Bigram):会看前面的一个词。
- 3-Gram(Trigram):会看前面两个词。
- 以此类推…
该模型基于马尔可夫假设。
马尔可夫假设:一个词出现的概率,依赖于它前面的 N-1 个词,而不是整个句子历史。
(厨房)妈妈说:“帮我拿一下冰箱里面的....”- 鸡蛋
- 牛脑
- 苹果
- …
在你的大脑中,就会自动的去预测下一个词。
不会弹出下面的词:
- 飞机
- 汽车
- 电脑
- …
工作原理
这里我们以 Bigram(2-Gram):会看前面的一个词。
假设我们有这样一个“语料库”,我们用这些数据来训练模型:
“我 爱 吃 苹果”“我 爱 吃 香蕉”“我 喜欢 吃 苹果”Bigram 模型会根据一个词统计下一个词出现的概率,这里我们可以数一数所有词对:
| 前一个词 | 下一个词 | 次数 |
|---|---|---|
| 我 | 爱 | 2 次 |
| 我 | 喜欢 | 1 次 |
| 爱 | 吃 | 2 次 |
| 喜欢 | 吃 | 1 次 |
| 吃 | 苹果 | 2 次 |
| 吃 | 香蕉 | 1 次 |
根据这个词的组合,就可以去预测一个词的下一个词,比如:
- 我后面出现“爱”的概率是 2/3,出现“喜欢”的概率是 1/3。
- 吃后面出现“苹果”的概率是 2/3,出现“香蕉”的概率是 1/3。
所以:
如果你看到“我 爱 吃”,那下一个词大概率是“苹果”!
代码实践
jieba 是 python 里面用于对中文分词的一个库。
“我喜欢吃苹果”
我、喜欢、吃、苹果
import jieba # 导入 jieba 中文分词库# 导入 defaultdict 和 Counter,用于构建频率统计表from collections import defaultdict, Counter
# 示例语料库(注意:这些是连续的中文句子,没有空格)corpus = [ "我喜欢吃苹果", "我喜欢吃香蕉", "她喜欢吃葡萄", "他不喜欢吃香蕉", "他喜欢吃苹果", "她喜欢吃草莓"]
# 1. 构建 Bigram 统计表,格式为 bigrams[前一个词][下一个词] = 出现次数bigrams = defaultdict(Counter) # 自动初始化嵌套结构,避免手动判断键是否存在
# 遍历每一句话for sentence in corpus: words = list(jieba.cut(sentence)) # 使用 jieba 对句子进行分词,返回词列表 # 构建二元组(Bigram):每两个连续词之间的搭配 for i in range(len(words) - 1): w1, w2 = words[i], words[i + 1] # 当前词和下一个词 bigrams[w1][w2] += 1 # 出现一次就 +1
# 2. 将频率转换成概率,构建 Bigram 概率模型bigram_probs = {} # 最终保存的是 P(w2 | w1) 的概率表
# 遍历所有前词(w1)for w1 in bigrams: total = sum(bigrams[w1].values()) # w1 后面出现所有词的总次数 # 计算每一个 w2 的条件概率:P(w2 | w1) = 频率 / 总数 bigram_probs[w1] = { w2: count / total for w2, count in bigrams[w1].items() }
# 3. 定义一个函数:输入一个词,预测它后面最可能出现的词(按概率从大到小排序)
def predict_next_word(word): if word not in bigram_probs: return "未知(没有数据)" # 如果这个词从未出现在训练语料中 # 按照概率从高到低排序返回 sorted_probs = sorted( bigram_probs[word].items(), key=lambda x: x[1], reverse=True ) return sorted_probs
# 4. 打印所有 Bigram 的条件概率print("=== Bigram 概率 ===")for w1 in bigram_probs: for w2 in bigram_probs[w1]: prob = bigram_probs[w1][w2] print(f"P({w2} | {w1}) = {prob:.2f}") # 输出形如:P(吃 | 喜欢) = 1.00
# 5. 测试预测某些词后面最可能出现的词print("\n=== 预测示例 ===")for test_word in ["我", "吃", "喜欢", "苹果"]: print(f"{test_word} → {predict_next_word(test_word)}")N-Gram 虽然比较简陋,但是它确确实实是基于统计语言建模的起点。
N-Gram模型缺陷
- 语境短视:只看前 N-1个词,无法理解长距离词性的依赖。
- “我昨天见到一个朋友,他说他非常喜欢编程。”
- 无法泛化:只能记住见过的词语组合,对没有见过的组合无能为力。
- 语料库:我爱吃西瓜(没有葡萄这个词)
- 永远不可能出现:我爱吃葡萄 这种组合
- 不具备语义理解能力
- 无法判断:喜欢 和 爱 这两个词,词性是相似
- 也不能区分:“我打了他”和“他打了我”
总结一句话:只会记录词语搭配,不会理解语言。
适用于早期的简单的文本处理任务。
-EOF-