N-Gram 模型

N-Gram 模型

温馨提示:本文最后更新于2024-12-05 15:17:04,某些文章具有时效性,若有错误或已失效,请在下方留言

将文本分割成连续的 N 个词的组合(即 N-Gram),来近似描述词序列的联合概率。基于前 N-1 个词来预测序列的第 N个词。

以词为Gram(元素)N-Gram模型如下图所示,其中 Unigram 中 N 值为 1,可以称之为一元组。Bigram 中 N 值为 2 ,是二元组,Trigram 是三元组。

以词为元素的 N-Gram 模型

模型构建过程

1、将给定的文本分割成连续的 N 个词的组合(N-Gram)

比如, 在 Bigram 模型中,将文本分割成多个由相邻的两个词构成的组合,称之为二元组

畅畅爱吃胡萝卜分割成二元组

2、统计每个 N-Gram 在文本中出现的次数,也就是词频

比如,二元组畅畅爱在语料库中出现了 3 次,即这个二元组的词频为 3。

二元组“畅畅爱”在语料库出现 3 次

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

比如,二元组畅畅爱在语料库中出现了3次,而二元组的前缀畅畅 在语料库中出现了10次,则给定畅畅,下一个词为 的概率为30%。

给定“畅畅”,下一个词为“爱”的概率为 30%

4、可以使用这些概率来预测文本中下一个词出现的可能性。多次迭代这个过程,甚至可以生成整个句子,也可以算出每个句子在语料库中出现的概率。

比如,从一个字畅畅,生成,再继续生成,直到畅畅爱吃肉这个句子。计算畅畅爱爱吃吃肉出现的概率,然后乘以各自的条件概率,就可以得到这个句子在语料库中出现的概率了。

哪个词更可能出现在“爱”后面

分词工具

英文常用的分词工具 NLTKspaCy

中文常用的分词工具 jieba

Bigram 模型示例

Biggram 字符预测模型整体结构

构建语料库

创建一个简单的数据集

# 构建一个数据集
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)

输出结果,如下所示

生成的文本: 我喜欢吃苹果

模型优缺点

优点:计算简单
缺点:无法捕捉到距离较远的词之间的关系

 

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容