赞
踩
将词汇按它们的词性(parts-of-speech, pos)分类以及相应的标注它们的过程被成为词性标注(part-of-speech tagging, pos tagging)或简称标注。词性标注也成为词性或词汇范畴。用于特定任务的标记的集合被称为一个标记集。
5.1 使用词性标注器
一个词性标注器(pos tagger)处理一个词序列,为每个词加一个词性标记
text = nltk.word_tokenize(“And now for something com”)
5.3 使用Python字典映射词及其属性
索引链表 VS 字典
文本在python中被视为一个词链表。链表的一个重要属性是我们可以通过给出其索引来看特定项目。
字典查询:我们使用一个关键字,如某人的名字、一个域名或一个英文单词,访问一个字典的条目;映射(map)、哈希表(hashmap)、哈希(hash)、关联数组(associative array)是字典的其他名字。一个词频表允许我们查一个词找出它在一个文本集合中的频率。
Python字典
我们定义 pos为一个空字典,然后给它添加四个项目,指定一些词的词性。当我们检查pos 的值时,我们看到一个键-值对的集合。字典不是序列而是映射,它的键并不按固有的顺序。
Pos = {}
Pos[‘ideas’]
Pos[‘colorless’]
for word in sorted(pos):
print (word + “:”, pos[word])
字典的方法keys()、values()、items()允许我们访问作为单独的链表的键、值以及键-值对。
pos.keys() [‘colorless’,’furiously’,’sleep’]
pos.values() [‘ADJ’,’ADV’,’V’]
pos.items() [(‘colorless’,‘ADJ’), (‘furiously’,’ADV’),’ (‘ideas’,V’)]
for key,val in sorted(pos.items()):
print (key + “:”, val)
使得某个词被赋予不同的词行 pos[‘sleep’] = [‘N’,’V’]
定义词典
我们可以使用键-值对格式创建字典。有两个方式做这个,我们通常使用第一个:
Pos = {‘colorless’: ‘ADJ’, ‘ideas’:’N’, ‘sleep’:’V’, ‘furiously’:’ADV’}
Pos = dict(colorless = ‘ADJ’, ideas=’N’, sleep=’V’, furiously=’ADV’)
字典的键是不可改变的类型,如字符串和元祖。
默认字典
如果一个字典能为新键自动创建一个条目并给它一个默认值,如0或者一个空链表。一种特殊的称为defaultdict的字典已经出现。为了使用它,我们必须提供一个参数,用来创建默认值,如:int、float、str、list、dict、tuple。
创建一个任一条目的默认值是’N’的字典,当我们访问一个不存在的条目时,它会自动添加到字典。pos = nltk.defaultdict(lambda: 'N') 。这个lambda表达式没有指定参数,所以我们用不带参数的括号调用它。
我们可以预处理一个文本,在一个默认字典的帮助下,替换低频词汇为一个特殊的“超出词汇表”标识符,UNK(out of vocabulary)。
我们需要创建一个默认字典,映射每个词为它们的替换词。最频繁的n个词将被映射到它们自己。其他的被映射到UNK。
Alice = nltk.corpus.gutenberg.words(‘carroll-alice.txt’)
Vocab = nltk.FreqDist(alice)
V1000 = list(vocab)[:1000]
Mapping = nltk.defaultdict(lambda: ‘UNK’)
for v in v1000:
mapping[v] = v
alice2 = [mapping[v] for v in alice]
alice2[:100]
len(set(alice2))
递增地更新字典
首先初始化一个空的defaultdict,然后处理文本中每个词性标记。如果标记以前没有见过,就默认计数为零。每次我们遇到一个标记,就使用+=运算符递增它的计数。
counts = nltk.defaultdict(int)
from nltk.corpus import brown
for (word, tag) in brown.tagged_words(categories = ‘news’):
counts[tag] += 1
counts[‘N’]
list(counts)
from operator import itemgetter
sorted(counts.items(), key=itemgetter(1), reverse=True)]
sorted()的最后一个参数指定项目是否应该按相反的顺序返回,即频率值递减。
示意版本:
my_dictionary = nltk.defaultdict(function to create default value)
for item in sequence:
my_dictionary[item_key] is updated with information about item
按它们最后两个字母索引词汇:
Last_letters = nltk.defaultdict(list)
Words = nltk.corpus.words.words(‘en’)
For word in words:
Key = word[-2:]
Last_letters[key].append(word)
Last_letters[‘ly’]
使用相同的模式创建一个颠倒顺序的词字典。
Anagrams = nltk.defaultdict(list)
For word in words:
Key = ‘’.join(sorted(word))
Anagrams[key].append(word)
Anagrams[‘aeilnrt’]
NLTK 以nltk.Index()的形式提供一个创建defaultdict(list)更方便的方式。
Anagrams = nltk.Index((‘’.join(sorted(w)), w) for w in words)
Anagrams[‘aeilnrt’]
nltk.index是一个额外支持初始化的defautdict(list)
复杂的键和值
研究一个词可能的标记范围,给定词本身和它前一个词的标记。
Pos = nltk.defaultdict(lambda: nltk.defaultdict(int))
Brown_news_tagged = brown.tagged_words(categories = ‘news’, simplify_tags=True)
For ((w1,t1), (w2,t2) in nltk.ibigrams(brown_news_tagged):
Pos[(t1, w2)][t2] += 1
Pos[(‘DET’, ‘right’)]
颠倒词典
Counts = nltk.defaultdict(int)
For word in nltk.corpus.gutenberg.words(‘milton-paradise.txt’)
Counts[word] += 1
[key for (key,value) in counts.items() if value == 32]
使用字典的uodate()方法加入再一些词到pos中,创建多个键具有相同的值的情况。Append()积累词和每个词性。
Pos.update({‘cats’: ‘N’, ‘scratch’:’V’, ‘peacefully’: ‘ADV’, ‘old’: ’ADJ’})
Pos2 = nltk.defaultdict(list)
For key, value in pos.items():
Pos2[value].append(key)
查找任意词性具有该词性的词。
Pos2 = nltk.Index((value, key) for (key, value) in pos.items())
Pos2[‘ADV’]
Python字典方法:常用的方法与字典相关习惯用法的总结
d = {} 创建一个空的字典,并分配给d
d[key] = value 分配一个值给一个给定的字典键
d.keys() 字典的键的链表
list(d) 字典的键的链表
sorted(d) 字典的键, 排序
key in d 测试一个特定的键是否在字典中
for key in d 遍历字典的键
d.values() 字典中的值的链表
dict([(k1, v1), (k2,v2), …]) 从一个键-值对链表创建一个字典
d1.update(d2) 添加d2中所有项目到d1
defaultdict(int) 一个默认值为0的字典
自动标注
我们将看到一个词的标记依赖于这个词和它在句子中的上下文。我们处理已标注句子层次而不是词汇层次的数据。我们以加载将要使用的数据开始。
From nltk.corpus import brown
Brown_tagged_sents = brown.tagged_sents(categories=’news’)
Brown_sents = brown.sents(categories=’news)
默认标注器
最简单的标注器是为每个标识符分配同样的标记。找出哪些标记是最有可能的(现在使用未简化标记集):
tags = [tag for (word, tag) in brown.tagged_words(categories=’news’)]
nltk.FreqDist(tags).max()
创建一个将所有词都标注成NN的标注器。
raw = ‘I do not like green eggs and ham, I do not like them Sam I am!’
tokens = nltk.word_tokenize(raw)
default_tagger = nltk.DefaultTagger(‘NN’)
default_tagger.tag(tokens)
正则表达式标注器
正则表达式标注器基于匹配模式分配标记给标识符。
Patterns = [
(r’.*ing$’, ‘VBG’),
(r’.*ed$’, ‘VBd’),
(r‘.*es$’, ‘VBZ’)
(r’.*ould$’, ‘MD’),
(r’.*\’s$’, ‘NN$’),
(r’.*s$’, ‘NNS’),
(r’.*’, ‘NN’)
]
建立标注器,并用它来标记一个句子。
regexp_tagger = nltk.RefexpTagger(patterns)
regexp_tagger.tag(brown_sents[3])
regexp_tagger.evaluate(brown_tagged_sents)
最终的正则表达式《.*》是一个全面捕捉的,标注所有词为名词。除了作为正则表达式标注器的一部分重新指定这个,这与默认标注器是等效的,但效率很低。
查询标注器
很多高频词没有NN标记,让我们找出100个最频繁的词,存储它们最可能的标记。然后我们可以使用这个信息作为“查询标注器”(NLTK UnigramTagger)的模型:
fd = nltk.FreqDist(brown.words(categories = ‘news’))
cfd = nltk.ConitionFreqDist(brown.tagged_words(categories=’news’))
most_freq_words = fd.keys()[:100]
likely_tags = dict((word, cfd[word].max()) for word in most_freq_words)
baseline_tagger = nltk.UnigramTagger(model=likely_tags)
baseline_tagger.evaluate(brown_tagged_sents)
仅仅知道100 个最频繁的词的标记就使我们能正确标注很大一部分标识符(近一半,事实上)。
我们要先使用查找表,如果它不能指定一个标记就使用默认标注器,这个过程叫做回退。
通过指定一个标注器作为另一个标注器的参数,现在查找标注器将只存储名词以外的词的词-标记对,只要它不能给一个词分配标记,它将会调用默认标注器。
baseline_tagger = nltk.UnigramTagger(model=likely_tags, backoff=nltk.DefaultTagger(‘NN’))
查找标注器的性能,使用不同大小的模型。
def performance(cfd, wordlist):
Lt = dict((word, cfd[word].max()) for word in wordlist)
Baseline_tagger = nltk.UnigramTagger(model=Lt, backoff=nltk.DefaultTagger(‘NN’))
Return baseline_tagger.evaluate(brown.tagged_sents(categories=’news’))
def display():
import pylab
words_by_freq = list(nltk.FreqDist(brown.words(categories=’news’)))
cfd = nltk.ConditionalFreqDist(brown.tagged_words(categories=’news’))
sizes = 2**pylab.arange(15)
perfs = [performance(cfd, words_by_freq[:size]) for size in sizes]
pylab.plot(sizes, perfs, ‘-bo’)
pylab.title(‘Loookup Tagger Performance with Varying Model size’)
pylab.xlabel(‘Model Size’)
pylab.ylabel(‘Performance’)
pylab.show()
display()
评估
一个模块输出中的任何错误都在下游模块大大的放大。使用黄金标准测试数据来代替。
5.5 N-gram 标注
一元标注(Unigram Tagging)基于一种简单的统计算法:对每个标识符分配这个独特的标识符最有可能的标记。一个一元标注器的行为就像一个查找标注器。除了有一个更方便的建立它的技术,称为训练。
我们训练一个一元标注器,用它来标注一个句子,然后评估:
from nltk.corpus import brown
brown_tagged_sents = brown.tagged_sents(categories=’news’)
brown_sents = brown.sents(categories=’news’)
unigram_tagger = nltk.UnigramTagger(brown_tagged_sents)
unigram_tagger.tag(brown_sents[2007])
unigram_tagger.evaluate(brown_tagged_sents)
我们训练一个UnigramTagger,通过在我们初始化标注器时指定已标注的句子数据作为参数。训练过程中涉及检查每个词的标记,将所有词的最可能的标记存储在一个字典里面,这个字典存储在标注器内部。
分离训练与测试数据
一个只是记忆它的训练数据,而不试图建立一个一般的模型的标注器会得到一个完美的得分,但在标注新的文本时将是无用的。分割测试数据,90%为测试数据,其他10%为测试数据。
size = int(len(brown_tagged_sents)*0.9)
size
train_sents = brown_tagged_sents[:size]
test_sents = brown_tagged_sents[size:]
unigram_tagger = nltk.UnigramTagger(train_sents)
unigram_tagger.evaluate(test_sents)
一般的N-gram的标注
在基于unigrams处理一个语言任务时,我们使用上下文中的一个项目。我们能做的最好是为每个词标注其先验的最可能标记。一个n-gram标注器挑选在给定上下文中最可能的标记。1元标注器(unigram tagger)2元标注器(bigram taggers)3元标注器(trigram taggers)
训练一个bigram标注器,然后用它来标注未标注的句子。
bigram_tagger = nltk.BigramTagger(train_sents)
bigram_tagger.tag(brown_sents[2007])
unseen_sent = brown_sents[4203]
bigram_tagger.tag(unseen_sent)
bigram_tagger.evaluate(test_sents)
当n 越大,上下文的特异性就会增加,我们要标注的数据中包含训练数据中不存在的上下文的几率也增大。这被称为数据稀疏问题,在NLP 中是相当普遍的。N-gram 标注器不应考虑跨越句子边界的上下文。因此,NLTK 的标注器被设计用于句子链表,一个句子是一个词链表。在一个句子的开始,tn-1和前面的标记被设置为None。
组合标注器
我们可以按如下方式组合bigram 标注器、unigram 标注器和一个默认标注器:
1. 尝试使用bigram 标注器标注标识符。
2. 如果bigram 标注器无法找到一个标记,尝试unigram 标注器。
3. 如果unigram 标注器也无法找到一个标记,使用默认标注器
大多数NLTK 标注器允许指定一个回退标注器。回退标注器自身可能也有一个回退标注器:
>>> t0 = nltk.DefaultTagger('NN')
>>> t1 = nltk.UnigramTagger(train_sents, backoff=t0)
>>> t2 = nltk.BigramTagger(train_sents, backoff=t1)
>>> t2.evaluate(test_sents)
标注生词
标注生词的方法仍然是回退到一个正则表达式标注器或一个默认标注器。
存储标注器
from cPickle import dump
output = open(‘t2.pk1’,’wb’)
dump(t2, output, -1)
output.close()
在一个单独的Python 进程中载入我们保存的标注器:
from cPickle import load
input = open(‘t2.pk1’, ‘rb’)
tagger = load(input)
input.close()
检查是否可以用来标注
text = """The board's action shows what free enterprise
... is up against in our complex maze of regulatory laws ."""
tokens = text.split()
tagger.tag(tokens)
性能限制
cfd = nltk.ConditionalFreqDist(
((x[1], y[1], z[0]), z[1])
for sent in brown_tagged_sents
for x, y, z in nltk.trigrams(sent))
>>> ambiguous_contexts = [c for c in cfd.conditions() if len(cfd[c]) > 1]
>>> sum(cfd[c].N() for c in ambiguous_contexts) / cfd.N()
一个方便的方式查看标注错误是混淆矩阵。它用图表表示期望的标记(黄金标准)与实际由标注器产生的标记:
test_tags = [tag for sent in brown.sents(categories='editorial')
for (word, tag) in t2.tag(sent)]
gold_tags = [tag for (word, tag) in brown.tagged_words(categories='editorial')]
print nltk.ConfusionMatrix(gold, test)
跨句子边界标注
句子层面的N-gram 标注。使用已标注句子的链表来训练、运行和评估标注器。
Browm_tagged_sents = brown.tagged_sents(categories=’news’)
Brown_sents = brown.sents(categories=’news’)
Size = int(len(brown_tagged_sents)*0.9)
Train_sents = brown_tagged_sents[:size]
Test_sents = brown_tagged_sents[size:]
T0 =nltk.DefaultTagger(‘NN’)
T1 =nltk.UnigramTagger(train_sents, backoff=t0)
T2 = nltk.BigramTagger(train_sents, backoff=t1)
T2.evaluate(test_sents)
5.6 基于转换的标注
考察Brill 标注,一种归纳标注方法,它的性能很好,使用的模型只有n-gram 标注器的很小一部分。Brill 标注是一种基于转换的学习。猜每个词的标记,然后返回和修复错误的。在这种方式中,Brill标注器陆续将一个不良标注的文本转换成一个更好的。与n-gram标注一样,这是有监督的学习方法,因为我们需要已标注的训练数据来评估标注器的猜测是否是一个错误。
我们将研究两个规则的运作:(a)当前面的词是TO 时,替换NN 为VB;(b)当下一
个标记是NNS 时,替换TO 为IN 。首先使用unigram 标注器标注,然后运用规则修正错误。
Nltk.tag.brill.demo()
5.7 如何确定一个词的分类
形态学线索
句法线索
语义线索
新词
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。