利用樸素貝葉斯實現簡單的留言過濾

一、樸素貝葉斯

  首先第一個問題,什麼是樸素貝葉斯?

  貝葉斯分類是一類分類算法的總稱,這類算法均以貝葉斯定理爲基礎,故統稱爲貝葉斯分類。而樸素樸素貝葉斯分類是貝葉斯分類中最簡單,也是常見的一種分類方法。而我們所想要實現的留言過濾其實是一種分類行爲,是通過對於概率的判斷,來對樣本進行一個歸類的過程。

  樸素貝葉斯分類(NBC)是以貝葉斯定理爲基礎並且假設特徵條件之間相互獨立的方法,先通過已給定的訓練集,以特徵詞之間獨立作爲前提假設,學習從輸入到輸出的聯合概率分佈,再基於學習到的模型,輸入A求出使得後驗概率最大的輸出B。

  樸素貝葉斯公式:

      

  或者說:

  

 

 

 

 

  當我們假設各項條件之間是相互獨立的,比如說“我覺得你很美”“他覺得你很美”,不論是“我”還是“他”覺得“你很美”都是無關的,並不會因爲是誰來評價而影響這個評價,那麼它就適合用樸素貝葉斯算法。

  舉一個很典型的例子,假設通過一些指標如長相、性格等來判斷一個人我們是否要嫁給他,有這樣一個表格:

長相 性格 身高 是否上進 結果
不上進 不嫁
上進 不嫁
上進
上進
上進 不嫁
不上進 不嫁
不上進
上進
上進
上進
不上進 不嫁
不上進 不嫁

 

 

 

 

 

 

 

 

 

 

  這時當我們遇到一個小夥子並且我們知道以上條件:長相醜,性格壞,身高低,不上進,現在就可以轉換成一個數學上的分類問題來比較 P(嫁|各項條件) 與 P(不嫁|各項條件) 誰的概率大我們就能給出嫁或者不嫁的答案。然而,我們需要保證這些條件之間沒有關聯,我們發現比如一個人美醜與他是否上進、一個人性格好壞和他身高之間是無關的,所以適用於樸素貝葉斯公式的條件,那麼久可以進行計算了。

  經過統計:

  p(嫁) = 6/12(總樣本數) = 1/2

  p(醜|嫁) = 3/6 = 1/2

  p(壞|嫁)= 1/6

  p(低|嫁) = 1/6

  p(不上進|嫁) = 1/6

  p(醜) = 1/3

  p(壞) = 1/3

  p(低) = 7/12

  p(不上進) = 1/3

  我們帶入公式P(嫁|醜、壞、低、不上進)=P(醜、壞、低、不上進|嫁)*P(嫁)/P(醜、壞、低、不上進)=[P(醜|嫁)*P(壞|嫁)*P(低|嫁)*P(不上進|嫁)] / [P(醜)*P(壞)*P(低)*P(不上進)]= (1/2*1/6*1/6*1/6*1/2)/(1/3*1/3*7/12*1/3)

  下面我們根據同樣的方法來求P(不嫁|醜、壞、低、不上進)= ((1/6*1/2*1*1/2)*1/2)/(1/3*1/3*7/12*1/3)

  由於P(嫁|醜、壞、低、不上進)<P(不嫁|醜、壞、低、不上進),所以我們得出結論 不嫁。

  

  這時就有了一個積蓄已久的問題,在計算之前我們爲什麼要保證各項條件之間相互獨立?

  假如沒有這個假設,那麼我們對右邊這些概率的估計其實是不可做的,這麼說,我們這個例子有4個特徵,其中帥包括{帥,醜},性格包括{不好,壞},身高包括{高,低},上進包括{不上進,上進},那麼四個特徵的聯合概率分佈總共是4維空間,總個數爲2*2*2*2=16個。在現實生活中,有非常多的特徵,每一個特徵的取值非常多,那麼通過統計來估計後面概率的值,變得幾乎不可做,這是爲什麼需要假設特徵之間獨立的原因。假如我們沒有假設特徵之間相互獨立,那麼我們統計的時候,就需要在整個特徵空間中去找,將會更多,比如我們就需要在嫁的條件下,去找四種特徵全滿足的人的個數,這樣的話,由於數據的稀疏性,很容易統計到0的情況。 這樣是不合適的。

  

  那麼我們就引出了下一個問題,如何解決0概率的問題?

  零概率問題:在計算新實例的概率時,如果某個分量在訓練集中從沒出現過,會導致整個實例的概率計算結果爲0。針對文本分類就是當一個詞語在訓練集中沒有出現過,那麼該詞語的概率是0,使用連乘法計算文本出現的概率時,整個文本出現的概率就也是0,得到的結果就會非常不合理!

  我們是不是可以對這種數據採用加一來解決?

  法國數學家拉普拉斯最早提出用加1的方法估計沒有出現過的現象的概率,所以加1平滑也叫做拉普拉斯平滑。就是對於一個離散的值我們在使用的時候不是直接輸出它的概率,而是對概率值進行“平滑” 處理。即默認所有的特徵都出現過一次,將概率改成下面的形式 其中 N 是全體特徵的總數。

 

 

   如由如下訓練數據學習一個樸素貝葉斯分類器並確定𝑥=(2,𝑆)^𝑇的類標記,特徵:𝑋1,𝑋2,取值的集合分別是{1,2,3},{S,M,L},類標記:Y∈{−1,1}:

  這還不夠,如果由於結果需要對很多個很小的數作乘法,則可能會出現下溢的情況,所以在進行處理的時候可以對概率的乘積取自然對數,根據自然對數函數的單調性,不會改變最終的大小關係,但是很好的防止了下溢出的問題。

 

二、用python去實現基於樸素貝葉斯的留言過濾

  首先要明確我們的訓練集由正常的文檔和侮辱性的文檔組成,能反映侮辱性文檔的是侮辱性詞彙的出現與否以及出現頻率。

  這樣的模型有以下兩種:

  • 詞集模型:對於給定文檔,只統計某個侮辱性詞彙(準確說是詞條)是否在本文檔出現。
  • 詞袋模型:對於給定文檔,統計某個侮辱性詞彙在本文當中出現的頻率,除此之外,往往還需要剔除重要性極低的高頻詞和停用詞。因此,詞袋模型更精煉,也更有效。

  那麼我們就要對樣本進行預處理和對訓練數據向量化,現在對於中文分詞,分詞工具有很多種,比如說:jieba分詞、thulac、SnowNLP等,這裏我們使用結巴分詞。安裝:

$ pip install jieba

  之後我們還需要用到python的numpy包。安裝:

$ pip install numpy

  

  下面展示實例代碼:

  去除停用詞:

import jieba

# 創建停用詞列表
def stopwordslist():
    stopwords = [line.strip() for line in open('chinsesstoptxt.txt',encoding='UTF-8').readlines()]
    return stopwords

# 對句子進行中文分詞
def seg_depart(sentence):
    # 對文檔中的每一行進行中文分詞
    print("正在分詞")
    sentence_depart = jieba.cut(sentence.strip())
    # 創建一個停用詞列表
    stopwords = stopwordslist()
    # 輸出結果爲outstr
    outstr = ''
    # 去停用詞
    for word in sentence_depart:
        if word not in stopwords:
            if word != '\t':
                outstr += word
                outstr += " "
    return outstr

#停用詞庫可以百度搜索下載

  留言過濾:

import numpy as np
import jieba

class isgentry():
    def __init__(self,testWords):
        self.testWords=testWords
    def loadData(self):
        wordList=[
                    ['','傻逼'],
                    ['','朋友','','','厲害'],
                    ['','看起來','非常','聰明','','喜歡',''],
                    ['','','噁心'],
[
'弱智'],['愚蠢'],[''],['神經病'],['笨蛋'],['腦殘'],['垃圾'],[''],['討厭'], ['美麗'],['睿智'],[''],[''],['博學'],['漂亮'],['實用'],[''] ]
     # 訓練樣本內容,可以採用傳入詞袋模型,也可以用詞集模型
        classList=[1,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0]
     # 訓練樣本的預分類
return wordList,classList def creatVocabList(self,wordList): vocabSet=set([]) for document in wordList: vocabSet=vocabSet|set(document) vocabList=list(vocabSet) return vocabList def setOfWords2Vec(self,vocabList,words): wordVec=[0]*len(vocabList) for word in words: if word in vocabList: wordVec[vocabList.index(word)]=1 return wordVec def bagOfWords2Vec(self,vocabList,words): wordVec=[0]*len(vocabList) for word in words: if word in vocabList: wordVec[vocabList.index(word)]+=1 else: pass return wordVec def trainNB(self,vocabList,trainMat,classList): numWords=len(vocabList) pSpam=(sum(classList)+1)/(len(classList)+2) p1Num=np.ones(numWords) p0Num=np.ones(numWords) p1Denom=0 p0Denom=0 for i in range(len(classList)): if classList[i]==1: p1Num+=trainMat[i] p1Denom+=sum(trainMat[i]) else: p0Num+=trainMat[i] p0Denom+=sum(trainMat[i]) p1Denom+=numWords p0Denom+=numWords p1Vec=np.log(p1Num/p1Denom) p0Vec=np.log(p0Num/p0Denom) return p1Vec,p0Vec,pSpam def classifyNB(self,vocabList,trainMat,newWordVec,classList): p1Vec,p0Vec,pSpam=self.trainNB(vocabList,trainMat,classList) p1=sum(newWordVec*p1Vec)+np.log(pSpam) p0=sum(newWordVec*p0Vec)+np.log(1-pSpam) return True if p1>p0 else False def runTest(self): wordList,classList=self.loadData() vocabList=self.creatVocabList(wordList) trainMat=[] for words in wordList: trainMat.append(self.setOfWords2Vec(vocabList,words)) testWords=jieba.cut(self.testWords) newWordVec=self.setOfWords2Vec(vocabList,testWords) result=self.classifyNB(vocabList,trainMat,newWordVec,classList) return True if result else False # testWords='你真醜,我討厭你' # result=isgentry(testWords).runTest() # 返回布爾值,真爲粗魯語言


三、用樸素貝葉斯的留言過濾的優缺點

優點:

  (1)算法邏輯簡單,易於實現(算法思路很簡單,只要使用貝葉斯公式轉化即可!)

  (2)分類過程中時空開銷小(假設特徵相互獨立,只會涉及到二維存儲)

缺點:

  理論上,樸素貝葉斯模型與其他分類方法相比具有最小的誤差率。但是實際上並非總是如此,這是因爲樸素貝葉斯模型假設屬性之間相互獨立,這個假設在實際應用中往往是不成立的,在屬性個數比較多或者屬性之間相關性較大時,分類效果不好。

而在屬性相關性較小時,樸素貝葉斯性能最爲良好。對於這一點,有半樸素貝葉斯之類的算法通過考慮部分關聯性適度改進。

 

所以,引出我們最後一個問題,如何改進樸素貝葉斯算法?

伯努利樸素貝葉斯:BernoulliNB   重複的詞語視爲只出現一次

多項式樸素貝葉斯:MultinomialNB   重複的詞語視爲出現多次

高斯樸素貝葉斯: GaussianNB   特徵屬性是連續數值

希望對正在入門機器學習的同學有幫助~  

 

  

  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章