02-NLP-05-使用HMM進行詞性標注


使用HMM進行詞性標注

這里我們用NLTK自帶的Brown詞庫進行學習。

假設我們的單詞集: words = w1 ... wN

Tag集: tags = t1 ... tN

P(tags | words) 正比於 P(ti | t{i-1}) * P(wi | ti)   類似貝葉斯公式(利用中間量)

為了找一個句子的tag,

我們其實就是找的最好的一套tags,讓他最能夠符合給定的單詞(words)。

首先,

導入需要的庫

In [1]:
import nltk
import sys
from nltk.corpus import brown

預處理詞庫

這里需要做的預處理是:給詞們加上開始和結束符號。

Brown里面的句子都是自己標注好了的,長這個樣子:(I , NOUN), (LOVE, VERB), (YOU, NOUN)

那么,我們的開始符號也得跟他的格式符合,

我們用:

(START, START) (END, END)

來代表

In [2]:
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,所以,大家想自己實現,可以去實現,不想的話,就用這里提供的方法)

In [3]:
# conditional frequency distribution
cfd_tagwords = nltk.ConditionalFreqDist(brown_tags_words)
# conditional probability distribution
cpd_tagwords = nltk.ConditionalProbDist(cfd_tagwords, nltk.MLEProbDist)

好,現在我們看看平面統計下來的結果:

In [4]:
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來。

In [5]:
brown_tags = [tag for (tag, word) in brown_tags_words ]
In [6]:
# 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)

好的,可以看看效果了:

In [7]:
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"))
If we have just seen 'DT', the probability of 'NN' is 0.5057722522030194
If we have just seen 'VB', the probability of 'JJ' is 0.016885067592065053
If we have just seen 'VB', the probability of 'NN' is 0.10970977711020183

一些有趣的結果:

那么,比如, 一句話,"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)
In [8]:
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)
 
The probability of the tag sequence 'START PP VB TO VB END' for 'I want to race' is: 1.0817766461150474e-14

Viterbi 的實現

如果我們手上有一句話,怎么知道最符合的tag是哪組呢?

首先,我們拿出所有獨特的tags(也就是tags的全集)

In [9]:
distinct_tags = set(brown_tags)
 

然后 隨手找句話

In [10]:
sentence = ["I", "want", "to", "race" ]
sentlen = len(sentence)
 

接下來,開始維特比:

從1循環到句子的總長N,記為i

每次都找出以tag X為最終節點,長度為i的tag鏈

In [11]:
viterbi = [ ]

同時,還需要一個回溯器:

從1循環到句子的總長N,記為i

把所有tag X 前一個Tag記下來。

In [12]:
backpointer = [ ]
In [13]:
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)
{'(-': 0.0, 'UH': 0.0, 'EX': 0.0, '--': 0.0, 'AP': 0.0, 'NI': 3.3324520848931064e-07, "''": 0.0, 'MD': 0.0, '``': 0.0, 'IN': 0.0, ')': 0.0, 'CC': 0.0, 'WQ': 0.0, 'DT': 0.0, 'RB': 0.0, 'DO': 0.0, 'NP': 1.7319067623793952e-06, 'RP': 0.0, '*-': 0.0, 'CS': 0.0, 'CD': 0.0, 'BE': 0.0, '*': 0.0, 'FW': 0.0, 'END': 0.0, 'RN': 0.0, 'AT': 0.0, 'WD': 0.0, 'PN': 0.0, ',-': 0.0, ',': 0.0, 'NR': 0.0, "'": 0.0, ':': 0.0, 'HV': 0.0, ':-': 0.0, 'TO': 0.0, ')-': 0.0, 'WR': 0.0, 'NN': 1.0580313619573935e-06, '.': 0.0, 'OD': 0.0, 'WP': 0.0, '(': 0.0, 'PP': 0.014930900689060006, 'QL': 0.0, 'AB': 0.0, 'JJ': 0.0, '.-': 0.0, 'VB': 0.0}
{'(-': 'START', 'UH': 'START', 'EX': 'START', '--': 'START', 'AP': 'START', 'NI': 'START', "''": 'START', 'MD': 'START', '``': 'START', 'IN': 'START', ')': 'START', 'CC': 'START', 'WQ': 'START', 'DT': 'START', 'RB': 'START', 'DO': 'START', 'NP': 'START', 'RP': 'START', '*-': 'START', 'CS': 'START', 'CD': 'START', 'BE': 'START', '*': 'START', 'FW': 'START', 'END': 'START', 'RN': 'START', 'AT': 'START', 'WD': 'START', 'PN': 'START', ',-': 'START', ',': 'START', 'NR': 'START', "'": 'START', ':': 'START', 'HV': 'START', ':-': 'START', 'TO': 'START', ')-': 'START', 'WR': 'START', 'NN': 'START', '.': 'START', 'OD': 'START', 'WP': 'START', '(': 'START', 'PP': 'START', 'QL': 'START', 'AB': 'START', 'JJ': 'START', '.-': 'START', 'VB': 'START'}

以上,是所有的第一個viterbi 和第一個回溯點。

接下來,把樓上這些,存到Vitterbi和Backpointer兩個變量里去

In [14]:
viterbi.append(first_viterbi)
backpointer.append(first_backpointer)

我們可以先看一眼,目前最好的tag是啥:

In [15]:
currbest = max(first_viterbi.keys(), key = lambda tag: first_viterbi[ tag ])
print( "Word", "'" + sentence[0] + "'", "current best two-tag sequence:", first_backpointer[ currbest], currbest)
Word 'I' current best two-tag sequence: START PP

好的

一些都清晰了

我們開始loop:

In [16]:
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)
Word 'want' current best two-tag sequence: PP VB
Word 'to' current best two-tag sequence: VB TO
Word 'race' current best two-tag sequence: IN NN

找END,結束:

In [17]:
# 找所有以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

In [18]:
current_best_tag = best_previous
for bp in backpointer:
    best_tagsequence.append(bp[current_best_tag])
    current_best_tag = bp[current_best_tag]

顯示結果:

In [19]:
best_tagsequence.reverse()
print( "The sentence was:", end = " ")
for w in sentence: print( w, end = " ")
print("\n")
print( "The best tag sequence is:", end = " ")
for t in best_tagsequence: print (t, end = " ")
print("\n")
print( "The probability of the best tag sequence is:", prob_tagsequence)
The sentence was: I want to race 

The best tag sequence is: START PP VB IN NN END 

The probability of the best tag sequence is: 5.71772824864617e-14
 

結果不是很好,說明要加更多的語料

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM