使用HMM进行词性标注
这里我们用NLTK自带的Brown词库进行学习。
假设我们的单词集: words = w1 ... wN
Tag集: tags = t1 ... tN
P(tags | words) 正比于 P(ti | t{i-1}) * P(wi | ti) 类似贝叶斯公式(利用中间量)
为了找一个句子的tag,
我们其实就是找的最好的一套tags,让他最能够符合给定的单词(words)。
首先,
导入需要的库
import nltk import sys from nltk.corpus import brown
预处理词库
这里需要做的预处理是:给词们加上开始和结束符号。
Brown里面的句子都是自己标注好了的,长这个样子:(I , NOUN), (LOVE, VERB), (YOU, NOUN)
那么,我们的开始符号也得跟他的格式符合,
我们用:
(START, START) (END, END)
来代表
brown_tags_words = [ ] for sent in brown.tagged_sents(): # 先加开头 brown_tags_words.append( ("START", "START") ) # 为了省事儿,我们把tag都省略成前两个字母 brown_tags_words.extend([ (tag[:2], word) for (word, tag) in sent ]) # 加个结尾 brown_tags_words.append( ("END", "END") )
词统计
这个时候,我们要把我们所有的词库中拥有的单词与tag之间的关系,做个简单粗暴的统计。
也就是我们之前说过的:
P(wi | ti) = count(wi, ti) / count(ti)
你可以自己一个个的loop全部的corpus,
当然,这里NLTK给了我们做统计的工具,(这属于没有什么必要的hack,装起逼来也不X,所以,大家想自己实现,可以去实现,不想的话,就用这里提供的方法)
# conditional frequency distribution cfd_tagwords = nltk.ConditionalFreqDist(brown_tags_words) # conditional probability distribution cpd_tagwords = nltk.ConditionalProbDist(cfd_tagwords, nltk.MLEProbDist)
好,现在我们看看平面统计下来的结果:
print("The probability of an adjective (JJ) being 'new' is", cpd_tagwords["JJ"].prob("new")) print("The probability of a verb (VB) being 'duck' is", cpd_tagwords["VB"].prob("duck")) The probability of an adjective (JJ) being 'new' is 0.01472344917632025 The probability of a verb (VB) being 'duck' is 6.042713350943527e-05
好,接下来,按照课上讲的,还有第二个公式需要计算:
P(ti | t{i-1}) = count(t{i-1}, ti) / count(t{i-1})
这个公式跟words没有什么卵关系。它是属于隐层的马科夫链。
所以 我们先取出所有的tag来。
brown_tags = [tag for (tag, word) in brown_tags_words ]
# count(t{i-1} ti) # bigram的意思是 前后两个一组,联在一起 cfd_tags= nltk.ConditionalFreqDist(nltk.bigrams(brown_tags)) # P(ti | t{i-1}) cpd_tags = nltk.ConditionalProbDist(cfd_tags, nltk.MLEProbDist)
好的,可以看看效果了:
print("If we have just seen 'DT', the probability of 'NN' is", cpd_tags["DT"].prob("NN")) print( "If we have just seen 'VB', the probability of 'JJ' is", cpd_tags["VB"].prob("DT")) print( "If we have just seen 'VB', the probability of 'NN' is", cpd_tags["VB"].prob("NN"))
一些有趣的结果:
那么,比如, 一句话,"I want to race", 一套tag,"PP VB TO VB" 这是问题1:
他们之间的匹配度有多高呢?
其实就是:链式法则的路径
P(START) * P(PP|START) * P(I | PP) *
P(VB | PP) * P(want | VB) *
P(TO | VB) * P(to | TO) *
P(VB | TO) * P(race | VB) *
P(END | VB)
prob_tagsequence = cpd_tags["START"].prob("PP") * cpd_tagwords["PP"].prob("I") * cpd_tags["PP"].prob("VB") * cpd_tagwords["VB"].prob("want") * cpd_tags["VB"].prob("TO") * cpd_tagwords["TO"].prob("to") * cpd_tags["TO"].prob("VB") * cpd_tagwords["VB"].prob("race") * cpd_tags["VB"].prob("END") print( "The probability of the tag sequence 'START PP VB TO VB END' for 'I want to race' is:", prob_tagsequence)
Viterbi 的实现
如果我们手上有一句话,怎么知道最符合的tag是哪组呢?
首先,我们拿出所有独特的tags(也就是tags的全集)
distinct_tags = set(brown_tags)
然后 随手找句话
sentence = ["I", "want", "to", "race" ] sentlen = len(sentence)
接下来,开始维特比:
从1循环到句子的总长N,记为i
每次都找出以tag X为最终节点,长度为i的tag链
viterbi = [ ]
同时,还需要一个回溯器:
从1循环到句子的总长N,记为i
把所有tag X 前一个Tag记下来。
backpointer = [ ]
first_viterbi = { } first_backpointer = { } for tag in distinct_tags: # don't record anything for the START tag if tag == "START": continue first_viterbi[ tag ] = cpd_tags["START"].prob(tag) * cpd_tagwords[tag].prob( sentence[0] ) first_backpointer[ tag ] = "START" print(first_viterbi) print(first_backpointer)
以上,是所有的第一个viterbi 和第一个回溯点。
接下来,把楼上这些,存到Vitterbi和Backpointer两个变量里去
viterbi.append(first_viterbi)
backpointer.append(first_backpointer)
我们可以先看一眼,目前最好的tag是啥:
currbest = max(first_viterbi.keys(), key = lambda tag: first_viterbi[ tag ]) print( "Word", "'" + sentence[0] + "'", "current best two-tag sequence:", first_backpointer[ currbest], currbest)
好的
一些都清晰了
我们开始loop:
for wordindex in range(1, len(sentence)): this_viterbi = { } this_backpointer = { } prev_viterbi = viterbi[-1] for tag in distinct_tags: # START没有卵用的,我们要忽略 if tag == "START": continue # 如果现在这个tag是X,现在的单词是w, # 我们想找前一个tag Y,并且让最好的tag sequence以Y X结尾。 # 也就是说 # Y要能最大化: # prev_viterbi[ Y ] * P(X | Y) * P( w | X) best_previous = max(prev_viterbi.keys(), key = lambda prevtag: prev_viterbi[ prevtag ] * cpd_tags[prevtag].prob(tag) * cpd_tagwords[tag].prob(sentence[wordindex])) this_viterbi[ tag ] = prev_viterbi[ best_previous] * cpd_tags[ best_previous ].prob(tag) * cpd_tagwords[ tag].prob(sentence[wordindex]) this_backpointer[ tag ] = best_previous # 每次找完Y 我们把目前最好的 存一下 currbest = max(this_viterbi.keys(), key = lambda tag: this_viterbi[ tag ]) print( "Word", "'" + sentence[ wordindex] + "'", "current best two-tag sequence:", this_backpointer[ currbest], currbest) # 完结 # 全部存下来 viterbi.append(this_viterbi) backpointer.append(this_backpointer)
找END,结束:
# 找所有以END结尾的tag sequence prev_viterbi = viterbi[-1] best_previous = max(prev_viterbi.keys(), key = lambda prevtag: prev_viterbi[ prevtag ] * cpd_tags[prevtag].prob("END")) prob_tagsequence = prev_viterbi[ best_previous ] * cpd_tags[ best_previous].prob("END") # 我们这会儿是倒着存的。。。。因为。。好的在后面 best_tagsequence = [ "END", best_previous ] # 同理 这里也有倒过来 backpointer.reverse()
最终:
回溯所有的回溯点
此时,最好的tag就是backpointer里面的current best
current_best_tag = best_previous for bp in backpointer: best_tagsequence.append(bp[current_best_tag]) current_best_tag = bp[current_best_tag]
显示结果:
best_tagsequence.reverse() print( "The sentence was:", end = " ") for w in sentence: print( w, end = " ") print(" ") print( "The best tag sequence is:", end = " ") for t in best_tagsequence: print (t, end = " ") print(" ") print( "The probability of the best tag sequence is:", prob_tagsequence)
结果不是很好,说明要加更多的语料