轉載請註明出處:http://www.cnblogs.com/marc01in/p/4775440.htmlhtml
和師弟師妹聊天時常常說起,如有志於從事數據挖掘、機器學習方面的工做,在大學階段就要把基礎知識都帶上。 機器學習在大數據浪潮中逐漸展現她的魅力,其實《機率論》、《微積分》、《線性代數》、《運籌學》、《信息論》等幾門課程算是前置課程,固然要轉化爲工程應用的話,編程技能也是須要的,而做爲信息管理專業的同窗,對於信息的理解、數據的敏感都是很好的加分項。python
不過光說不練,給人的留下的印象是極爲淺薄的,從一些你們都熟悉的角度切入,或許更容易能讓人有所體會。算法
下面進入正題。編程
BTW,若是觀點錯誤或者引用侵權的歡迎指正交流。微信
樸素貝葉斯,之因此稱爲樸素,是由於其中引入了幾個假設(不用擔憂,下文會說起)。而正由於這幾個假設的引入,使得模型簡單易理解,同時若是訓練得當,每每能收穫不錯的分類效果,所以這個系列以naive bayes開頭和你們見面。app
由於樸素貝葉斯是貝葉斯決策理論的一部分,因此咱們先快速瞭解一下貝葉斯決策理論。dom
假設有一個數據集,由兩類組成(簡化問題),對於每一個樣本的分類,咱們都已經知曉。數據分佈以下圖(圖取自MLiA):機器學習
如今出現一個新的點new_point (x,y),其分類未知。咱們能夠用p1(x,y)表示數據點(x,y)屬於紅色一類的機率,同時也能夠用p2(x,y)表示數據點(x,y)屬於藍色一類的機率。那要把new_point歸在紅、藍哪一類呢?ide
咱們提出這樣的規則:post
若是p1(x,y) > p2(x,y),則(x,y)爲紅色一類。
若是p1(x,y) <p2(x,y), 則(x,y)爲藍色一類。
換人類的語言來描述這一規則:選擇機率高的一類做爲新點的分類。這就是貝葉斯決策理論的核心思想,即選擇具備最高几率的決策。
用條件機率的方式定義這一貝葉斯分類準則:
若是p(red|x,y) > p(blue|x,y), 則(x,y)屬於紅色一類。
若是p(red|x,y) < p(blue|x,y), 則(x,y)屬於藍色一類。
也就是說,在出現一個須要分類的新點時,咱們只須要計算這個點的
max(p(c1 | x,y),p(c2 | x,y),p(c3 | x,y)...p(cn| x,y))。其對於的最大機率標籤,就是這個新點的分類啦。
那麼問題來了,對於分類i 如何求解p(ci| x,y)?
沒錯,就是貝葉斯公式:
公式暫不推導,先描述這個轉換的重要性。紅色、藍色分類是爲了幫助理解,這裏要換成多維度說法了,也就是第二部分的實例:判斷一條微信朋友圈是否是廣告。
前置條件是:咱們已經擁有了一個平日廣大用戶的朋友圈內容庫,這些朋友圈當中,若是真的是在作廣告的,會被「熱心網友」打上「廣告」的標籤,咱們要作的是把全部內容分紅一個一個詞,每一個詞對應一個維度,構建一個高維度空間 (別擔憂,這裏未出現向量計算)。
當出現一條新的朋友圈new_post,咱們也將其分詞,而後投放到朋友圈詞庫空間裏。
這裏的X表示多個特徵(詞)x1,x2,x3...組成的特徵向量。
P(ad|x)表示:已知朋友圈內容而這條朋友圈是廣告的機率。
利用貝葉斯公式,進行轉換:
P(ad|X) = p(X|ad) p(ad) / p(X)
P(not-ad | X) = p(X|not-ad)p(not-ad) / p(X)
比較上面兩個機率的大小,若是p(ad|X) > p(not-ad|X),則這條朋友圈被劃分爲廣告,反之則不是廣告。
看到這兒,實際問題已經轉爲數學公式了。
看公式推導 (公式圖片引用):
樸素貝葉斯分類的正式定義以下:
1、設爲一個待分類項,而每一個a爲x的一個特徵屬性。
2、有類別集合。
3、計算。
4、若是,則
。
那麼如今的關鍵就是如何計算第3步中的各個條件機率。咱們能夠這麼作:
1、找到一個已知分類的待分類項集合,這個集合叫作訓練樣本集。
2、統計獲得在各種別下各個特徵屬性的條件機率估計。即。
3、若是各個特徵屬性是條件獨立的,則根據貝葉斯定理有以下推導:
由於分母對於全部類別爲常數,由於咱們只要將分子最大化皆可。又由於各特徵屬性是條件獨立的,因此有:
這裏要引入樸素貝葉斯假設了。若是認爲每一個詞都是獨立的特徵,那麼朋友圈內容向量能夠展開爲分詞(x1,x2,x3...xn),所以有了下面的公式推導:
P(ad|X) = p(X|ad)p(ad) = p(x1, x2, x3, x4...xn | ad) p(ad)
假設全部詞相互條件獨立,則進一步拆分:
P(ad|X) = p(x1|ad)p(x2|ad)p(x3|ad)...p(xn|ad) p(ad)
雖然現實中,一條朋友圈內容中,相互之間的詞不會是相對獨立的,由於咱們的天然語言是講究上下文的╮(╯▽╰)╭,不過這也是樸素貝葉斯的樸素所在,簡單的看待問題。
看公式p(ad|X)=p(x1|ad)p(x2|ad)p(x3|ad)...p(xn|ad) p(ad)
至此,P(xi|ad)很容易求解,P(ad)爲詞庫中廣告朋友圈佔全部朋友圈(訓練集)的機率。咱們的問題也就迎刃而解了。
到這裏,應該已經有心急的讀者掀桌而起了,搗鼓半天,沒有應用。 (╯‵□′)╯︵┻━┻
"Talk is cheap, show me the code."
邏輯均在代碼註釋中,由於用python編寫,和僞代碼沒啥兩樣,並且我也懶得畫圖……
1 #encoding:UTF-8 2 ''' 3 Author: marco lin 4 Date: 2015-08-28 5 ''' 6 7 from numpy import * 8 import pickle 9 import jieba 10 import time 11 12 stop_word = [] 13 ''' 14 停用詞集, 包含「啊,嗎,嗯」一類的無實意詞彙以及標點符號 15 ''' 16 def loadStopword(): 17 fr = open('stopword.txt', 'r') 18 lines = fr.readlines() 19 for line in lines: 20 stop_word.append(line.strip().decode('utf-8')) 21 fr.close() 22 23 ''' 24 建立詞集 25 params: 26 documentSet 爲訓練文檔集 27 return:詞集, 做爲詞袋空間 28 ''' 29 def createVocabList(documentSet): 30 vocabSet = set([]) 31 for document in documentSet: 32 vocabSet = vocabSet | set(document) #union of the two sets 33 return list(vocabSet) 34 35 ''' 36 載入數據 37 ''' 38 def loadData(): 39 return None 40 41 ''' 42 文本處理,若是是未處理文本,則先分詞(jieba分詞),再去除停用詞 43 ''' 44 def textParse(bigString, load_from_file=True): #input is big string, #output is word list 45 if load_from_file: 46 listOfWord = bigString.split('/ ') 47 listOfWord = [x for x in listOfWord if x != ' '] 48 return listOfWord 49 else: 50 cutted = jieba.cut(bigString, cut_all=False) 51 listOfWord = [] 52 for word in cutted: 53 if word not in stop_word: 54 listOfWord.append(word) 55 return [word.encode('utf-8') for word in listOfWord] 56 57 ''' 58 交叉訓練 59 ''' 60 CLASS_AD = 1 61 CLASS_NOT_AD = 0 62 63 def testClassify(): 64 listADDoc = [] 65 listNotADDoc = [] 66 listAllDoc = [] 67 listClasses = [] 68 69 print "----loading document list----" 70 71 #兩千個標註爲廣告的文檔 72 for i in range(1, 1001): 73 wordList = textParse(open('subject/subject_ad/%d.txt' % i).read()) 74 listAllDoc.append(wordList) 75 listClasses.append(CLASS_AD) 76 #兩千個標註爲非廣告的文檔 77 for i in range(1, 1001): 78 wordList = textParse(open('subject/subject_notad/%d.txt' % i).read()) 79 listAllDoc.append(wordList) 80 listClasses.append(CLASS_NOT_AD) 81 82 print "----creating vocab list----" 83 #構建詞袋模型 84 listVocab = createVocabList(listAllDoc) 85 86 docNum = len(listAllDoc) 87 testSetNum = int(docNum * 0.1); 88 89 trainingIndexSet = range(docNum) # 創建與全部文檔等長的空數據集(索引) 90 testSet = [] # 空測試集 91 92 # 隨機索引,用做測試集, 同時將隨機的索引從訓練集中剔除 93 for i in range(testSetNum): 94 randIndex = int(random.uniform(0, len(trainingIndexSet))) 95 testSet.append(trainingIndexSet[randIndex]) 96 del(trainingIndexSet[randIndex]) 97 98 trainMatrix = [] 99 trainClasses = [] 100 101 for docIndex in trainingIndexSet: 102 trainMatrix.append(bagOfWords2VecMN(listVocab, listAllDoc[docIndex])) 103 trainClasses.append(listClasses[docIndex]) 104 105 print "----traning begin----" 106 pADV, pNotADV, pClassAD = trainNaiveBayes(array(trainMatrix), array(trainClasses)) 107 108 print "----traning complete----" 109 print "pADV:", pADV 110 print "pNotADV:", pNotADV 111 print "pClassAD:", pClassAD 112 print "ad: %d, not ad:%d" % (CLASS_AD, CLASS_NOT_AD) 113 114 args = dict() 115 args['pADV'] = pADV 116 args['pNotADV'] = pNotADV 117 args['pClassAD'] = pClassAD 118 119 fw = open("args.pkl", "wb") 120 pickle.dump(args, fw, 2) 121 fw.close() 122 123 fw = open("vocab.pkl", "wb") 124 pickle.dump(listVocab, fw, 2) 125 fw.close() 126 127 errorCount = 0 128 for docIndex in testSet: 129 vecWord = bagOfWords2VecMN(listVocab, listAllDoc[docIndex]) 130 if classifyNaiveBayes(array(vecWord), pADV, pNotADV, pClassAD) != listClasses[docIndex]: 131 errorCount += 1 132 doc = ' '.join(listAllDoc[docIndex]) 133 print "classfication error", doc.decode('utf-8', "ignore").encode('gbk') 134 print 'the error rate is: ', float(errorCount) / len(testSet) 135 136 # 分類方法(這邊只作二類處理) 137 def classifyNaiveBayes(vec2Classify, pADVec, pNotADVec, pClass1): 138 pIsAD = sum(vec2Classify * pADVec) + log(pClass1) #element-wise mult 139 pIsNotAD = sum(vec2Classify * pNotADVec) + log(1.0 - pClass1) 140 141 if pIsAD > pIsNotAD: 142 return CLASS_AD 143 else: 144 return CLASS_NOT_AD 145 146 ''' 147 訓練 148 params: 149 tranMatrix 由測試文檔轉化成的詞空間向量 所組成的 測試矩陣 150 tranClasses 上述測試文檔對應的分類標籤 151 ''' 152 def trainNaiveBayes(trainMatrix, trainClasses): 153 numTrainDocs = len(trainMatrix) 154 numWords = len(trainMatrix[0]) #計算矩陣列數, 等於每一個向量的維數 155 numIsAD = len(filter(lambda x: x == CLASS_AD, trainClasses)) 156 pClassAD = numIsAD / float(numTrainDocs) 157 pADNum = ones(numWords); pNotADNum = ones(numWords) 158 pADDenom = 2.0; pNotADDenom = 2.0 159 160 for i in range(numTrainDocs): 161 if trainClasses[i] == CLASS_AD: 162 pADNum += trainMatrix[i] 163 pADDenom += sum(trainMatrix[i]) 164 else: 165 pNotADNum += trainMatrix[i] 166 pNotADDenom += sum(trainMatrix[i]) 167 168 pADVect = log(pADNum / pADDenom) 169 pNotADVect = log(pNotADNum / pNotADDenom) 170 171 return pADVect, pNotADVect, pClassAD 172 173 ''' 174 將輸入轉化爲向量,其所在空間維度爲 len(listVocab) 175 params: 176 listVocab-詞集 177 inputSet-分詞後的文本,存儲於set 178 ''' 179 def bagOfWords2VecMN(listVocab, inputSet): 180 returnVec = [0]*len(listVocab) 181 for word in inputSet: 182 if word in listVocab: 183 returnVec[listVocab.index(word)] += 1 184 return returnVec 185 186 ''' 187 讀取保存的模型,作分類操做 188 ''' 189 def adClassify(text): 190 fr = open("args.pkl", "rb") 191 args = pickle.load(fr) 192 pADV = args['pADV'] 193 pNotADV = args['pNotADV'] 194 pClassAD = args['pClassAD'] 195 fr.close() 196 197 fr = open("vocab.pkl", "rb") 198 listVocab = pickle.load(fr) 199 fr.close() 200 201 if len(listVocab) == 0: 202 print "got no args" 203 return 204 205 text = textParse(text, False) 206 vecWord = bagOfWords2VecMN(listVocab, text) 207 class_type = classifyNaiveBayes(array(vecWord), pADV, pNotADV, pClassAD) 208 209 print "classfication type:%d" % class_type 210 211 212 if __name__ == "__main__": 213 loadStopword() 214 while True: 215 opcode = raw_input("input 1 for training, 2 for ad classify: ") 216 if opcode.strip() == "1": 217 begtime = time.time() 218 testClassify() 219 print "cost time total:", time.time() - begtime 220 else: 221 text = raw_input("input the text:") 222 adClassify(text) 223
代碼測試效果:
一、訓練。
二、實例測試。
分類爲1則歸爲廣告,0爲普通文本。
p.s.
此分類器的準確率,實際上是比較依賴於訓練語料的,機器學習算法就和純潔的小孩同樣,取決於其成長(訓練)條件,「吃的是草擠的是奶」,但,「不是全部的牛奶,都叫特侖蘇」。