首先來看一下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的相關資訊
<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. 通過在訓練集上的訓練,我們得到了以上的機率分布,然後就可以使用訓練好的模型來分類了,我們看一下安裝了蘑菇街和支付寶錢包的使用者是男還是女
7. 讓我們看一下安傳過了蘑菇街和支付寶錢包的使用者男女的可能性
prob(female) 0.994878529146
prob(male) 0.00512147085357
8. 如果我們的輸入中,有一個全新的應用“這個應用不存在”,這裡的處理是不處理它
這樣通過使用nltk,相比自己實作來說有了更簡潔的代碼,并且更容易維護,希望對有需要的同學有幫助。