1. 程式人生 > >使用NLTK的樸素貝葉斯分類器來訓練並完成分類工作

使用NLTK的樸素貝葉斯分類器來訓練並完成分類工作

NLTK是Python的一個自然語言處理的模組,其中實現了樸素貝葉斯分類演算法。以下,就使用上一篇文中提到的資料,來應用這個模組實現樸素貝葉斯分類。NLTK的實現更加泛化,所以在應用到我們的資料上時需要做一點的轉化。

首先來看一下NLTK官方文件中給出的一個簡單明瞭的例子,在瞭解這個例子之後,再設法將同樣的模型應用到自己的資料集上。官方給出的例子是英文名中,在知道名字中最後一個字母后,判斷這個名字對應的人是男是女。

#coding=utf-8
import random, nltk
from nltk.corpus import names

def gender_features(word):
    '''提取每個單詞的最後一個字母作為特徵'''
    return {'last_letter': word[-1]}
# 先為原始資料打好標籤
labeled_names = ([(name, 'male') for name in names.words('male.txt')] + [(name, 'female') for name in names.words('female.txt')])
# 隨機打亂打好標籤的資料集的順序,
random.shuffle(labeled_names)
# 從原始資料中提取特徵(名字的最後一個字母, 參見gender_features的實現)
featuresets = [(gender_features(name), gender) for (name, gender) in labeled_names]
# 將特徵集劃分成訓練集和測試集
train_set, test_set = featuresets[500:], featuresets[:500]
# 使用訓練集訓練模型(核心就是求出各種後驗概率)
classifier = nltk.NaiveBayesClassifier.train(train_set)
# 通過測試集來估計分類器的準確性
print(nltk.classify.accuracy(classifier, test_set))
# 如果一個人的名字的最後一個字母是‘a’,那麼這個人是男還是女
print(classifier.classify({'last_letter': 'a'}))
# 找出最能夠區分分類的特徵值
classifier.show_most_informative_features(5)
以上程式的輸出如下:
0.754
female
Most Informative Features
             last_letter = u'a'           female : male   =     35.6 : 1.0
             last_letter = u'k'             male : female =     30.7 : 1.0
             last_letter = u'f'             male : female =     16.6 : 1.0
             last_letter = u'p'             male : female =     12.5 : 1.0
             last_letter = u'm'             male : female =     11.1 : 1.0
從結果中,我們可以看到,通過訓練集訓練出的模型,在應用到測試集上時,其準確率為75%;如果一個人的名字以字母‘a’結束,那麼此分類器將其劃分為女性;最後輸出了最能區分男女的5個屬性值的資料,比如,對於字母‘a’來說,它作為女性名的最後一個字母的可能性是男性的35倍。

可以看到NLTK的樸素貝葉斯實現之中,它的輸入的訓練集的輸入是類似於以下的形式:

[

({'attr1':val1, 'attr2': val2, 'attr3': val3 ... 'attrn': valn}, label1),

({'attr1':val1, 'attr2': val2, 'attr3': val3 ... 'attrn': valn}, label2),

......

]

其中,每個特徵對應一個標籤,在以上的官方的例子中,特徵就只有一個,last_letter;而特徵的可能值是26個字母。對應到自己的資料,對應一個使用者就不止有一個特徵了,而是使用者安裝的APP名稱列表,同時又由於每個使用者安裝的APP可能不同,所以不同的使用者所對應的特徵的長度也是可能不同的;而每個屬性(APP名稱)對應的值只有兩個:安裝或者沒安裝。

以下的程式碼中的註釋以及輸出說明了整個轉化過程(使用了上篇文章中第三步中生成的資料):

#!/usr/local/bin/python2.7
# encoding: utf-8
from collections import defaultdict
import nltk

def gender_features(appnamelist):
    features = defaultdict(bool)
    for appname in appnamelist:
        features[appname] = True
    return features

if __name__ == '__main__':
    raw_data = defaultdict(lambda: defaultdict(list))
    with open('data/genderapplist.log') as f:
        for line in f:
            cells = line.strip().split('\t')
            if len(cells) == 3:
                imei, gender, appname = cells
                gender = 'male' if gender == '男性應用' else 'female'
                raw_data[gender][imei].append(appname)
    
    labeled_applist = [(appnamelist, 'male') for appnamelist in raw_data['male'].values()] + [(appnamelist, 'female') for appnamelist in raw_data['female'].values()]
    featuresets = [(gender_features(appnamelist), gender) for appnamelist, gender in labeled_applist]
    train_set, test_set = featuresets[500:], featuresets[:500]
    classifier = nltk.NaiveBayesClassifier.train(train_set)
    
    # 在訓練生成的分類器classifier中,有兩個屬性儲存著貝葉斯分類器所需要的先驗和後驗概率:
    # _label_probdist 儲存了標籤的分佈
    # _feature_probdist 儲存了每個APPNAME對應的後驗分佈
    # 通過下面的程式碼我們可以看到它們的值
    print '以下是 _label_probdist的相關資訊'
    print '1. 型別' 
    print type(classifier._label_probdist)
    print '2. 標籤的整體分佈狀況'
    classifier._label_probdist.freqdist().tabulate()
    print '3. 由第二步推出的標籤的概率分佈'
    print classifier._label_probdist.prob('female'), classifier._label_probdist.prob('male')
    
    print '*' * 32
    
    # _feature_probdist的值
    print '以下是 _feature_probdist的相關資訊'
    print '1. 型別'
    print type(classifier._feature_probdist)
    print '2. 從1的輸出中可以看到其型別為dict,我們看它的一個key和value即可'
    print classifier._feature_probdist.items()[6302]
    print '3. 從2中可以看到,其代表了,在標籤為female的情況下,安裝了支付寶錢包這個應用的概率分佈'
    classifier._feature_probdist.items()[6302][1].freqdist().tabulate()
    print '4. 3的輸出,我們非常熟悉,也就是在所有4910個female使用者中,有77個安裝了支付寶錢包,沒有安裝的有4833個'
    print '有了這個分佈,我們就可以計算出P(True|female, 支付寶錢包),其意義就是,在female使用者中,支付寶錢包這個屬性為True的可能性為'
    print classifier._feature_probdist.items()[6302][1].prob(True)
    print '5. 然後你會發現4中輸出的P(True|female, 支付寶錢包)並不正好等於77./4910,這是因為使用ELEProbDist'
    print '也就是“期望相似性概率估計”,這種方法避免了P(True|female, 支付寶錢包)=0情況的出現,從而避免模型失效'
    print '6. 通過在訓練集上的訓練,我們得到了以上的概率分佈,然後就可以使用訓練好的模型來分類了,我們看一下安裝了蘑菇街和支付寶錢包的使用者是男還是女'
    print classifier.classify({'蘑菇街':True, '支付寶錢包': True})
    print '7. 讓我們看一下安傳過了蘑菇街和支付寶錢包的使用者男女的可能性'
    print 'Prob(female)', classifier.prob_classify({'蘑菇街':True, '支付寶錢包': True}).prob('female')
    print 'Prob(male)', classifier.prob_classify({'蘑菇街':True, '支付寶錢包': True}).prob('male')
    print '8. 如果我們的輸入中,有一個全新的應用“這個應用不存在”,這裡的處理是不處理它'
    print 'Prob(female)', classifier.prob_classify({'蘑菇街':True, '支付寶錢包': True, '這個應用不存在':True}).prob('female')
    print 'Prob(male)', classifier.prob_classify({'蘑菇街':True, '支付寶錢包': True, '這個應用不存在':True}).prob('male')

以上程式的輸出為:
以下是 _label_probdist的相關資訊
1. 型別
<class 'nltk.probability.ELEProbDist'>
2. 標籤的整體分佈狀況
female male 
4910 4420 
3. 由第二步推出的標籤的概率分佈
0.526256564141 0.473743435859
********************************
以下是 _feature_probdist的相關資訊
1. 型別
<type 'dict'>
2. 從1的輸出中可以看到其型別為dict,我們看它的一個key和value即可
(('female', '\xe6\x94\xaf\xe4\xbb\x98\xe5\xae\x9d\xe9\x92\xb1\xe5\x8c\x85'), <ELEProbDist based on 4910 samples>)
3. 從2中可以看到,其代表了,在標籤為female的情況下,安裝了支付寶錢包這個應用的概率分佈
None True 
4833   77 
4. 3的輸出,我們非常熟悉,也就是在所有4910個female使用者中,有77個安裝了支付寶錢包,沒有安裝的有4833個
有了這個分佈,我們就可以計算出P(True|female, 支付寶錢包),其意義就是,在female使用者中,支付寶錢包這個屬性為True的可能性為
0.0157809000204
5. 然後你會發現4中輸出的P(True|female, 支付寶錢包)並不正好等於77./4910,這是因為使用ELEProbDist
也就是“期望相似性概率估計”,這種方法避免了P(True|female, 支付寶錢包)=0情況的出現,從而避免模型失效
6. 通過在訓練集上的訓練,我們得到了以上的概率分佈,然後就可以使用訓練好的模型來分類了,我們看一下安裝了蘑菇街和支付寶錢包的使用者是男還是女
female
7. 讓我們看一下安傳過了蘑菇街和支付寶錢包的使用者男女的可能性
Prob(female) 0.994878529146
Prob(male) 0.00512147085357
8. 如果我們的輸入中,有一個全新的應用“這個應用不存在”,這裡的處理是不處理它
Prob(female) 0.994878529146
Prob(male) 0.00512147085357

這樣通過使用NLTK,相比自己實現來說有了更簡潔的程式碼,並且更容易維護,希望對有需要的同學有幫助。