<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Ch5 Categorizing and Tagging Words
本章的目標是回答這些問題
Step1:  上面的範例中，CC是對等連接詞、RB是副詞、IN是介系詞、NN是名詞、JJ則是形容詞。如果想知道詳細的tag定義，可以用nltk.help.upenn_tagset('RB')來查詢。
Tagged Corpora
在NLTK的習慣上，tagged token會表示成由單字和tag結合的tuple，但是儲存在corpus中的資料，則是一個字串包含單字和tag，中間以'/'分隔，例如'fly/NN'。利用str2tuple可以從corpus中的字串轉成tuple。
Step2:  corpus中也有tagged sentences
Step3:  Mapping Words to Properties
要儲存mapping資料，最自然的方法就是用dictionary，又稱為associative array或hash array。一般的陣列是用整數作為index，但dictionary中則是用任何hasable的資料作為index，例如字串、tuple等等。
mapping資料的應用有
Step4:  Default Dictionary
如果試著存取不存在的key，會造成錯誤。defaultdict可以使存取不存在的key時，自動新增一個預設值。
Step5:  Inverting a Dictionary
dict的設計是用key來查詢value，如果你希望用value來查詢key，速度會很慢。一種簡單的解法是用(value,key)重新產生一個dict，就可以用value查詢key。
Step6:  更簡單的方法
Step7:  Summary Dictionary Methods
d = {}
Step8:  Default Tagger
第一步，先準備一個預設的tagger，統計所有part-of-speech中，那一種詞性出現頻率最高，就視為預設值。
Step9:  Regular Expression Tagger
用經驗判斷那些字尾可能是什麼詞性，然後用regular expression寫成條件。
Step10:  Unigram Tagger
Step11:  unigram tagger是統計每個字最常出現的詞性，因此訓練資料越大，就會越準確。但遇到沒看過的字，就會傳回None。因此需要設backoff，當unigram tagger無法判斷時，用另一個tagger來輔助。
Step12:  兩個重點
Step13:  Storing Taggers
因為training tagger很花時間，所以儲存結果是必要的。利用cPickle.dump來寫出binary格式的物件
Step14:  Confusion Matrix
<ASSISTANT_TASK:>
Python Code:
import nltk
text = nltk.word_tokenize("And now for something completely different")
nltk.pos_tag(text)
Explanation: Ch5 Categorizing and Tagging Words
本章的目標是回答這些問題:
什麼是lexical categories? 它們如何應用在NLP中?
要儲存單字和分類的資料結構是什麼?
如何自動為每個單字分類?
本章會提到一些基本的NLP方法，例如sequence labeling、n-gram models、backoff、evaluation。
辨識單字的part-of-speech(詞性)並標記的過程稱為tagging，或稱part-of-speech tagging、POS tagging。在一般的NLP流程中，tagging是接在tokenization後面。part-of-speech又稱為word class或lexical category。而可供選擇的tag集合稱為tagset。
Using a Tagger
End of explanation
nltk.tag.str2tuple('fly/NN')
# tagged_words() 是一個已經表示成tuple形態的資料
nltk.corpus.brown.tagged_words()
# 用參數 tagset='universal' 可以換成簡單的tag
nltk.corpus.brown.tagged_words(tagset='universal')
# 利用 FreqDist 統計詞性的數量
tag_fd = nltk.FreqDist(tag for (word, tag) in nltk.corpus.brown.tagged_words(tagset='universal'))
tag_fd.most_common()
%matplotlib inline
tag_fd.plot()
tag_cd = nltk.ConditionalFreqDist(nltk.corpus.brown.tagged_words(tagset='universal'))
# 查詢某單字的常用POS
tag_cd['yield']
Explanation: 上面的範例中，CC是對等連接詞、RB是副詞、IN是介系詞、NN是名詞、JJ則是形容詞。如果想知道詳細的tag定義，可以用nltk.help.upenn_tagset('RB')來查詢。
Tagged Corpora
在NLTK的習慣上，tagged token會表示成由單字和tag結合的tuple，但是儲存在corpus中的資料，則是一個字串包含單字和tag，中間以'/'分隔，例如'fly/NN'。利用str2tuple可以從corpus中的字串轉成tuple。
End of explanation
nltk.corpus.brown.tagged_sents(tagset='universal')[0]
Explanation: corpus中也有tagged sentences:
End of explanation
pos = {}  # 在python中定義dictionary最簡單的方法
pos['hello'] = 'world'
pos['right'] = 'here'
pos
[w for w in pos]  # 用for的時候會找出key
pos.keys()
pos.items()
pos.values()
pos = dict(hello = 'world', right = 'here')  # 另一種定義方式
pos
Explanation: Mapping Words to Properties
要儲存mapping資料，最自然的方法就是用dictionary，又稱為associative array或hash array。一般的陣列是用整數作為index，但dictionary中則是用任何hasable的資料作為index，例如字串、tuple等等。
mapping資料的應用有:
書的index: 將單字mapping到頁數
thesaurus: 將字義mapping到一組同義字
字典: 將單字mapping到單字的解釋
比較字集: 將單字mapping到多國語言的單字
End of explanation
f = nltk.defaultdict(int)
f['color'] = 4
f
f['dream']  # dream不存在，但查詢時會自動新增
f  # 查詢dream後，就直接新增了一個dream
f = nltk.defaultdict(lambda: 'xxx')
f['hello'] = 'world'
f
f['here'] = f['here'] + 'comment'
f
Explanation: Default Dictionary
如果試著存取不存在的key，會造成錯誤。defaultdict可以使存取不存在的key時，自動新增一個預設值。
End of explanation
old = dict(nltk.corpus.brown.tagged_words()[:100])
new = dict((value, key) for (key, value) in old.items())
new['JJ']  # 雖然成功的反相，但只能查出最後輸入的字
new2 = nltk.defaultdict(list)  # 當key不存在時，都視為empty list
for (key, value) in old.items():
    new2[value].append(key)
new2['JJ']
Explanation: Inverting a Dictionary
dict的設計是用key來查詢value，如果你希望用value來查詢key，速度會很慢。一種簡單的解法是用(value,key)重新產生一個dict，就可以用value查詢key。
End of explanation
new3 = nltk.Index((value, key) for (key, value) in old.items())
new3['JJ']
Explanation: 更簡單的方法: 利用nltk內建的函式。
End of explanation
from nltk.corpus import brown
brown_tagged_sents = brown.tagged_sents(categories='news')
brown_sents = brown.sents(categories='news')
Explanation: Summary Dictionary Methods
d = {}: 建立空的dict
d[key] = value: 為key指定新的value
d.keys(): 傳回list of keys
list(d): 傳回list of keys
d.values(): 傳回list of values
sorted(d): 傳回sorted list of keys
key in d: 如果d有包含key則傳回True
for key in d: 依序傳回每一個Key
d1.update(d2): 將d2的每個item複製到d1
defaultdict(int): 預設value為0的dict
defaultdict(list): 預設value為[]的dict
Automatic Tagging
End of explanation
tags = [tag for (word, tag) in brown.tagged_words(categories='news')]
nltk.FreqDist(tags).max()
default_tagger = nltk.DefaultTagger('NN')  # 因為NN頻率最高，所以未知詞性的情況一律當成NN
default_tagger.tag(nltk.word_tokenize('i like my mother and dog'))
# 當然預測的準確率很差，因為只有13%是真的NN
default_tagger.evaluate(brown_tagged_sents)
Explanation: Default Tagger
第一步，先準備一個預設的tagger，統計所有part-of-speech中，那一種詞性出現頻率最高，就視為預設值。
End of explanation
patterns = [
    (r'.*ing$', 'VBG'),
    (r'.*ed$', 'VBD'),
    (r'.*es$', 'VBZ'),
    (r'.*ould$', 'MD'),
    (r'.*\'s$', 'NN$'),
    (r'.*s$', 'NNS'),
    (r'^-?[0-9]+(.[0-9]+)?$', 'CD'),
    (r'.*', 'NN')
]
regexp_tagger = nltk.RegexpTagger(patterns)
regexp_tagger.tag(nltk.word_tokenize('i could be sleeping in 9 AM'))
regexp_tagger.evaluate(brown_tagged_sents)
Explanation: Regular Expression Tagger
用經驗判斷那些字尾可能是什麼詞性，然後用regular expression寫成條件。
End of explanation
unigram_tagger = nltk.UnigramTagger(brown.tagged_sents(categories='news')[:500])
unigram_tagger.tag(nltk.word_tokenize('i could be sleeping in 9 AM'))
Explanation: Unigram Tagger
End of explanation
unigram_tagger = nltk.UnigramTagger(brown.tagged_sents(categories='news')[:500],
                                   backoff = regexp_tagger)
unigram_tagger.evaluate(brown_tagged_sents[500:])
unigram_tagger = nltk.UnigramTagger(brown.tagged_sents(categories='news')[:4000],
                                   backoff = regexp_tagger)
unigram_tagger.evaluate(brown_tagged_sents[4000:])
Explanation: unigram tagger是統計每個字最常出現的詞性，因此訓練資料越大，就會越準確。但遇到沒看過的字，就會傳回None。因此需要設backoff，當unigram tagger無法判斷時，用另一個tagger來輔助。
End of explanation
bigram_tagger = nltk.BigramTagger(brown.tagged_sents(categories='news')[:4000])
bigram_tagger.tag(nltk.word_tokenize('i could be sleeping in 9 AM'))
bigram_tagger = nltk.BigramTagger(brown.tagged_sents(categories='news')[:4000],
                                 backoff=unigram_tagger)
bigram_tagger.evaluate(brown_tagged_sents[4000:])
Explanation: 兩個重點:
隨著training data增加，準確率也有提升，利用unigram最高可以到90%左右
記得將training/testing data分開，否則準確率是不準的
Bigram Tagger
統計兩個單字組成的bigram來作tagger。precision較高，但recall很低，一旦遇到不認識的字就馬上出現None。
End of explanation
from cPickle import dump
output = open('t2.pkl', 'wb')
dump(bigram_tagger, output, -1)
output.close()
from cPickle import load
input = open('t2.pkl', 'rb')
tagger = load(input)
input.close()
tagger.evaluate(brown_tagged_sents[4000:])
Explanation: Storing Taggers
因為training tagger很花時間，所以儲存結果是必要的。利用cPickle.dump來寫出binary格式的物件
End of explanation
brown_sents = brown.sents()
brown_tagged_sents = brown.tagged_sents(tagset = 'universal')
default_tagger = nltk.DefaultTagger('NOUN')
unigram_tagger = nltk.UnigramTagger(brown_tagged_sents[:4000], backoff=default_tagger)
bigram_tagger = nltk.BigramTagger(brown_tagged_sents[:4000], backoff=unigram_tagger)
unigram_tagger.tag(nltk.word_tokenize('I like your mother'))
test = [tag for sent in brown_sents[4000:] for (word, tag) in bigram_tagger.tag(sent)]
gold = [tag for sent in brown_tagged_sents[4000:] for (word, tag) in sent]
print nltk.ConfusionMatrix(gold, test)
Explanation: Confusion Matrix
End of explanation
<END_TASK>