HMM在分词中的应用

     在前一篇博客中,讨论了隐马尔可夫模型的原理,三个问题,以及每个问题的解决方案和对应的算法推导。在本文中,就利用HMM中的预测问题来实现汉语分词。
     对于一段文字,对里边的词(可以是单个字,也可以是多个字)进行标注,标注的类型分为4中,分别为:Begin(开头)、End(结尾)、Middle(中间)、Single(单独)。因此我们需要有一个已经分好词的训练数据,训练数据据。点击获取数据集

计算 λ = ( A , B , π ) \lambda=(A,B,\pi) λ=(A,B,π)

     根据训练集计算处HMM需要的 λ = ( A , B , π ) \lambda=(A,B,\pi) λ=(A,B,π),计算过程如下:

def get_ABPi(train_data, sep="  "):
    """
    train_data: 已经分好词的数据集
    sep: 分词使用的分隔符
    """
    tokens = train_data.split(sep)
    # 初始状态转移值,0,1,2,3分别代表Begin,Middle,End,Single
    pi = np.zeros(4)
    # 状态转移矩阵
    A = np.zeros((4, 4))
    # 发射矩阵,取值65535主要是为了适应所有字符,也可以根据自己的数据集字符数量选定
    B = np.zeros((4, 65536))
    last_token = tokens[0]
    for token in tokens:
        token = token.strip()
        len_token = len(token)
        # 如果前一个词的长度为1,则说明前一个状态是Single,如果长度不是1,则说明前一个状态是End
        last_token_state = 3 if len(last_token) == 1 else 2
        if len_token == 0:
            continue
        if len_token == 1:
            # 当前词长度为1时,改变pi,A,B中Single的数量
            pi[3] += 1
            A[last_token_state][3] += 1
            B[3][ord(token)] += 1
        elif len_token == 2:
            # 当前词长度为2时,改变pi,A,B中Begin,End的数量
            pi[0] += 1
            pi[2] += 1
            A[0][2] += 1
            A[last_token_state][0] += 1
            B[0][ord(token[0])] += 1
            B[2][ord(token[1])] += 1
        else:
            # 当前词为其他长度时,改变pi,A,B中Begin,Middle,End的数量
            pi[0] += 1
            pi[2] += 1
            pi[1] += len_token - 2
            A[0][1] += 1
            A[1][1] += len_token - 3
            A[1][2] += 1
            A[last_token_state][0] += 1
            B[0][ord(token[0])] += 1
            B[2][ord(token[len_token-1])] += 1
            for i in range(1, len_token-1):
                B[1][ord(token[i])] += 1
        last_token = token
    # 这里将为0的数据重置为极小值,防止出现除以0的错误
    pi[pi == 0] = 1e-32
    A[A == 0] = 1e-32
    B[B == 0] = 1e-32
    # 这里用对数log主要是为了提高精度(后边的操作都是乘法,用对数就可以变成加法或减法)
    pi = np.log(pi) -  np.log(np.sum(pi))
    A = np.log(A) -  np.log(np.sum(A, 1).reshape((-1, 1)))
    B = np.log(B) - np.log(np.sum(B, 1).reshape((-1, 1)))
    return A, B, pi
维特比算法分词

     根据从训练集中获取到的 λ = ( A , B , π ) \lambda=(A,B,\pi) λ=(A,B,π),来预测分词,这里使用维特比算法进行计算。(如需看详细推导过程,请查看我的维特比算法推导
     t t t时刻与 t + 1 t+1 t+1时刻 δ \delta δ之间的关系如下:
δ t + 1 ( i ) = b i ( o t + 1 ) max ⁡ 1 ≤ j ≤ N δ t ( j ) a j i \delta_{t+1}(i)= b_i(o_{t+1})\max_{1\le j \le N} \delta_t(j) a_{ji} δt+1(i)=bi(ot+1)1jNmaxδt(j)aji
     特别的, t = 1 t=1 t=1时刻: δ 1 ( i ) = b i ( o 1 ) π ( i ) \delta_1(i)=b_i(o_1)\pi(i) δ1(i)=bi(o1)π(i)

def viterbi(pi, A, B, O):
    """
    pi: 初始状态转移概率
    A:  状态转移矩阵
    B:  发射矩阵
    """
    O = O.strip()
    len_O = len(O)
    len_pi = len(pi)
    if len_O == 0:
        return None
    # 保存当前时刻状态的delta达到最大概率时,前一个时刻的状态
    states = np.full((len_O, len_pi), fill_value=0.00)
    # 保存当前时刻状态的delta达到最大概率时,delta的值
    deltas = np.full((len_O, len_pi), fill_value=0.00)
    # 计算t=1时的delta值
    for j in range(len_pi):
        deltas[0][j] = pi[j] + B[j][ord(O[0])]
    # 计算t>1时的delta值
    for k in range(1, len_O):
        # 计算每一个可能状态
        for i in range(len_pi):
            deltas[k][i] = -1e20
            # 计算当前时刻delta的最大值
            for j in range(len_pi):
                current = deltas[k-1][j] + A[j][i]
                if current > deltas[k][i]:
                    # 记录当前最大的delta值
                    deltas[k][i] = current
                    # 记录当前delta达到最大时,前一刻的状态
                    states[k][i] = j
            deltas[k][i] += B[i][ord(O[k])]
    # 求出最后一个时刻的最大delta值所在的状态
    max1 = deltas[len_O-1][0]
    best_state = np.zeros(len_O)
    for i in range(len_pi):
        if deltas[len_O-1][i] > max1:
            max1 = deltas[len_O-1][i]
            best_state[len_O-1] = i
    # 根据最优状态通过state往回寻找,确定最佳路径
    for k in range(len_O-2, -1, -1):
        best_state[k] = states[k+1][int(best_state[k+1])]
    return best_state

     通过维特比算法就可以得出当前文本的分词结果,根据 best_state将结果呈现出来,部分结果如下:

     受限于训练集,所以效果不是太理想,如果训练集足够大,相信应该能取得不错的结果。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值