• 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("
    ")
    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)
    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
    
     

    结果不是很好,说明要加更多的语料

     
  • 相关阅读:
    shell数组
    Apache HTTP Server 与 Tomcat 的三种连接方式介绍
    实现Java动态类载入机制
    Tomcat 阀
    MYSQL 常用命令
    MYSQL字符数字转换
    主题:MySQL数据库操作实战
    日本手机三大代理商的UA
    Java解析XML文档——dom解析xml (转载)
    MS sql server和mysql中update多条数据的例子
  • 原文地址:https://www.cnblogs.com/Josie-chen/p/9143924.html
Copyright © 2020-2023  润新知