用HMM进行词性标注解决状态转移概率0、发射概率为0的方法

利用HMM进行词性标注碰到的壁

1.大意失荆州

笔者上个星期上了一门创新研修课需要利用HMM来进行词性标注,我一开始的思路大致如下:

  • 首先利用语料库训练出每个隐状态的初始概率、状态之间的转移概率和隐状态生成观测值的发射概率,
  • 然后对于给定的观测序列利用viterbi算法求出最大概率的隐状态序列,我的算法在原来的训练集上面进行词性标注效果很好。

于是我乐呵呵的拿到讲台上去讲,但是老师二话不说,让我找个句子随便来测试一下,我自信满满,上百度随便找了一段话,用我之前写的分词算法分好词,然后来进行词性标注,也就是这,出大问题了!

我拿着一段从来没出现在训练语料库的句子进行词性标注,出现了这样的结果:
在这里插入图片描述
也就是说,所有的词都被标注为同一个词性。
一开始我觉得肯定是句子的问题,然后我找了很多的测试句子,发现结果都一样!
我一开始找的viterbi算法如下所示:

def viterbi(obs, states, start_p, trans_p, emit_p):
    """
    :param obs: 可见序列
    :param states: 隐状态
    :param start_p: 开始概率
    :param trans_p: 转换概率
    :param emit_p: 发射概率
    :return: 序列+概率
    """
    path = {}
    V = [{}]  # 记录第几次的概率
    for state in states:
        V[0][state] = start_p[state] * emit_p[state].get(obs[0], 0)
        path[state] = [state]
    for n in range(1, len(obs)):
        V.append({})
        newpath = {}
        for k in states:
            pp,pat=max([(V[n - 1][j] * trans_p[j].get(k,0) * emit_p[k].get(obs[n], 0) ,j )for j in states])
            V[n][k] = pp
            newpath[k] = path[pat] + [k]
            # path[k] = path[pat] + [k]#不能提起变,,后面迭代好会用到!
        path=newpath
    (prob, state) = max([(V[len(obs) - 1][y], y) for y in states])
    return prob, path[state]

oh,我猛然想起来,我没有对状态转移概率为0的情况进行讨论,也没有对发射概率为0的情况进行讨论(这个是最主要的),也就是说对于新出现的词,通过原先我们训练出的语料库得到的发射概率中,不存在任何一种隐状态能够生成这个观测值。

我在github上找了很多很多的不同版本的viterbi算法,发现他们都没有对这种情况进行讨论。

2.后悔可及

但是肯定是要对上述的情况进行讨论,我的想法是对于状态转移概率为0、发射概率为0的观测值需要赋值一个非常小非常小的值,这样才能保证正确的分词结果。我的代码如下:

def viterbi(obs, states, start_p, trans_p, emit_p):
    """
    :param obs: 可见序列
    :param states: 隐状态
    :param start_p: 开始概率
    :param trans_p: 转换概率
    :param emit_p: 发射概率
    :return: 序列+概率
    """
    path = {}
    v = [{}]  # 记录第几次的概率
    for state in states:
        if obs[0] not in emit_p[state]:
            v[0][state] = start_p[state] * 1e-20
        else :
            v[0][state] = start_p[state] * emit_p[state].get(obs[0], 0)
        path[state] = [state]
    # 以时间循环,从第一天开始循环
    for t in range(1, len(obs)):
        # print obs[t]
        v.append({})
        newpath = {}
        for y1 in states :   # 从path中取路径,作为上一个开始的路径
            max_prob = -1
            for y0 in v[t-1]:
                if y1 not in trans_p[y0] :#判断转移概率是否为0
                    print("转移"+y0 + y1)
                    if obs[t] not in emit_p[y1]:#判断发射率是否为0
                        nprob = v[t - 1][y0] * 1e-20 * 1e-20
                    else:
                        nprob = v[t - 1][y0] * 1e-20 * emit_p[y1][obs[t]]
                else:
                    if obs[t] not in emit_p[y1]:
                        nprob = v[t - 1][y0] * trans_p[y0][y1] * 1e-20
                    else:
                        nprob = v[t - 1][y0] * trans_p[y0][y1] * emit_p[y1][obs[t]]
                if nprob > max_prob:
                    # 暂时记录到y1状态节点最后的概率
                    max_prob = nprob
                    # 暂时记录到y1状态节点的上一个y0节点
                    max_state = y0
            # 保存到当前的y1状态的最好的概率值
            v[t][y1] = max_prob
            newpath[y1] = path[max_state] + [y1]
        path = newpath
    (prob, state) = max([(v[len(obs) - 1][y], y) for y in states])
    return path[state]

很幸运,我的分析是正确的,我找了很多的测试集进行测试,都能够很好的进行词性标注。上面我那个标注有误的句子用改正后的viterbi算法标注如下:
在这里插入图片描述
这下可以放心的去上课了

3.三省吾身

这下子应该对于HMM进行词性标注有点掌握了。附上训练部分的代码。(viterbi算法代码上面给了)

# -*- coding:utf-8 -*-
import sys
import collections

start_c={}#开始概率,就是一个字典,state:chance=Word/lines
transport_c={}#转移概率,是字典:字典,state:{state:num,state:num....}   num=num(state1)/num(statess)
emit_c={}#发射概率,也是一个字典,state:{word:num,word,num}  num=num(word)/num(words)
Count_dic = {}  # 一个属性下的所有单词,为了求解emit
state_list = ['Ag', 'a', 'ad', 'an', 'Bg', 'b', 'c', 'Dg',
           'd', 'e', 'f', 'h', 'i', 'j', 'k', 'l',
           'Mg', 'm', 'Ng', 'n', 'nr', 'ns', 'nt', 'nx',
           'nz', 'o', 'p', 'q', 'Rg', 'r', 's','na',
           'Tg', 't','u', 'Vg', 'v', 'vd', 'vn','vvn',
           'w', 'Yg', 'y', 'z']
lineCount=-1#句子总数,为了求出开始概率
for state0 in state_list:
    transport_c[state0]={}
    for state1 in state_list:
        transport_c[state0][state1]=0.0
    emit_c[state0]={}
    start_c[state0]=0.0
vocabs=[]
classify=[] #存放每一行的每个状态的列表
class_count=collections.defaultdict(list) #存放每一行的每个状态对应的词的列表
for state in state_list:
    class_count[state]=0.0
with open('人民日报1998下半年/199808.txt','r',encoding='gb2312') as filess:
    for line in filess:  #一次处理全部的行??列表可能会影响效率??
        line=line.strip()
        if not line:continue
        lineCount += 1#应该在有内容的行处加 1
        words=line.split(" ")#分解为多个单词
        for word in words:
            #osition= word.index('/')  #如果是[中国人民/n]
            for i in range(len(word)):
                if word[i] =='/':
                    position = i
            if '[' in word and ']' in word:
                vocabs.append(word[1:position])
                vocabs.append(word[position+1:-1])
                break
            if  '[' in word:
                vocabs.append(word[1:position])
                classify.append(word[position+1:])
                break
            if ']' in  word:
                vocabs.append(word[:position])
                classify.append(word[position+1:-1])
                break
            vocabs.append(word[:position])
            classify.append(word[position+1:])

        if  len(vocabs)!=len(classify):
            print('词汇数量与类别数量不一致')
            break  #不一致退出程序
            # start_c = {}  # 开始概率,就是一个字典,state:chance=Word/lines
            # transport_c = {}  # 转移概率,是字典:字典,state:{state:num,state:num....}   num=num(state1)/num(statess)
            # emit_c = {}  # 发射概率,也是一个字典,state:{word:num,word,num}  num=num(word)/num(words)
        else:
            for n in range(0,len(vocabs)):
                class_count[classify[n]] += 1.0
                if vocabs[n] in emit_c[classify[n]]:
                    emit_c[classify[n]][vocabs[n]] += 1.0
                else:
                    emit_c[classify[n]][vocabs[n]] = 1.0
                if n==0:
                    start_c[classify[n]] += 1.0
                else:
                    transport_c[classify[n-1]][classify[n]]+=1.0
        vocabs = []
        classify = []
for state in state_list:
    start_c[state]=start_c[state]*1.0/lineCount
    for li in emit_c[state]:
        emit_c[state][li]=emit_c[state][li]/class_count[state]
    for li in transport_c[state]:
        transport_c[state][li]=transport_c[state][li]/class_count[state]
file0=open('start.txt','w',encoding='utf8')
file0.write(str(start_c))
file1=open('tran.txt','w',encoding='utf8')
file1.write(str(transport_c))
file2=open('emit.txt','w',encoding='utf8')
file2.write(str(emit_c))
file0.close()
file1.close()
file2.close()

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值