最近一直在閱讀《Natural Language Processing with Python》,在閱讀該書的6.2節的Sentence Segmentation時,發現錯誤比較多。現在記錄下來,也許可以幫到其他人,也許以後可以整理一下給該書作者發個郵件。
在說明問題之前,在這裡先列出我的軟體版本:
Python:2.5
Nltk:2.0b2
本節內容簡介
Sentence Segmentation(我不知道如何翻譯,下文中稱作“句子劃分”)把一段文字分成一組句子,由於句子的結尾一般有比較特殊的標點,所以句子劃分可以被看做一個針對標點符號的分類問題,即當我們遇到一個標點符號時,判斷它是不是一個句子的結束。
本節中對於句子劃分任務用的方法是:利用監督學習之樸素Bayes方法,和一般監督學習演算法一樣,其基本步驟為:
1,資料預先處理:把資料整理成一種合適的格式,這樣就便於下一步的特徵提取。
>>> sents = nltk.corpus.treebank_raw.sents()<br />>>> tokens = []<br />>>> boundaries = set()<br />>>> offset = 0<br />>>> for sent in nltk.corpus.treebank_raw.sents():<br />... tokens.extend(sent)<br />... offset += len(sent)<br />... boundaries.add(offset-1)
2,特徵提取:提取一些比較有分辨能力的特徵。
>>> def punct_features(tokens, i):<br />... return {'next-word-capitalized': tokens[i+1][0].isupper(),<br />... 'prevword': tokens[i-1].lower(),<br />... 'punct': tokens[i],<br />... 'prev-word-is-one-char': len(tokens[i-1]) == 1}
3,準備訓練資料和測試資料
>>> featuresets = [(punct_features(tokens, i), (i in boundaries))<br />... for i in range(1, len(tokens)-1)<br />... if tokens[i] in '.?!']<br />>>> size = int(len(featuresets) * 0.1)<br />>>> train_set, test_set = featuresets[size:], featuresets[:size]
4,訓練:利用樸素貝葉斯演算法訓練。
classifier = nltk.NaiveBayesClassifier.train(train_set)
5,測試:測試訓練出來的分類器效果如何。
>>> nltk.classify.accuracy(classifier, test_set)<br />0.97419354838709682
那麼如何用該分類器來對一段文字進行句子劃分呢?本節用的方法就是檢查每個標點,判斷它是不是句子的邊界,如果是就把文字從該標點出分開。
def segment_sentences(words):<br /> start = 0<br /> sents = []<br /> for i, word in words:<br /> if word in '.?!' and classifier.classify(words, i) == True:<br /> sents.append(words[start:i+1])<br /> start = i+1<br /> if start < len(words):<br /> sents.append(words[start:])
錯誤之處及改正方法
下面我將指出該節代碼中的錯誤之處,並給出相應的解決方案。
問題1:特徵提取函數可能產生越界。
特徵提取函數中的代碼:
'next-word-capitalized': tokens[i+1][0].isupper()
有可能越界,即如果i是這tokens序列的最後一個字元的索引值,那麼上面的代碼就會越界。而且這種情況會經常發生,因為段文字的最後一個字元通常是“.?!”(對英文而言)中的一個,所以一定會執行改行代碼。
解決辦法:因為一般在句尾遇到“.?!”標點之一時,標誌的一句話的結束,那我們就捕獲該異常並把“next-word-capitalized”的值設定為True。
問題2及解決方案:一個小的列印錯誤,書中忘了把對要分類的資料進行特徵提取。
“segment_sentence”函數中第4行的“classifier.classify(words, i) == True”應該改為“classifier.classify(punct_features(words, i))”
問題3:segment_sentence函數才執行完後,我如何才能獲得句子劃分的結果?由於儲存劃分結果的sents變數是一個局部變數,在執行完後,我們在函數外邊是得不到結果的,而且其內部也沒有列印該結果。
解決辦法:在函數結尾加上一句“return sents”,讓其把劃分結果返回。
問題4:當我調用“classifier.show_most_informative_features()”時,提示如下錯誤:
“File "E:/編程工具/py2.5/lib/site-packages/nltk/classify/naivebayes.py", line 144, in show_most_informative_features“
“TypeError: 'bool' object is unsubscriptable”
我針對該錯誤,找到檔案“naivebayes.py”,其144行語句是:
print ('%24s = %-14r %6s : %-6s = %s : 1.0' %
(fname, fval, l1[:6], l0[:6], ratio))
解決辦法:由於,我們的分類任務的類別只有兩個,即True和False,在代碼中即用l1和l0來表示,由於在144行對其執行了下標操作,從而導致了前面的錯誤。我猜想nltk作者可能假設所有類別都是字串,他們為了在輸出的時候保持格式較短,才在這裡做了下標操作。所以,我的解決辦法是在對l1和l0進行下標操作之前把其轉化為字串,即把上面的代碼改為:
print ('%24s = %-14r %6s : %-6s = %s : 1.0' %
(fname, fval, str(l1)[:6], str(l0)[:6], ratio))
至此,我解決了我遇到的問題,我才可以放心的進行下面章節的學習。