文章目录
目标
基于人民日报标注语料(1998年1-8月),训练一个Bigram语言模型,并预测任意给定语句的语言概率。
1. 读取txt文件
Python从txt文件中逐行读取数据
有两种方法的区别
f.read()
读取到的类型是str
f.readlines()
读取到的类型是list
,可以进行逐行处理
如下:
f1 = open("./dict.txt","r", encoding="utf8")
lines1 = f1.read()
print(type(lines1))
print("lines1 =", lines1)
f2 = open("./dict.txt","r", encoding="utf8")
lines2 = f2.readlines()
print(type(lines2))
print("lines2 =", lines2)
# 使用readlines读取到list可以进行逐行处理
for line in lines2:
print(line)
# 输出如下:
"""
<class 'str'>
lines1 = 明天吃什么
今天天气真好
<class 'list'>
lines2 = ['明天吃什么\n', '今天天气真好']
明天吃什么
今天天气真好
"""
(语料图片因为放出来审核不过,就不放了)
2. 正则表达式的使用(语料预处理)
主要是使用的re的sub函数进行字符替换。
语法:
re.sub(pattern, repl, string, count=0, flags=0)
参数:
pattern
: 正则中的模式字符串。
repl
: 替换的字符串,也可为一个函数。
string
: 要被查找替换的原始字符串。
count
: 模式匹配后替换的最大次数,默认 0 表示替换所有的匹配。
主要使用的就是前三个参数
- 第一个即匹配规则,要替换什么东西
- 第二个即替换后字符,将上述字符替换为什么
- 第三个即原始字符串
import re # re 模块使 Python 语言拥有全部的正则表达式功能。
# 对语料的预处理
def Pretreatment(file_path):
intab = "0123456789"
outtab = "0123456789"
trantab = str.maketrans(intab,outtab) # 将所有的in字符与对应的out字符建立映射
f1 = open("./finish.txt", "w", encoding='utf-8')
for line in open(file_path):
new_line = re.sub(r" |/t|/n|/m|/v||/u|/a|/w|/q|r|t|/k|/f|/p|n|/c|s|/|d|i|b|l|j|e|v|g|N|V|R|T|y|o|A|D|h|z|x|A|B|M|a|Y|\d{8}-\d{2}-\d{3}-\d{3}", "", line)
new_line = new_line.translate(trantab) # 将所有的in字符用对应的out字符替代
f1.write(new_line)
f1.close()
上代码中有0-9的数字替换,因为训练语料中的数字很奇怪,就批量替换为0-9。同时也是一种批量替换字符串的方法。
translate批量替换字符串
- 构造一个
intab
字符串,是待替换字符 - 构造一个
outtab
字符串,是替换后的字符 - 用
trantab = str.maketrans(intab,outtab)
的maketrans方法将两个字符串中的字符一一对应。 - 用
new_line = new_line.translate(trantab)
将每一行的字符进行替换。
最后将处理后的每一行字符串写入“filter.txt”文件中。
3. 为每一句话添加头和尾
给每句话的头部添加“BOS”,尾部添加“EOS”
方法比较巧妙
- 前两个if是将一行字符串中头部和尾部的标点符号去掉
- s_modify1的re.sub将字符串中每个标点符号替换为“EOS BOS”
- s_modify2是为整个字符串添加头尾
# 将一句话添加头和尾
def Modify(s):
#将结尾标点符号截掉
if s[-1] in (r"[%s]+"%punc):
s = s[:-1] # 截取字符串从开头到倒数一个字符的子串
if s[0] in (r"[%s]+"%punc): # 如果该行开头有待替代符号
s = s[1:]
#添加起始符BOS和终止符EOS
s_modify1 = re.sub(r"[%s]+"%punc, "EOS BOS", s) ## r'\w+'为正则表达式,匹配多个英文单词或者数字
s_modify2="BOS"+s_modify1+"EOS"
return s_modify2
3. 构建训练语料库
- 逐行读取文件
- 调用2中的函数为每个句子添加头尾
- 加载自定义词典,进行分词
- 构建train_list存储训练语料分词的列表
- 构建train_dicts存储训练语料分词的词频
# 构建训练语料库
def BuildTrainCorpus(file_path):
f = open(file_path, "r", encoding="utf-8")
text = f.readlines()
train_list = [] # 存储训练预料分词的列表
train_dicts = {} # 存储训练预料词汇的词频
jieba.load_userdict("./dict.txt") # 加载自定义字典
for line in text:
line = line.strip('\n') # 去除句末隐藏的换行符
if line != "": # 避免读取空行
s_temp = Modify(line) # 为一行语句添加头和尾
word_list = jieba.lcut(s_temp, cut_all=False) # 分词
while ' ' in word_list: # 删除分词后分到的空格
word_list.remove(' ')
train_list.extend(word_list)
f.close()
# 统计词频
for word in train_list:
if word not in train_dicts:
train_dicts[word] = 1
else:
train_dicts[word] += 1
return train_list, train_dicts
4. 构建测试语料
用上述3中的部分代码构建测试语料列表test_list
# 构建测试语料
def BuildTestCorpus(sen):
jieba.load_userdict("./dict.txt") # 加载自定义字典
sen = sen.strip('\n')
s_temp = Modify(sen)
test_list = jieba.lcut(s_temp, cut_all=False) # 分词
while ' ' in test_list:
test_list.remove(' ')
return test_list
5. 计算语句概率
- 首先申请空间,相当于初始化一个count列表,用来记录二元语法正确匹配的词的个数。最后作为概率的分子。
- 遍历测试的字符串
- 遍历语料字符串,因为是二元语法,不用比较语料字符串的最后一个字符
- 如果测试的第一个词和语料的第一个词相等则比较第二个词(二元语法)
- 遍历count_list计算句子的概率
- 过程中使用加法平滑进行数据平滑,防止出现概率为0的情况。即在分子上加1,分母上加测试语料的词的个数
- 连乘得到整个句子的概率
# 计算语句概率
def Probability(train_list, train_dicts, test_list):
count_list=[0]*(len(test_list)-1) # 申请空间
# 遍历测试的字符串
for i in range(0, len(test_list)-1):
# 遍历语料字符串,因为是二元语法,不用比较语料字符串的最后一个字符
for j in range(0,len(train_list)-1):
#如果测试的第一个词和语料的第一个词相等则比较第二个词(二元语法)
if test_list[i]==train_list[j]:
if test_list[i+1]==train_list[j+1]:
count_list[i]+=1
p = 1
for i in range(len(count_list)):
# 使用加法平滑进行数据平滑,防止出现概率为0的情况
c_ii = float(count_list[i]) + 1
c_i = float(train_dicts.get(test_list[i], 1)) + len(test_list)
p *= c_ii / c_i # 概率累乘
return p
最后是完整的代码:
import re
import jieba
# 待替代符号
punc = r"""\n !?。"#《》$%&'()*+,-/:;<=>@[\]^_`{|}~⦅⦆「」、〃》「」『』【】〔〕〖〗〘〙〚〛〜〝〞〟〰〾〿–—‘’‛“”„‟…‧﹏.#$%&'()*+,-./:;<=>?@[\]^_`{|}~“”?,!【】()。:;’‘……¥·"""
# 对语料的预处理
def Pretreatment(file_path):
intab = "0123456789"
outtab = "0123456789"
trantab = str.maketrans(intab,outtab) # 将所有的in字符与对应的out字符建立映射
f1 = open("./filter.txt", "w", encoding='utf-8') # 构造过滤后的文件
for line in open(file_path):
new_line = re.sub(r" |/t|/n|/m|/v||/u|/a|/w|/q|r|t|/k|/f|/p|n|/c|s|/|d|i|b|l|j|e|v|g|N|V|R|T|y|o|A|D|h|z|x|A|B|M|a|Y|\d{8}-\d{2}-\d{3}-\d{3}", "", line)
new_line = new_line.translate(trantab) # 将所有的in字符用对应的out字符替代
f1.write(new_line)
f1.close()
# 将一句话添加头和尾
def Modify(s):
#将结尾标点符号截掉
if s[-1] in (r"[%s]+"%punc):
s = s[:-1] # 截取字符串从开头到倒数一个字符的子串
if s[0] in (r"[%s]+"%punc): # 如果该行开头有待替代符号
s = s[1:]
#添加起始符BOS和终止符EOS
s_modify1 = re.sub(r"[%s]+"%punc, "EOS BOS", s) ## r'\w+'为正则表达式,匹配多个英文单词或者数字
s_modify2="BOS"+s_modify1+"EOS"
return s_modify2
# 构建训练语料库
def BuildTrainCorpus(file_path):
f = open(file_path, "r", encoding="utf-8")
text = f.readlines()
train_list = [] # 存储训练预料分词的列表
train_dicts = {} # 存储训练预料词汇的词频
jieba.load_userdict("./dict.txt") # 加载自定义字典
for line in text:
line = line.strip('\n') # 去除句末隐藏的换行符
if line != "": # 避免读取空行
s_temp = Modify(line) # 为一行语句添加头和尾
word_list = jieba.lcut(s_temp, cut_all=False) # 分词
while ' ' in word_list: # 删除分词后分到的空格
word_list.remove(' ')
train_list.extend(word_list)
f.close()
# 统计词频
for word in train_list:
if word not in train_dicts:
train_dicts[word] = 1
else:
train_dicts[word] += 1
return train_list, train_dicts
# 构建测试语料
def BuildTestCorpus(sen):
jieba.load_userdict("./dict.txt") # 加载自定义字典
sen = sen.strip('\n')
s_temp = Modify(sen)
test_list = jieba.lcut(s_temp, cut_all=False) # 分词
while ' ' in test_list:
test_list.remove(' ')
return test_list
# 计算语句概率
def Probability(train_list, train_dicts, test_list):
count_list=[0]*(len(test_list)-1) # 申请空间
# 遍历测试的字符串
for i in range(0, len(test_list)-1):
# 遍历语料字符串,因为是二元语法,不用比较语料字符串的最后一个字符
for j in range(0,len(train_list)-1):
#如果测试的第一个词和语料的第一个词相等则比较第二个词(二元语法)
if test_list[i]==train_list[j]:
if test_list[i+1]==train_list[j+1]:
count_list[i]+=1
p = 1
for i in range(len(count_list)):
# 使用加法平滑进行数据平滑,防止出现概率为0的情况
c_ii = float(count_list[i]) + 1
c_i = float(train_dicts.get(test_list[i], 1)) + len(test_list)
p *= c_ii / c_i # 概率累乘
return p
if __name__ == '__main__':
# 第二次运行可注释
Pretreatment("E:\QQ接受\训练语料\训练语料.txt")
train_list, train_dicts = BuildTrainCorpus("./filter.txt")
print("BuildTrainCorpus 完成")
sen1 = "今天真开心呀!"
sen2 = "全世界有数千种语言,最具语言天赋的人也只能说数十种,普通人能够学会两三种语言已属不易。"
test_list1 = BuildTestCorpus(sen1)
test_list2 = BuildTestCorpus(sen2)
print("BuildTestCorpus 完成")
p1 = Probability(train_list, train_dicts, test_list1)
p2 = Probability(train_list, train_dicts, test_list2)
print("sen1 的概率为:", p1)
print("sen2 的概率为:", p2)
用到的知识
正则表达式:re.match、re.search、re.sub、re.compile、findall、re.finditer、re.split
意境级讲解 jieba分词和词云、关键词抽取:TextRank、TF-IDF