赞
踩
本篇博客中我们将学习如何使用KenLM工具构建统计语言模型,并使用它完成一个典型的'智能纠错'文本任务。
目录
- # 安装依赖
- !apt install libboost-all-dev
- !apt install libbz2-dev
- !apt install libeigen3-dev
- #下载kenlm压缩包并在当前目录解压
- !wget -O - https://kheafield.com/code/kenlm.tar.gz | tar xz
- #在kenlm下新建build目录
- !mkdir kenlm/build
- #编译
- !cd kenlm/build && cmake .. && make -j8
!cd kenlm/build && make install
使用预处理(stemming等)好的英文训练数据(语料库),查看部分内容:
- !head -5 /data/NLP/Language_Models/lm_train_data #/代表根目录 ./表示当前目录(可以省略) ../表示当前目录的父目录
- #head -5 查看前5行
通过命令行的方式使用kenlm,在之前的语料库上训练语言模型(计算各种组合的条件概率),命令:
-o 后面的数字5代表使用N-gram的N取值为5。text.arpa 表示kenlm训练得到的文件格式为.arpa格式,名字为text。
- # 我们训练一个简单的2-gram语言模型
- !lmplz -o 2 </data/NLP/Language_Models/lm_train_data> /data/NLP/Language_Models/lm.arpa
对训练得到的文件进行压缩:将arpa文件转换为binary文件,这样可以对arpa文件进行压缩和序列化,提高后续在python中加载的速度。针对我们训练的到的 lm.arpa 文件其转换命令为:
!build_binary -s /data/NLP/Language_Models/lm.arpa /data/NLP/Language_Models/lm.bin
- !ls -l /data/NLP/Language_Models/lm.arpa #压缩前
- !ls -l /data/NLP/Language_Models/lm.bin #压缩后
虽然大小没有发生太大的变化,但是压缩后会大大提高Python加载的速度。
- # 安装KenLM的python接口
- !pip install -i https://pypi.tuna.tsinghua.edu.cn/simple kenlm
- # 加载模型
- import kenlm
-
- ## 将训练得到的文件导入到 kenlm 语言模型中
- model = kenlm.LanguageModel("/data/NLP/Language_Models/lm.bin")
- # 使用语言模型对句子进行打分
- sentence = 'how are you'
- print(model.score(sentence))
-
- sentence = 'you are a good man'
- print(model.score(sentence))
-
- sentence = 'you are a a a a a abandon'
- print(model.score(sentence))
每个句子通过语言模型都会得到一个概率(0-1),然后对概率值取log得到分数,得分值越接近于0越好。可见前两个正常句子的分值比第三个非正常句子的分值要大。
一般都要对计算的概率值做log变换,不然连乘值太小了,在程序里会出现 inf 值。
我们来使用语言模型完成一个简单的智能纠错任务,在给定的测试文本中,有一些文本部分单词使用错误,最简单的一个任务是,一句话中可能会有一些不定冠词a或者an,根据后续接的单词不同,我们要选择用a或者an,但是有些地方的使用会出错,我们有没有办法把它找出来呢?
注意到这个问题中,我们使用a或者an其实只和后一个单词有关系,所以我们用2-gram就够用啦(其他问题可能情况会复杂一些)。我们整个流程如下。
- import nltk
- from collections import Counter
- # 统计句子中的a和an个数的函数
- def get_target_num(line):
- #返回某一行的a或an的数量
- if " a " in line or " an " in line:
- count = Counter(nltk.word_tokenize(line))["a"] + Counter(nltk.word_tokenize(line))["an"]
- return count
如果原句中没有a或者an,任务结束。如果有的话,可能会有多个位置需要替换,这其实是一个排列组合的问题。如果句子中含有3个a和1个an,那么我需要插入的个数就是 4 个空,每个空都有a,an两种选择。排列组合的结果就是如下十六种:
思路如下:
- # !pip install -i https://pypi.tuna.tsinghua.edu.cn/simple nltk
- # nltk.download('punkt')
- nltk.download('punkt')
- import codecs
- from collections import Counter
- import nltk
- import itertools
-
- # 读取文件内容
- def readfile(filepath):
- fp = codecs.open(filepath, "r", encoding="utf-8")
- content = fp.read()
- fp.close()
- return content
-
- # 按行加载文件
- # 对文件内容按行进行切分 返回一个列表
- def read_words_list(filepath):
- wordslist = readfile(filepath).splitlines()
- return wordslist
-
- # 保存文件的方法
- def save_file(savepath, content):
- fp = codecs.open(savepath,"w",encoding='utf-8')
- fp.write(content)
- fp.close()
-
- # 对 a 和 an 分布的可能性进行枚举,然后对句子中 a/an 所在的位置进行替换插入
- def generate_candidate_list(template_str, count):
- res = []
- tem_arr = template_str.split()
- #根据count 生成排列组合
- all_iters = list(itertools.product(("a", "an"), repeat = count))
- for iter in all_iters: #取一个组合 对原句子的###进行替换
- sentence = generate_sentence(tem_arr, iter) #得到替换后的新句子
- res.append(sentence) #res存储基于所有组合,替换后的新句子
- return res
-
- # 将列表中的数据插入到句子的占位符中
- def generate_sentence(tem_arr, iter):
- s = []
- id = 0
- for i in range(0,len(tem_arr)): #遍历句子的每一个词
- term = tem_arr[i]
- if term != "###": #如果不等于### 直接追加
- s.append(term)
- else: #如果是占位符 则把生成的组合 依次填到###中
- s.append(iter[id])
- id += 1
- return ' '.join(s) #返回一个字符串 用空格分隔
-
- # 定义输入和输出文件路径
- input_file = "/data/NLP/Language_Models/test_set"
- output_file = "./output_correction.txt"
-
- # 判断句子中是否存在一个 a/an ,如果有就将对应的 a 替换成 an
- # 分别对含有 a 和 an 的句子进行打分,用语言模型判别每个句子的的得分
- # 如果替换后的得分更加高了,那么说明原来句子里的 a/an 使用错误。
- def spelling_correction(input_file, output_file):
- changed_line_num = 0
- for line in read_words_list(input_file):
- if " a " in line or " an " in line:
- # 获取句子中含有的 a/an 单独子串的数量
- count = Counter(nltk.word_tokenize(line))["a"] + Counter(nltk.word_tokenize(line))["an"]
- # 将句子中相应位置的子串都变为占位符###
- line_new = line.replace(" a ", " ### ")
- line_new = line.replace(" an ", " ### ")
- # 得到新生成的替换后的句子列表
- candidates = generate_candidate_list(line_new, count)
-
- # 判断得分最高的句子是否为原句子
- line_best = line #存储得分最高的句子 初始化为原句子
- changed = 0 # 相比较使用句子字符串比对或者重新比较原句子和最高分句子的得分,使用标志位的方法更加方便。
- for s in candidates: #遍历基于所有组合 替换后得到的句子
- if model.score(s) > model.score(line_best):
- line_best = s
- changed += 1
- if changed != 0: #打印替换信息
- changed_line_num += 1
- str_output = str(changed_line_num) + ":\n" + line + "\n>>\n" + line_best + "\n"
- print(str_output)
- save_file(output_file, str_output)
- print("完成所有内容校对和纠正!")
spelling_correction(input_file, output_file)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。