【NLP CODE】基于统计的中文分词算法2:隐马尔可夫模型(维特比算法)

HMM算法实现:

# https://blog.csdn.net/wk19951125/article/details/88862095

class HMM(object):
    def __init__(self):
        import os

        # 主要是用于存取算法中间结果,不用每次都训练模型
        self.model_file = 'hmm_model.pkl'
        # 状态值集合
        self.state_list = ['B', 'M', 'E', 'S']
        # 参数加载,用于判断是否需要重新加载model_file
        self.load_para = False

    # 用于加载已计算的中间结果,当需要重新训练时,需初始化清空结果
    def try_load_model(self, trained):
        if trained:
            import pickle
            with open(self.model_file, 'rb') as f:
                self.A_dic = pickle.load(f)
                self.B_dic = pickle.load(f)
                self.Pi_dic = pickle.load(f)
                self.load_para = True

        else:
            # 状态转移概率(状态->状态的条件概率)
            self.A_dic = {}
            # 发射概率(状态->词语的条件概率)
            self.B_dic = {}
            # 状态的初始概率
            self.Pi_dic = {}
            self.load_para = False

    # 计算转移概率、发射概率以及初始概率
    def train(self, path):

        # 重置几个概率矩阵
        self.try_load_model(False)

        # 统计状态出现次数,求p(o)
        Count_dic = {}

        # 初始化参数
        def init_parameters():
            for state in self.state_list:
                self.A_dic[state] = {s: 0.0 for s in self.state_list}
                self.Pi_dic[state] = 0.0
                self.B_dic[state] = {}

                Count_dic[state] = 0

        def makeLabel(text):
            out_text = []
            if len(text) == 1:
                out_text.append('S')
            else:
                out_text += ['B'] + ['M'] * (len(text) - 2) + ['E']

            return out_text

        init_parameters()
        line_num = -1
        # 观察者集合,主要是字以及标点等
        words = set()
        with open(path, encoding='utf8') as f:
            for line in f:
                line_num += 1

                line = line.strip()
                if not line:
                    continue

                word_list = [i for i in line if i != ' ']
                words |= set(word_list)  # 更新字的集合

                linelist = line.split()

                line_state = []
                for w in linelist:
                    line_state.extend(makeLabel(w))
                
                assert len(word_list) == len(line_state)

                for k, v in enumerate(line_state):
                    Count_dic[v] += 1
                    if k == 0:
                        self.Pi_dic[v] += 1  # 每个句子的第一个字的状态,用于计算初始状态概率
                    else:
                        self.A_dic[line_state[k - 1]][v] += 1  # 计算转移概率
                        self.B_dic[line_state[k]][word_list[k]] = \
                            self.B_dic[line_state[k]].get(word_list[k], 0) + 1.0  # 计算发射概率
        
        self.Pi_dic = {k: v * 1.0 / line_num for k, v in self.Pi_dic.items()}
        self.A_dic = {k: {k1: v1 / Count_dic[k] for k1, v1 in v.items()}
                      for k, v in self.A_dic.items()}
        #加1平滑
        self.B_dic = {k: {k1: (v1 + 1) / Count_dic[k] for k1, v1 in v.items()}
                      for k, v in self.B_dic.items()}
        #序列化
        import pickle
        with open(self.model_file, 'wb') as f:
            pickle.dump(self.A_dic, f)
            pickle.dump(self.B_dic, f)
            pickle.dump(self.Pi_dic, f)

        return self

    def viterbi(self, text, states, start_p, trans_p, emit_p): # 维特比算法
        V = [{}]
        path = {}
        for y in states:
            V[0][y] = start_p[y] * emit_p[y].get(text[0], 0)
            path[y] = [y]
        for t in range(1, len(text)):
            V.append({})
            newpath = {}
            
            #检验训练的发射概率矩阵中是否有该字
            neverSeen = text[t] not in emit_p['S'].keys() and \
                text[t] not in emit_p['M'].keys() and \
                text[t] not in emit_p['E'].keys() and \
                text[t] not in emit_p['B'].keys()
            for y in states:
                emitP = emit_p[y].get(text[t], 0) if not neverSeen else 1.0 #设置未知字单独成词
                (prob, state) = max(
                    [(V[t - 1][y0] * trans_p[y0].get(y, 0) *
                      emitP, y0)
                     for y0 in states if V[t - 1][y0] > 0])
                V[t][y] = prob
                newpath[y] = path[state] + [y]
            path = newpath
            
        if emit_p['M'].get(text[-1], 0)> emit_p['S'].get(text[-1], 0):
            (prob, state) = max([(V[len(text) - 1][y], y) for y in ('E','M')])
        else:
            (prob, state) = max([(V[len(text) - 1][y], y) for y in states])
        
        return (prob, path[state])

    def cut(self, text):
        import os
        if not self.load_para:
            self.try_load_model(os.path.exists(self.model_file))
        prob, pos_list = self.viterbi(text, self.state_list, self.Pi_dic, self.A_dic, self.B_dic)      
        begin, next = 0, 0    
        for i, char in enumerate(text):
            pos = pos_list[i]
            if pos == 'B':
                begin = i
            elif pos == 'E':
                yield text[begin: i+1]
                next = i+1
            elif pos == 'S':
                yield char
                next = i+1
        if next < len(text):
            yield text[next:]
View Code

分别用 自定义语料库,微软语料库,北大语料库 训练,并对比输出结果。(第一个语料库在HMM算法的链接里面,原作者有下载。第二个第三个网上很多,搜:icwb2-data)

1.自定义语料库

#####测试
from hmm import HMM

hmm_test = HMM()
hmm_test.train('CWS_HMM/trainCorpus.txt_utf8.txt')

text='我们在野生动物园玩'
res=hmm_test.cut(text)
print(text)
print(str(list(res)))

text='他是研究生物化学的'
res=hmm_test.cut(text)
print(text)
print(str(list(res)))

text='商品和服务'
res=hmm_test.cut(text)
print(text)
print(str(list(res)))

text='南京市长江大桥'
res=hmm_test.cut(text)
print(text)
print(str(list(res)))

text='下雨天留客天留我不留'
res=hmm_test.cut(text)
print(text)
print(str(list(res)))
View Code

2.微软语料库

# icwb2-data 数据集是由北京大学、香港城市大学、台湾 CKIP, Academia Sinica 及中国微软研究所联合发布的数据集,
# 用以进行中文分词模型的训练。
# 其中 AS 和 CityU 为繁体中文数据集,PK 和 MSR 为简体中文数据集。

from hmm import HMM

hmm_test = HMM()
hmm_test.train('icwb2-data/training/msr_training.utf8.txt')

text='我们在野生动物园玩'
res=hmm_test.cut(text)
print(text)
print(str(list(res)))

text='他是研究生物化学的'
res=hmm_test.cut(text)
print(text)
print(str(list(res)))

text='商品和服务'
res=hmm_test.cut(text)
print(text)
print(str(list(res)))

text='南京市长江大桥'
res=hmm_test.cut(text)
print(text)
print(str(list(res)))

text='下雨天留客天留我不留'
res=hmm_test.cut(text)
print(text)
print(str(list(res)))
View Code

3.北大语料库

# icwb2-data 数据集是由北京大学、香港城市大学、台湾 CKIP, Academia Sinica 及中国微软研究所联合发布的数据集,
# 用以进行中文分词模型的训练。
# 其中 AS 和 CityU 为繁体中文数据集,PK 和 MSR 为简体中文数据集。

from hmm import HMM

hmm_test = HMM()
hmm_test.train('icwb2-data/training/pku_training.utf8.txt')

text='我们在野生动物园玩'
res=hmm_test.cut(text)
print(text)
print(str(list(res)))

text='他是研究生物化学的'
res=hmm_test.cut(text)
print(text)
print(str(list(res)))

text='商品和服务'
res=hmm_test.cut(text)
print(text)
print(str(list(res)))

text='南京市长江大桥'
res=hmm_test.cut(text)
print(text)
print(str(list(res)))

text='下雨天留客天留我不留'
res=hmm_test.cut(text)
print(text)
print(str(list(res)))
View Code

 

结果对比:

 

自定义语料库

微软语料库

北大语料库

我们在野生动物园玩

['我们', '在', '野', '生动物园', '玩']

['我们', '在', '野', '生动', '物园', '玩']

['我们', '在', '野生', '动物', '园玩']

他是研究生物化学的

['他是', '研究', '生物', '化学', '的']

['他是', '研究', '生物', '化学', '的']

['他', '是', '研究', '生物', '化学', '的']

商品和服务

['商品', '和', '服务']

['商品', '和', '服务']

['商品', '和', '服务']

南京市长江大桥

['南京', '市长', '江', '大桥']

['南京', '市长', '江大桥']

['南京', '市长', '江', '大桥']

下雨天留客天留我不留

['下雨', '天', '留', '客天', '留我', '不', '留']

['下雨', '天留', '客天', '留', '我', '不留']

['下雨', '天', '留客', '天留', '我', '不', '留']

 

 

 

 

 

 

 

 

 

 

 

 

 

再来跟前面基于字典的方法比较下:

 

正向最大匹配法

反向最大匹配法

双向最大匹配法

我们在野生动物园玩

我们/在野/生动/物/园/玩

我们/在/野生动物园/玩

我们/在/野生动物园/玩

他是研究生物化学的

他/是/研究生/物化/学/的/

他/是/研究/生物化学/的/

他/是/研究/生物化学/的/

商品和服务

商品/和服/务/

商品/和/服务/

 

南京市长江大桥

南京市/长江/大桥/

南京/市长/江大桥/

 

下雨天留客天留我不留

下雨/天/留客/天/留/我/不留/

下/雨天/留客/天/留/我/不留/

 

 

 

 

 

 

 

 

 

 

 

 

 

 

看看当前的在线平台效果:

 

https://hanlp.hankcs.com/

搜狗

清华

Stanford CoreNLP 

云孚科技

我们在野生动物园玩

我们|r 在|p 野生|f 动物园|n 玩|v

我们_r 在_p 野生_v 动物园_n 玩_v

 


  

他是研究生物化学的

 他|r 是|v 研究|v 生物|n 化学|n 的|u  他_r 是_v 研究_v 生物化学_n 的_u     

商品和服务

 商品|n 和|c 服务|v

 商品_n 和_c 服务_v

 

 

 

南京市长江大桥

 

 南京市|n 长江|n 大桥|n

 南京市_ns 长江_ns 大桥_n

   

下雨天留客天留我不留

 

 下|n 雨天|n 留客|v 天|n 留|v 我|r 不留|v

 下雨_v 天_t 留_v 客天_n 留_v 我_r 不_d 留_v

   

posted on 2020-07-05 01:28  HBU_DAVID  阅读(333)  评论(0编辑  收藏  举报

导航