轉載請注明出處:http://www.cnblogs.com/marc01in/p/4775440.html
引
和師弟師妹聊天時經常提及,若有志於從事數據挖掘、機器學習方面的工作,在大學階段就要把基礎知識都帶上。 機器學習在大數據浪潮中逐漸展示她的魅力,其實《概率論》、《微積分》、《線性代數》、《運籌學》、《信息論》等幾門課程算是前置課程,當然要轉化為工程應用的話,編程技能也是需要的,而作為信息管理專業的同學,對於信息的理解、數據的敏感都是很好的加分項。
不過光說不練,給人的留下的印象是極為淺薄的,從一些大家都熟悉的角度切入,或許更容易能讓人有所體會。
下面進入正題。
BTW,如果觀點錯誤或者引用侵權的歡迎指正交流。
一、朴素貝葉斯算法介紹
朴素貝葉斯,之所以稱為朴素,是因為其中引入了幾個假設(不用擔心,下文會提及)。而正因為這幾個假設的引入,使得模型簡單易理解,同時如果訓練得當,往往能收獲不錯的分類效果,因此這個系列以naive bayes開頭和大家見面。
因為朴素貝葉斯是貝葉斯決策理論的一部分,所以我們先快速了解一下貝葉斯決策理論。
假設有一個數據集,由兩類組成(簡化問題),對於每個樣本的分類,我們都已經知曉。數據分布如下圖(圖取自MLiA):
現在出現一個新的點new_point (x,y),其分類未知。我們可以用p1(x,y)表示數據點(x,y)屬於紅色一類的概率,同時也可以用p2(x,y)表示數據點(x,y)屬於藍色一類的概率。那要把new_point歸在紅、藍哪一類呢?
我們提出這樣的規則:
如果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、訓練。
2、實例測試。
分類為1則歸為廣告,0為普通文本。
p.s.
此分類器的准確率,其實是比較依賴於訓練語料的,機器學習算法就和純潔的小孩一樣,取決於其成長(訓練)條件,“吃的是草擠的是奶”,但,“不是所有的牛奶,都叫特侖蘇”。