动态规划下的维特比算法在词性预测上的应用

2020-06-12  本文已影响0人  雍珑庚

上一篇提到了分词的一些东西, 这片我来写写关于词性的标注问题.

对于文章的的每个词语的词性研究有很重要的意义, 通过词与词之间的关系, 可以发掘很多有用的信息.

什么是词性标注

词性标注(Part-of-Speech tagging 或POS tagging),又称词类标注或者简称标注,是指为分词结果中的每个单词标注一个正确的词性的程序,也即确定每个词是名词、动词、形容词或其他词性的过程。在汉语中,词性标注比较简单,因为汉语词汇词性多变的情况比较少见,大多词语只有一个词性,或者出现频次最高的词性远远高于第二位的词性。据说,只需选取最高频词性,即可实现80%准确率的中文词性标注程序。

比如以这句话S={我,经常,在,微信,公众号,码龙社,看,文章}通过已知词语的词性,当给出一个新的句子,求解最后一个单词的词性是什么?

简单推理

我们可以简单的使用一个公式推理一下

\begin{aligned}P(Z|S) &=P(S|Z)·P(Z) \\ &=P(w_1w_2...w_N|Z_1Z_2...Z_N)P(Z_1Z_2...Z_n)\\ &=\prod_{i=1}^{n}·P(w_i|Z_i)·P(Z_1)·P(Z_2|Z_1)·P(Z_3|Z_2)...P(Z_n|Z_{n-1}) \end{aligned}

其中Z代表句子, Z代表词语词性, 基于朴素贝叶斯理论, 可以进行上面的推导, 但是这只是理论为, 我们的目的是要应用于生活实践, 所以很有必要对这个公式进行化简, 使得能够在计算机上运算.

推理演绎

image-20200612093538868.png

为了, 对上面的公式进行化简, 我们还的根据现实, 对条件概率的=进行重新整理, 比如我们预测码龙社的词性,

\begin{aligned} \hat{Z} &=argmaxP(Z|S) \\ &=argmax\prod_{i=1}^nP(W_i|Z_i)·P(Z_1)·\prod_{t=2}^nP(Z_t|Z_{t-1}) \\ &=argmax\log(\prod_{i=1}^nP(W_i|Z_i)·P(Z_1)·\prod_{t=2}^nP(Z_t|Z_{t-1})) \\ &=argmax \underbrace{ \prod_{i=1}^n\log P(W_i|Z_i)}_A+\underbrace {\log P(Z_1)}_B+\underbrace{\log \prod_{t=2}^nP(Z_t|Z_{t-1})}_{\pi} \end{aligned}

经过一系列化简, 我们终于把乘法转换成了加法, 然后依据下面的推理公式, 对比上图, 我我们很容易看出A代表每一列的词语所对应的词性概率, B代表一句话第一个词语的在每种词性下的概率, 而B则代表了, 这条最优路径中词性的转换概率.

为此, 我们就可以看出, 其实也是在求一个最优路径问题, 动态规划的范畴. 不过这个怎么计算呢, 虽然经过多次转换, 把一个现实复杂的问题,转换成数学问题, 最终转换成计算机问题, 不过好像还是不好处理. 不过这里, 已经有前人给我们栽好了树苗, 我们只需乘凉即可. 这颗树苗就是什么的维特比算法, 它能解决多步骤每步多选择模型的最优选择问题.

维特比算法简介

viterbi算法其实就是多步骤每步多选择模型的最优选择问题,其在每一步的所有选择都保存了前续所有步骤到当前步骤当前选择的最小总代价(或者最大价值)以及当前代价的情况下前继步骤的选择。依次计算完所有步骤后,通过回溯的方法找到最优选择路径。符合这个模型的都可以用viterbi算法解决。是不是很神奇, 不光能解决词性标注, 以后碰到类似的问题, 都可以用这个算法. 瞬间有没有感觉, 自己的技能有上升一个等级,哈哈.

为此,我们先了解一下维特比算法,维特比算法 (Viterbi algorithm) 是机器学习中应用非常广泛的动态规划算法,在求解隐马尔科夫、条件随机场的预测以及seq2seq模型概率计算等问题中均用到了该算法。实际上,维特比算法不仅是很多自然语言处理的解码算法,也是现代数字通信中使用最频繁的算法。

算法的实现

回到词性标注, 我们可以看出

对于每一个词语来说, 第一个词语存在:P(词性)+P(词语)|当前词性

对于非第一个词语来水, P(当前词性|上一个词性)+P(单词i|当前词性)

那么相当于与从最后一列找最大的,然后在到前一列找最大的, 为了代码简单, 这里初始化部分数据

def viterbi(obs_len, states_len, init_p, trans_p, emit_p):
    max_p = np.zeros((states_len, obs_len))  
    path = np.zeros((states_len, obs_len))  
    for i in range(states_len):
        max_p[i][0] = init_p[i] * emit_p[i][0]
        path[i][0] = i

   
    for obs_index in range(1, obs_len):
        new_path = np.zeros((states_len, obs_len))
        for hid_index in range(states_len):
            max_prob = -1
            pre_state_index = 0
            for i in range(states_len):
                each_prob = max_p[i][obs_index - 1] * trans_p[i][hid_index] * emit_p[hid_index][obs_index]

                if each_prob > max_prob:
                    max_prob = each_prob
                    pre_state_index = i

            max_p[hid_index][obs_index] = max_prob
            for m in range(obs_index):
                new_path[hid_index][m] = path[pre_state_index][m]
            new_path[hid_index][obs_index] = hid_index
        path = new_path

    max_prob = -1
    last_state_index = 0
    for hid_index in range(states_len):
        if max_p[hid_index][obs_len - 1] > max_prob:
            max_prob = max_p[hid_index][obs_len - 1]
            last_state_index = hid_index
    return path[last_state_index]

对于我们的测试用例S={我,经常,在,微信,公众号,码龙社,看,文章}, 会得到下面这样的结果

我/AT 经常/NN 在/BEZ 微信/IN 公众号/AT 码龙社/NN

完整代码你可以在这里查看到https://github.com/DragonYong/Jun--Dragon/blob/master/viterbi_word_part.ipynb

让我们一起分享,共同成长,分享使我们在编程路上并不孤独。快来扫描二维码,与博主一起快乐学习吧!

上一篇下一篇

猜你喜欢

热点阅读