一起来学自然语言处理----分类和标注词汇


  早在开始学习语文的时候就学过名词、动词、形容词和副词之间的差异。这些“词类”不是闲置的文法家的发明,而是对许多语言处理任务都有用的分类。正如我们将看到的,这些分类源于对文本中词的分布的简单的分析。
  一路上,我们将介绍 NLP 的一些基本技术,包括序列标注、N-gram 模型、回退和评估。这些技术在许多方面都很有用,标注为我们提供了一个表示它们的简单的上下文。我们还将看到标注为何是典型的 NLP 流水线中继分词之后的第二个步骤。
  将词汇按它们的词性(parts-of-speech,POS)分类以及相应的标注它们的过程被称为词性标注(part-of-speech tagging, POS tagging)或干脆简称标注。词性也称为词类或词汇范畴。用于特定任务的标记的集合被称为一个标记集。

使用词性标注器

  一个词性标注器(part-of-speech tagger 或 POS tagger)处理一个词序列,为每个词附加一个词性标记(不要忘记 import nltk):

import nltk
text = nltk.word_tokenize("And now for something completely different")
nltk.pos_tag(text)
[('And', 'CC'),
 ('now', 'RB'),
 ('for', 'IN'),
 ('something', 'NN'),
 ('completely', 'RB'),
 ('different', 'JJ')]
#让我们来看看另一个例子,这次包括一些同形同音异义词:
text = nltk.word_tokenize("They refuse to permit us to obtain the refuse permit")
nltk.pos_tag(text)
[('They', 'PRP'), ('refuse', 'VBP'), ('to', 'TO'), ('permit', 'VB'), ('us', 'PRP'),
('to', 'TO'), ('obtain', 'VB'), ('the', 'DT'), ('refuse', 'NN'), ('permit', 'NN')]

  在这里我们看到 and 是 CC,并列连词;now 和 completely 是 RB,副词;for 是 IN,介词;something 是 NN,名词;different 是 JJ,形容词。
  词汇类别,如“名词”,和词性标记,如 NN,看上去似乎有其用途,但在细节上将使许多读者感到晦涩。你可能想知道为什么要引进这种额外的信息。这些类别中很多都源于对文本中词的分布的浅层的分析。考虑下面的分析,涉及 woman(名词),bought(动词),over(介词)和 the(限定词)。text.similar()方法为一个词 w 找出所有上下文 词性相同的词w1w2,然后找出所有出现在相同上下文中的词 w’,即 w1w2.

text = nltk.Text(word.lower() for word in nltk.corpus.brown.words())
text.similar('woman')
man time day year car moment world house family child country boy
state job place way war girl work word

text.similar('over')
in on to of and for with from at by that into as up out down through
is all about

text.similar('bought')
made said done put had seen found given left heard was been brought
set got that took in told felt

标注语料库

1. 表示已标注的标识符

  按照 NLTK 的约定,一个已标注的标识符使用一个由标识符和标记组成的元组来表示。我们可以使用函数str2tuple()从表示一个已标注的标识符的标准字符串创建一个这样的特殊元组:

tagged_token = nltk.tag.str2tuple('fly/NN')
tagged_token
('fly', 'NN')

print(tagged_token[0],tagged_token[1])
fly NN

  我们可以直接从一个字符串构造一个已标注的标识符的链表。第一步是对字符串分词以便能访问单独的词/标记字符串,然后将每一个转换成一个元组(使用 str2tuple())。

sent = '''
The/AT grand/JJ jury/NN commented/VBD on/IN a/AT number/NN of/IN
other/AP topics/NNS ,/, AMONG/IN them/PPO the/AT Atlanta/NP and/CC
Fulton/NP-tl County/NN-tl purchasing/VBG departments/NNS which/WDT it/PP
said/VBD ``/`` ARE/BER well/QL operated/VBN and/CC follow/VB generally/R
accepted/VBN practices/NNS which/WDT inure/VB to/IN the/AT best/JJT
interest/NN of/IN both/ABX governments/NNS ''/'' ./.
'''
print([nltk.tag.str2tuple(t) for t in sent.split()])
[('The', 'AT'), ('grand', 'JJ'), ('jury', 'NN'), ('commented', 'VBD'), ('on', 'IN'), ('a', 'AT'), 
('number', 'NN'), ('of', 'IN'), ('other', 'AP'), ('topics', 'NNS'), (',', ','), ('AMONG', 'IN'), 
('them', 'PPO'), ('the', 'AT'), ('Atlanta', 'NP'), ('and', 'CC'), ('Fulton', 'NP-TL'), 
('County', 'NN-TL'), ('purchasing', 'VBG'), ('departments', 'NNS'), ('which', 'WDT'), 
('it', 'PP'), ('said', 'VBD'), ('``', '``'), ('ARE', 'BER'), ('well', 'QL'), 
('operated', 'VBN'), ('and', 'CC'), ('follow', 'VB'), ('generally', 'R'), ('accepted', 'VBN'), 
('practices', 'NNS'), ('which', 'WDT'), ('inure', 'VB'), ('to', 'IN'), ('the', 'AT'), 
('best', 'JJT'), ('interest', 'NN'), ('of', 'IN'), ('both', 'ABX'), ('governments', 'NNS'), 
("''", "''"), ('.', '.')]

2. 读取已标注的语料库

  NLTK 中包括的若干语料库已标注了词性。下面是一个你用文本编辑器打开一个布朗语料库的文件就能看到的例子:

The/at Fulton/np-tl County/nn-tl Grand/jj-tl Jury/nn-tl said/vbd Friday/nr an/at investigation/nn of/in Atlanta’s/np$ recent/jj primary/nn election/nn produced/vbd / no/at
evidence/nn ‘’/’’ that/cs any/dti irregularities/nns took/vbd place/nn ./.

  其他语料库使用各种格式存储词性标记。NLTK 中的语料库阅读器提供了一个统一的接口,使你不必理会这些不同的文件格式。与刚才提取并显示的上面的文件不同,布朗语料库的语料库阅读器按如下所示的方式表示数据。注意:部分词性标记已转换为大写的;自从布朗语料库发布以来,这已成为标准的做法。
  只要语料库包含已标注的文本,NLTK 的语料库接口都将有一个 tagged_words()方法。下面是一些例子,再次使用布朗语料库所示的输出格式:

print(nltk.corpus.nps_chat.tagged_words())
[('now', 'RB'), ('im', 'PRP'), ('left', 'VBD'), ...]

print(nltk.corpus.conll2000.tagged_words())
[('Confidence', 'NN'), ('in', 'IN'), ('the', 'DT'), ...]

  NLTK 中还有其他几种语言的已标注语料库,包括中文,印地语,葡萄牙语,西班牙语,荷兰语和加泰罗尼亚语。这些通常含有非 ASCII 文本,当输出较大的结构如列表时,Python 总是以十六进制显示这些。
  如果语料库也被分割成句子,将有一个 tagged_sents()方法将已标注的词划分成句子,而不是将它们表示成一个大链表。在我们开始开发自动标注器时,这将是有益的,因为它们在句子链表上被训练和测试,而不是词。

3. 简化的词性标记集

  已标注的语料库使用许多不同的标记集约定来标注词汇。为了帮助我们开始,我们将看
一看一个简化的标记集。
在这里插入图片描述

使用字典映射词及其属性

  正如我们已经看到, (word, tag)形式的一个已标注词是词和词性标记的关联。一旦我们开始做词性标注,我们将会创建分配一个标记给一个词的程序,标记是在给定上下文中最可能的标记。我们可以认为这个过程是从词到标记的映射。在 Python 中最自然的方式存储映射是使用所谓的字典数据类型(在其他的编程语言又称为关联数组或哈希数组)。

1. 默认字典

  关于字典,在这里不在赘述,各位可以自行学习。这里需要说的是一种称为默认字典的东西。
  如果我们试图访问一个不在字典中的键,会得到一个错误。然而,如果一个字典能为这个新键自动创建一个条目并给它一个默认值,如 0 或者一个空链表,将是有用的。自从 Python 2.5 以来,一种特殊的称为 defaultdict 的字典已经出现(from collections import defaultdict)。(NLTK 提供了 nltk.defaultdict。)为了使用它,我们必须提供一个参数,用来创建默认值,如:int、float、str、list、dict、tuple。

import nltk
frequency = nltk.defaultdict(int)
frequency['colorless'] = 4
frequency['ideas']
0

pos = nltk.defaultdict(list)
pos['sleep'] = ['N', 'V']
pos['ideas']
[]

  前面的例子中指定字典项的默认值为一个特定的数据类型的默认值。然而,也可以指定任何我们喜欢的默认值,只要提供可以无参数的被调用产生所需值的函数的名子。让我们回到我们的词性的例子,创建一个任一条目的默认值是’N’的字典�。当我们访问一个不存在的条目�时,它会自动添加到字典�

pos = nltk.defaultdict(lambda: 'N')
pos['colorless'] = 'ADJ'
pos['blog'] 
'N'

pos.items()
dict_items([('colorless', 'ADJ'), ('blog', 'N')])

  让我们来看看默认字典如何被应用在较大规模的语言处理任务中。许多语言处理任务— —包括标注——费很大力气来正确处理文本中只出现过一次的词。如果有一个固定的词汇和没有新词会出现的保证,它们会有更好的表现。我们可以预处理一个文本,在一个默认字典的帮助下,替换低频词汇为一个特殊的“超出词汇表”标识符,UNK(out of vocabulary)。

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]
print(alice2[:100])
['[', 'Alice', "'", 's', 'Adventures', 'in', 'Wonderland', 'by', 'UNK', 'UNK', 'UNK', 'UNK', 
'CHAPTER', 'I', '.', 'Down', 'the', 'Rabbit', '-', 'UNK', 'Alice', 'was', 'beginning', 'to', 
'get', 'very', 'tired', 'of', 'sitting', 'by', 'her', 'sister', 'on', 'the', 'bank', ',', 'and', 
'of', 'having', 'nothing', 'to', 'do', ':', 'once', 'or', 'twice', 'she', 'had', 'peeped', 
'into', 'the', 'book', 'her', 'sister', 'was', 'reading', ',', 'but', 'it', 'had', 'no', 
'pictures', 'or', 'UNK', 'in', 'it', ',', "'", 'and', 'what', 'is', 'the', 'use', 'of', 'a', 
'book', ",'", 'thought', 'Alice', "'", 'without', 'pictures', 'or', 'conversation', "?'", 'So', 
'she', 'was', 'considering', 'in', 'her', 'own', 'mind', '(', 'as', 'well', 'as', 'she', 
'could', ',']

2. 递增的更新字典

  我们可以使用字典计数出现的次数,首先初始化一个空的 defaultdict,然后处理文本中每个词性标记。如果标记以前没有见过,就默认计数为零。

counts = nltk.defaultdict(int)
from nltk.corpus import brown
for (word, tag) in brown.tagged_words(categories='news'):
    counts[tag] += 1

counts['AT']
8893
from operator import itemgetter
sorted(counts.items(), key=itemgetter(1), reverse=True)

  上面这个例子,演示了一个重要的按值排序一个字典的习惯用法,sorted()的第一个参数要排序的项目,它是由一个 POS 标记和一个频率组成的元组的链表。第二个参数使用函数 itemgetter()指定排序键。在一般情况下,itemgetter(n)返回一个函数,这个函数可以在一些其他序列对象上被调用获得这个序列的第 n 个元素。例如这里的1是指按值排序,0则是按键排序,第三个参数reverse代表是否降序。

3. 颠倒字典

  字典支持高效查找,只要你想获得任意键的值。如果 d 是一个字典,k 是一个键,输入d[K],就立即获得值。给定一个值查找对应的键要慢一些和麻烦一些:

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]
['mortal',
 'Against',
 'Him',
 'There',
 'brought',
 'King',
 'virtue',
 'every',
 'been',
 'thine']

  如果我们希望经常做这样的一种“反向查找”,建立一个映射值到键的字典是有用的。在没有两个键具有相同的值情况,这是一个容易的事。只要得到字典中的所有键-值对,并创建一个新的值-键对字典。下一个例子演示了用键-值对初始化字典 pos 的另一种方式。

pos = {'colorless': 'ADJ', 'ideas': 'N', 'sleep': 'V', 'furiously': 'ADV'}
pos2 = dict((value, key) for (key, value) in pos.items())
pos2['N']
'ideas'

  但遗憾的是,在处理词标注、频率计数经常遇到两个键具有相同的值情况,为了演示,修改上述字典,并演示解决办法。

# 给前面例子的pos增加内容
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['N']
['ideas', 'cats']

在这里插入图片描述

自动标注

  我们看到一个词的标记依赖于这个词和它在句子中的上下文。出于这个原因,我们将处理(已标注)句子层次而不是词汇层次的数据。

1. 默认标注器

  最简单的标注器是为每个标识符分配同样的标记。这似乎是一个相当平庸的一步,但它建立了标注器性能的一个重要的底线。为了得到最好的效果,我们用最有可能的标记标注每个词。让我们找出哪个标记是最有可能的(现在使用未简化标记集):

from nltk.corpus import brown
tags = [tag for (word, tag) in brown.tagged_words(categories='news')]
nltk.FreqDist(tags).max()
'NN'

  然后现在我们可以创建一个将所有词都标注成 NN 的标注器。

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

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)
[('I', 'NN'),
 ('do', 'NN'),
 ('not', 'NN'),
 ('like', 'NN'),
 ('green', 'NN'),
 ('eggs', 'NN'),
 ('and', 'NN'),
 ('ham', 'NN'),
 (',', 'NN'),
 ('I', 'NN'),
 ('do', 'NN'),
 ('not', 'NN'),
 ('like', 'NN'),
 ('them', 'NN'),
 ('Sam', 'NN'),
 ('I', 'NN'),
 ('am', 'NN'),
 ('!', 'NN')]

  不出所料,这种方法的表现相当不好。在一个典型的语料库中,它只标注正确了八分之一的标识符,正如我们在这里看到的:

default_tagger.evaluate(brown_tagged_sents)
0.13089484257215028

  默认的标注器给每一个单独的词分配标记,即使是之前从未遇到过的词。碰巧的是,一旦我们处理了几千词的英文文本之后,大多数新词都将是名词。正如我们将看到的,这意味着,默认标注器可以帮助我们提高语言处理系统的稳定性。

2.正则表达式标注器

  正则表达式标注器基于匹配模式分配标记给标识符。例如:我们可能会猜测任一以 ed结尾的词都是动词过去分词,任一以’s 结尾的词都是名词所有格。可以用一个正则表达式的列表表示这些:

patterns = [
(r'.*ing$', 'VBG'), # gerunds
(r'.*ed$', 'VBD'), # simple past
(r'.*es$', 'VBZ'), # 3rd singular present
(r'.*ould$', 'MD'), # modals
(r'.*\'s$', 'NN$'), # possessive nouns
(r'.*s$', 'NNS'), # plural nouns
(r'^-?[0-9]+(.[0-9]+)?$', 'CD'), # cardinal numbers
(r'.*', 'NN') # nouns (default)
]
regexp_tagger = nltk.RegexpTagger(patterns)
regexp_tagger.tag(brown_sents[3])

# 评价结果
regexp_tagger.evaluate(brown_tagged_sents)
0.20326391789486245

  最终的正则表达式«.*»是一个全面捕捉的,标注所有词为名词。除了作为正则表达式标注器的一部分重新指定这个,这与默认标注器是等效的(只是效率低得多)。

3. 查询标注器

  很多高频词没有 NN 标记。让我们找出 100 个最频繁的词,存储它们最有可能的标记。然后我们可以使用这个信息作为“查找标注器”(NLTK UnigramTagger)的模型:

fd = nltk.FreqDist(brown.words(categories='news'))
cfd = nltk.ConditionalFreqDist(brown.tagged_words(categories='news'))
most_freq_words = list(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)

0.3329355371243312

  现在应该并不奇怪,仅仅知道 100 个最频繁的词的标记就使我们能正确标注很大一部分标识符(近三分之一,事实上)。让我们来看看它在一些未标注的输入文本上做的如何:

sent = brown.sents(categories='news')[3]
baseline_tagger.tag(sent)
[('``', '``'),
 ('Only', 'RB'),
 ('a', 'AT'),
 ('relative', 'JJ'),
 ('handful', 'NN'),
 ('of', 'IN'),
 ('such', 'JJ'),
 ('reports', 'NNS'),
 ('was', 'BEDZ'),
 ('received', 'VBD'),
 ("''", "''"),
 (',', ','),
 ('the', 'AT'),
 ('jury', 'NN'),
 ('said', 'VBD'),
 (',', ','),
 ('``', '``'),
 ('considering', 'IN'),
 ('the', 'AT'),
 ('widespread', 'JJ'),
 ('interest', 'NN'),
 ('in', 'IN'),
 ('the', 'AT'),
 ('election', 'NN'),
 (',', ','),
 ('the', 'AT'),
 ('number', 'NN'),
 ('of', 'IN'),
 ('voters', 'NNS'),
 ('and', 'CC'),
 ('the', 'AT'),
 ('size', 'NN'),
 ('of', 'IN'),
 ('this', 'DT'),
 ('city', 'NN'),
 ("''", "''"),
 ('.', '.')]

  从上面结果可以看到许多词都被分配了一个 None 标签,因为它们不在 100 个最频繁的词之中。在这些情况下,我们想分配默认标记 NN。换句话说,我们要先使用查找表,如果它不能指定一个标记就使用默认标注器,这个过程叫做回退。我们可以做到这个,通过指定一个标注器作为另一个标注器的参数,如下所示。现在查找标注器将只存储名词以外的词的词-标记对,只要它不能给一个词分配标记,它将会调用默认标注器。

baseline_tagger = nltk.UnigramTagger(model=likely_tags,backoff=nltk.DefaultTagger('NN'))
baseline_tagger.evaluate(brown_tagged_sents)
0.46063806511923944
sent = brown.sents(categories='news')[3]
baseline_tagger.tag(sent)
[('``', '``'),
 ('Only', 'RB'),
 ('a', 'AT'),
 ('relative', 'JJ'),
 ('handful', 'NN'),
 ('of', 'IN'),
 ('such', 'JJ'),
 ('reports', 'NNS'),
 ('was', 'BEDZ'),
 ('received', 'VBD'),...]

N-gram标注

1. 一元标注(Unigram Tagging)

  一元标注器基于一个简单的统计算法:对每个标识符分配这个独特的标识符最有可能的标记。例如:它将分配标记 JJ 给词 frequent 的所有出现,因为 frequent 用作一个形容词(例如:a frequent word)比用作一个动词(例如:I frequent this cafe)更常见。一个一元标注器的行为就像一个查找标注器,除了有一个更方便的建立它的技术,称为训练。在下面的代码例子中,我们训练一个一元标注器,用它来标注一个句子,然后评估:

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)
0.9349006503968017

  从上面结果来看,效果很好,是不是这个标注器就是好标注器呢?当然不是,上面的例子忽略了一个致命的问题,那就是测试数据和训练数据是同一个数据,这样的标注器在实际数据中的表现差强人意。所以我们应该分割数据,90%为测试数据,其余 10%为测试数据,这个思路和机器学习的测试训练一样。

size = int(len(brown_tagged_sents) * 0.9)
train_sents = brown_tagged_sents[:size]
test_sents = brown_tagged_sents[size:]
unigram_tagger = nltk.UnigramTagger(train_sents)
unigram_tagger.evaluate(test_sents)
0.8121200039868434

2. 一般的 N-gram 的标注

  在基于 unigrams 处理一个语言处理任务时,我们使用上下文中的一个项目。标注的时候,我们只考虑当前的标识符,与更大的上下文隔离。给定一个模型,我们能做的最好的是为每个词标注其先验的最可能的标记。这意味着我们将使用相同的标记标注一个词,如 wind,不论它出现的上下文是 the wind 还是 to wind。一个 n-gram 标注器是一个 unigram 标注器的一般化,它的上下文是当前词和它前面 n-1 个标识符的词性标记。
  NgramTagger 类使用一个已标注的训练语料库来确定对每个上下文哪个词性标记最有可能。在这里,我们看到一个 n-gram 标注器的特殊情况,即一个 bigram 标注器。首先,我们训练它,然后用它来标注未标注的句子:

size = int(len(brown_tagged_sents) * 0.9)
train_sents = brown_tagged_sents[:size]
test_sents = brown_tagged_sents[size:]
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)
0.10206319146815508

  请注意,bigram 标注器能够标注训练中它看到过的句子中的所有词,但对一个没见过的句子表现很差,例如上面例子中的million,即使是在训练过程中看到过的,只是因为在训练过程中从来没有见过它前面有一个 None 标记的词。因此,标注器标注句子的其余部分也失败了(上面例子结果)。它的整体准确度得分非常低。
  当 n 越大,上下文的特异性就会增加,我们要标注的数据中包含训练数据中不存在的上下文的几率也增大。这被称为数据稀疏问题,在 NLP 中是相当普遍的。因此,我们的研究结果的精度和覆盖范围之间需要有一个权衡(这与信息检索中的精度/召回权衡有关)。

3. 组合标注器

  解决精度和覆盖范围之间的权衡的一个办法是尽可能的使用更精确的算法,但却在很多时候落后于具有更广覆盖范围的算法。例如:我们可以按如下方式组合 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)
0.844911791089405

  请注意,我们在标注器初始化时指定回退标注器,从而使训练能利用回退标注器。于是,如果在一个上下文中 bigram 标注器将分配与它的 unigram 回退标注器一样的标记,那么 bigram 标注器丢弃训练的实例。这样保持尽可能小的 bigram 标注器模型。我们可以进一步指定一个标注器需要看到一个上下文的多个实例才能保留它。例如:nltk.BigramTagger(sents, cutoff=2, backoff=t1)将会丢弃那些只看到一次或两次的上下文。

4. 存储标注器

  在大语料库上训练一个标注器可能需要大量的时间。没有必要在每次我们需要的时候训练一个标注器,很容易将一个训练好的标注器保存到一个文件以后重复使用。让我们保存我们的标注器 t2 到文件 t2.pkl(这个东西类似于机器学习中的模型,不是用的时候才训练,而是训练保存起来,用的时候直接用):

# 保存
from pickle import dump
output = open('t2.pkl', 'wb')
dump(t2, output, -1)
output.close()
# 加载
from pickle import load
input = open('t2.pkl', '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)
[('The', 'AT'),
 ("board's", 'NN$'),
 ('action', 'NN'),
 ('shows', 'NNS'),
 ('what', 'WDT'),
 ('free', 'JJ'),
 ('enterprise', 'NN'),
 ('is', 'BEZ'),
 ('up', 'RP'),
 ('against', 'IN'),
 ('in', 'IN'),
 ('our', 'PP$'),
 ('complex', 'JJ'),
 ('maze', 'NN'),
 ('of', 'IN'),
 ('regulatory', 'NN'),
 ('laws', 'NNS'),
 ('.', '.')]

基于转换的标注

  n-gram 标注器的一个潜在的问题是它们的 n-gram 表的大小(或语言模型)。如果使用各种语言技术的标注器部署在移动计算设备上,在模型大小和标注器性能之间取得平衡是很重要的。使用回退标注器的 n-gram 标注器可能存储 trigram 和 bigram 表,这是很大的稀疏阵列,可能有数亿条条目。
  第二个问题是关于上下文的。n-gram 标注器从前面的上下文中获得的唯一的信息是标记,虽然词本身可能是一个有用的信息源。n-gram 模型使用上下文中的词的其他特征为条件是不切实际的。在本节中,我们考察 Brill 标注,一种归纳标注方法,它的性能很好,使用的模型只有 n-gram 标注器的很小一部分。
  Brill 标注是一种基于转换的学习,以它的发明者命名。一般的想法很简单:猜每个词的标记,然后返回和修复错误的。在这种方式中,Brill 标注器陆续将一个不良标注的文本转换成一个更好的。与 n-gram 标注一样,这是有监督的学习方法,因为我们需要已标注的训练数据来评估标注器的猜测是否是一个错误。然而,不像 n-gram 标注,它不计数观察结果,只编制一个转换修正规则链表。
  Brill 标注的的过程通常是与绘画类比来解释的。假设我们要画一棵树,包括大树枝、树枝、小枝、叶子和一个统一的天蓝色背景的所有细节。不是先画树然后尝试在空白处画蓝色,而是简单的将整个画布画成蓝色,然后通过在蓝色背景上上色“修正”树的部分。以同样的方式,我们可能会画一个统一的褐色的树干再回过头来用更精细的刷子画进一步的细节。Brill 标注使用了同样的想法:以大笔画开始,然后修复细节,一点点的细致的改变。
  让我们看看下面的例子:
(1) The President said he will ask Congress to increase grants to states for vocational rehabilitation.(总统表示:他将要求国会增加拨款给各州用于职业康复。)
我们将研究两个规则的运作:(a)当前面的词是 TO 时,替换 NN 为 VB;(b)当下一个标记是 NNS 时,替换 TO 为 IN。下图说明了这一过程,首先使用 unigram 标注器标注,然后运用规则修正错误。
在这里插入图片描述
  Brill 标注器的另一个有趣的特性:规则是语言学可解释的。与采用潜在的巨大的 n-gram 表的 n-gram 标注器相比,我们并不能从直接观察这样的一个表中学到多少东西,而 Brill标注器学到的规则可以。

如何确定一个词的分类

  我们已经详细研究了词类,现在转向一个更基本的问题:如何决定一个词属于哪一类?首先应该考虑什么?在一般情况下,语言学家使用形态学、句法和语义线索确定一个词的类别。

1. 形态学线索

  一个词的内部结构可能为这个词分类提供有用的线索。举例来说:-ness 是一个后缀,与形容词结合产生一个名词,如 happy→happiness,ill→illness。因此,如果我们遇到的一个以-ness 结尾的词,很可能是一个名词。同样的,-ment 是与一些动词结合产生一个名词的后缀,如 govern→government 和 establish→establishment。英语动词也可以是形态复杂的。例如:一个动词的现在分词以-ing 结尾,表示正在进行的还没有结束的行动(如:falling,eating)的意思。-ing 后缀也出现在从动词派生的名词中,如:the falling of the leaves(这被称为动名词)。

2. 句法线索

  另一个信息来源是一个词可能出现的典型的上下文语境。例如:假设我们已经确定了名词类。那么我们可以说,英语形容词的句法标准是它可以立即出现在一个名词前,或紧跟在词 be 或 very 后。根据这些测试,near 应该被归类为形容词:
a. the near window
b. The end is (very) near.

3. 语义线索

  最后,一个词的意思对其词汇范畴是一个有用的线索。例如:名词的众所周知的一个定义是根据语义的:“一个人、地方或事物的名称。”在现代语言学,词类的语义标准受到怀疑,主要是因为它们很难规范化。然而,语义标准巩固了我们对许多词类的直觉,使我们能够在不熟悉的语言中很好的猜测词的分类。例如:如果我们都知道荷兰语词 verjaardag 的意思与英语词 birthday 相同,那么我们可以猜测 verjaardag 在荷兰语中是一个名词。然而,一些修补是必要的:虽然我们可能翻译 zij is vandaag jarig as it’s her birthday today,词 jarig在荷兰语中实际上是形容词,与英语并不完全相同。

4. 新词

  所有的语言都学习新的词汇。比如后面添加到牛津英语词典中的一个单词列表包括 cyberslacker、fatoush、blamestorm、SARS、cantopop、bupkis、noughties、muggle 和 robata。请注意,所有这些新词都是名词,这反映在名词被称为开放类。相反,介词被认为是一个封闭类。也就是说,只有有限的词属于这个类别(例如:above、along、at、below、beside、between、during、for、from、in、near、on、outside、over、past、through、towards、under、up、with),词类成员随着很长时间的推移才逐渐改变。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值