Python NLTK Ngram标注器使用词元上下文而非标签上下文
我一直在使用NLTK的单词标记器(Unigram tagger),通过模型关键字传入一个单词列表来进行特定的标记:
nd = dict((x,'CFN') for x in common_first_names)
...
t4 = nltk.UnigramTagger(model=nd, backoff=t3)
我想从我的文档中提取一些非常具体的信息,而这些文档的标点、大小写和语法质量都差别很大,所以用现成的语料库来训练并没有取得很好的效果。我一直在按照上面的方式自己进行标记,同时使用正则表达式和默认标记器来精确标记我想要的内容。
我想像上面那样使用二元标记器(Bigram)和三元标记器(Trigram),传入一个单词组合的模型,这样序列中的最后一个单词就可以根据前面的单词来进行标记,类似于:
# 'the' gets different tag depending on preceding word
{
('for','the') : 'FT',
('into','the') : 'IT',
('on','the') : 'OT'
}
但我通过阅读代码、调试,最后再读书时才发现,N元标记器(Ngram taggers)使用的是标签,而不是单词本身,作为左侧的上下文。因为像'for'、'into'和'on'这些词可能会被标记成一样的,所以我无法区分它们。此外,依赖标签使得N元标记器在没有一个大且相关的训练集时几乎没用,因为一旦遇到一个没有标记的单词或者一个标记方式不在训练数据中的单词,它们就会失效。
我搜索了很多,但没有找到任何关于这个问题的讨论。关于N元标记器的讨论似乎都假设需要训练数据,而不是模型。有没有办法用单词作为上下文进行标记,而不是用标签呢?谢谢
1 个回答
我想我找到了一个解决方案,虽然这只是经过仔细查看代码后的猜测。我创建了自己的Ngram标记器,作为NLTK NgramTagger类的一个子类,代码如下:
class myNgramTagger(nltk.NgramTagger):
"""
My override of the NLTK NgramTagger class that considers previous
tokens rather than previous tags for context.
"""
def __init__(self, n, train=None, model=None,
backoff=None, cutoff=0, verbose=False):
nltk.NgramTagger.__init__(self, n, train, model, backoff, cutoff, verbose)
def context(self, tokens, index, history):
#tag_context = tuple(history[max(0,index-self._n+1):index])
tag_context = tuple(tokens[max(0,index-self._n+1):index])
return tag_context, tokens[index]
我唯一修改的那一行是在上下文方法中的注释部分,我把历史列表改成了标记列表。我基本上只是猜测这样做可能会达到我想要的效果,但看起来它在模型和训练数据上都能正常工作。
test_sent = ["When","a","small","plane","crashed","into","the","river","a","general","alert","was","a","given"]
tm2 = {
(('When',), 'a') : "XX",
(('into',), 'the') : "YY",
}
tm3 = {
(('a','general'), 'alert') : "ZZ",
}
taggerd = nltk.DefaultTagger('NA')
tagger2w = myNgramTagger(2,model=tm2,backoff=taggerd)
tagger3w = myNgramTagger(3,model=tm3,backoff=tagger2w)
print tagger3w.tag(test_sent)
[('When', 'NA'), ('a', 'XX'), ('small', 'NA'), ('plane', 'NA'), ('crashed', 'NA'), ('into', 'NA'), ('the', 'YY'), ('river', 'NA'), ('a', 'NA'), ('general', 'NA'), ('alert', 'ZZ'), ('was', 'NA'), ('a', 'NA'), ('given', 'NA')]
所以,仅仅通过在一个方法中改变一个词,我似乎就成功实现了我想要的功能,即使用标记作为上下文进行Ngram标记,而不是使用标签。
我还尝试了类似的做法,使用布朗语料库中的新闻类别进行训练(这也是我选择测试句子的原因),结果看起来也很好,实际上比使用标签的效果还要好,因为它能够标记句子中所有可以识别的部分,而不是在遇到不认识的部分时就停止:
from nltk.corpus import brown
brown_tagged_sents = brown.tagged_sents(categories='news')
brown_tagger_bigram = myNgramTagger(2,brown_tagged_sents)
brown_tagger_trigram = myNgramTagger(3,brown_tagged_sents,backoff=brown_tagger_bigram)
print brown_tagger_trigram.tag(test_sent)
[('When', u'WRB'), ('a', u'AT'), ('small', u'JJ'), ('plane', None), ('crashed', None), ('into', None), ('the', u'AT'), ('river', None), ('a', None), ('general', u'JJ'), ('alert', None), ('was', None), ('a', u'AT'), ('given', u'VBN')]
把这个和普通的NLTK Ngram标记器进行比较,实际上显示这是一个改进:
from nltk.corpus import brown
brown_tagged_sents = brown.tagged_sents(categories='news')
brown_tagger_bigram = nltk.NgramTagger(2,brown_tagged_sents)
brown_tagger_trigram = nltk.NgramTagger(3,brown_tagged_sents,backoff=brown_tagger_bigram)
print brown_tagger_trigram.tag(test_sent)
[('When', u'WRB'), ('a', u'AT'), ('small', u'JJ'), ('plane', None), ('crashed', None), ('into', None), ('the', None), ('river', None), ('a', None), ('general', None), ('alert', None), ('was', None), ('a', None), ('given', None)]
使用标记上下文进行标记的结果在句子的最后都还不错,而使用标签上下文的标记只在第三个词之前有效。