Python自然語言處理 5 分類和標註詞彙
目標:
(1)什麼是詞彙分類,在自然語言處理中它們如何使用?
(2)對於儲存詞彙和它們的分類來說什麼是好的Python資料結構?
(3)如何自動標註文字中每個詞彙的詞類?
基本技術,包括序列標註,N-gram模型,回退和評估
一 使用詞性標註器
同音同義詞,文字轉語音系統通常要進行詞性標註,以便能正確讀課文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')]
二 標註語料庫
#表示已標註的識別符號
tagged_token = nltk.tag.str2tuple('fly/NN')
tagged_token
('fly', 'NN')
#讀取已標註的語料庫
NLTK中包括的若干語料庫已標註了詞性
nltk.corpus.brown.tagged_words()
#簡化的詞性標記集
#名詞from nltk.corpus import brown brown_news_tagged = brown.tagged_words(categories='news',tagset = 'universal') tag_fd = nltk.FreqDist(tag for (word,tag) in brown_news_tagged) tag_fd.keys() [u'ADV', u'NOUN', u'ADP', u'PRON',
word_tag_pairs = nltk.bigrams(brown_news_tagged)
list(nltk.FreqDist(a[1] for (a,b) in word_tag_pairs if b[1] == 'N'))
#動詞
wsj = nltk.corpus.treebank.tagged_words(tagset = 'universal')
word_tag_fd = nltk.FreqDist(wsj)
[word+"/"+tag for (word,tag) in word_tag_fd if tag.startswith('V')]
#形容詞和副詞
#未簡化的標記
#探索已標註的語料庫
三 使用Python字典對映詞及其屬性 P206
#索引連結串列VS字典
#PYthon字典
pos = {}
pos['colorless'] = 'ADJ'
pos['ideas'] = 'N'
pos['sleep'] = 'V'
pos['furiously'] = 'ADV'
pos
{'colorless': 'ADJ', 'furiously': 'ADV', 'ideas': 'N', 'sleep': 'V'}
for word in sorted(pos):
print word + ":", pos[word]
#定義字典
#預設字典
#遞增地更新字典
#複雜的鍵和值 #顛倒字典四 自動標註
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()
u'NN'
將所有詞都標註成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)
[('I', 'NN'),
('do', 'NN'),
('not', 'NN'),
('like', 'NN'),
評估
default_tagger.evaluate(brown_tagged_sents)
0.13089484257215028
#正則表示式標註器
基於匹配模式分配標記給識別符號,如認為以ed結尾的詞都是動詞過去分詞
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])
[(u'``', 'NN'),
(u'Only', 'NN'),
(u'a', 'NN'),
(u'relative', 'NN'),
(u'handful', 'NN'),
評估
regexp_tagger.evaluate(brown_tagged_sents)
0.20326391789486245
#查詢標註器
nltk.UnigramTagger()
**開發已標註語料庫是一個重大的任務,為確保高品質的標註,除了資料,它會產生複雜的工具,文件和實踐.標記集和其他編碼方案不可避免地依賴於一些理論主張,不是所有的理論主張都被共享.然而,語料庫的創作者往往竭盡全力使他們的工作儘可能中立,以最大限度地提高其工作的有效性
fd = nltk.FreqDist(brown.words(categories='news'))
cfd = nltk.ConditionalFreqDist(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)
0.005171350717027666
五 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])
[(u'Various', u'JJ'),
(u'of', u'IN'),
(u'the', u'AT'),
(u'apartments', u'NNS'),
(u'are', u'BER'),
#分離訓練和測試資料
90%為訓練資料,10%為測試資料
size = int(len(brown_tagged_sents) * 0.9)
size
4160
train_sents = brown_tagged_sents[:size] # ******
test_sents = brown_tagged_sents[size:]
unigram_tagger = nltk.UnigramTagger(train_sents) # <-------
unigram_tagger.evaluate(test_sents)
0.8120203329014253
#一般的N-gram的標註
bigram_tagger = nltk.BigramTagger(train_sents)
bigram_tagger.tag(brown_sents[2007])
[(u'Various', u'JJ'),
(u'of', u'IN'),
(u'the', u'AT'),
(u'apartments', u'NNS'),
bigram_tagger.evaluate(test_sents)
0.10276088906608193
當n越大時,上下文的特異性就會增加,要標註的資料中包含訓練資料中不存在的上下文的機率也增加.這被稱為資料稀疏問題,在NLP中是相當普遍的.因此,研究結果的精度和覆蓋範圍之間需要有一個權衡(這與資訊檢索中的精度/召回權衡有關)
#組合標註器
解決精度和覆蓋範圍之間權衡的一個辦法是儘可能地使用更精確的演算法,但卻在很多時候卻遜於覆蓋範圍更廣的演算法.如組合bigram標註器和unigram標註器和一個預設標註器.
- 嘗試使用bigram標註器標註識別符號
- 如果bigram標準器無法找到標記,嘗試unigram標註器
- 如果unigram標註器也無法找到標記,使用預設標註器
t0 = nltk.DefaultTagger('NN')
t1 = nltk.UnigramTagger(train_sents, backoff=t0) #回退
t2 = nltk.BigramTagger(train_sents, backoff=t1)
t2.evaluate(test_sents)
0.844911791089405
#標註生詞
方法是回退到正則表示式標註器或預設標註器
#儲存標註器
沒有必要重複訓練標註器,可將一個訓練好的標註器儲存到檔案為以後重複使用.將標註器t2儲存到檔案t2.pkl
from cPickle import dump
output = open('t2.pkl', 'wb')
dump(t2, output, -1)
output.close()
from cPickle 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', u'AT'),
("board's", u'NN$'),
('action', 'NN'),
('shows', u'NNS'),
('what', u'WDT'),
('free', u'JJ'),
#效能限制
n-gram標註器效能的上限是什麼?參考trigram標註器.
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_tags, test_tags)
分析標註器效能界限的另一種方式是人工標註者經過商討得到的
#跨句子邊界標註
brown_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)
0.844911791089405
六 基於轉換的標註
n-gram標註器存在的一個潛在的問題是n-gram表的大小(或語言模型)。如果將各種語言技術的標註器部署在移動計算裝置上,在模型大小和標註器效能之間取得平衡是很重要的。
第二個問題是關於上下文的。
在本節中,我們利用Brill標註,它是一種歸納標註方法,效能好,使用的模型僅有n-gram標註器的很小一部分
七 如何確定一個詞的分類
形態學線索
句法線索
語義線索
新詞
詞性標記集中的形態學