【Python】NLP建立Bigram模型计算语句概率

目标

基于人民日报标注语料(1998年1-8月),训练一个Bigram语言模型,并预测任意给定语句的语言概率。

1. 读取txt文件

Python从txt文件中逐行读取数据
有两种方法的区别

  1. f.read()读取到的类型是str
  2. 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批量替换字符串

Python 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

python去除空格,tab制表符和\n换行符的小技巧

python怎么移除字符串中的换行符

意境级讲解 jieba分词和词云、关键词抽取:TextRank、TF-IDF

其他参考

统计语言模型Python实现

情人节礼物------用她的照片和我们的聊天记录生成词云~

1.2 Bigram计算句子的概率、python实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值