一.分词介绍

这一节介绍下中文分词,以便将前面的内容串起来,中文分词可以看做给定一个可观测状态(即中文字),预测其最有可能的隐状态(是否切分)的过程,比如如下例子(来自于《自然语言处理入门》):

avatar

可以将每个字对应的状态看做“过”或者“切”,比如上面的结果经过分词后为:“参观”,“了”,“北京”,“天安门”,但是呢,这样的风格比较粗糙,为了捕捉汉字的不同构成概率信息,通常使用{B,M,E,S}标注集,包括词语首尾(Begin,End),词中(Middle)以及单字成词(Single),这样上面的过程可以进一步细化如下:
avatar

二.实践

了解了原理后,接下来在《人民日报》1988的语料上做训练,使用BMES的方式进行标注

In [1]:
visible_seqs=[]
hidden_seqs=[]
char2idx={}
idx2hidden={0:"B",1:"M",2:"E",3:"S"}
In [2]:
count=0
for line in open("./data/people_daily_mini.txt",encoding="utf8"):
    visible_seq=[]
    hidden_seq=[]
    arrs=line.strip().split(" ")
    for item in arrs:
        if len(item)==1:
            hidden_seq.append(3)
        elif len(item)==2:
            hidden_seq.extend([0,2])
        else:
            hidden_seq.extend([0]+[1]*(len(item)-2)+[2])
        for c in item:
            if c in char2idx:
                visible_seq.append(char2idx[c])
            else:
                char2idx[c]=count
                visible_seq.append(count)
                count+=1
        visible_seqs.append(visible_seq)
        hidden_seqs.append(hidden_seq)
In [3]:
len(visible_seqs),len(hidden_seqs),len(char2idx)
Out[3]:
(1087083, 1087083, 4656)
In [4]:
#训练模型
import os
os.chdir('../')
from ml_models.pgm import HMM
hmm=HMM(hidden_status_num=4,visible_status_num=len(char2idx))
hmm.fit_with_hidden_status(visible_seqs,hidden_seqs)

看看分词效果

In [5]:
def seg(vis,hid):
    rst=[]
    for i in range(0,len(hid)):
        if hid[i] in [2,3]:
            rst.append(vis[i])
            rst.append("   ")
        else:
            rst.append(vis[i])
    return "".join(rst)
In [6]:
seq="我和我的祖国,一刻也不能分离"
seg(seq,hmm.predict_hidden_status([char2idx[c] for c in seq]))
Out[6]:
'我   和   我   的   祖国   ,   一刻   也   不能   分离   '
In [7]:
seq="小龙女说,我也想过过过过过过过的生活"
seg(seq,hmm.predict_hidden_status([char2idx[c] for c in seq]))
Out[7]:
'小龙   女   说   ,   我   也   想过   过过   过过   过过   的   生活   '
In [8]:
seq="我爱马云爸爸"
seg(seq,hmm.predict_hidden_status([char2idx[c] for c in seq]))
Out[8]:
'我爱   马云   爸爸   '

在看看观测状态的概率呢?差距没那么大了...

In [9]:
import numpy as np
print(np.log(hmm.predict_joint_visible_prob([char2idx[c] for c in "我爱马云爸爸"])))
print(np.log(hmm.predict_joint_visible_prob([char2idx[c] for c in "马云爸爸爱我"])))
-6.359733718580848
-6.651434674059153

三.扩展

只要是序列标注的问题其实都可以用到HMM,比如NLP中的词性标注(POS),命名实体识别(NER)等,只是换个不一样的隐状态标注即可,比如最上面的举例,如果是作NER的话,“北京天安门”就不该切分为“北京”和“天安门”,而是应该作为一个整体,所以训练集大概需要这样标注:

avatar

这里,同样采用了{B,M,E,S}标注集,只是面向的是实体,而不是分词,由于实体类型很多,所以{B,M,E}其实通常有多类,比如上面是针对地名的,那可能还有针对人名,机构名等,另外常用的实体标注集还有:

(1){B,I,O}:B-X 代表实体X的开头, I-X代表实体的结尾 O代表不属于任何类型;

(2){B,I,O,E,S}:B-X 表示开始,I-X表示内部, O表示非实体 ,E-X实体尾部,S-X表示该词本身就是一个实体

In [ ]: