1.一元标注器(Unigram Tagging)
一元标注器利用一种简单的统计算法,对每个标注符分配最有可能的标记。例如:它将分配标记JJ给词frequent,因为frequent用作形容词更常见。一元标注器的行为与查找标注器相似,建立一元标注器的技术,称为训练。在下面的代码例子中,“训练”一个一元标注器,用它来标注一个句子,然后进行评估。
1 >>> from nltk.corpus import brown 2 >>> brown_tagged_sents=brown.tagged_sents(categories='news') //‘news’类别下,已经被标记的句子 3 >>> brown_sents=brown.sents(categories='news') //‘news’类别下,未被标记的句子 4 >>> import nltk 5 >>> unigram_tagger=nltk.UnigramTagger(brown_tagged_sents) //用已经被标记的句子训练一元标注器 6 >>> unigram_tagger.tag(brown_sents[2007]) //用生成的一元标注器去标记新的句子 7 [('Various', 'JJ'), ('of', 'IN'), ('the', 'AT'), ('apartments', 'NNS'), ('are', 'BER'), ('of', 'IN'), ('the', 'AT'), ('terrace', 'NN'), ('ty 8 pe', 'NN'), (',', ','), ('being', 'BEG'), ('on', 'IN'), ('the', 'AT'), ('ground', 'NN'), ('floor', 'NN'), ('so', 'QL'), ('that', 'CS'), ('en 9 trance', 'NN'), ('is', 'BEZ'), ('direct', 'JJ'), ('.', '.')] 10 >>> unigram_tagger.evaluate(brown_tagged_sents) //评估标注器的性能 11 0.9349006503968017 12 >>>
上面代码中,使用unigram_tagger.tag(brown_sents[2007]) 来标记的是brwon_sents的第2008个句子,因为brown_sents中以句子为单位,每个句子以词list的形式存在,所以必须制定目标标记的句子,而不能一次性标记多个句子。
>>> brown_sents [['The', 'Fulton', 'County', 'Grand', 'Jury', 'said', 'Friday', 'an', 'investigation', 'of', "Atlanta's", 'recent', 'primary', 'election', ' produced', '``', 'no', 'evidence', "''", 'that', 'any', 'irregularities', 'took', 'place', '.'], ['The', 'jury', 'further', 'said', 'in', 't erm-end', 'presentments', 'that', 'the', 'City', 'Executive', 'Committee', ',', 'which', 'had', 'over-all', 'charge', 'of', 'the', 'election ', ',', '``', 'deserves', 'the', 'praise', 'and', 'thanks', 'of', 'the', 'City', 'of', 'Atlanta', "''", 'for', 'the', 'manner', 'in', 'which ', 'the', 'election', 'was', 'conducted', '.'], ...] >>>
通过在初始化标注器时指定已标注的句子数据作为参数来训练一元标注器。训练过程中涉及检查每个词的标记,将所有词的最可能标记存储在一个字典里面,这个字典存储在标注器内部。
2.分离训练和测试数据
在一些数据上训练标注器,必须注意不要在相同的数据上测试。如果一个标注器只是单纯地去记忆它的训练数据,而不试图建立一般的模型,测试结果会更好,但在标注新的文本时不起作用。相反,我们应该分割数据,90%为训练数据,其余10%为测试数据。
1 >>> size=int(len(brown_tagged_sents)*0.9) 2 >>> size 3 4160 4 >>> train_sents=brown_tagged_sents[:size] 5 >>> test_sents=brown_tagged_sents[size:] 6 >>> unigram_tagger=nltk.UnigramTagger(train_sents) 7 >>> unigram_tagger.evaluate(test_sents) 8 0.8124190172430977 9 >>>
显然得分更糟糕了,但是对这种标注器是无用的情况有了更好的了解。
3.一般的N-gram的标注
当基于unigrams处理语言 处理任务时,可使用上下文中的项目。标注时,只考虑当前的标识符,而不考虑其他上下文。给定一个模型,最好是为每个词标注其先验的最可能的标记。这意味着将使用相同的标记标注词。n-gram标注器是ungram标注器的一般化,它的上下文是当前词和它前面n-1个标识符的词性标记。
1-gram标注器(unigram tagger)是一元标注器的另一个名称:即用于标注上下文是标识符本身的标识符。2-gram标注器也称为二元标注器(bigram taggers), 3-gram标注器也称为三元标注器(trigram taggers).
NgramTagger 类使用一个已标注的训练语料库来确定每个上下文中哪个词性标记最有可能。下面的例子中,我们看到n-gram标注器的一个特殊情况,即bigram标注器,首先训练它,然后用它来标注未标注的句子。
1 >>> bigram_tagger=nltk.BigramTagger(train_sents) 2 >>> bigram_tagger.tag(brown_sents[2007]) 3 [('Various', 'JJ'), ('of', 'IN'), ('the', 'AT'), ('apartments', 'NNS'), ('are', 'BER'), ('of', 'IN'), ('the', 'AT'), ('terrace', 'NN'), ('ty 4 pe', 'NN'), (',', ','), ('being', 'BEG'), ('on', 'IN'), ('the', 'AT'), ('ground', 'NN'), ('floor', 'NN'), ('so', 'CS'), ('that', 'CS'), ('en 5 trance', 'NN'), ('is', 'BEZ'), ('direct', 'JJ'), ('.', '.')] 6 >>> unseen_sent=brown_sents[4203] 7 >>> bigram_tagger.tag(unseen_sent) 8 [('The', 'AT'), ('population', 'NN'), ('of', 'IN'), ('the', 'AT'), ('Congo', 'NP'), ('is', 'BEZ'), ('13.5', None), ('million', None), (',', 9 None), ('divided', None), ('into', None), ('at', None), ('least', None), ('seven', None), ('major', None), ('``', None), ('culture', None), 10 ('clusters', None), ("''", None), ('and', None), ('innumerable', None), ('tribes', None), ('speaking', None), ('400', None), ('separate', No 11 ne), ('dialects', None), ('.', None)] 12 >>> bigram_tagger.evaluate(test_sents) 13 0.10276088906608193 14 >>>
注意,bigram标注器能够标注训练中它看到过的句子中的所有词,但对一个没见过的句子却不行。只要遇到一个新词就无法给它分配标记。它不能标注下面的词,即使在训练过程中看到过的,因为在训练过程中从来没有见过他前面有None标记的词。因此,标注器也无法标注句子的其余部分。它的整体准确度得分非常低,从上面运行结果来看只有0.1左右。
当n 越大时,上下文的特异性就会增加,要标注的数据中包含训练数据中不存在的上下文的几率也增大。这被称为数据稀疏问题,在NLP中是相当普遍的。因此,研究结果的精度和覆盖范围之间需要有一个权衡。
N-gram标注器不应该考虑跨越句子边界的上下文,因此,nltk的标注器被涉及用于句子链表,一个句子是一个词链表。在一个句子的开始,tn-1和前面的标记被设置为None。
4.组合标注器
解决精度和覆盖范围之间权衡的一个办法是尽可能地使用更精确的算法,但却在很多时候却逊于覆盖范围更广的算法。例如:可以按如下方式组合bigram标注器,unigram标注器和一个默认标注器。
- 尝试使用bigram标注器标注标识符
- 如果bigram标注器无法找到标记,尝试unigram标注器。
- 如果unigram标注器也无法找到标记,使用默认标注器。
大多数nltk标注器允许指定回退标注器。回退标注器自身可能也有回退标注器。
1 >>> t0=nltk.DefaultTagger('NN') 2 >>> t1=nltk.UnigramTagger(train_sents, backoff=t0) 3 >>> t2=nltk.BigramTagger(train_sents, backoff=t1) 4 >>> t2.evaluate(test_sents) 5 0.8466061995415131 6 >>>
注意:在标注器初始化时要指定回退标注器,从而训练时才能利用回退标注器。于是,如果在上下文中bigram标注器将分配与它的unigram回退标注器一样的标记,那么bigram标注器丢弃训练实例。这样可以保持尽可能小的bigram标注器模型。可以进一步确定的是标注器需要保存上下文多个实例。例如:nltk.BigramTagger(sents, cutoff=2, backoff=t1)将丢弃那些只出现一次或两次的上下文。
5.标注生词
标注生词的方法是回退到正则表达式标注器或默认标注器。这些都无法利用上下文。因此,如果标注器遇到词blog,但训练过程中没有看到过,它会分配相同的标记,不论这个词出现的上下文是the blog 还是to blog。
基于上下文标注生词的方法是限制标注器的词汇表为最频繁的n个词。训练时,unigram标注器可能会将UNK标注名词。然而,n-gram标注器会检测其他标记的上下文。例如:如果前面的词是to(标注未TO),那么UNK可能会被标注为一个动词。
6.存储标注器
原因:在大语料库中训练标注器可能需要花费大量时间,而且没有必要重复训练标注器。
解决方案:将一个训练好的标注器保存到文件中供以后重复使用。
实例:将标注器t2保存到文件t2.pkl。
7.性能限制
调查标注器性能的方法:
- 根据经验
- 研究它的错误
训练数据中的歧义可产生标注器性能的上限。有时更多的上下文能解决这些歧义。然而,在其他情况下,只有参考语法或现实世界的知识才能解决歧义。尽管存在缺陷,但词性标注在利用统计方法进行自然语言处理的发展过程中起到了核心作用。