基于条件随机场模型的中文分词

一、上机目的
1.了解中文分词的基本概念和原理。
2.掌握中文分词的常用算法,包括基于词库的分词技术、基于条件随机场模型的中文分词,熟悉算法流程,能推导算法结果。
3. 编程实现基于条件随机场模型的中文分词算法。

二、上机内容与要求
4. 使用自己熟悉的编程语言实现基于条件随机场模型的中文分词算法。
5. 按照算法流程,逐步编程实现,不得调用已有的分词算法。
6. 实例验证,包括课本“希腊的经济结构较特殊”及自行指定另一句子,给分词结果截图作为实验结果。

三、上机步骤

1 条件随机场模型对应于一个无向图,用于在给定需要标记的观察序列的条件下,计算整个标记序列的联合概率分布。从中文分词的角度来分析,每个词语中的每一个字都存在4种可能的状态,分别是词头(Begin)、词中(Middle)、词尾(End)和单字成词(Single),简称B、M、E、S。给定句子中的每个词则被视为条件随机场模型中的观察序列,条件随机场模型求解的就是句子中字的标记序列的联合概率分布,然后对标记序列进行回溯路径。
求解标记序列的联合概率分布的整体思路是:准备语料库;语料库特征初步学习;词语特征学习;开始分词。
标记序列的联合概率分布的求解公式为:
S[字][当前状态] = MAX(P[上一个字的状态][当前状态] x S[上一个字][任何一种状态] ) + W前(后)[前(后)一个字的状态][当前的字] + R[当前状态概率]

2 采用条件随机
2.1准备语料库
随机场模型是基于统计学习的方法,在采用条件随机场模型进行分词以前,需要准备一个语料库。语料库是由大量的句子组成,并且尽可能包含各种句式,语料库的质量直接影响联合概率分布和分词的效果,词语之间采用空格符分隔。例如下面的句子:
尽管 印尼 中央 和 地方政府 已 派出 上千人 的 灭火队,但 由于 该 地区 长期 干旱 少雨,所以 火势 至今 未 得到 有效 控制。

2.2 语料库特征学习
根据第一步分好词的语料库,我们可以看出一个字的状态可能有四种可能,分别是词头(Begin)、词中(Middle)、词尾(End)和单字成词(Single),简称B、M、E、S;当然,标点符号也是需要标记的,但一般只有一种状态,就是单字成词。我们这一步需要做的就是把语料库中的词进行标记,根据第一步的分词结果进行标记,标记后的结果如下所示:
尽\B管\E印\B尼\E中\B央\E和\S地\B方\M政\M府\E已\S派\B出\E上\B千\M人\E的\S灭\B火\M队\E,\S但\S由\B于\E该\S地\B区\E长\B期\E干\B旱\E少\B雨\E,\S所\B以\E火\B势\E至\B今\E未\S得\B到\E有\B效\E控\B制\E。\S
由上面的标注的可以看出,只要将一个句子的每一个字的状态标出,即相当于已经分词;因此,可将中文分词转换成序列标注问题,对一个未分词的序列进行标注(标注状态为B,M,E,S)可知道分词结果。例如将句子“我们爱中国”分词为“我\B们\E爱\S中\B国\E”,表示将句子分词为“我们 爱 中国”。

2.3 词语特征学习
基于用户输入的字符串,针对字符串中的每一个字,统计它在语料库中出现的次数,例如‘希’字在语料库中出现了1352次。
针对具体的某个字,计算它的状态分别为词头(Begin)、词中(Middle)、词尾(End)和单字成词(Single)的概率;例如:出现1352次的“希”字,其中有1208次希的状态是B,则“希”字的状态为B的概率为1208/1352=0.8935
针对某一个字,计算它的状态分别为词头(Begin)、词中(Middle)、词尾(End)和单字成词(Single)时,每一个字都有属于自己的状态,后面这一个字也有自己的状态,计算下一个字分别为词头(Begin)、词中(Middle)、词尾(End)和单字成词(Single)的概率,这里需要注意,只考虑下一个字的状态而不管下一个字是什么字。此过程计算的就是状态之间的相互转移的矩阵,针对每一个字的4种状态,转移到4种状态,就构成了一个4*4的矩阵,矩阵中的值就是他们相互之间转移概率。
当某一个字出现的时候,计算下一个字出现的内容,并计算两个字同时出现的概率。例如:状态为B的希字,下一个字的状态为E的腊字的概率是0.04216;还要计算当某一个字出现的时候,计算上一个字出现的内容,并计算两个字同时出现的概率。例如:状态为B的腊字,上一个字的状态为B的希字的概率是0.7024。还要考虑一个问题,就是第一个字没有前一个字,最后一个字没有后一个。

2.4 开始分词
求解每个字对应的特征,根据状态信息绘制字与状态的初始矩阵映射关系表,在未分词前,这个矩阵的初始值都为0。
依据公式求解每一个字的每一种状态的值。对于一个字符串,只要先求出第一个字的四种状态下的值,后面的类似。但不同的是,计算第一个字时,没有max部分。
第二步求映射矩阵时,总是从四个值中选一个最大值,对应的下表为0,1,2,3,然后在映射表中标记选中最大值得下标。
完成了映射矩阵和标记了下标之后,就可以进行回溯路径了。将映射矩阵的最后一个字的最大值取出,从后往前回溯,第一个数字就是最大值的下标,然后看它标记的数字,这个数字就来源于上一个字的位置,再记录下标……,
写出回溯路径后,就可以对字符串进行序列标记,0对应B,1对应M,2对应E,3对应S,标记完成即相当于分词完成。
3.1 词语特征学习
由于语料库msr_training.utf8.ic已经好了序列,帮我们省略了前两步的工作,为了方便,我把语料库另存为msr_training.utf8.txt文本文件,接下来的操作都是以这个文本文件进行的。
根据我的思路,首先第一步就是把语料库的内容当成一个字符串进行处理。面对四百多万行的文本文件,进行普通的字符流或字节流读取操作是很费时间的,因此我想到了利用字符流+缓冲区进行操作,使读取速度大大加快。把文件的读取内容赋值给可变的StringBuffer类型的变量,待完全读取后,再赋给String类型变量,因为之后语料库的内容是不变的。
统计输入的字符串中的每一个字在语料库中出现的次数。例如输入了“希腊的经济结构较特殊”,定义了一个数组,分别记录这十个字在语料库中的次数,每一个字分别遍历语料库进行统计,统计的结果如下:

s = input()      #输入要分词的字符串
ss = s     
with open("./1.txt",'r',encoding='UTF-8') as f:
    data1 = f.read()
    #print(data)
leng = len(s)
d = {
    
    }
for i in range(leng):
    d.update({
    
    s[i]:data1.count(s[i])})
print(d)
with open("./1.txt",'r',encoding='UTF-8') as f:
    data = f.readlines()
len_data = len(data)

在这里插入图片描述
2 字符串中每个字分别为B M E S的概率

#字符串中每个字分别为B M E S的概率
R = []     #下面计算的映射矩阵
for i in range(leng):
    B,M,E,S =0,0,0,0
    for j in range(len_data):
        if s[i] == data[j][0] and data[j][2] == 'B':
            B += 1
        if s[i] == data[j][0] and data[j][2] == 'M':
            M += 1
        if s[i] == data[j][0] and data[j][2] == 'E':
            E += 1
        if s[i] == data[j][0] and data[j][2] == 'S':
            S += 1
    r = [B/data1.count(s[i]),M/data1.count(s[i]),E/data1.count(s[i]),S/data1.count(s[i])]
    R.append(r)
    # print(s[i], '分别为B M E S 的次数:')
    # print(B, '  ', M, '  ', E, '  ', S)
    # print(s[i], '分别为B M E S 的概率:')
    # print(B/data1.count(s[i]), '  ', M/data1.count(s[i]), '  ', E/data1.count(s[i]), '  ', S/data1.count(s[i]))

在这里插入图片描述

2 统计当某一个字分别为B,M,E,S的状态出现时,上一个字的状态为B,M,E,S时的个数

z_tai = []
P = []
for i in range(leng):
    B_B,B_M,B_E,B_S =0,0,0,0
    M_B, M_M, M_E, M_S = 0, 0, 0, 0
    E_B, E_M, E_E, E_S = 0, 0, 0, 0
    S_B, S_M, S_E, S_S = 0, 0, 0, 0
    sum_B,sum_M,sum_E,sum_S = 0,0,0,0
    z_tai = []
    for j in range(1,len_data):
        if s[i] == data[j][0] and data[j][2] == 'B' and data[j - 1][2] == 'B':
            B_B += 1
        if s[i] == data[j][0] and data[j][2] == 'B' and data[j - 1][2] == 'M':
            B_M += 1
        if s[i] == data[j][0] and data[j][2] == 'B' and data[j - 1][2] == 'E':
            B_E += 1
        if s[i] == data[j][0] and data[j][2] == 'B' and data[j - 1][2] == 'S':
            B_S += 1
        sum_B = B_B + B_M + B_E + B_S
        if s[i] == data[j][0] and data[j][2] == 'M' and data[j - 1][2] == 'B':
            M_B += 1
        if s[i] == data[j][0] and data[j][2] == 'M' and data[j - 1][2] == 'M':
            M_M += 1
        if s[i] == data[j][0] and data[j][2] == 'M' and data[j - 1][2] == 'E':
            M_E += 1
        if s[i] == data[j][0] and data[j][2] == 'M' and data[j - 1][2] == 'S':
            M_S += 1
        sum_M = M_B+M_M+M_E+M_S

        if s[i] == data[j][0] and data[j][2] == 'E' and data[j - 1][2] == 'B':
            E_B += 1
        if s[i] == data[j][0] and data[j][2] == 'E' and data[j - 1][2] == 'M':
            E_M += 1
        if s[i] == data[j][0] and data[j][2] == 'E' and data[j - 1][2] == 'E':
            E_E += 1
        if s[i] == data[j][0] and data[j][2] == 'E' and data[j - 1][2] == 'S':
            E_S += 1
        sum_E = E_B+E_M+E_E+E_S

        if s[i] == data[j][0] and data[j][2] == 'S' and data[j - 1][2] == 'B':
            S_B += 1
        if s[i] == data[j][0] and data[j][2] == 'S' and data[j - 1][2] == 'M':
            S_M += 1
        if s[i] == data[j][0] and data[j][2] == 'S' and data[j - 1][2] == 'E':
            S_E += 1
        if s[i] == data[j][0] and data[j][2] == 'S' and data[j - 1][2] == 'S':
            S_S += 1
        sum_S = S_B+S_M+S_E+S_S
    tem = [B_B,B_M,B_E,B_S,M_B,M_M,M_E,M_S,E_B,E_M,E_E,E_S,S_B,S_M,S_E,S_S]
    tem1 =[sum_B,sum_M,sum_E,sum_S]
    for z in range(len(tem1)):
        if tem1[z] == 0:
            z_tai.append([0,0,0,0])
        else:
            z_tai.append([tem[z*4]/tem1[z],tem[z*4+1]/tem1[z],tem[z*4+2]/tem1[z],tem[z*4+3]/tem1[z]])

    #z_tai.append([[B_B/sum_B,B_M/sum_B,B_E/sum_B,B_S/sum_B],[M_B/sum_M,M_M/sum_M,M_E/sum_M,M_S/sum_M],[E_B/sum_E,E_M/sum_E,E_E/sum_E,E_S/sum_E],[S_B/sum_S,S_M/sum_S,S_E/sum_S,S_S/sum_S]])
    print(s[i], '上一个字状态BMES的概率:')
    print(z_tai)
    P.append(z_tai)

在这里插入图片描述4 统计当字符串中的某一个字出现在语料库时,下一个字的状态分别为为B,M,E,S同时出现的概率

w_hou = []
for i in range(1):
    B, M, E, S = 0, 0, 0, 0
    count = 0
    hou = []
    for j in range(1, len_data):
        if s[0] == data[j][0] and data[j-1][0] in '。”,、' and data[j][2] == 'B':
            B += 1
        if s[0] == data[j][0] and data[j-1][0] in '。”,、' and data[j][2] == 'M':
            M += 1
        if s[0] == data[j][0] and data[j-1][0] in '。”,、' and data[j][2] == 'E':
            E += 1
        if s[0] == data[j][0] and data[j-1][0] in '。”,、' and data[j][2] == 'S':
            S += 1
    #print(s[0], '句首首个子前面为空时 B M E S 的概率:')
    count = data1.count(s[0])
    tem = [B, M, E, S]
    for z in range(4):
        if tem[z] == 0:
            hou.append(0)
        else:
            hou.append(tem[z]/count)
w_hou.append(hou)

for i in range(leng-1):
    B,M,E,S =0,0,0,0
    count = 0
    hou = []
    for j in range(len_data-1):
        if s[i] == data[j][0] and s[i+1] == data[j+1][0] and data[j+1][2] == 'B':
            B += 1
        if s[i] == data[j][0] and s[i+1] == data[j+1][0] and data[j+1][2] == 'M':
            M += 1
        if s[i] == data[j][0] and s[i+1] == data[j+1][0] and data[j+1][2] == 'E':
            E += 1
        if s[i] == data[j][0] and s[i+1] == data[j+1][0] and data[j+1][2] == 'S':
            S += 1

    #print(s[i],'出现时,语料库中下一个字分别为B M E S 与字符串下一个字相同的概率:')
    count = data1.count(s[i])
    tem = [B,M,E,S]
    count = data1.count(s[0])
    tem = [B, M, E, S]
    for z in range(4):
        if tem[z] == 0:
            hou.append(0)
        else:
            hou.append(tem[z]/count)
    w_hou.append(hou)

这个好像求过

#统计当字符串中的某一个字出现在语料库时,
#上一个字的状态分别为为B,M,E,S同时出现的个数
# print(s[0], '出现时,语料库中下一个字分别为B M E S 与字符串下一个字相同的次数:')
# print(0, '  ', 0, '  ', 0, '  ', 0)
# for i in range(1,leng):
#     B,M,E,S =0,0,0,0
#     count = 0
#     for j in range(1,len_data):
#         if s[i] == data[j][0] and s[i-1] == data[j-1][0] and data[j-1][2] == 'B':
#             B += 1
#         if s[i] == data[j][0] and s[i-1] == data[j-1][0] and data[j-1][2] == 'M':
#             M += 1
#         if s[i] == data[j][0] and s[i-1] == data[j-1][0] and data[j-1][2] == 'E':
#             E += 1
#         if s[i] == data[j][0] and s[i-1] == data[j-1][0] and data[j-1][2] == 'S':
#             S += 1
#
#     print(s[i],'出现时,语料库中下一个字分别为B M E S 与字符串下一个字相同的次数:')
#     print(B, '  ', M, '  ', E, '  ', S)

#统计当字符串中的某一个字出现在语料库时
#上一个字的状态分别为为B,M,E,S同时出现的概率

5计算这个w前
在这里插入图片描述

#计算第一个字的状态
w_qian =[]
qian =[]
for i in range(1):
    B, M, E, S = 0, 0, 0, 0
    count = 0
    for j in range(1, len_data):
        if s[0] == data[j][0] and data[j-1][0] in '。”,、' and data[j][2] == 'B':
            B += 1
        if s[0] == data[j][0] and data[j-1][0] in '。”,、' and data[j][2] == 'M':
            M += 1
        if s[0] == data[j][0] and data[j-1][0] in '。”,、' and data[j][2] == 'E':
            E += 1
        if s[0] == data[j][0] and data[j-1][0] in '。”,、' and data[j][2] == 'S':
            S += 1
    #print(s[0], '句首首个子前面为空时 B M E S 的概率:')
    count = data1.count(s[0])
    tem = [B, M, E, S]
    for z in range(4):
        if tem[z] == 0:
            qian.append(0)
        else:
            qian.append(tem[z]/count)
w_qian.append(qian)

for i in range(1,leng):
    B,M,E,S =0,0,0,0
    count = 0
    qian =[]
    for j in range(1,len_data):
        if s[i] == data[j][0] and s[i-1] == data[j-1][0] and data[j-1][2] == 'B':
            B += 1
        if s[i] == data[j][0] and s[i-1] == data[j-1][0] and data[j-1][2] == 'M':
            M += 1
        if s[i] == data[j][0] and s[i-1] == data[j-1][0] and data[j-1][2] == 'E':
            E += 1
        if s[i] == data[j][0] and s[i-1] == data[j-1][0] and data[j-1][2] == 'S':
            S += 1

    #print(s[i],'出现时,语料库中上一个字分别为B M E S 与字符串上一个字相同的概率:')
    count = data1.count(s[i])
    tem = [B,M,E,S]
    for z in range(4):
        if tem[z] == 0:
            qian.append(0)
        else:
            qian.append(tem[z]/count)
    w_qian.append(qian)
#print(w_qian)
#希腊的经济结构较特殊   

在这里插入图片描述
6 计算w后的
在这里插入图片描述

#统计当字符串中的某一个字出现在语料库时,下一个字的状态分别为为B,M,E,S同时出现的概率
w_hou = []
for i in range(1):
    B, M, E, S = 0, 0, 0, 0
    count = 0
    hou = []
    for j in range(1, len_data):
        if s[0] == data[j][0] and data[j-1][0] in '。”,、' and data[j][2] == 'B':
            B += 1
        if s[0] == data[j][0] and data[j-1][0] in '。”,、' and data[j][2] == 'M':
            M += 1
        if s[0] == data[j][0] and data[j-1][0] in '。”,、' and data[j][2] == 'E':
            E += 1
        if s[0] == data[j][0] and data[j-1][0] in '。”,、' and data[j][2] == 'S':
            S += 1
    #print(s[0], '句首首个子前面为空时 B M E S 的概率:')
    count = data1.count(s[0])
    tem = [B, M, E, S]
    for z in range(4):
        if tem[z] == 0:
            hou.append(0)
        else:
            hou.append(tem[z]/count)
w_hou.append(hou)

for i in range(leng-1):
    B,M,E,S =0,0,0,0
    count = 0
    hou = []
    for j in range(len_data-1):
        if s[i] == data[j][0] and s[i+1] == data[j+1][0] and data[j+1][2] == 'B':
            B += 1
        if s[i] == data[j][0] and s[i+1] == data[j+1][0] and data[j+1][2] == 'M':
            M += 1
        if s[i] == data[j][0] and s[i+1] == data[j+1][0] and data[j+1][2] == 'E':
            E += 1
        if s[i] == data[j][0] and s[i+1] == data[j+1][0] and data[j+1][2] == 'S':
            S += 1

    #print(s[i],'出现时,语料库中下一个字分别为B M E S 与字符串下一个字相同的概率:')
    count = data1.count(s[i])
    tem = [B,M,E,S]
    count = data1.count(s[0])
    tem = [B, M, E, S]
    for z in range(4):
        if tem[z] == 0:
            hou.append(0)
        else:
            hou.append(tem[z]/count)
    w_hou.append(hou)

7计算映射矩阵

S = []
lu_jing = []
for i in range(1):
    ying_B = w_qian[i][0]+w_hou[i][0]+R[i][0]
    ying_M = w_qian[i][1]+w_hou[i][1]+R[i][1]
    ying_E = w_qian[i][2]+w_hou[i][2]+R[i][2]
    ying_S = w_qian[i][3]+w_hou[i][3]+R[i][3]
    s = [ying_B,ying_M,ying_E,ying_S]
    S.append(s)
    lu_jing.append([0,0,0,0])
for i in range(1,leng):
    max1 = [P[i][0][0]*S[i-1][0], P[i][0][1]*S[i-1][1], P[i][0][2]*S[i-1][2],P[i][0][3]*S[i-1][3]]
    max1_B = max(max1)
    ying_B = max1_B+w_qian[i][0] + w_hou[i][0] + R[i][0]
    lu_B = max1.index(max1_B)

    max1 = [P[i][1][0] * S[i - 1][0], P[i][1][1] * S[i - 1][1], P[i][1][2] * S[i - 1][2],P[i][1][3] * S[i - 1][3]]
    max1_M = max(max1)
    ying_M = max1_M + w_qian[i][1] + w_hou[i][1] + R[i][1]
    lu_M = max1.index(max1_M)

    max1 = [P[i][2][0] * S[i - 1][0], P[i][2][1] * S[i - 1][1], P[i][2][2] * S[i - 1][2],P[i][2][3] * S[i - 1][3]]
    max1_E = max(max1)
    ying_E = max1_E + w_qian[i][2] + w_hou[i][2] + R[i][2]
    lu_E = max1.index(max1_E)

    max1 = [P[i][3][0] * S[i - 1][0], P[i][3][1] * S[i - 1][1], P[i][3][2] * S[i - 1][2],P[i][3][3] * S[i - 1][3]]
    max1_S = max(max1)
    ying_S = max1_S + w_qian[i][3] + w_hou[i][3] + R[i][3]
    lu_S = max1.index(max1_S)

    lu_jing.append([lu_B,lu_M,lu_E,lu_S])
    s = [ying_B, ying_M, ying_E, ying_S]
    S.append(s)

在这里插入图片描述
8回溯路径可以转换为状态标记,0代表B,1代表M,2代表E,3代表S

hui_su=[]
ind = S[-1].index(max(S[-1]))
hui_su.append(ind)
for i in range(len(lu_jing)-1,0,-1):
    tem = lu_jing[i][ind]
    hui_su.append(tem)
    ind = tem
hui_su = hui_su[::-1]
hui_zhuang =[]
for i in hui_su:
    if i == 0:
        hui_zhuang.append('B')
    if i == 1:
        hui_zhuang.append('M')
    if i == 2:
        hui_zhuang.append('E')
    if i == 3:
        hui_zhuang.append('S')
jie_gou =''
ind_1 = []
for i in range(leng):     #每一个字一个状态
    if hui_zhuang[i] == 'E' or hui_zhuang[i] =='S':
        ind_1.append(i)
for i in range(leng):
    print(ss[i],end='')
    if i in ind_1:
        print('/',end='')

结果
在这里插入图片描述感觉效率很慢,希望有大佬带带小白
写得比较简陋~感觉 给给意见修改 没动力修改 累了哈哈

猜你喜欢

转载自blog.csdn.net/jcjic/article/details/109210276