n-gram语言模型——句子概率分布计算与平滑

n-gram语言模型——句子概率分布计算与平滑

  • 前言
  • 语言模型
  • 等价假设
  • n元语法
  • 句子概率分布计算方式
  • 数据平滑
    • Lidstone平滑(1-gram)
    • Laplace平滑(1-gram)
    • 附上两种平滑在1-gram下代码
    • Lidstone平滑与Laplace平滑(2-gram)
    • 附上两种平滑在2-gram下代码

请添加图片描述

前言

  语言模型(Language Model, LM)在自然语言处理(NLP)领域扮演着核心角色,特别是在统计模型驱动的汉语自动分词和句法分析等领域。目前,广泛采用的是N-gram语法模型,这种模型以其构建的简便性和直观性而著称,但同时也因数据稀疏性问题而不得不使用平滑(Smoothing)技术。

  N-gram模型由于计算和实现简单,非常适合于计算资源有限的场景。但是在理解和生成自然语言的复杂结构方面性能比较差。

  神经网络方法适用于资源充足的情况。最新的NLP模型,如BERT、GPT和其他基于Transformer的模型,已经在很多语言任务中取得了非常好的效果,这些模型能够更加有效地捕捉语言的深层次语义,并处理长距离的依赖关系。

  尽管如此,N-gram模型仍然在某些特定的简单任务中有其应用价值。本篇博客将介绍N-gram的基础理论。

语言模型

  一个语言模型旨在构建单个字符串的概率分布 p ( s ) p(s) p(s),其中 p ( s ) p(s) p(s)反映的是字符串 s s s作为一个句子出现的频率。例如,在一个口语化的语言模型中(这句话可以理解为‘在一个语料库下’、‘在一个数据集下’),"Okay"一词在每100个句子中大约出现一次,因此p(“Okay”)≈0.01。

  对于一个由n个词构成的句子 s = w 1 w 2 . . . w n ​ s = w_1w_2...w_n​ s=w1w2...wn,其概率可以用如下公式计算:

p ( S ) = p ( w 1 ) ⋅ p ( w 2 ∣ w 1 ) ⋅ p ( w 3 ∣ w 1 w 2 ) ⋅ . . . ⋅ p ( w n ∣ w 1 w 2 . . . w n − 1 ) p(S) = p(w_1) \cdot p(w_2|w_1) \cdot p(w_3|w_1w_2) \cdot ... \cdot p(w_n|w_1w_2...w_{n-1}) p(S)=p(w1)p(w2w1)p(w3w1w2)...p(wnw1w2...wn1)

  在上述公式中,生成第i个词的概率取决于它之前的i−1个词 w 1 w 2 . . . w i − 1 w_1w_2...w_{i-1} w1w2...wi1,这个词序列被称为历史。随着历史长度的增加,有可能的历史组合数呈指数级增长。这样的话,我们也不可能算出来一个长句子的概率。

等价假设

  由于历史在训练数据中的出现可能性极低,我们几乎无法直接从训练数据中准确估计出模型的参数。实际情况是,许多历史事件可能根本不会在训练数据中出现。为了克服这一问题,我们可以采用将历史映射到等价类的方法,其中等价类的数量远少于历史的数量。如果假设

p ( w i ∣ w 1 , w 2 , . . . , w i − 1 ) = p ( w i ∣ E ( w 1 , w 2 , . . . , w i − 1 ) ) p(w_i | w_{1}, w_2, ..., w_{i-1}) = p(w_i | E(w_{1}, w_2, ..., w_{i-1})) p(wiw1,w2,...,wi1)=p(wiE(w1,w2,...,wi1))

  那么模型的自由参数数量将会显著减少。有多种方法可以将历史映射为等价类,其中一种实用的方法是将两个历史 w i − n + 2 , . . . , w i w_{i-n+2}, ..., w_{i} win+2,...,wi​和 u i − n + 2 , . . . , u i u_{i-n+2}, ..., u_{i} uin+2,...,ui映射到同一个等价类,当且仅当这两个历史的最近的n−1个词是相同的。

n元语法

  符合上述条件的语言模型被称为n-gram或n-gram文法。通常情况下,n的取值不会太大,否则会造成等价类数量繁多,自由参数过多的问题仍旧存在。在实际应用中,通常采用n等于3的模型。当n为1时,即第i个词的出现独立于它之前的历史,此时的模型称为unigram;当n为2时,即第i个词的出现仅与其前一个词有关,此模型称为bigram,也就是一阶马尔可夫链;当n为3时,即第i个词的出现仅与其前两个词有关,此模型称为trigram,也就是二阶马尔可夫链。

  以bigram为例,可以近似认为一个词的出现概率仅依赖于它前面的一个词:

  为了让 p ( w i ∣ w i − 1 ) p(w_i | w_{i-1}) p(wiwi1)在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(MarkBOS)p(wroteMark)p(awrote)p(booka)p(EOSbook)

  为了估计 p ( w i ∣ w i − 1 ) p(w_i | w_{i-1}) p(wiwi1),可以简单地计算在某一文本中单词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(wiwi1)=wc(wi1,w)c(wi1,wi)

  上述公式即为最大似然估计(Maximum Likelihood Estimation, MLE)。对于更高阶的n-gram模型,这一公式同样适用。

句子概率分布计算方式

  下面这段代码是使用jieba进行中文分词和nltk计算bigrams(二元语法)的频率分布的简单例子,然后再用这个模型来计算另一个测试文本的联合概率。

import nltk
from nltk import ConditionalFreqDist
import jieba

# 示例文本
text = "哈哈你真好哈哈你不是学生哈哈我也不是"
# jieba分词
tokens = jieba.cut(text)
# 生成bigrams
bi_grams = nltk.ngrams(tokens, 2)
# 计算频率分布
cfd = ConditionalFreqDist(bi_grams)

test_text = "你不是学生"
# 分词
test_tokens = list(jieba.cut(test_text))
# 计算频率分布
P = 1.0
for i, x in enumerate(test_tokens):
    if i == len(test_tokens) - 1:
        continue
    P *= cfd[x].freq(test_tokens[i + 1])
print(P) # 0.5

  使用nltk.ngrams函数把分词后的词语生成二元语法序列。nltk.ngrams传入词语列表和n的值,返回一个字典,包含所有的n-grams序列。

  创建ConditionalFreqDist实例cfd,将bigrams作为输入。ConditionalFreqDist类是用来计算每个条件(在bigrams中是第一个词)对应的事件(第二个词)的频率分布。上述代码中cfd值如下:

在这里插入图片描述

  最后算得这个测试句子的概率是0.5。但是这段代码有个假设: 没有考虑bigram出现次数为0的情况,这在实际应用中可能会导致概率为0。所以我们要使用平滑技术来处理这个问题。

举个具体例子:由于语料库中’哈哈’后面没有出现过’真’,所以在计算含’哈哈真’序列的句子时会报错。

数据平滑

  这里给大家演示nltk库提供的LidstoneLaplace平滑。

  上文说过,在统计语言模型中,如果一个n-gram在训练数据中从未出现过,它的概率将是零。这是不理想的,因为这意味着整个句子的概率也将是零,即使这个句子在现实中是可能出现的。LidstoneLaplace平滑通过向计数中添加一个小的非零值来解决这个问题。

Lidstone平滑(1-gram)

  Lidstone平滑是一种给每个计数添加一个小正数γ的方法。假设我们有一个词汇表V,大小为|V|,对于任何n-gram的计数c,平滑后的概率计算如下:

P L i d s t o n e ( w ) = c ( w ) + γ N + γ × ∣ V ∣ P_{Lidstone}(w) = \frac{c(w) + \gamma}{N + \gamma \times |V|} PLidstone(w)=N+γ×Vc(w)+γ

  其中:

  • P L i d s t o n e ( w ) P_{Lidstone}(w) PLidstone(w)是词w的Lidstone平滑后的概率。
  • c ( w ) c(w) c(w) 是词w在训练集中的原始计数。
  • N N N是训练集中所有词的总计数。
  • ∣ V ∣ |V| V是词汇表的大小。
  • γ \gamma γ是一个介于0和1之间的平滑参数。

Laplace平滑(1-gram)

  Laplace平滑(又称为加一平滑)是Lidstone平滑的一个特例,其中γ=1。这意味着我们向每个n-gram的计数添加1,以避免任何n-gram的概率为零。平滑后的概率计算如下:

P L a p l a c e ( w ) = c ( w ) + 1 N + ∣ V ∣ P_{Laplace}(w) = \frac{c(w) + 1}{N + |V|} PLaplace(w)=N+Vc(w)+1

  在Laplace平滑中,向分母中添加整个词汇表的大小,以确保总概率和为1。

  Laplace平滑假设所有未见的n-gram都有相同的概率,而Lidstone平滑则允许通过选择不同的γ值给未见的n-gram分配不同的概率。当γ趋近于0时,Lidstone平滑可以逼近未平滑的模型,而当γ增大时,可以逼近Laplace平滑。通过调整γ的值,Lidstone平滑提供了比Laplace平滑更多的灵活性。

附上两种平滑在1-gram下代码

import nltk
from nltk.probability import LidstoneProbDist, LaplaceProbDist
from nltk import FreqDist
import jieba

# 示例文本
text = "哈哈你真好,哈哈你不是学生,哈哈我也不是,我不能用学生证"
# jieba分词
tokens = jieba.cut(text)

freq_dist = FreqDist(tokens)

# 使用Lidstone平滑
# gamma值小于1的Lidstone平滑
lidstone_estimator = LidstoneProbDist(freq_dist, gamma=0.1, bins=freq_dist.B())
print(f"'the'的Lidstone平滑概率: {lidstone_estimator.prob('the')}")

# 使用Laplace平滑
# Laplace平滑是gamma=1的特殊情况
laplace_estimator = LaplaceProbDist(freq_dist, bins=freq_dist.B())
print(f"'the'的Laplace平滑概率: {laplace_estimator.prob('the')}")

  计算方式就是套公式,每个词都是根据它的词频算的,很简单。但是我们更想算2元语法和3元语法,以2元语法举例,我们想算在词 w 1 w_1 w1下词 w 2 w_2 w2的概率,而不是1元语法词 w 2 w_2 w2的独立概率。所以要说到这两种平滑的2元语法形式。

说白了,1元是概率分布,2元以及更高是条件频率分布。

Lidstone平滑与Laplace平滑(2-gram)

  Lidstone和Laplace平滑都可以用于任何n-gram模型,不仅限于1-gram(unigrams)。对于2-grams(bigrams)、3-grams(trigrams)或更高阶的n-grams,原理是相同的。区别在于你要如何定义条件和事件的关系。

  对于bigrams来说,平滑不仅要应用到单个词,还要考虑词对的出现频率。假设我们有一个 b i g r a m ( w 1 , w 2 ) bigram (w_{1}, w_{2}) bigram(w1,w2),其中 w 1 w_{1} w1​ 是第一个词, w 2 w_{2} w2 是第二个词。在未平滑的情况下,我们通常会计算 w 2 w_{2} w2 w 1 w_{1} w1 之后出现的次数,然后除以 w 1 w_{1} w1​ 出现的总次数来估计概率。但是,如果某个bigram在训练数据中从未出现过,就会遇到零概率问题。

  为了平滑bigram模型,我们将Lidstone或Laplace平滑应用于这些计数。具体来说,对于Lidstone平滑, b i g r a m ( w 1 , w 2 ) bigram(w_{1}, w_{2}) bigram(w1,w2) 的概率可以这样计算:

P L i d s t o n e ( w 2 ∣ w 1 ) = c ( w 1 , w 2 ) + γ c ( w 1 ) + γ × ∣ V ∣ P_{Lidstone}(w_{2}|w_{1}) = \frac{c(w_{1}, w_{2}) + \gamma}{c(w_{1}) + \gamma \times |V|} PLidstone(w2w1)=c(w1)+γ×Vc(w1,w2)+γ

  对于Laplace平滑,bigram的概率则是:

P L a p l a c e ( w 2 ∣ w 1 ) = c ( w 1 , w 2 ) + 1 c ( w 1 ) + ∣ V ∣ P_{Laplace}(w_{2}|w_{1}) = \frac{c(w_{1}, w_{2}) + 1}{c(w_{1}) + |V|} PLaplace(w2w1)=c(w1)+Vc(w1,w2)+1

  在这里:

  • c ( w 1 , w 2 ) c(w_{1}, w_{2}) c(w1,w2) b i g r a m ( w 1 , w 2 ) bigram (w_{1}, w_{2}) bigram(w1,w2) 在训练集中的原始计数。
  • c ( w 1 ) c(w_{1}) c(w1)是第一个词 w 1 w_{1} w1​ 在训练集中的原始计数。
  • ∣ V ∣ |V| V 是词汇表的大小,即不同词的数量。

  对于bigrams或更高阶的n-grams,我们通常会有一个条件频率分布,其中每个条件(如 w 1 w_{1} w1)都有自己的频率分布。当应用Lidstone或Laplace平滑时,会分别更新这些条件分布中的每个概率。

附上两种平滑在2-gram下代码

import nltk
from nltk.probability import LidstoneProbDist, LaplaceProbDist
from nltk.corpus import brown
from nltk import FreqDist, ConditionalFreqDist

import jieba

# 示例文本
text = "哈哈你真好,哈哈你不是学生,哈哈我也不是,我不能用学生证"
# 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 get_prob(test_text):
    # 分词
    test_tokens = list(jieba.cut(test_text))
    # 计算频率分布
    lidstone_P = 1.0
    laplace_P = 1.0
    for i, x in enumerate(test_tokens):
        if i == len(test_tokens) - 1:
            continue
        lidstone_P *= lidstone_cfd[x].prob(test_tokens[i + 1])
        laplace_P *= laplace_cfd[x].prob(test_tokens[i + 1])
    print("Lidstone平滑句子概率:", lidstone_P, end=" ")
    print("Laplace平滑句子概率:", laplace_P)

get_prob("你哈哈不是学生,你不能用学生证")
get_prob("学生哈哈不是你,你不能用学生证")

  输出如下:
在这里插入图片描述

  可以看到在训练语料下,句子一概率更高,说明更合理一些。

  变量lidstone_cfd的内容如下:
在这里插入图片描述
  比较值得注意的一点就是,语料里面没有出现过’你哈哈不是’这样的序列,但是使用了平滑技术之后,我们的代码不会报错,而且最终概率也不为零了。平滑通过向计数中添加一个小的非零值来解决这个问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/130640.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

黑窗口连接远程服务

ssh root192.168.x.x 回车输入密码 查看docker docker ps 停止正在运行的服务 docker stop xxxxx 删除服务 docker rm xxxxx 查看镜像 docker images 删除镜像 docker rmi xxxxx 删除镜像 启动并运行整个服务 docker compose up -d jar包名称 idea 使用tcp方式连接docker 配置d…

【扩散模型】实战:创建一个类别条件扩散模型

创建一个类别条件扩散模型 1. 配置和数据准备2. 创建一个以类别为条件的UNet模型3. 训练和采样 本文介绍一种给扩散模型添加额外条件信息的方法。具体地,将在MNIST数据集上训练一个以类别为条件的扩散模型。并且可以在推理阶段指定想要生成的是哪个数字。 1. 配置和…

Navicat 解放双手的自动运行功能

Navicat 的自动运行功能可以帮助用户自动化重复性和周期性的任务,提高工作效率和数据安全性。用户可以根据自己的需求设置自动运行的任务和计划,以确保数据库管理和数据操作的顺利进行。为帮助用户更便捷、更直观地了解自动运行功能以及电子邮件通知的操…

烟草5G智慧工厂数字孪生可视化平台,赋能烟草工业数字化智慧转型

随着卷烟工厂提质增效需求增强,信息化建设推进及生产制造系统智能化改革发展,各生产单元逐步升级完善数字化,最终实现智能制造成为必然趋势。因此,5G卷烟加工工厂的数字化转型迫在眉睫。中国烟草制造行业正迈向全新的市场经济时代…

win11 idea 错误: 找不到或无法加载主类

买了新电脑win11系统,配置环境之后运行项目,始终运行不起来,一直报 刚开始以为是环境没装好,但是我创建其他项目运行时是正常的 纠结了好久突然发现,是不是因为项目路径中有中文造成的找不到编译文件 最后把项目改为…

汽车标定技术(九)--标定常量与#pragma的趣事

目录 1. 不添加#pragma语句 2. 添加#pragma语句 3. 标定量只给flash空间,不给ram指定空间 4. 总结 在之前不会使用overlay机制的时候,我们想要做汽车标定,标定常量编译出来的地址一般都应该是ram的地址,而且在链接文件中都会指…

HTML5学习系列之简单使用1

HTML5学习系列之简单使用1 前言基础显示学习定义网页标题定义网页元信息定义网页元信息定义文档结构div元素di和classtitlerole注释 总结 前言 下班加班期间的简单学习。 基础显示学习 定义网页标题 <html lang"en"> <head> <title>从今天开始努…

内存缓存系统

胤凯 (oyto.github.io)&#xff0c;欢迎到我的博客阅读。 今天我们围绕一个面试题来实现一个内存缓存系统。 面试题内容 1. 支持设置过期时间&#xff0c;精度到秒 2. 支持设置最大内存&#xff0c;当内存超出时做出合理的处理 3. 支持并发安全 4. 按照以下接口要求实现 typ…

【poi导出excel模板——通过建造者模式+策略模式+函数式接口实现】

poi导出excel模板——通过建造者模式策略模式函数式接口实现 poi导出excel示例优化思路代码实现补充建造者模式策略模式 poi导出excel示例 首先我们现看一下poi如何导出excel&#xff0c;这里举个例子&#xff1a;目前想要导出一个Map<sex,List>信息&#xff0c;sex作为…

使用Dockerfile依赖maven基础镜像部署springboot的程序案例

1、准备springboot Demo代码 就一个controller层代码&#xff0c;返回当前时间及hello world 2、项目根目录下&#xff0c;新建DockerFile文件 注意&#xff0c;等本地配置完毕后&#xff0c;Dockerfile文件需要与项目helloworld同级&#xff0c;这里先放项目里面 3、docker …

【MATLAB源码-第73期】基于matlab的OFDM-IM索引调制系统不同子载波数目误码率对比,对比OFDM系统。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 OFDM-IM索引调制技术是一种新型的无线通信技术&#xff0c;它将正交频分复用&#xff08;OFDM&#xff09;和索引调制&#xff08;IM&#xff09;相结合&#xff0c;以提高频谱效率和系统容量。OFDM-IM索引调制技术的基本思想…

Flink SQL自定义标量函数(Scalar Function)

使用场景&#xff1a; 标量函数即 UDF&#xff0c;⽤于进⼀条数据出⼀条数据的场景。 开发流程&#xff1a; 实现 org.apache.flink.table.functions.ScalarFunction 接⼝实现⼀个或者多个⾃定义的 eval 函数&#xff0c;名称必须叫做 eval&#xff0c;eval ⽅法签名必须是 p…

快速入门安装及使用git与svn的区别常用命令

一、导言 1、什么是svn&#xff1f; SVN是Subversion的简称&#xff0c;是一个集中式版本控制系统。与Git不同&#xff0c;SVN没有分布式的特性。在SVN中&#xff0c;项目的代码仓库位于服务器上&#xff0c;团队成员通过向服务器提交和获取代码来实现版本控制。SVN记录了每个…

Hbuilder打包项目为h5

Hbuilder打包项目为h5 manifest.json 配置 修改 web 配置下的 页面标题、路由模式、运行的基础路径 发行 H5 发行 填入网站标题和网站域名 编译 编译完成之后存放在 unpackage/dist/build/h5 目录下

Day26力扣打卡

打卡记录 搜索旋转排序数组&#xff08;二分&#xff09; 链接 class Solution {int findMin(vector<int> &nums) {int left -1, right nums.size() - 1; // 开区间 (-1, n-1)while (left 1 < right) { // 开区间不为空int mid left (right - left) / 2;if…

医学图像 ABIDE 等数据集 .nii.gz Python格式化显示

nii.gz 文件 .nii.gz 文件通常是医学影像数据的一种常见格式&#xff0c;比如神经影像&#xff08;如脑部MRI&#xff09;。这种文件格式通常是经过gzip压缩的NIfTI格式&#xff08;Neuroimaging Informatics Technology Initiative&#xff09;。 要在Python中查看.nii.gz文…

设备零部件更换ar远程指导系统加强培训效果

随着科技的发展&#xff0c;AR技术已经成为了一种广泛应用的新型技术。AR远程指导系统作为AR技术的一种应用&#xff0c;具有非常广泛的应用前景。 一、应用场景 气象监测AR教学软件适用于多个领域&#xff0c;包括气象、环境、地理等。在教学过程中&#xff0c;软件可以帮助学…

黑客(网络安全)技术——高效自学1.0

前言 前几天发布了一篇 网络安全&#xff08;黑客&#xff09;自学 没想到收到了许多人的私信想要学习网安黑客技术&#xff01;却不知道从哪里开始学起&#xff01;怎么学 今天给大家分享一下&#xff0c;很多人上来就说想学习黑客&#xff0c;但是连方向都没搞清楚就开始学习…

Paimon 与 Spark 的集成(一)

Paimon Apache Paimon (incubating) 是一项流式数据湖存储技术&#xff0c;可以为用户提供高吞吐、低延迟的数据摄入、流式订阅以及实时查询能力。Paimon 采用开放的数据格式和技术理念&#xff0c;可以与 ApacheFlink / Spark / Trino 等诸多业界主流计算引擎进行对接&#xf…

听GPT 讲Rust源代码--library/core/src(2)

题图来自 5 Ways Rust Programming Language Is Used[1] File: rust/library/core/src/iter/adapters/by_ref_sized.rs 在Rust的源代码中&#xff0c;rust/library/core/src/iter/adapters/by_ref_sized.rs 文件实现了 ByRefSized 适配器&#xff0c;该适配器用于创建一个可以以…