python nltk學習——分類和標註詞彙

英文文檔 http://www.nltk.org/book/
中文文檔 https://www.bookstack.cn/read/nlp-py-2e-zh/0.md
以下編號按個人習慣

Categorizing and Tagging Words(分類和標註詞彙)

1 Using a Tagger(使用標註器)

詞性標註器處理一個單詞序列,爲每個詞附加一個詞性標記。
nltk中提供了標註器pos_tag(),函數參數爲詞彙列表。

text = nltk.word_tokenize("And now for something completely different")
tag_result = nltk.pos_tag(text)
# 詞性情況。cc-並列連詞,RB-副詞,IN-介詞,NN-名詞,JJ-形容詞
print(tag_result)

2 Tagged Corpora(已經被標記的語料庫)

首先使用元組,例如(詞符,標記),來表示一個已標註的詞符。str2tuple()函數將一個已標註的詞符的字符串,例如 詞彙/標記,轉換成元組。示例如下:

tagged_token = nltk.tag.str2tuple('fly/NN')
print(tagged_token)         # ('fly', 'NN')
print(tagged_token[0])      # fly
print(tagged_token[1])      # NN

除了可以從一個短字符串構造出元組,還可以將一個長字符串構造成已標註的詞符的列表。需要遍歷長字符串。代碼如下:

 # 三引號可以換行寫代碼
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/PPS
said/VBD ``/`` ARE/BER well/QL operated/VBN and/CC follow/VB generally/RB
accepted/VBN practices/NNS which/WDT inure/VB to/IN the/AT best/JJT
interest/NN of/IN both/ABX governments/NNS ''/'' ./.
'''
# split()不帶參數,會把所有的空格(空格符,製表符,換行符)當做分隔符
tagged_token_1 = [nltk.tag.str2tuple(string) for string in sent.split()]
print(tagged_token_1)

3 Reading Tagged Corpora(讀取已標註的語料庫)

只要語料庫中包含了已標註的文本,可以使用nltk語料庫提供的tagged_words()方法得到。

# 讀取已標註的語料庫
tagged_words_1 = nltk.corpus.brown.tagged_words()
print(tagged_words_1)
# 由於並非所有的語料庫都採用同一組標記,因此可以指定tagset參數爲universal來獲取以通用詞性標記的詞彙列表
tagged_words_2 = nltk.corpus.brown.tagged_words(tagset='universal')
print(tagged_words_2)

4 Unsimplified Tags(未簡化的標記)

dict(字典):dict是一個可變的數據類型,格式爲{key:value,key:value},dict的key必須是不可變的數據類型,且value的數據類型任意。注意鍵值對若是字符串用單引號。

5 Mapping Words to Properties Using Python Dictionaries(使用python字典映射單詞到屬性)

此章節主要是使用字典。字典中的key不能重複,且key是不可變的類型。
首先基本的建立字典,其中有兩種方法來定義一個字典(第一個常用)。

# 使用鍵值對格式來創建一個字典
pos = {'colorless': 'ADJ', 'ideas': 'N', 'sleep': 'V', 'furiously': 'ADV'}
pos = dict(colorless='ADJ', ideas="N", sleep="V", furiously="ADV")

向其中填充鍵值對

# 填充鍵值對
pos = {}
print(pos)    # {}
pos['colorless'] = 'ADJ'
print(pos)      # {'colorless': 'ADJ'}
pos['ideas'] = 'N'
pos['sleep'] = 'V'
print(pos)    # {'colorless': 'ADJ', 'ideas': 'N', 'sleep': 'V'}

字典中檢索是通過鍵來檢索的

# 使用鍵來檢索字典
value_1 = pos['ideas']
print(value_1)    # N
# 當我們檢索一個沒有分配值的鍵,就會拋紅
print(pos['green'])     # KeyError: 'green'

上述代碼中我們試圖訪問一個不在字典中的鍵,會得到一個錯誤。然而,如果希望一個字典能爲這個新鍵自動創建一個條目並給它一個默認值,如 0 或者一個空鏈表或者我們自定義的值,這也是可以實現的。

# 定義一個默認字典,且默認值的類型是int
frequency = defaultdict(int)
frequency['colorless'] = 9
print(frequency['ideas'])  # 0

# 定義一個默認字典,value類型爲list
pos = defaultdict(list)
pos['sleep'] = ['NOUN', 'VERB']
# 自動創建的新鍵對應的value默認爲[]
print(pos['ideas'])       # []
print(pos['sleep'])       # ['NOUN', 'VERB']

# 當我們想讓默認值是我們自定義的值時,需要提供一個無參有返回值的函數。
pos1 = defaultdict(lambda: 'DEFAULT_VALUE')
print(pos1['blog'])   # DEFAULT_VALUE

找到字典中的鍵,可以通過將字典轉換爲列表。有三種方法如下:

# 將一個字典轉換爲list,得到的list是字典中key的列表
pos_key_list = list(pos)
# 對一個字典進行排序,得到的結果是個列表,並且列表中的內容是key的有序列表
pos_key_sorted_list = sorted(pos)
# 針對字典的for循環,都是對key的遍歷。字典for循環的結果是個列表
pos_loop_list = [w for w in pos if w.endswith('s')]

學習下字典中的方法keys(),values(),items()。另外可以使用sorted(pos.items())對其進行排序,按它們的第一個元素排序(如果第一個元素相同,就使用它們的第二個元素)

# pos.keys()獲得單獨的鍵列表
print(pos.keys())         # dict_keys(['colorless', 'ideas', 'sleep'])
print(list(pos.keys()))   # ['colorless', 'ideas', 'sleep']
# 獲得單獨的值列表
print(pos.values())       # dict_values(['ADJ', 'N', 'V'])
# 獲得鍵值對列表
print(pos.items())        # dict_items([('colorless', 'ADJ'), ('ideas', 'N'), ('sleep', 'V')])

由於在nlp中,將相同字母組成的單詞積累到一起,是很常用的任務。因此nltk提供了更方便的實現方式,如下:

# 相同字母組成的單詞積累到一起。使用字典
def anagram_dictionary():
    anagrams = defaultdict(list)
    words = nltk.corpus.words.words('en')
    for word in words:
        # key爲單詞中字母的有序排列,增序
        key = ''.join(sorted(word))
        anagrams[key].append(word)
    # ------------------------------------------------
    
    # 上述幾行代碼等價於下一行代碼。nltk提供了一個更方便的方式
    anagrams_1 = nltk.Index((''.join(sorted(word)), word) for word in words)
    return anagrams

反轉字典的三個方式,及其反轉結果。

 tagged_words_1 = nltk.corpus.brown.tagged_words(categories='news', tagset='universal')
# 這種反轉的方式,會將value值相同的項覆蓋,只留下一個
tagged_words_2 = dict((value, key) for (key, value) in tagged_words_1)

# 以下方式會將相同value的key連成list
tagged_words_3 = defaultdict(list)
for key, value in tagged_words_1:
    tagged_words_3[value].append(key)
    
# nltk的索引,可以實現反轉。其實就是將相同的key的值,積累到一起
tagged_words_4 = nltk.Index((value, key) for (key, value) in tagged_words_1)

print(tagged_words_2['NOUN'])
print(tagged_words_3['NOUN'])
print(tagged_words_4['NOUN'])

6 Automatic Tagging(自動標註)

6.1 The Default Tagger(默認標註器)

默認標註器可以幫助我們提高語言處理系統的穩定性

# 創建一個將所有詞都標註成tag的標註器
def default_tagger_TAG(tag):
    raw = 'I do not like green eggs and ham, I do not like them Sam I am!'
    tokens = nltk.word_tokenize(raw)
    # 定義一個默認的標註器。標註內容爲tag
    default_tagger = nltk.DefaultTagger(tag)
    # 標註一個詞彙列表
    tokens_tagged = default_tagger.tag(tokens)
    print(tokens_tagged)
    return default_tagger

若想知道定義的默認標註器正確標註了多少詞彙,可以使用evaluate()方法,如下:

get_default_tagger = default_tagger_TAG(max_tag)
# 將一句話中的詞按上下文的內容意思來進行標註
brown_tagged_sents = nltk.corpus.brown.tagged_sents(categories='news')
right = get_default_tagger.evaluate(brown_tagged_sents)
print(right)     # 0.13089484257215028

6.2 The Regular Expression Tagger(正則表達式標註器)

正則表達式標註器基於匹配模式分配標記給詞。例如,我們可能會猜測任一以 ed 結尾的詞都是動詞過去分詞,任一以’s 結尾的詞都是名詞所有格。

# 正則表達式標註器
def regular_expression_tagger():
    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')]
    brown_sents = nltk.corpus.brown.sents(categories='news')
    brown_tagged_sents = nltk.corpus.brown.tagged_sents(categories='news')
    # 創建一個正則表達式標註器。匹配規則按patterns
    regexp_tagger = nltk.RegexpTagger(patterns)
    # 標註
    brown_sents_tag = regexp_tagger.tag(brown_sents[3])
    print(brown_sents_tag)
    # 計算標註正確情況
    right = regexp_tagger.evaluate(brown_tagged_sents)    
    print(right)   # 0.20186168625812995

6.3 The Lookup Tagger(查詢標註器)

# 查詢標註器
def lookup_tagger():
    brown_words = nltk.corpus.brown.words(categories='news')
    brown_sents = nltk.corpus.brown.sents(categories='news')
    brown_tagged_words = nltk.corpus.brown.tagged_words(categories='news')
    brown_tagged_sents = nltk.corpus.brown.tagged_sents(categories='news')

    fd = nltk.FreqDist(brown_words)
    cfd = nltk.ConditionalFreqDist(brown_tagged_words)

    most_freq_words = fd.most_common(100)
    # 最可能的標記,即最頻繁的100個詞的標記
    likely_tags = dict((word, cfd[word].max()) for (word, _) in most_freq_words)
    # backoff回退,用來指定默認標註器。當使用model指定的標記不能給一個詞分配標記時,就使用默認標註器
    baseline_tagger = nltk.UnigramTagger(model=likely_tags,backoff=nltk.DefaultTagger('NN'))
    right = baseline_tagger.evaluate(brown_tagged_sents)
    print(right)   # 0.5817769556656125

    print(baseline_tagger.tag(brown_sents[3]))

查詢標註器隨着模型規模的增長,最初的性能增加迅速,最終達到一個穩定水平,此時模型的規模大量增加時,性能提高很小。

7 N-Gram Tagging(n-gram標註)

7.1 Unigram Tagging(一元標註器)

一元標註器需要通過訓練,然後才能用來標註。一元標註器依據指定的已標註的句子,檢查每個詞的標記,統計出每個詞最可能的標記,並將這些存儲在標註器內部的一個字典中。一元標註器的行爲類似查找標註器。

# 一元標註器。行爲類似查找標註器
def unigram_tagging():
    # 訓練一個一元標註器,通過指定的已標註的句子數據作爲參數。
    # 在這個訓練過程中,統計了每個詞的標記,將所有詞的最可能的標記存儲在一個字典中,這個過程由nltk提供的初始化函數完成
    unigram_tagger = nltk.UnigramTagger(brown_tagged_sents)
    # 依據標註器內部訓練好的字典,進行標註
    tagged_sent = unigram_tagger.tag(brown_sents[2007])
    print(tagged_sent)
    right = unigram_tagger.evaluate(brown_tagged_sents)
    print(right)

爲保證標註器的質量,最好將數據集分爲訓練集和測試集,在初始化標註器使用訓練集,評估標註器使用測試集。

# 90%訓練,10%測試
size = int(len(brown_tagged_sents) * 0.9)
train_sents = brown_tagged_sents[:size]
test_sents = brown_tagged_sents[size:]

7.2 General N-Gram Tagging(一般的n-gram標註)

n-gram標註器考慮第n個詞和前n-1個詞的標記,從而給出第n個詞最可能的標記。如下,即一直wn,t1,t2,t3…tn-1,求tn
在這裏插入圖片描述
當n越大,上下文的特異性就增加,要進行標註的數據中,不在訓練數據中的機率增大,即數據稀疏問題。
以二元標註器爲例,代碼如下:

# 一般的n-gram標註
def general_nGram_tagging():
    # 以下測試二元標註器。此標註器能夠標註訓練中看到過的句子中的所有詞,但對沒見過的句子表現差。即遇到新詞無法分配標記
    # 因爲n-gram標註器,根據第n個詞和前n-1個詞的標記,來求得第n個詞的標記。必須要條件匹配,才能確定第n個詞的標記
    bigram_tagger = nltk.BigramTagger(train_sents)
    tag_sents = bigram_tagger.tag(brown_sents[2007])
    print(tag_sents)

    # 測試不在訓練數據中的數據的標註情況
    unseen_sent = brown_sents[4203]
    tag_sent_2 = bigram_tagger.tag(unseen_sent)
    print(tag_sent_2)

    right = bigram_tagger.evaluate(test_sents)
    print(right)    # 0.10206319146815508

7.3 Combining Taggers(組合標註器)

解決精度和覆蓋範圍之間的權衡的辦法就是儘可能的使用更精確的算法。可以組合二元標註器、一元注器和一個默認標註器。代碼如下:

# 組合標註器
def combining_taggers():
    # 以下組合了二元標註器、一元標註器、默認標註器
    # 首先嚐試使用二元標註器標註標識符。
    # 如果二元標註器無法找到一個標記,嘗試一元標註器。
    # 如果一元標註器也無法找到一個標記,使用默認標註器。
    tag_0 = nltk.DefaultTagger('NN')
    tag_1 = nltk.UnigramTagger(train_sents, backoff=tag_0)
    # 爲保持二元標註器儘可能的小,指定了cutoff的值,那麼標註器會丟棄那些出現次數小於等於cutoff的實例。
    tag_2 = nltk.BigramTagger(train_sents, cutoff=2, backoff=tag_1)
    right = tag_2.evaluate(test_sents)
    print(right)  # 0.8424200139539519

7.4 Storing Taggers(存儲標註器)

將一個訓練好的標註器保存到一個文件中,以便以後能重複使用
保存標註器代碼如下:

# 存儲標註器。將一個訓練好的標註器保存到一個文件中,以便以後能重複使用
def store_tagger():
    tag_0 = nltk.DefaultTagger('NN')
    tag_1 = nltk.UnigramTagger(train_sents, backoff=tag_0)
    tag_2 = nltk.BigramTagger(train_sents, backoff=tag_1)
    from pickle import dump
    output = open('tag_2.pkl', 'wb')
    dump(tag_2, output, -1)
    output.close()

載入標註器代碼如下:

# 載入標註器
def get_tagger():
	from pickle import load
    input = open('tag_2.pkl', 'rb')
    tagger = load(input)
    input.close()
    return tagger

8 Transformation-Based Tagging(基於轉換的標註)

Brill標註是一種歸納標註方法,基於轉換的學習。不計數觀察結果,只編制一個轉換修正規則列表。Brill想法是以大筆畫開始,然後修復細節,一點點的細緻的改變。
規則類似於:Replace NN with VB when the previous word is TO;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章