原理
在分類(classification)問題中,常常需要把一個事物分到某個類別。一個事物具有很多屬性,把它的眾多屬性看做一個向量,即x=(x1,x2,x3,…,xn),用x這個向量來代表這個事物。類別也是有很多種,用集合Y=y1,y2,…ym表示。如果x屬於y1類別,就可以給x打上y1標籤,意思是說x屬於y1類別。這就是所謂的分類(Classification)。
x的集合記為X,稱為屬性集。一般X和Y的關係是不確定的,你只能在某種程度上說x有多大可能性屬於類y1,比如說x有80%的可能性屬於類y1,這時可以把X和Y看做是隨機變數,P(Y|X)稱為Y的後驗機率(posterior probability),與之相對的,P(Y)稱為Y的先驗機率(prior probability)1。
在訓練階段,我們要根據從訓練資料中收集的資訊,對X和Y的每一種組合學習後驗機率P(Y|X)。分類時,來了一個執行個體x,在剛才訓練得到的一堆後驗機率中找出所有的P(Y|x), 其中最大的那個y,即為x所屬分類。根據貝葉斯公式,後驗機率為P(Y|X)=P(X|Y)P(Y)P(X)
在比較不同Y值的後驗機率時,分母P(X)總是常數,因此可以忽略。先驗機率P(Y)可以通過計算訓練集中屬於每一個類的訓練樣本所佔的比例容易地估計。這裡直接計算P(Y|X)比較麻煩,而樸素貝葉斯分類提出了一個獨立性假設:x1,x2,...,xn相互獨立,這也是被稱之為“樸素”的原因之一。於是P(X|Y) = P(x1|Y)P(x2|Y)...P(xn|Y),而P(xi|Y)很好求了。
對於二元分類,要比較P(Y=1|X)和P(Y=0|X)的大小,只需比較分子P(X|Y)P(Y)部分。因此只需計算n個條件機率和先驗機率。 文本分類 在文本分類中,假設我們有一個文檔d∈X,X是文檔向量空間(document space),和一個固定的類集合C={c1,c2,…,cj},類別又稱為標籤。顯然,文檔向量空間是一個高維度空間。我們把一堆打了標籤的文檔集合<d,c>作為訓練樣本,<d,c>∈X×C。例如:
<d,c>={Beijing joins the World Trade Organization, China}
對於這個只有一句話的文檔,我們把它歸類到 China,即打上china標籤。
我們期望用某種訓練演算法,訓練出一個函數γ,能夠將文檔映射到某一個類別:
γ:X→C
這種類型的學習方法叫做有監督學習,因為事先有一個監督者(我們事先給出了一堆打好標籤的文檔)像個老師一樣監督著整個學習過程。
樸素貝葉斯分類器是一種有監督學習,常見有兩種模型,多項式模型(multinomial model)和伯努利模型(Bernoulli model)。
多項式模型
在多項式模型中, 設某文檔d=(t1,t2,…,tk),tk是該文檔中出現過的單詞,允許重複,則
先驗機率P(c)= 類c下單詞總數/整個訓練樣本的單詞總數
類條件機率P(tk|c)=(類c下單詞tk在各個文檔中出現過的次數之和+1)/(類c下單詞總數+|V|)
V是訓練樣本的單詞表(即抽取單詞集合,單詞出現多次,只算一個),|V|則表示訓練樣本包含多少種單詞。在這裡,m=|V|, p=1/|V|。
P(tk|c)可以看作是單詞tk在證明d屬於類c上提供了多大的證據,而P(c)則可以認為是類別c在整體上佔多大比例(有多大可能性)。
伯努利模型
P(c)= 類c下檔案總數/整個訓練樣本的文檔總數
P(tk|c)=(類c下包含單詞tk的檔案數+1)/(類c下文檔總數+2) 在這裡,m=2, p=1/2。
#這裡一定要注意:伯努利是以文檔為粒度的,所以分母是文檔總數,而不是網上以訛傳訛的類c下單詞總數
這裡貼幾個以訛傳訛的連結:
http://blog.csdn.net/kongying168/article/details/7026389
http://cn.soulmachine.me/blog/20100528/
還有好多其他的就不一一列舉了。
上面兩個模型中的分子分母都加上了一些數,這是為了防止某個條件機率為0,從而整個P(X|Y) = P(x1|Y)P(x2|Y)...P(xn|Y)乘積為0。
執行個體示範 對一個郵件進行垃圾分類,資料集來自加州大學歐文分校的http://archive.ics.uci.edu/ml/datasets/Spambase spambase 資料格式說明如下:
英文的,我就不解釋了(主要是解釋起來費勁,-_-。sorry。)
為了判別分類模型的好壞,可以計算AUC值。 本次實驗採用sklearn包中的metrics計算ROC曲線和AUC值(ROC和AUC詳見http://blog.csdn.net/chjjunking/article/details/5933105);而這個是需要每個樣本的後驗機率的。因此分子P(X)的值也要求出來,將整個式子轉換下: P(Y|X)=P(X|Y)P(Y)P(X) = P(x1|Y=1)P(x2|Y=1)...P(xn|Y=1)P(Y=1) / (P(x1|Y=1)P(x2|Y=1)...P(xn|Y=1) + P(x1|Y=0)P(x2|Y=0)...P(xn|Y=0)) 分子分母上下同時除以P(x1|Y=0)P(x2|Y=0)...P(xn|Y=0)得 公式不好打啊,過幾天用手寫,然後傳圖片吧,-_-。sorry。
好了,廢話不多說,上代碼: NaiveBayes.py
#!/usr/bin/python# NaiveBayes classification# lming_08 2014.07.06import mathimport numpy as npfrom sklearn import metricsclass NaiveBayes: def __init__(self, trainFile): self.trainingFile = trainFile self.trainingData = self.read_data(trainFile) # Read training or testing data def read_data(self, file): data = [] fd = open(file) for line in fd: arr = line.rstrip().split(',') # Turn an instance's last column(y column) as integer arr[-1] = int(arr[-1]) # Append the instance to trainingData data.append(tuple(arr)) fd.close() return data def train_model_with_Bernoulli(self): self.sumPosInstance = 0. self.sumNegInstance = 0. self.termfreq = {} for instance in self.trainingData: if int(instance[-1]) == 1: self.sumPosInstance += 1 else: self.sumNegInstance += 1 for i in range(len(instance) - 1): key = str() if i < 55: if float(instance[i]) > 0: key = "freq" + "|" + str(i + 1) + "|" + "1" else: key = "freq" + "|" + str(i + 1) + "|" + "0" else: key = "length" + "|" + str(i + 1) + "|" + instance[i] if key not in self.termfreq: self.termfreq[key] = [0, 0] if int(instance[-1]) == 1: self.termfreq[key][1] += 1 else: self.termfreq[key][0] += 1 # prior_prob = p(y = 1) self.prior_prob = self.sumPosInstance / (self.sumPosInstance + self.sumNegInstance) # prior_ratio = p(y=1) / p(y=0) self.prior_ratio = self.sumPosInstance / self.sumNegInstance # the function should be called before predict() def set_testfile(self, testFile): self.testing_data = self.read_data(testFile) def predict(self): self.testingY = [] self.predict_result = [] for instance in self.testing_data: self.predict_result.append(self.predict_instance_with_Bernoulli(instance)) self.testingY.append(instance[-1]) def get_statistics(self): true_classify_count = 0. false_classify_count = 0. for instance in self.testing_data: post_prob = self.predict_instance_with_Bernoulli(instance) if post_prob >= 0.5 and instance[-1] == 1: true_classify_count += 1 elif post_prob >= 0.5 and instance[-1] == 0: false_classify_count += 1 elif post_prob < 0.5 and instance[-1] == 1: false_classify_count += 1 elif post_prob < 0.5 and instance[-1] == 0: true_classify_count += 1 return true_classify_count, false_classify_count def predict_instance_with_Bernoulli(self, instance): f = 0. for i in range(len(instance) - 1): key = str() if i < 55: if float(instance[i]) > 0: key = "freq" + "|" + str(i + 1) + "|" + "1" else: key = "freq" + "|" + str(i + 1) + "|" + "0" else: key = "length" + "|" + str(i + 1) + "|" + instance[i] if key in self.termfreq: f += math.log( (self.termfreq[key][1] + 1) / ((self.termfreq[key][0] + 2) * self.prior_ratio) ) posterior_ratio = self.prior_ratio * math.exp(f) # posterior probability prob = posterior_ratio / (1. + posterior_ratio) return prob def getAUC(self): y = np.array(self.testingY) pred = np.array(self.predict_result) fpr, tpr, thresholds = metrics.roc_curve(y, pred) # metrics.roc_curve(y, pred, pos_label=1) auc = metrics.auc(fpr, tpr) return aucdef main(trainFile, testFile): nb = NaiveBayes(trainFile) nb.train_model_with_Bernoulli() nb.set_testfile(testFile) nb.predict() print("the value of AUC: " % nb.getAUC()) print("the value of priori probability:" % nb.priorProb)if __name__ == "__main__": if len(argv) != 3: print "Usage: python %s trainFile(in) testFile(out)" % __file__ sys.exit(0) main(argv[1], argv[2])
PartitionFile.py 用於將資料檔案分割為訓練檔案(80%)和測試檔案(20%)
#!/usr/bin/python# Partitioning a file into training(80%) and testing file(20%)# lming_08 2014.07.06from random import randintdef partition_file(file, train_file, test_file): ltest = [] ltrain = [] fd = open(file, "r") train_fd = open(train_file, "w") test_fd = open(test_file, "w") test_index = 0 train_index = 0 for line in fd: rnum = randint(1, 10) if rnum == 5 or rnum == 6: test_index += 1 ltest.extend(line) if test_index == 100: test_fd.writelines(ltest) ltest = [] test_index = 0 else: train_index += 1 ltrain.extend(line) if train_index == 100: train_fd.writelines(ltrain) ltrain = [] train_index = 0 if len(ltest) > 0: test_fd.writelines(ltest) if len(ltrain) > 0: train_fd.writelines(ltrain) train_fd.close() test_fd.close() fd.close()
Classify.py 執行檔案
#!/usr/bin/python%# main execute file# lming_08 2014.07.06import sysfrom sys import argvfrom NaiveBayes import NaiveBayesfrom PartitionFile import partition_filedef main(srcFile, trainFile, testFile): partition_file(srcFile, trainFile, testFile) nb = NaiveBayes(trainFile) nb.train_model_with_Bernoulli() nb.set_testfile(testFile) nb.predict() print("the value of AUC: %f" % nb.getAUC()) print("the value of priori probability: %f" % nb.prior_prob) true_classify_count, false_classify_count = nb.get_statistics() error_rate = false_classify_count/(true_classify_count + false_classify_count) print("the error rate : %f" % error_rate)if __name__ == "__main__": if len(argv) != 4: print "Usage: python %s srcFile(in) trainFile(out) testFile(out)" % __file__ sys.exit(0) main(argv[1], argv[2], argv[3])
執行結果為:
可以看到AUC值還是比較高的,分類錯誤率為9.87%,還是可以接受的。 最後再吐槽下,上文中指出的以訛傳訛的演算法竟然在《機器學習實戰》這本書中出現了,而網上好多同學也學習過書中的代碼,可是並沒有人懷疑並指出這個。
參考文獻 http://blog.csdn.net/tbkken/article/details/8062358
http://blog.csdn.net/kongying168/article/details/7026389
http://cn.soulmachine.me/blog/20100528/
http://www.chepoo.com/naive-bayesian-text-classification-algorithm-to-learn.html
http://blog.csdn.net/chjjunking/article/details/5933105