实验内容

  • 用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的子项中。进行平滑处理时,令abc都加一,也就是{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)形式,容易平滑和处理

实验步骤

数据预处理

因为traintest都是以文本的形式给出的,而我们利用语言模型生成句子是以词项为基本单位的。因此,我们需要从文本中提取词项,以构建语言模型和测试语言模型

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语言模型相关推荐

  1. 深度学习与自然语言处理实验——中文信息熵的计算

    深度学习与自然语言处理实验--中文信息熵的计算 问题描述 实验原理 信息熵 语言模型的参数估计 实验过程 实验数据的预处理 一元模型 二元模型 三元模型 实验结果 语料信息统计 不同模型下的实验结果 ...

  2. 基于多模型融合的用户画像分析统计方法研究

    摘  要 随着信息技术的快速发展和大数据技术的广泛应用,企业的营销和产品的设计,对精细化.精准化的要求越来越高.主流的电商平台.搜索引擎以及短视频平台均推出了基于用户画像的个性化推荐服务,这其中相当一 ...

  3. 如何对batch的数据求Gram矩阵

    Gram矩阵概念和理解 在风格迁移中,我们要比较生成图片和风格图片的相似性,评判标准就是通过计算Gram矩阵得到的.关于Gram矩阵的定义,可以参考[1]. 由这个矩阵的样子,很容易就想到协方差矩阵. ...

  4. Gram矩阵+Gram矩阵和协方差矩阵的关系

    目录 Gram矩阵简介 协方差矩阵 Gram矩阵 和 协方差矩阵的关系 Gram Matrix代码 Gram矩阵简介 gram矩阵是计算每个通道 i 的feature map与每个通道 j 的feat ...

  5. 对gram.y的解析(一)

    源码链接 https://www.gitlink.org.cn/Eao3piq4e/openGauss-server/tree/master/src%2Fcommon%2Fbackend%2Fpars ...

  6. gram是什么意思中文翻译_-gram是什么意思_-gram的翻译_音标_读音_用法_例句_爱词霸在线词典...

    全部 A few twentieths of a gram can be critical. 即使重量仅有1克的二十分之几都可能是关键性的. 柯林斯例句 A Chinese speech recogn ...

  7. LG gram 2023款 评测

    LG gram 2023 将会搭载英特尔 13代酷睿新品,而且配备 RTX 3050 独显,支持 VRR 可变刷新率. 在本月初的 CES 2023 上,LG 面向全球推出这款笔记本电脑,包括全新 g ...

  8. python中nlp的库_用于nlp的python中的网站数据清理

    python中nlp的库 The most important step of any data-driven project is obtaining quality data. Without t ...

  9. 如何构建一个简单的图书推荐系统

    原作者:venkat-raman 出处:https://www.linkedin.com/pulse/content-based-recommender-engine-under-hood-venka ...

  10. 【字源大挪移—读书笔记】 第一部分:字首

    [1] 字首:[1.1]表示[否定]的字首.[1.2]表示[方位]的字首.[1.3]表示[程度]的字首.[1.4]表示[状态.现象]的字首.[1.5]表示[数字]的字首 [1.1] 表示[否定]的字首 ...

最新文章

  1. SPOJ GSS3-Can you answer these queries III-分治+线段树区间合并
  2. Linux入门基础教程之Linux下软件安装
  3. java 转换上传文档_自己编写JAVA环境下的文件上传组件 (转)
  4. 安装oracle 11gR2单实例+ASM
  5. Acwing第 39 场周赛【完结】
  6. mysql乐观和悲观锁实现_mysql实现乐观锁和悲观锁该怎么编写?
  7. 利用usb远程控制linux,Linux编程控制硬件(5) ---- 操作USB手柄
  8. java中输出系统时间
  9. 软件测试除了边界值还有什么,在软件测试中,假定 X 为整数,10≤X≤100,用边界值分析法,那么 X 在测试 中应该取( )边界值...
  10. 第 1 节:前端面试指南 — 简历篇
  11. 谷歌浏览器安卓_用谷歌服务更安全了,安卓手机可充当物理安全密匙
  12. 家的N次方 经典台词
  13. 关于单链表的几个问题
  14. linux运维架构师职业规划
  15. 网易云音乐ios旧版本安装包_网易云音乐产品分析报告
  16. SQL Server 2017 AlwaysOn AG 自动初始化(十六)
  17. scrollView截取指定区域的图片
  18. GraPhlAn:最美进化树或层级分类树学习笔记
  19. python 16进制转中文_求助~ 16进制数据转不了汉字
  20. C++的O2、O3到底是个什么鬼

热门文章

  1. 利用VScode 编写C51/stm32代码
  2. qq消息定时自动发送的简单实现策略
  3. leaflet 加载高德地图
  4. linux设置的依赖关系,linux:dpkg:依赖关系问题使得 skype 的配置工作不能继续:问题解决方法...
  5. 计算机发表sci论文,sci2区计算机论文容易发表吗?
  6. android模拟程序被杀死,Android模拟后台进程被杀
  7. 苹果发布新召回计划,这款iPhone两年内免费维修
  8. NOIP2015酱油记
  9. STM32MP157网络环境 TFYPNFS搭建手册-学习记录
  10. 阿里云服务器使用docker安装mysql