貝葉斯算法是基於統計學的一種機率分類方法,而樸素貝葉斯是其中最簡單的一種;樸素貝葉斯屬於監督學習的算法之一,通常用來解決分類問題,咱們之因此稱之爲"樸素",是由於整個形勢化過程只作最原始、最簡單的假設,即假設數據集全部的樣本之間都是獨立存在,互不影響的。python
用一個條件機率公式更好的理解這個假設條件,假設一個樣本中有(a一、a二、a三、... an)共n個樣本,如有P(a1,a2,a3,...,an) = P(a1) P(a2) P(a3)...P(an),則稱該數據集中各個樣本之間獨立存在。算法
假設咱們有一個數據集,共有兩個特徵,分別爲三角形和圓形,以下圖所示:
若用P1(x,y)表示數據點(x,y)屬於圓形的機率,用P2(x,y)表示數據點(x,y)屬於三角形的機率,那麼對於一個新數據點(x,y),能夠用如下規則判斷其類別:數組
總得來講,未知屬性的類別趨向機率高的。這就是貝葉斯決策理論的核心思想,即選擇具備最高几率的決策。app
條件機率公式是機率論中十分基礎的一個公式,即在事件B發生的狀況下,事件A也發生的機率,以下文氏圖:
經過這幅文氏圖,在在事件B發生的狀況下,事件A也發生的機率以下less
同理可得ide
最後推得條件機率的計算公式以下函數
這個公式被稱爲貝葉斯準則,它告訴咱們如何交換條件機率中的條件和結果,例如已知P(B | A),如何計算P(A | B)。post
這裏有幾個概念須要瞭解:學習
因此條件機率也能夠理解成:測試
*後驗機率 = 先驗機率 調整因子**
其中"調整因子"的值對條件機率的影響以下:
再有一年半,偶也要面臨考研or就業的抉擇,向周圍同窗詢問了他們的選擇,獲得這麼一份小數據集,偶也總結了一下自身條件,學習成績通常、自學能力不錯、家裏的經濟條件也容許,那我是選擇考研仍是就業呢?
成績 | 自學能力 | 家庭條件 | 選擇 |
---|---|---|---|
學霸 | 強 | 好 | 考研 |
通常 | 強 | 差 | 考研 |
學渣 | 弱 | 好 | 考研 |
學霸 | 強 | 差 | 就業 |
通常 | 弱 | 好 | 就業 |
學渣 | 弱 | 差 | 就業 |
對於這個例子,按照貝葉斯公式進行求解,能夠轉化成P(考研 | 通常 強 好)和P(就業 | 通常 強 好)兩類,由於貝葉斯的思想就是根據最高几率判斷類別。
"先驗機率"P(考研)很容易計算,可是"可能性函數"中的分母P(通常 強 好)殊不知如何計算,這裏須要引入一個新的公式——全機率公式。
因此依據全機率公式P(通常 強 好)求值公式以下:
最後依據貝葉斯準則可計算兩者的機率:
其中考研的機率爲80%,就業的機率爲20%,因此就我本身的條件而言,該算法將我分配至考研黨中。咱們從小學就學過一個道理,分母相同的兩個分數,分子大的分數大,由於樸素貝葉斯的思想是要依據機率判斷類別,因此就能夠省去計算全機率這一步,在編寫程序的時候能夠提升效率。
從文本中獲取特徵,需先將文本拆分。這裏的特徵是來自文本的詞條,一個詞條是字符的任意組合。對於文本而言,能夠將詞條想象成單詞;對於IP地址而言,又能夠將詞條想象成兩個點間的數字組合,不一樣類型的文本,詞條的類型能夠不一樣。而後將每個文本片斷表示爲一個詞條向量,其中值爲1表示詞條出如今文檔中,0則表示詞條未出現。
平時在刷微博的時候,無論事情好與壞,評論老是有好有壞,由於避免不了總有槓精的存在。構建一個快速過濾器,這個過濾器的功能就是分類好壞評論,若是某條評論使用了負面或者侮辱性的語言,則將該評論斷定爲侮辱類評論,反之則將其歸爲非侮辱類評論,其中侮辱類用1表示,非侮辱類用0表示。
假設咱們已經獲取到文本數據,先考慮出如今文本中的全部單詞,決定將哪些詞歸入詞彙表或者說所要的詞聚集合,而後將文本中的句子轉化爲向量,以方便對文本中每句話的類別進行判斷。
#設置文本數據集 def loadDataSet(): #文本語句切分 postingList=[['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'], ['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'], ['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'], ['stop', 'posting', 'stupid', 'worthless', 'garbage'], ['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'], ['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']] #類別標籤的向量 classVec = [0,1,0,1,0,1] return postingList,classVec
loadDataSet函數建立了實驗文本,主要的操做是將每一句話切分紅若干個單詞,而且建立了一個類別標籤列表,其中1表明侮辱類,0表明非侮辱類,是經過人的判斷後進行標註。
#建立詞彙表 def createVocabList(dataSet): #建立一個空的不重複列表 vocabSet = set([]) for document in dataSet: #取二者並集 vocabSet = vocabSet | set(document) return list(vocabSet) #判斷 def setOfWords2Vec(vocabList, inputSet): #建立一個元素都爲0的向量 returnVec = [0] * len(vocabList) for word in inputSet: if word in vocabList: #若詞彙表包含該詞彙,則將該位置的0變爲1 returnVec[vocabList.index(word)] = 1 return returnVec #將全部詞條向量彙總 def get_Mat(inputSet): trainMat = [] #建立空列表 vocabList = createVocabList(inputSet) #遍歷輸入文本,將每一個詞條向量加入列表中 for Set in inputSet: returnVec = setOfWords2Vec(vocabList,Set) trainMat.append(returnVec) return trainMat
createVocabList函數的做用是經過set方法已經取並集的方式返回一個包含文本中全部出現的不重複詞的集合;setOfWords2Vec函數的輸入參數爲詞彙表和某個文本,輸出的是文本向量,向量的元素包括1或0,分別表示詞彙表中的單詞是否出如今輸入的文本中,思路是首先建立一個同詞彙表等長的向量,並將其元素都設置爲0,而後遍歷輸入文本的單詞,若詞彙表中出現了本文的單詞,則將其對應位置上的0置換爲1。
代碼運行截圖以下
例如詞彙表中第四個單詞has在第一個輸入文本中出現,則向量中的第4個元素置爲1;同理詞彙表中最後一個單詞not在第二個輸入文本中出現,則向量中最後一個元素置爲1。
這裏若是重寫上文提過的貝葉斯準則,W爲一個向量,它由多個數值組成,Ci表明類別,即侮辱類or非侮辱類,公式以下:
若使用上述公式對一個未知類進行判斷,咱們只需比較兩個兩個機率值的大小便可,首先經過類別i的文本數除以總文本數能夠計算出P(Ci)的數值;而後計算P(W | Ci),由於W能夠展開爲一個個獨立特徵,那麼P(W0,W1,W2...Wn | Ci) = P(W0 | Ci)P(W1 | Ci)P(W2 | Ci)...P(Wn | Ci),簡化了計算的過程。
代碼以下:
def trainNB(trainMatrix,trainCategory): #訓練文本數量 numTrainDocs = len(trainMatrix) #每篇文本的詞條數 numWords = len(trainMatrix[0]) #文檔屬於侮辱類(1)的機率 pAbusive = sum(trainCategory)/float(numTrainDocs) #建立兩個長度爲numWords的零數組 p0Num = np.zeros(numWords) p1Num = np.zeros(numWords) #分母初始化 p0Denom = 0.0 p1Denom = 0.0 for i in range(numTrainDocs): if trainCategory[i] == 1: #統計侮辱類的條件機率所需的數據,即P(w0|1),P(w1|1),P(w2|1)··· p1Num += trainMatrix[i] #print(p1Num) p1Denom += sum(trainMatrix[i]) #print(p1Num) else: #統計非侮辱類的條件機率所需的數據,即P(w0|0),P(w1|0),P(w2|0)··· p0Num += trainMatrix[i] p0Denom += sum(trainMatrix[i]) #計算詞條出現的機率 p1Vect = p1Num/p1Denom p0Vect = p0Num/p0Denom return p0Vect,p1Vect,pAbusive
trainNB函數的輸入參數包括文本矩陣trainMatrix和每一個詞條的類別標籤所構成的向量trainCategory。首先文本屬於侮辱類的機率只須要將侮辱性詞條的個除以總詞條個數便可;計算P(W | C1)和P(W | C0)時,須要將其分子和分母初始化,遍歷輸入文本時,一旦某個詞語(侮辱性or非侮辱性)在某一文檔中出現,則該詞對應的個數(p1Num或p0Num)就加1,而且在總文本中,該詞條的總次數也相應加1。
代碼運行截圖以下
這裏打印出屬於侮辱性的詞條,當不一樣詞條出現同一詞語時,會在詞條向量的同一位置上累加,最後每個詞語出現的次數除以總數就獲得了相應的機率,例如出現兩次—>0.10五、出現三次—>0.15七、出現一次—>0.052。
def classifyNB(vec2Classify, p0V,P1V,PAb): #將對應元素相乘 p1 = reduce(lambda x,y:x*y, vec2Classify * p1V) * pAb p0 = reduce(lambda x,y:x*y, vec2Classify * p0V) * (1.0 - PAb) print('p0:',p0) print('p1:',p1) if p1 > p0: return 1 else: return 0
classifyNB函數傳入的4個參數分別爲測試文本的向量以及訓練函數trainNB返回的三個參數,其做用是將文本的向量與p1V和p2V分別對應相乘,並乘以pAb和其二分類對應機率(1.0-pAb),而後比較p1與p2的大小判別出測試文本屬於屬於哪一類,這裏舉一個reduce方法的小例子,方便理解。
reduce(lambda x,y:x+y,[1,2,3,4]) ''' 10 '''
reduce方法是將兩個元素以某種操做運算符爲條件歸併成一個結果,而且它是一個迭代的過程,每次調用該方法直至獲得最後一個結果,例如上面數組[1,2,3,4]以加法爲操做運算實現1+2;3+3;6+4 = 10的操做過程。
下面經過調用前文的函數,對測試數據進行分類操做,代碼以下:
def testingNB(testVec): #建立實驗樣本 postingList,classVec = loadDataSet() #建立詞彙表 vocabSet = createVocabList(postingList) #將實驗樣本向量彙總 trainMat = get_Mat(postingList) #訓練算法 p0V,P1V,PAb = trainNB(trainMat,classVec) #將測試文本向量化 The_test = setOfWords2Vec(vocabSet,testVec) #判斷類別 if classifyNB(The_test,p0V,P1V,PAb): print(testVec,"侮辱類") else: print(testVec,"非侮辱類")
傳入測試數據testVec,並返回分類結果以下圖:
哎呀,這stupid怎麼還能被判斷成非侮辱類了呢?會不會是程序變蠢了?程序是正常的,可是須要對程序作一點改進,咱們都知道0是一個特別牛皮的數,由於不論什麼數字乘以0結果都得0,因此只要p1V向量和測試向量有一個對應位置上同時都爲0,那麼最終結果必定爲0。爲了下降上述影響,能夠將全部詞的出現數初始化爲1,並將分母初始化爲2,這種方法被稱爲拉普拉斯平滑。
這部分對trainNB函數作如下更改:
p0Num = np.ones(numWords) p1Num = np.ones(numWords) p0Denom = 2.0 p1Denom = 2.0
除此以外,還有一個問題是下溢出,什麼是下溢出呢?在許多很小的數相乘時,當計算乘積 P(W0 | Ci)P(W1 | Ci)P(W2 | Ci)...P(Wn | Ci)時,因爲大部分因子都很是小,因此程序會下溢出或者得不到正確答案,好比程序會將乘積很是小的結果四捨五入後獲得0,一種經典的解決辦法是取乘積的天然對數。
在代數中有*ln(ab) = ln(a)+ln(b)**,由乘法轉爲加法後,就能夠避免下溢出或者浮點數舍入致使的錯誤,有人可能會擔憂,兩者計算出的結果是有差別的,這是事實,可是對於咱們所須要的分類結果是無影響的。
f(x)與ln(x)的曲線以下圖:
經過觀察這兩條曲線會發現它們在相同的區域同時增長或同時減小,而且在相同點取到極值,雖然兩者的極值不一樣,但不影響最終結果,由於咱們只需經過比較兩者值的大小來判斷測試數據的類別。
這部分對trainNB函數作如下更改:
p1Vect = np.log(p1Num/p1Denom) p0Vect = np.log(p0Num/p0Denom)
前面計算機率時作了取對數操做,因爲*log(ab) = log(a)+log(b)**,因此能夠對classifyNB函數進行改進,用sum方法代替reduce方法便可。
具體代碼以下:
def classifyNB(ClassifyVec, p0V,p1V,pAb): #p1 = reduce(lambda x,y:x*y, ClassifyVec * p1V) * pAb #p0 = reduce(lambda x,y:x*y, ClassifyVec * p0V) * (1.0 - PAb) #將對應元素相乘 p1 = sum(ClassifyVec * p1V) + np.log(pAb) p0 = sum(ClassifyVec * p0V) + np.log(1.0 - pAb) print('p0:',p0) print('p1:',p1) if p1 > p0: return 1 else: return 0
最後測試總體代碼運行截圖以下:
經過p0與p1的比較,能夠正確的將測試文本進行分類,stupid最後被斷定爲侮辱類,看來程序是不會變蠢的,會變蠢的是我。
前面程序中,咱們將每一個次的出現與否做爲一個特徵,這能夠被描述爲詞集模型。若是一個詞在文本中出現不止一次,不能將其單純的做爲特徵同等看待,由於其中涉及到了權重不一樣,這種方法被稱爲詞袋模型。在詞袋中,每一個單詞能夠出現若干次,而在詞集中,每一個詞只能出現一次。能夠在詞集模型的基礎上加以修改,將其轉換成詞袋模型。
代碼以下:
def setOfWords2Vec(vocabList, inputSet): #建立一個元素都爲0的向量 returnVec = [0] * len(vocabList) for word in inputSet: if word in vocabList: #若每當文本中出現詞彙表中的單詞一次,就將該位置的數字加1 returnVec[vocabList.index(word)] += 1 return returnVec
樸素貝葉斯對應優勢以下:
樸素貝葉斯對應缺點以下:
本文就樸素貝葉斯該算法的原理進行簡單介紹,下篇文章會介紹樸素貝葉斯的應用實例。
關注公衆號【奶糖貓】後臺回覆「Bayes」可獲取源碼供參考,感謝閱讀。