2024-12-05 15:17:04
,某些文章具有时效性,若有错误或已失效,请在下方留言。将文本分割成连续的 N
个词的组合(即 N-Gram
),来近似描述词序列的联合概率。基于前 N-1
个词来预测序列的第 N
个词。
以词为Gram(元素)
的 N-Gram模型
如下图所示,其中 Unigram
中 N 值为 1,可以称之为一元组。Bigram
中 N 值为 2 ,是二元组,Trigram
是三元组。

模型构建过程
1、将给定的文本分割成连续的 N 个词的组合(N-Gram)
比如, 在 Bigram
模型中,将文本分割成多个由相邻的两个词构成的组合,称之为二元组
。

2、统计每个 N-Gram 在文本中出现的次数,也就是词频
比如,二元组畅畅爱
在语料库中出现了 3 次,即这个二元组的词频为 3。

3、为了得到一个词在给定上下文中出现的概率,可以利用条件概率公式计算。具体来讲,就是计算给定前N-1个词时,下一个词出现的概率。这个概率可以通过计算某个 N-Gram 出现的次数与前 N-1个词(前缀)出现的次数之比得到。
比如,二元组畅畅爱
在语料库中出现了3次,而二元组的前缀畅畅
在语料库中出现了10次,则给定畅畅
,下一个词为 爱
的概率为30%。

4、可以使用这些概率来预测文本中下一个词出现的可能性。多次迭代这个过程,甚至可以生成整个句子,也可以算出每个句子在语料库中出现的概率。
比如,从一个字畅畅
,生成爱
,再继续生成吃
,直到畅畅爱吃肉
这个句子。计算畅畅爱
爱吃
吃肉
出现的概率,然后乘以各自的条件概率,就可以得到这个句子在语料库中出现的概率了。

分词工具
英文常用的分词工具 NLTK
、spaCy
等
中文常用的分词工具 jieba
Bigram 模型示例

构建语料库
创建一个简单的数据集
# 构建一个数据集
corpus = [
"我喜欢吃苹果",
"我喜欢吃香蕉",
"她喜欢吃葡萄",
"他不喜欢吃香蕉",
"他喜欢吃苹果",
"她喜欢吃草莓"
]
句子分割
定义一个分词函数,用它将文本分割成单个汉字字符,针对字符来计算 Bigram词频。
# 定义一个分词函数,将文本转为单个字符列表
def tokenize(text):
return [char for char in text] # 将文本拆分为字符列表
计算词频
定义计算 N-Gram 词频的函数,并在数据集上应用这个函数,指定参数n为2,以生成 Bigram,然后把所有的词频都显示出来。
# 定义计算 N-Gram 词频函数
from collections import defaultdict, Counter
def count_ngrams(corpus, n):
# 创建一个字典,存储 N-Gram 计数
ngrams_count = defaultdict(Counter)
# 遍历语料库中的每个文本
for text in corpus:
# 对文本进行分词
tokens = tokenize(text)
# 遍历分词结果,生成 N-Gram
for i in range(len(tokens) - n + 1):
# 创建一个 N-Gram 元组
ngram = tuple(tokens[i:i+n])
# 获取 N-Gram 前缀
prefix = ngram[:-1]
# 获取 N-Gram 目标单字
token = ngram[-1]
# 更新 N-Gram 技术
ngrams_count[prefix][token] += 1
return ngrams_count
# 计算 Bigram 词频
bigram_counts = count_ngrams(corpus, 2)
print("Bigram 词频:")
for prefix, counts in bigram_counts.items():
print("{}: {}".format("".join(prefix), dict(counts)))
输出结果,如下所示
Bigram 词频:
我: {'喜': 2}
喜: {'欢': 6}
欢: {'吃': 6}
吃: {'苹': 2, '香': 2, '葡': 1, '草': 1}
苹: {'果': 2}
香: {'蕉': 2}
她: {'喜': 2}
葡: {'萄': 1}
他: {'不': 1, '喜': 1}
不: {'喜': 1}
草: {'莓': 1}
计算概率
根据词频计算每一个 Bigram
出现的概率。也就是计算给定前一个词时,下一个词出现的可能性,这是通过计算某个 Bigram
词频与前缀词频之比得到的。
# 定义计算 N-Gram 出现概率的函数
def ngram_probabilities(ngram_counts):
# 创建一个字典,存储 N-Gram 出现的概率
ngram_probs = defaultdict(Counter)
# 遍历 N-Gram 前缀
for prefix, tokens_count in ngram_counts.items():
# 计算当前前缀的 N-Gram 计数
total_count = sum(tokens_count.values())
# 遍历每个前缀的 N-Gram
for token, count in tokens_count.items():
# 计算每个 N-Gram 出现的概率
ngram_probs[prefix][token] = count / total_count
return ngram_probs
#计算 Bigram 出现的概率
bigram_probs = ngram_probabilities(bigram_counts)
#打印 Bigram 概率
print("\nBigarm 出现的概率: ")
for prefix, probs in bigram_probs.items():
print("{}: {}".format("".join(prefix), dict(probs)))
输出结果,如下所示
Bigarm 出现的概率:
我: {'喜': 1.0}
喜: {'欢': 1.0}
欢: {'吃': 1.0}
吃: {'苹': 0.3333333333333333, '香': 0.3333333333333333, '葡': 0.16666666666666666, '草': 0.16666666666666666}
苹: {'果': 1.0}
香: {'蕉': 1.0}
她: {'喜': 1.0}
葡: {'萄': 1.0}
他: {'不': 0.5, '喜': 0.5}
不: {'喜': 1.0}
草: {'莓': 1.0}
定义生成下一词函数
定义生成下一个词的函数,基于N-Gram
出现的概率计算特定前缀出现后的下一个词。
# 定义生成下一词的函数
def generate_next_token(prefix, ngram_probs):
# 如果前缀不在N-Gram 中,返回 None
if not prefix in ngram_probs:
return None
# 获取当前前缀的下一个词的概率
next_token_probs = ngram_probs[prefix]
# 选择概率最大的词作为下一个词
next_token = max(next_token_probs, key=next_token_probs.get)
return next_token
这段代码接收一个词序列(称为前缀)和一个包含各种可能的下一个词及其对应概率的词典。首先,检查前缀是否在词典中。如果前缀不存在于词典中,那么函数返回None,表示无法生成下一个词。如果前缀存在于词典中,该函数就会从词典中取出这个前缀对应的下一个词的概率。接着,函数会在其中找到概率最大的词,然后将这个词作为下一个词返回。
生成连续文本
# 定义生成连续文本的函数
def generate_text(prefix, ngram_probs, n, length=6):
# 将前缀转换为字符列表
tokens = list(prefix)
# 根据指定长度生成文本
for _ in range(length -len(prefix)):
# 获取当前前缀的下一个词
next_token = generate_next_token(tuple(tokens[-(n-1)]), ngram_probs)
# 如果下一个词为None,跳出循环
if not next_token:
break
# 将下一个词添加到生成的文本中
tokens.append(next_token)
# 将字符列表连接成字符串
return "".join(tokens)
这个函数首先将前缀字符串转化为字符列表tokens
,以便后续操作。然后进入一
个循环,循环的次数等于生成文本的目标长度length
减去前缀的长度。循环的目的是生成足够长度的文本。在循环中,函数会调用之前定义的generate_next_token
函数,以获取下一个词。
这个函数会考虑到当前的n-1个词(也就是前缀的最后1-1个词),以及所有可
能的下一个词及其对应的概率。如果 generate_next_token 函数返回的下一个词是
None(也就是没有找到合适的下一个词),那么循环会提前结束,不再生成新的词。如果函数成功找到了下一个词,那么这个词会被添加到字符列表tokens 的尾部。当循环结束时,函数将使用Python 的join 方法,将字符列表连接成一个字符串,也就是函数生成的一段连续文本。
# 输入一个前缀,生成文本
generated_text = generate_text("我", bigram_probs, 2)
# 打印生成的文本
print("\n 生成的文本:", generated_text)
输出结果,如下所示
生成的文本: 我喜欢吃苹果
模型优缺点
优点:计算简单
缺点:无法捕捉到距离较远的词之间的关系
暂无评论内容