n-gram语言模型------文本生成源码
- n-gram模型的基本原理
- 文本生成的步骤
-
- [1. 准备和分词](#1. 准备和分词)
- [2. 构建n-gram模型](#2. 构建n-gram模型)
- [3. 平滑技术的应用](#3. 平滑技术的应用)
- [4. 生成文本](#4. 生成文本)
- 源码
在自然语言处理的领域中,n-gram语言模型是一种基础而强大的工具。它通过考虑词汇的序列来预测文本内容,从而有效地用于文本生成任务。这篇博客中将探讨如何利用n-gram模型,特别是在处理中文文本时,使用jieba进行分词和nltk库进行模型构建。
我在上一篇博客里讲解了n-gram的原理,参考:n-gram语言模型------句子概率分布计算与平滑。
n-gram模型的基本原理
n-gram模型基于一个简单的假设:一个词的出现只与它前面的有限个词相关。这种模型可以分为不同的类型,如bigrams(二元模型)和trigrams(三元模型),取决于我们考虑前面多少个词。
以bigram
为例,可以近似认为一个词的出现概率仅依赖于它前面的一个词:
为了让 p ( w i ∣ w i − 1 ) p(w_i | w_{i-1}) p(wi∣wi−1)在i为1时有意义,我们通常会在句子开头添加一个开始标记(BOS),并在句子结尾添加一个结束标记(EOS),以此包含在概率计算中。例如,要计算Mark wrote a book
的概率,我们会这样计算:
p ( Mark wrote a book ) = p ( Mark ∣ BOS ) ⋅ p ( wrote ∣ Mark ) ⋅ p ( a ∣ wrote ) ⋅ p ( book ∣ a ) ⋅ p ( EOS ∣ book ) p(\text{Mark wrote a book}) = p(\text{Mark} | \text{BOS}) \cdot p(\text{wrote} | \text{Mark}) \cdot p(\text{a} | \text{wrote}) \cdot p(\text{book} | \text{a}) \cdot p(\text{EOS} | \text{book}) p(Mark wrote a book)=p(Mark∣BOS)⋅p(wrote∣Mark)⋅p(a∣wrote)⋅p(book∣a)⋅p(EOS∣book)
为了估计 p ( w i ∣ w i − 1 ) p(w_i | w_{i-1}) p(wi∣wi−1),可以简单地计算在某一文本中单词w的频率,然后对其进行归一化。若用c表示在给定文本中的出现次数,我们可以使用如下公式:
p ( w i ∣ w i − 1 ) = c ( w i − 1 , w i ) ∑ w c ( w i − 1 , w ) p(w_i | w_{i-1}) = \frac{c(w_{i-1}, w_i)}{\sum_{w} c(w_{i-1}, w)} p(wi∣wi−1)=∑wc(wi−1,w)c(wi−1,wi)
上述公式即为最大似然估计(Maximum Likelihood Estimation, MLE)。对于更高阶的n-gram模型,这一公式同样适用。
文本生成的步骤
1. 准备和分词
使用jieba对中文文本进行分词,这是处理中文n-gram模型的第一步。分词后的结果用来构建n-gram模型。
2. 构建n-gram模型
利用nltk的ngrams函数,从分词结果中创建bigrams序列。然后使用这些bigrams来构建一个条件频率分布对象,用于后续的文本生成。
3. 平滑技术的应用
在n-gram模型中,为了处理那些在训练数据中未出现的词组合,需要采用平滑技术。Lidstone平滑和Laplace平滑是两种常见的方法。这些方法通过添加一个小的非零值到词组合的计数中,避免了零概率问题,使模型更加健壮。
Lidstone平滑和Laplace平滑参考之前的博客 n-gram语言模型------句子概率分布计算与平滑
4. 生成文本
文本生成的过程是从一个初始词开始,根据条件频率分布连续生成下一个词。这个过程重复进行,直到达到所需的词数或遇到停止条件。
源码
下面是使用Laplace平滑的n-gram模型来成文本的示例:
python
import nltk
from nltk.probability import LidstoneProbDist, LaplaceProbDist
from nltk.corpus import brown
from nltk import FreqDist, ConditionalFreqDist
import random
import jieba
# 示例文本 读取ylk.txt
text = open("ylk.txt", encoding="utf-8").read()
# jieba分词
tokens = jieba.cut(text)
# 生成bigrams
bi_grams = list(nltk.ngrams(tokens, 2))
# 创建条件频率分布对象
cfd = ConditionalFreqDist(bi_grams)
# # 使用Lidstone平滑
# # gamma值小于1的Lidstone平滑
lidstone_cfd = {condition: LidstoneProbDist(cfd[condition], gamma=0.1) for condition in cfd.conditions()}
# 使用Laplace平滑
# Laplace平滑是gamma=1的特殊情况
laplace_cfd = {condition: LaplaceProbDist(cfd[condition]) for condition in cfd.conditions()}
def generate_text(initial_word, cfd, num_words=50):
current_word = initial_word
generated_text = [current_word]
for _ in range(num_words - 1):
if current_word not in cfd:
break
next_word = random.choices(
population=list(cfd[current_word].samples()),
weights=[cfd[current_word].prob(w) for w in cfd[current_word].samples()]
)[0]
generated_text.append(next_word)
current_word = next_word
return ''.join(generated_text)
# 示例:从"哈哈"开始生成文本
print(generate_text("方锐", laplace_cfd, 100))
print(generate_text("方锐", lidstone_cfd, 100))
代码里我下了4M多的网络小说作为语料库。然后根据主角的名字开始生成,结果如下:
感觉lidstone效果更好一点。
通过使用n-gram模型结合平滑技术,能够有效地生成符合语言规律的文本。这种方法虽然简单,但在许多应用场景下仍然非常有效,特别是在资源有限的情况下。
随着深度学习技术的发展,出现了更复杂的语言模型,n-gram模型感觉在文本生成领域已经不行了~