uni-gram与bi-gram语言模型
实验内容
- 用python编程实践语言模型(uni-gram和bi-gram),加入平滑技术。
- 计算test.txt中句子的PPL,对比uni-gram和bi-gram语言模型效果。
遇到和解决的问题
问题1
- 问题:列表和字典作为实参传入函数时,在函数体内部改变形参,会导致实参也发生改变
- 解决:一维列表传入使用
list.copy()
,二维字典传入使用copy.deepcopy(dict)
- 详情可见:Python中实参随形参改变而改变的问题_长命百岁️的博客-CSDN博客。该博客编写于实验过程中,针对本问题进行了解答。
问题2
- 问题:程序运算速度很慢,尤其是
bi-gram
阶段 - 解答:原本的程序是每次向函数中传入一个测试语句,然后对整体词典进行一次平滑处理操作。且使用实参传入时需要使用
copy
函数。尤其是在进行bi-gram
过程时,需要使用deepcopy
函数,非常慢。之后直接对整个test
文本进行一次平滑操作。前后结果基本没有发生变化
问题3
- 问题:
bi-gram
的概率计算方式和平滑处理方式不对,导致困惑度偏大 - 解答:
- 一开始尝试过两种存储方式,句子概率计算方式均为
P(abc) = P(a)P(b|a)P(c|b)
- 第一种:
{a: {b: 1, c: 2}}
,当进行平滑处理时,令b
,c
都加一,也就是{a: {b: 2, c: 3}}
,这样的话,a
出现的次数b + c
就从1+2
变成2+3
了,对单个a
来说就不是加一平滑了 - 第二种:
{a: {a: 3, b: 1, c: 2}}
,将a
存入a
的子项中。进行平滑处理时,令a
,b
,c
都加一,也就是{a: {a: 4, b: 2, c: 3}}
,这样的话a = b + c
就不成立了
- 第一种:
- 给句子添加首位,令
abc
变成begin abc end
。这样的话,我们统计的时候,就可以仅统计P(a|b)
形式的词频就好了,因为P(begin abc end) = P(begin)P(a|begin)P(b|a)P(c|b)P(end|c)
。一句话的开头,P(begin) = 1
。所以可以写成P(begin abc end) = P(a|begin)P(b|a)P(c|b)P(end|c)
。全是P(a|b)
形式,容易平滑和处理
- 一开始尝试过两种存储方式,句子概率计算方式均为
实验步骤
数据预处理
因为train
和test
都是以文本的形式给出的,而我们利用语言模型生成句子是以词项为基本单位的。因此,我们需要从文本中提取词项,以构建语言模型和测试语言模型
needless_words = ['!', ',', '.', '?', ':', ';', '<', '>'] # 常见标点符号
读取训练数据
- 输入:字符串,文件路径
- 输出:二维列表, 每个列表中存储一句话提取出来的词
def read_train_file(file_path): # 返回的是所有的词,格式是二维列表,每句的词组成一个列表,res_list = []with open(file_path, 'r', encoding='utf-8') as f:reader = f.readlines()for line in reader:split_line = line.strip().split('__eou__') # 分句words = [nltk.word_tokenize(each_line) for each_line in split_line] # 分词,每句的词都是一个列表for i in words:need_word = [word.lower() for word in i if word not in needless_words] # 删除常见标点,并将所有词进行小写处理if len(need_word) > 0:res_list.append(need_word)return res_list
读取测试数据
- 输入:字符串,文件路径
- 输出:二维列表,每个列表存储一句话(未经过词项提取)
def read_test_file(file_path): # 返回的是所有的句子,格式是二维列表,每个句子都是一个列表res_list = []with open(file_path, 'r', encoding='utf-8') as f:reader = f.readlines()for line in reader:split_line = line.strip().split('__eou__') # 分句for i in split_line:if len(i) > 0:res_list.append(i)return res_list
uni-gram
词项统计(train)
- 输入:二维列表,就是
read_train_file
函数的输出内容 - 输出:字典,格式为
{a: 10}
,含义是:a
在训练数据中出现了 10 次 - 注意:我们同时计算了训练数据的总词量
total_words
。因为后面要算的是一个词出现的概率,total_words
可作为分母
def uni_gram(word_list): # 计算词频,返回的是一个保留词频的字典,word_list格式是二维列表global total_wordsuni_dict = defaultdict(float)for line in word_list:for word in line:total_words += 1 # 计算总的词的个数uni_dict[word] += 1 # 计算词频return uni_dict
- 输入:二维列表,就是
加一平滑 + 困惑度计算
输入:
word_dict
字典,就是uni_gram
函数的输出内容。sens
二维列表,就是read_test_file
函数的输出内容输出:列表,存储了测试数据中每个句子的困惑度
加一平滑:
为防止测试数据中出现了训练数据中从未出现的词,而导致一句话出现的概率为 0。我们在遍历训练数据时,当遇到字典中没有出现过的词时,我们将其添加到字典中,令其出现的次数为 0。
之后我们对字典中的所有词的词项都加 1。
计算每个词出现的概率:C(wi)C(w_i)C(wi) 为原词频,NNN 为训练数据总词数,VVV 是新增加的 1 的个数
计算句子出现的概率:P(abcd)=P(a)P(b)P(c)P(d)P(abcd) = P(a)P(b)P(c)P(d)P(abcd)=P(a)P(b)P(c)P(d),这里为了减小误差,我们将累乘变成 logloglog 累加的形式
计算困惑度
def ppl_compute(word_dict, sens): # word_dict是存储词频的字典, sen是没经过分词的一个 test 句子temp = []for sen in sens:words = nltk.word_tokenize(sen)need_words = [word.lower() for word in words if word not in needless_words] # 提取出句子中所有的词项temp.append(need_words)for word in need_words: # test语句中未在 train 时出现过的词,新加入if word not in word_dict:word_dict[word] = 0for word in word_dict: # 所有词项的词频都加 1,进行平滑处理word_dict[word] += 1word_dict[word] /= len(word_dict) + total_words # 每个词都加一后的增加量 + 原有的词的总数for need_words in temp:res_ppl = 1for word in need_words:res_ppl += log(word_dict[word], 2) # 防止累乘出现 res_ppl = 0 的情况uni_ppl.append(pow(2, -(res_ppl / len(need_words))))
bi-gram
词项统计(train)
- 输入:二维列表,就是
read_train_file
函数的输出内容 - 输出:二维字典,格式为
{a: {b: 1, c: 2}}
,含义:在a
出现的情况下,b
出现 1 次,c
出现 2 次 - 注意:这里我们加了开头和结尾,具体原因会在
遇到和解决的问题中阐述
。
def bi_gram(word_list): # 统计 bi_gram 的词频,返回一个二维字典bi_dict = defaultdict(dict)for words in word_list:words.insert(0, 'nsy6666') # 每行的词加个开头words.append('nsy6666///') # 每行的词加个结尾for index in range(len(words) - 1):if words[index + 1] not in bi_dict[words[index]]: # 其他词作为子项bi_dict[words[index]][words[index + 1]] = 1else:bi_dict[words[index]][words[index + 1]] += 1return bi_dict
- 输入:二维列表,就是
加一平滑 + 困惑度计算
- 输入:
bi_word
字典,就是bi_gram
函数的输出内容。sens
二维列表,就是read_test_file
函数的输出内容 - 输出:列表,存储了测试数据中每个句子的困惑度
- 加一平滑:
- 为防止测试数据中出现了训练数据中从未出现的词对,而导致一句话出现的概率为 0。我们在遍历训练数据时,当遇到字典中没有出现过的词对时,我们将其添加到字典中,令其出现的次数为 0。
- 计算P(a∣b)P(a|b)P(a∣b),
b
出现的情况下,a
出现的概率。就是bi_word[b][a] / b出现的次数
。 - 计算句子出现的概率:P(abc)=P(a∣begin)P(b∣a)P(c∣b)P(end∣c)P(abc) = P(a|begin)P(b|a)P(c|b)P(end|c)P(abc)=P(a∣begin)P(b∣a)P(c∣b)P(end∣c)
- 和
uni-gram
一样计算困惑度
def ppl_compute_bi(bi_word, sens):temp = []for sen in sens: # 遍历每个句子words = nltk.word_tokenize(sen)need_words = [word.lower() for word in words if word not in needless_words] # 提取出句子中所有的词项need_words.insert(0, 'nsy6666') # 每行的词加个开头need_words.append('nsy6666///') # 每行的词加个结尾temp.append(need_words)for index in range(len(need_words) - 1): # 添加 test 句子中同时出现的 bi_gram,但未在 train 中同时出现的 bi_gramif need_words[index + 1] not in bi_word[need_words[index]]:bi_word[need_words[index]][need_words[index + 1]] = 0for first_word in bi_word: # 对 bi_gram 词项进行平滑处理for second_word in bi_word[first_word]:bi_word[first_word][second_word] += 1for first_word in bi_word: # 对 bi_gram 词项进行平滑处理。不能只使用 need_words,因为中间有很多重复的词,会进行不该进行的除法tt = sum(bi_word[first_word].values()) # 需要提前定义在这里,否则后面进行除法之后,这个值就发生改变了for second_word in bi_word[first_word]:bi_word[first_word][second_word] /= ttfor need_words in temp:res_ppl = 0for index in range(len(need_words) - 1):res_ppl += log(bi_word[need_words[index]][need_words[index + 1]], 2)bi_ppl.append(pow(2, -(res_ppl / (len(need_words) - 1))))
- 输入:
实验结果
uni-gram
print(sum(uni_ppl) / len(uni_ppl)) # 取所有句子困惑度的平均值 >>723.2634736604283
bi-gram
print(sum(bi_ppl) / len(bi_ppl)) # 取所有句子困惑度的平均值 >>51.02679427126319
github: 代码及数据地址
uni-gram与bi-gram语言模型相关推荐
- 深度学习与自然语言处理实验——中文信息熵的计算
深度学习与自然语言处理实验--中文信息熵的计算 问题描述 实验原理 信息熵 语言模型的参数估计 实验过程 实验数据的预处理 一元模型 二元模型 三元模型 实验结果 语料信息统计 不同模型下的实验结果 ...
- 基于多模型融合的用户画像分析统计方法研究
摘 要 随着信息技术的快速发展和大数据技术的广泛应用,企业的营销和产品的设计,对精细化.精准化的要求越来越高.主流的电商平台.搜索引擎以及短视频平台均推出了基于用户画像的个性化推荐服务,这其中相当一 ...
- 如何对batch的数据求Gram矩阵
Gram矩阵概念和理解 在风格迁移中,我们要比较生成图片和风格图片的相似性,评判标准就是通过计算Gram矩阵得到的.关于Gram矩阵的定义,可以参考[1]. 由这个矩阵的样子,很容易就想到协方差矩阵. ...
- Gram矩阵+Gram矩阵和协方差矩阵的关系
目录 Gram矩阵简介 协方差矩阵 Gram矩阵 和 协方差矩阵的关系 Gram Matrix代码 Gram矩阵简介 gram矩阵是计算每个通道 i 的feature map与每个通道 j 的feat ...
- 对gram.y的解析(一)
源码链接 https://www.gitlink.org.cn/Eao3piq4e/openGauss-server/tree/master/src%2Fcommon%2Fbackend%2Fpars ...
- gram是什么意思中文翻译_-gram是什么意思_-gram的翻译_音标_读音_用法_例句_爱词霸在线词典...
全部 A few twentieths of a gram can be critical. 即使重量仅有1克的二十分之几都可能是关键性的. 柯林斯例句 A Chinese speech recogn ...
- LG gram 2023款 评测
LG gram 2023 将会搭载英特尔 13代酷睿新品,而且配备 RTX 3050 独显,支持 VRR 可变刷新率. 在本月初的 CES 2023 上,LG 面向全球推出这款笔记本电脑,包括全新 g ...
- python中nlp的库_用于nlp的python中的网站数据清理
python中nlp的库 The most important step of any data-driven project is obtaining quality data. Without t ...
- 如何构建一个简单的图书推荐系统
原作者:venkat-raman 出处:https://www.linkedin.com/pulse/content-based-recommender-engine-under-hood-venka ...
- 【字源大挪移—读书笔记】 第一部分:字首
[1] 字首:[1.1]表示[否定]的字首.[1.2]表示[方位]的字首.[1.3]表示[程度]的字首.[1.4]表示[状态.现象]的字首.[1.5]表示[数字]的字首 [1.1] 表示[否定]的字首 ...
最新文章
- SPOJ GSS3-Can you answer these queries III-分治+线段树区间合并
- Linux入门基础教程之Linux下软件安装
- java 转换上传文档_自己编写JAVA环境下的文件上传组件 (转)
- 安装oracle 11gR2单实例+ASM
- Acwing第 39 场周赛【完结】
- mysql乐观和悲观锁实现_mysql实现乐观锁和悲观锁该怎么编写?
- 利用usb远程控制linux,Linux编程控制硬件(5) ---- 操作USB手柄
- java中输出系统时间
- 软件测试除了边界值还有什么,在软件测试中,假定 X 为整数,10≤X≤100,用边界值分析法,那么 X 在测试 中应该取( )边界值...
- 第 1 节:前端面试指南 — 简历篇
- 谷歌浏览器安卓_用谷歌服务更安全了,安卓手机可充当物理安全密匙
- 家的N次方 经典台词
- 关于单链表的几个问题
- linux运维架构师职业规划
- 网易云音乐ios旧版本安装包_网易云音乐产品分析报告
- SQL Server 2017 AlwaysOn AG 自动初始化(十六)
- scrollView截取指定区域的图片
- GraPhlAn:最美进化树或层级分类树学习笔记
- python 16进制转中文_求助~ 16进制数据转不了汉字
- C++的O2、O3到底是个什么鬼
热门文章
- 利用VScode 编写C51/stm32代码
- qq消息定时自动发送的简单实现策略
- leaflet 加载高德地图
- linux设置的依赖关系,linux:dpkg:依赖关系问题使得 skype 的配置工作不能继续:问题解决方法...
- 计算机发表sci论文,sci2区计算机论文容易发表吗?
- android模拟程序被杀死,Android模拟后台进程被杀
- 苹果发布新召回计划,这款iPhone两年内免费维修
- NOIP2015酱油记
- STM32MP157网络环境 TFYPNFS搭建手册-学习记录
- 阿里云服务器使用docker安装mysql