自然语言是人类在沟通中形成的一套规则体系。规则有强有弱,比如非正式场合使用口语,正式场合下的书面语。
要处理自然语言,也要遵循这些形成的规则,否则就会得出令人无法理解的结论。下面介绍一些术语的简单区别。 文法:等同于语法(grammar),文章的书写规范,用来描述语言及其结构,它包含句法和词法规范。 句法:Syntax,句子的结构或成分的构成与关系的规范。 词法:Lexical,词的构词,变化等的规范。 词性标注,或POS(Part Of Speech),是一种分析句子成分的方法,通过它来识别每个词的词性。
下面简要列举POS的tagset含意,详细可看nltk.help.brown_tagset()
标记 | 词性 | 示例 |
---|---|---|
ADJ | 形容词 | new, good, high, special, big, local |
ADV | 动词 | really, already, still, early, now |
CONJ | 连词 | and, or, but, if, while, although |
DET | 限定词 | the, a, some, most, every, no |
EX | 存在量词 | there, there’s |
MOD | 情态动词 | will, can, would, may, must, should |
NN | 名词 | year,home,costs,time |
NNP | 专有名词 | April,China,Washington |
NUM | 数词 | fourth,2016, 09:30 |
PRON | 代词 | he,they,us |
P | 介词 | on,over,with,of |
TO | 词to | to |
UH | 叹词 | ah,ha,oops |
VB | 动词 | |
VBD | 动词过去式 | made,said,went |
VBG | 现在分词 | going,lying,playing |
VBN | 过去分词 | taken,given,gone |
WH | wh限定词 | who,where,when,what |
1 使用NLTK对英文进行词性标注
1.1 词性标注示例
import nltk
sent="I am going to Beijing tomorrow";
tokens = nltk.word_tokenize(sent)
taged_sent = nltk.pos_tag(tokens)
print taged_sent
#[('I', 'PRP'), ('am', 'VBP'), ('going', 'VBG'), ('to', 'TO'), ('Beijing', 'NNP'), ('tomorrow', 'NN'), ('.', '.')]
1.2 语料库的已标注数据
语料类提供了下列方法可以返回预标注数据。
方法 | 说明 |
---|---|
tagged_words(fileids,categories) | 返回标注数据,以词列表的形式 |
tagged_sents(fileids,categories) | 返回标注数据,以句子列表形式 |
tagged_paras(fileids,categories) | 返回标注数据,以文章列表形式 |
2 标注器
2.1 默认标注器
最简单的词性标注器是将所有词都标注为名词NN。这种标注器没有太大的价值。正确率很低。下面演示NLTK提供的默认标注器的用法。
import nltk
from nltk.corpus import brown
default_tagger = nltk.DefaultTagger('NN')
sents = 'I am going to Beijing.'
print default_tagger.tag(sents)
#[('I', 'NN'), ('am', 'NN'), ('going', 'NN'), ('to', 'NN'), ('Beijing', 'NN')]
tagged_sents = brown.tagged_sents(categories='news')
print default_tagger.evaluate(tagged_sents) #0.131304
2.2 基于规则的标注器
从默认标注器的评估来看,只有13%的正确率。为了改进这一效果,我们使用一些规则来提高正确率。比如对于ing结尾则柡注为VG,ed结尾则标注为VD。可以通过正则表达式标注器实现这个想法。
import nltk
from nltk.corpus import brown
pattern =[
(r'.*ing$','VBG'),
(r'.*ed$','VBD'),
(r'.*es$','VBZ'),
(r'.*\'s$','NN$'),
(r'.*s$','NNS'),
(r'.*', 'NN') #未匹配的仍标注为NN
]
sents = 'I am going to Beijing.'
tagger = nltk.RegexpTagger(pattern)
print tagger.tag(nltk.word_tokenize(sents))
#[('I', 'NN'), ('am', 'NN'), ('going', 'VBG'), ('to', 'NN'), ('Beijing', 'VBG'), ('.', 'NN')]
tagged_sents = brown.tagged_sents(categories='news')
print default_tagger.evaluate(tagged_sents) #0.1875
2.3 基于查表的标注器
经过增加简单的规则,可以提升默认标注器的准确度,但仍不够好。为此我们统计一下部分高频词的词性,比如经常出现的100个词的词性。利用单个词的词性的统计知识来进行标注,这就是Unigram模型的思想。
import nltk
from nltk.corpus import brown
fdist = nltk.FreqDist(brown.words(categories='news'))
common_word = fdist.most_common(100)
cfdist = nltk.ConditionalFreqDist(brown.tagged_words(categories='news'))
table= dict((word, cfdist[word].max()) for (word, _) in common_word)
uni_tagger = nltk.UnigramTagger(model=table,backoff=nltk.DefaultTagger('NN'))
print uni_tagger.evaluate(tagged_sents) #0.5817
只利用前100个词的历史统计数据便能获得58%的正确率,加大这个词的数量更可以继续提升标注的正确率,当为8000时可以达到90%的正确率。这里我们对不在这100个词的其他词统一回退到默认标注器。
3 训练N-gram标注器
3.1 一般N-gram标注
在上一节中,已经使用了1-Gram,即Unigram标注器。考虑更多的上下文,便有了2/3-gram,这里统称为N-gram。注意,更长的上正文并不能带来准确度的提升。
除了向N-gram标注器提供词表模型,另外一种构建标注器的方法是训练。N-gram标注器的构建函数如下:__init__(train=None, model=None, backoff=None),可以将标注好的语料作为训练数据,用于构建一个标注器。
import nltk
from nltk.corpus import brown
brown_tagged_sents = brown.tagged_sents(categories='news')
train_num = int(len(brown_tagged_sents) * 0.9)
x_train = brown_tagged_sents[0:train_num]
x_test = brown_tagged_sents[train_num:]
tagger = nltk.UnigramTagger(train=x_train)
print tagger.evaluate(x_test) #0.81
对于UniGram,使用90%的数据进行训练,在余下10%的数据上测试的准确率为81%。如果改为BiGram,则正确率会下降到10%左右。
3.2 组合标注器
可以利用backoff参数,将多个组合标注器组合起来,以提高识别精确率。
import nltk
from nltk.corpus import brown
pattern =[
(r'.*ing$','VBG'),
(r'.*ed$','VBD'),
(r'.*es$','VBZ'),
(r'.*\'s$','NN$'),
(r'.*s$','NNS'),
(r'.*', 'NN') #未匹配的仍标注为NN
]
brown_tagged_sents = brown.tagged_sents(categories='news')
train_num = int(len(brown_tagged_sents) * 0.9)
x_train = brown_tagged_sents[0:train_num]
x_test = brown_tagged_sents[train_num:]
t0 = nltk.RegexpTagger(pattern)
t1 = nltk.UnigramTagger(x_train, backoff=t0)
t2 = nltk.BigramTagger(x_train, backoff=t1)
print t2.evaluate(x_test) #0.863
从上面可以看出,不需要任何的语言学知识,只需要借助统计数据便可以使得词性标注做的足够好。
对于中文,只要有标注语料,也可以按照上面的过程训练N-gram标注器。
4 更进一步
nltk.tag.BrillTagger实现了基于转换的标注,在基础标注器的结果上,对输出进行基于规则的修正,实现更高的准确度。
5 示例:中文标注器的训练
下面基于Unigram训练一个中文词性标注器,语料使用网上可以下载得到的人民日报98年1月的标注资料。百度网盘链接。
# coding=utf-8
import nltk
import json
lines = open('9801.txt').readlines()
all_tagged_sents = []
for line in lines:
line = line.decode('utf-8')
sent = line.split()
tagged_sent = []
for item in sent:
pair = nltk.str2tuple(item)
tagged_sent.append(pair)
if len(tagged_sent)>0:
all_tagged_sents.append(tagged_sent)
train_size = int(len(all_tagged_sents)*0.8)
x_train = all_tagged_sents[:train_size]
x_test = all_tagged_sents[train_size:]
tagger = nltk.UnigramTagger(train=x_train,backoff=nltk.DefaultTagger('n'))
tokens = nltk.word_tokenize(u'我 认为 不丹 的 被动 卷入 不 构成 此次 对峙 的 主要 因素。')
tagged = tagger.tag(tokens)
print json.dumps(tagged,encoding='UTF-8', ensure_ascii=False)
#["我", "R"], ["认为", "V"], ["不丹", "n"], ["的", "U"], ["被动", "A"], ["卷入", "V"], ["不", "D"], ["构成", "V"], ["此次", "R"], ["对峙", "V"], ["的", "U"], ["主要", "B"], ["因素。", "n"]
print tagger.evaluate(x_test) #0.871
简单的训练,标注器便取得了87.1%的成绩,还是不错的。