關於分類文本,有三個問題
- 怎么識別出文本中用於明顯分類的特征
- 怎么構建自動分類文本的模型
- 相關的語言知識
按照這個思路,博主進行了艱苦學習(手動捂臉。。)
一、監督式分類:建立在訓練語料基礎上的分類
訓練過程中,特征提取器將輸入轉化為特征集,並且記錄對應的正確分類。生成模型。預測過程中,未見過的輸入被轉換特征集,通過模型產生預測標簽。
特征提取器和朴素貝葉斯分類器
特征提取器返回字典,這個字典被稱為特征集。然后利用
nltk自帶的朴素貝葉斯分類器NaiveBayesClassifier
生成分類器。並且可以用nltk.classify.accuracy(分類器,測試集)
測試准確度。
import nltk
from nltk.corpus import names
import random
def gender_features(word): #特征提取器
return {'last_letter':word[-1]} #特征集就是最后一個字母
names = [(name,'male') for name in names.words('male.txt')]+[(name,'female') for name in names.words('female.txt')]
random.shuffle(names)#將序列打亂
features = [(gender_features(n),g) for (n,g) in names]#返回對應的特征和標簽
train,test = features[500:],features[:500] #訓練集和測試集
classifier = nltk.NaiveBayesClassifier.train(train) #生成分類器
print('Neo is a',classifier.classify(gender_features('Neo')))#分類
print(nltk.classify.accuracy(classifier,test)) #測試准確度
classifier.show_most_informative_features(5)#得到似然比,檢測對於哪些特征有用
當然,當我們訓練大的語料庫的時候,鏈表會占用很大內存。這時候nltk提供了:
apply_features
,會生成鏈表,但是不會在內存中存儲所有對象。
from nltk.classify import apply_features
train_set = apply_features(gender_features,names[500:])
test_set = apply_features(gender_features,names[:500])
過擬合:當特征過多
當特征過多的時候(特征集的鍵值過多),會對一般化的新例子不起作用,稱為過擬合。如果抉擇特征集的大小,需要不停的測試,找到最吻合的特征集。
錯誤分析
為了使特征提取器准確度更高,一般將源數據分為兩大部分,三小部分:
- 開發集:
- 訓練集:負責開發
- 開發測試集:負責錯誤分析
- 測試集:負責最終評估
下面是查找報錯信息的案例:
import nltk
from nltk.corpus import names
import random
def gender_features(word): #特征提取器
return {'last_letter':word[-1]} #特征集就是最后一個字母
names = [(name,'male') for name in names.words('male.txt')]+[(name,'female') for name in names.words('female.txt')]
train_names = names[1500:]
devtest_names = names[500:1500]
test_names = names[:500]
train_set = [(gender_features(n),g) for (n,g) in train_names]
devtest_set = [(gender_features(n),g) for (n,g) in devtest_names]
test_set = [(gender_features(n),g) for (n,g) in test_names]
classifier = nltk.NaiveBayesClassifier.train(train_set)
print(nltk.classify.accuracy(classifier,devtest_set))
######################記錄報錯的案例###############################
errors = []
for (name,tag) in devtest_names:
guess = classifier.classify(gender_features(name))
if guess!=tag:
errors.append((tag,guess,name))
##################################################################
我們發現准確度低,因為倒數第二個字母也很有關聯。所以我們可以改進特征提取器:
def gender_features(word): #特征提取器
return {'last_letter':word[-1],'last__letter':word[-2]} #特征集就是最后一個字母和倒數第二個字母
觀察結果,發現,准確度提高了12%。重復這個過程,使得特征提取器更加完善。
二、實例:文本分類和詞性標注
文本分類
這里的分類標簽選成詞匯,通過對文本前N個詞的觀察,得到預測標簽。
import nltk
from nltk.corpus import movie_reviews
all_words = nltk.FreqDist(w.lower() for w in movie_reviews.words())
word_features = all_words.most_common(2) #前兩千個最常出現的單詞
def document_features(document):
document_words = set(document)
features = {}
for (word,freq) in word_features:
features['contains(%s)'%word] = (word in document_words) #參數文檔中是否包含word:True/False
return features
documents = [(list(movie_reviews.words(fileid)),category) for category in movie_reviews.categories() for fileid in movie_reviews.fileids(category)]
random.shuffle(documents)
features = [(document_features(d),c)for (d,c) in documents]
train_set,test_set = features[100:],features[:100]
classifier = nltk.NaiveBayesClassifier.train(train_set)
print(nltk.classify.accuracy(classifier,test_set))
詞性標注:“決策樹”分類器
這里的分類器是決策樹分類器:
DecisionTreeClassifier
。可以通過classifier.pseudocode(depth = ?)
這查詢深度為depth的樹,並且打印出來。順便表示,我再走下面的程序的時候,電腦炸了。建議在集群上運行。
from nltk.corpus import brown
import nltk
suffix_fdist = nltk.FreqDist()
for word in brown.words():
word = word.lower()
#suffix_fdist.inc(word[-1:]) python2
suffix_fdist[word[-1:]] += 1 #python3
suffix_fdist[word[-2:]] += 1
suffix_fdist[word[-3:]] += 1
common_suffixes = suffix_fdist.most_common(100) #獲得常見特征鏈表
#定義特征提取器:
def pos_features(word):
features = {}
for (suffix,times) in common_suffixes:
features['endswith(%s)' % suffix] = word.lower().endswith(suffix)
return features
tagged_words = brown.tagged_words(categories='news')
featuresets = [(pos_features(n),g)for (n,g) in tagged_words]
size = int(len(featuresets)*0.1)
train_set , test_set= featuresets[size:], featuresets[:size]
classifier = nltk.DecisionTreeClassifier.train(train_set) #“決策樹分類器”
print(nltk.classify.accuracy(classifier,test_set))
三、更近一步的連續分類或貪婪序列分類:在朴素貝葉斯和“決策樹”之后
這種分類模型是為了獲取相關分類之間的依賴關系。為第一個輸入找到最佳標簽,然后再次基礎上找到對應的下一個輸入的最佳標簽。不斷重復,以至所有輸入都被貼上標簽。所以,我們需要提供一個參數history,用來擴展特征。
事實證明,我的電腦又炸了。
利用聯合分類器模型進行詞性標注:
import nltk
from nltk.corpus import brown
#帶有歷史的特征提取器
def pos_features(sentence,i,history):
features = {'suffix(1)':sentence[i][-1:],\
'suffix(2)':sentence[i][-2:],\
'suffix(3)':sentence[i][-3:]}
if i==0:#當它在分界線的時候,沒有前置word 和 word-tag
features['prev-word'] = '<START>'
features['prev-tag'] = '<START>'
else:#記錄前面的history
features['prev-word'] = sentence[i-1]
features['prev-tag'] = history[i-1]
return features
'''
###########流程式###############
tagged_sents = brown.tagged_sents(categories="news")
size = int(len(tagged_sents)*0.1)
train_sents,test_sents = tagged_sents[size:],tagged_sents[:size]
train_set = []
for tagged_sent in train_sents:
untagged_set = nltk.tag.untag(tagged_sent)
history = []
for i,(word,tag) in enumerate(tagged_sent):
featureset = pos_features(untagged_set,i,history)
history.append(tag)
train_set.append((featureset,tag))
classifier = nltk.NaiveBayesClassifier.train(train_set)
'''
#########類思想重寫##################
class ConsecutivePosTagger(nltk.TaggerI): #這里定義新的選擇器類,繼承nltk.TaggerI
def __init__(self,train_sents):
train_set = []
for tagged_sent in train_sents:
untagged_set = nltk.tag.untag(tagged_sent) #去標簽化
history = []
for i,(word,tag) in enumerate(tagged_sent):
featureset = pos_features(untagged_set,i,history)
history.append(tag) #將tag添加進去
train_set.append((featureset,tag)) #拿到了訓練集
self.classifier = nltk.NaiveBayesClassifier.train(train_set) #創建訓練模型
def tag(self,sentence): #必須定義tag方法
history = []
for i,word in enumerate(sentence):
featureset = pos_features(sentence,i,history)
tag = self.classifier.classify(featureset)
history.append(tag)
return zip(sentence,history)
tagged_sents = brown.tagged_sents(categories="news")
size = int(len(tagged_sents)*0.1)
train_sents,test_sents = tagged_sents[size:],tagged_sents[:size]
#print(train_sents)
tagger = ConsecutivePosTagger(train_sents)
print(tagger.evaluate(test_sents))
四、評估
之前我們選擇測試集和開發集,都是在一個原有集合下。這樣,示例相似程度很大,不利於推廣到其他數據集。而評估最簡單的度量就是准確度,即:
accuracy()
函數。除了這個,精確度、召回率和F-度量值也確實影響了准確度。
- 精確度:發現項目中多少是相關的。TP/(TP+FP)
- 召回率:表示相關項目發現了多少。TP(TP+FN)
- F-度量值:精確度和召回率的調和平均數。
其中,T:true;P:Positive;F:false;N:negative。組合即可。例如TP:真陽性(正確識別為相關的),TN:真陰性(相關項目中錯誤識別為不想關的)
五、三種分類器的總結
之前我們發現。同樣的特征集,朴素貝葉斯分類器就可以輕松跑完,但是決策樹分類器不行。除了過擬合的因素外,還是因為樹結構強迫特征按照特定的順序檢查,即便他是重復的,而在回溯的過程中,又有重復運算,導致時間和空間的雙重浪費。
朴素貝葉斯分類器允許所有恩正“並行”起作用,從計算每個標簽的先驗概率開始。並且建立朴素貝葉斯的時候采用了平滑技術(在給定的貝葉斯模型上)。
最后的最大熵分類器,使用搜索技術找出一組能最大限度的提高分類器性能的參數。由於他會用迭代優化技術選擇參數,花費時間很長。
六、后記
努力地看書了,然而還是沒有看懂。感覺是因為相應的數學知識和算法知識沒到位。以后積累充足會重看。
當然,對於現在用的層面來說,較深入的了解原理,基本可以解決大多數問題。但是要是做到算法優化,還是要自己去調參,或者改進算法。
由於博主水平有限,希望各路大牛不li賜教。
歡迎進一步交流本博文相關內容:
博客園地址 : http://www.cnblogs.com/AsuraDong/
CSDN地址 : http://blog.csdn.net/asuradong
也可以致信進行交流 : xiaochiyijiu@163.com
歡迎轉載 , 但請指明出處 : )