Python NLTK学习6(创建词性标注器)

本系列博客为学习《用Python进行自然语言处理》一书的学习笔记。

默认标注器

默认标注器为每个单词分配同样的标记,尽管很平庸,但它也是有作用的,先看示例:

import nltk
raw = "You are a good man, but i don't love you!"
tokens = nltk.word_tokenize(raw)

default_tagger = nltk.DefaultTagger('NN')
tagged_words = default_tagger.tag(tokens)
print(tagged_words)

结果为:

[('You', 'NN'), ('are', 'NN'), ('a', 'NN'), ('good', 'NN'), ('man', 'NN'), (',', 'NN'), ('but', 'NN'), ('i', 'NN'), ('do', 'NN'), ("n't", 'NN'), ('love', 'NN'), ('you', 'NN'), ('!', 'NN')]

DefaultTagger的构造函数接受一个标记字符串作为参数,生成一个默认标注器对象,从结果可以看到默认标注器把所有的词都标记为NN。

DefaultTagger::tag(tokens):对指定的单词列表进行标记,返回被标记后的单词列表。

DefaultTagger::evaluete(tagged_sents):使用已经被标记的句子评价标注器,返回正确率0~1.0。

from nltk.corpus import brown
tagged_sents = brown.tagged_sents(categories='news')
print(default_tagger.evaluate(tagged_sents))

结果为:

0.13089484257215028

我们可以看到我们自己创建的默认标注器正确率仅为0.13。

查询标注器

默认标注器对所有单词使用同一个标记,准确率太低,我们可以考虑指定不同的单词为不同的标记,先看一个示例:

# 对新闻文本进行频率分布,找出新闻文本最常用的100个单词
fd = nltk.FreqDist(brown.words(categories='news'))
most_common_pairs = fd.most_common(100)
most_common_words = [i[0] for i in most_common_pairs]

# 对标记后的新闻文本进行条件频率分布,这样我们就可以找到指定单词最多的标记是哪一个
cfd = nltk.ConditionalFreqDist(brown.tagged_words(categories='news'))

# 找出最常用的100个单词的最多标记
likely_tags = dict((word, cfd[word].max()) for word in most_common_words)

# 使用(单词-标记)字典作为模型,生成查询标注器
baseline_tagger = nltk.UnigramTagger(model=likely_tags)
tagged_sents = brown.tagged_sents(categories='news')
print(baseline_tagger.evaluate(tagged_sents))

结果为:

0.45578495136941344

这次我们创建的标注器的正确率为0.45,比我们的默认标注器要好很多。UnigramTagger类的构造函数接受一个(单词-标记)字典作为模型,可以直接生成一个标注器。事实上UnigramTagger和DefaultTagger类都继承自TaggerI,TaggerI具有tag和evaluete方法,所以UnigramTagger也具有tag和evaluete方法。

因为我们只指定了100个单词的标记,我们来看看我们创建的标注器对未指定的单词是如何标记的。

raw = "You are a good man, but i don't love you!"
tokens = nltk.word_tokenize(raw)
print(baseline_tagger.tag(tokens))

结果为:

[('You', None), ('are', 'BER'), ('a', 'AT'), ('good', None), ('man', None), (',', ','), ('but', 'CC'), ('i', None), ('do', None), ("n't", None), ('love', None), ('you', None), ('!', None)]

许多词被分配为None标签,这是因为它们不包含在100单词中。针对这种情况,我们可以给它们一个默认标记。换句话说我们要先使用查找表,如果它不能指定一个标记就使用默认标注器,这个过程叫做回退。

# 使用默认标注器作为回退
baseline_tagger2 = nltk.UnigramTagger(model=likely_tags, backoff=nltk.DefaultTagger('NN'))
tagged_sents = brown.tagged_sents(categories='news')
print(baseline_tagger2.evaluate(tagged_sents))

结果为:

0.5817769556656125

我们可以看到正确率提升了

如果我们增大单词数量,则正确率还会提升。

# 对新闻文本进行频率分布,找出新闻文本最常用的500个单词
fd = nltk.FreqDist(brown.words(categories='news'))
most_common_pairs = fd.most_common(500)
most_common_words = [i[0] for i in most_common_pairs]

# 对标记后的新闻文本进行条件频率分布,这样我们就可以找到指定单词最多的标记是哪一个
cfd = nltk.ConditionalFreqDist(brown.tagged_words(categories='news'))

# 找出最常用的500个单词的最多标记
likely_tags = dict((word, cfd[word].max()) for word in most_common_words)

# 使用(单词-标记)字典作为模型,生成查询标注器
baseline_tagger = nltk.UnigramTagger(model=likely_tags, backoff=nltk.DefaultTagger('NN'))
tagged_sents = brown.tagged_sents(categories='news')
print(baseline_tagger.evaluate(tagged_sents))

结果为:

0.6789983491457326

一元标注器

一元标注器基于一个简单的统计算法:对每个单词分配这个单词最有可能的标记。一元标注器的行为就像一个查询标注器,但是它不需要我们提供model,我们只需要提供训练样本也就是被标记的句子列表,标注器会使用这些样本进行训练,将所有词的最可能标记存储在一个字典里,例子如下:

import nltk
from nltk.corpus import brown
tagged_sents = brown.tagged_sents(categories='news')

# 生成一元标注器
unigram_tagger = nltk.UnigramTagger(train=tagged_sents)
print(unigram_tagger.evaluate(tagged_sents))

结果为:

0.9349006503968017

这个结果比我们前面的查询标注器要好很多,并且一元标注器不需要我们自己去统计每个单词最有可能的标记。

然而使用同一个数据集作为训练集和测试集不是一个好的做法,如果我们训练出来的标注器过拟合我们也不能知道,接下来我们要分离训练集和测试集,我们把数据集的90%作为训练集,10%作为测试集。

tagged_sents = brown.tagged_sents(categories='news')
size = int(len(tagged_sents) * 0.9)
train_sets = tagged_sents[:size]
test_sets = tagged_sents[size:]

# 生成一元标注器
unigram_tagger = nltk.UnigramTagger(train=train_sets)
print(unigram_tagger.evaluate(train_sets))
print(unigram_tagger.evaluate(test_sets))

结果为:

0.9353630649241612

0.8115219774743347

我们可以看到一元标注器在测试集上的正确率为0.81

二元标注器

尽管我们对每个单词分配了这个标识符最有可能的标记,但是在不同的上下文环境中,单词是有可能是其他的标记的。所以一个单词的标记不仅仅和它自身有关系,还可能和它的前一个单词或者更前面的单词有关系。二元标注器就是一个可以考虑单词自身以及前一个单词的标注器。

tagged_sents = brown.tagged_sents(categories='news')
size = int(len(tagged_sents) * 0.9)
train_sets = tagged_sents[:size]
test_sets = tagged_sents[size:]

# 生成二元标注器
bigram_tagger = nltk.BigramTagger(train=train_sets)
print(bigram_tagger.evaluate(train_sets))
print(bigram_tagger.evaluate(test_sets))

结果为:

0.7890434263872471

0.10186384929731884

二元标注器会考查一个单词本身和它前一个单词的标记,如果遇到一个新词,那么二元标注器就没法标记它,并且还会导致接下来的单词都没法标记,所以我们会看到二元标注器在测试集上正确率很低。

组合标注器

前面我们对查询标注器设置了一个回退标注器(默认标注器),事实上大部分的NLTK标注器都可以设置回退标注器,这样我们就可以把二元标注器、一元标注器、默认标注器组合起来得到一个组合标注器,例如我们可以按照下列方式组合:

  1. 尝试使用bigram标注器标注单词。
  2. 如果bigram标注器无法找到一个标记,尝试unigram标注器。
  3. 如果unigram标注器无法找到一个标记,使用默认标注器。

代码如下:

import nltk
from nltk.corpus import brown

# 划分训练集和测试集
tagged_sents = brown.tagged_sents(categories='news')
size = int(len(tagged_sents) * 0.9)
train_sets = tagged_sents[:size]
test_sets = tagged_sents[size:]

# 训练标注器,并把它们组合起来
t0 = nltk.DefaultTagger('NN')
t1 = nltk.UnigramTagger(train=train_sets, backoff=t0)
t2 = nltk.BigramTagger(train=train_sets, backoff=t1)

# 查看标注器性能
print(t2.evaluate(train_sets))
print(t2.evaluate(test_sets))

结果为:

0.9735641453364413

0.8459085019435861

从结果我们可以看到相比于一元标注器,我们的组合标注器还是有提高的。

总结

  • TaggerI::tag(tokens):对指定的单词列表进行标记,返回被标记后的单词列表
  • TaggerI::evaluete(tagged_sents):使用已经被标记的句子评价标注器,返回正确率0~1.0

DefaultTagger, UnigramTagger, BigramTagger都继承至TaggerI

DefaultTagger

默认标注器的构造函数接受一个标记作为参数,生成标注器

UnigramTagger

一元标注器接受被标记的句子列表作为参数,生成标注器

BigramTagger

二元标注器接受被标记的句子列表作为参数,生成标注器

其他章节链接

Python NLTK学习1(Text对象)

Python NLTK学习2(FreqDist对象)

Python NLTK学习3(语料库)

Python NLTK学习4(条件频率分布)

Python NLTK学习5(词性标注)

Python NLTK学习6(创建词性标注器)

Python NLTK学习7(对中文昵称进行性别分类)

Python NLTK学习8(正则表达式分块器)

Python NLTK学习9(评估分类器的方法)

Python NLTK学习10(评估分块器)

Python NLTK学习11(命名实体识别和关系抽取)


分享给朋友阅读吧


您还未登录,登录微博账号发表精彩评论

 微博登录


最新评论

    还没有人评论...

 

 

刘杰

28岁, 现居苏州

微博:

微信:

BurnellLIU

邮箱:

burnell_liu@outlook.com

Github:

https://github.com/BurnellLiu

简介:

努力做一个快乐的程序员, good good study, day day up!

友情链接: Will Mao

苏ICP备16059872号. Copyright © 2016. http://www.burnelltek.com. All rights reserved.