1. 程式人生 > >機器學習實戰讀書筆記(3)--樸素貝葉斯

機器學習實戰讀書筆記(3)--樸素貝葉斯

基於貝葉斯決策理論的分類方法

優點:在資料較少的情況下仍然有效,可以處理多類別問題

缺點:對輸入資料的準備方式比較敏感,需要標稱資料.確定貝葉斯最優假設的計算代價較大

樸素貝葉斯是貝葉斯決策理論的一部分.貝葉斯決策理論的核心思想:一個數據集包括2類(或兩類以上)資料,這些資料有一些維度,如果已知一個數據的特徵,由該特徵得到其屬於第一類的可能性 p 1 (

x , y ) 和其屬於第二類的可能性 p 2 ( x , y )
,當 p 1 ( x , y ) > p 2 (
x , y )
時,認為該資料屬於第一類而不屬於第二類.

為了計算上文中提到的概率,需要引入條件概率和貝葉斯變換.

條件概率:

p ( A | B ) = P ( A B ) P ( B )

貝葉斯變換:

P ( A | B ) = P ( B | A ) P ( A ) P ( B )

通過貝葉斯變換,可以將求P(c1|x,y)轉化為求P(x,y|c1)P(c1),進而通過訓練資料集的帶有標籤的特徵訓練,在c先驗概率下求x,y的概率,求得P(c1|x,y),對未知標籤的(x,y)進行判斷.

樸素貝葉斯的假設:
- 一個特徵或單詞的出現的可能性與其他鄰近單詞沒有關係.(不準確但是有效,樸素的含義)
- 每個特徵同等重要

使用貝葉斯進行文字分類

從文字獲取特徵,即需要拆分文字,特徵來自詞條(token),一個詞條是字元的任意組合,一個文字可以表示為一個詞向量,其中為1表示這個詞條存在,為0表示未出現.

from numpy import *
import feedparser

# 準備資料
def loadDataSet():
    postingList = [['my', 'dog', 'has', 'flea', \
                    'problem', '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]  # 1代表侮辱性文字, 0代表正常言論
    return postingList, classVec

首先做準備工作包括對文字提取詞,並製作詞表,所謂詞表就是出現過的所有有效詞的集合,有了詞表之後,將文字按照詞表轉換成詞向量.

def createVocabList(dataSet):
    '''
    從資料集提取所有出現過的詞作為詞表
    dataSet可以是一個二維陣列
    '''
    vocabSet = set([])  # 建立一個空集
    for document in dataSet:
        vocabSet = vocabSet | set(document)  # 建立兩個集合的並集
    return list(vocabSet)


def setOfWords2Vec(vocabList, inputSet):
    '''詞集模型, 將資料集中的詞彙列表轉換為詞向量'''
    returnVec = [0]*len(vocabList) # 建立一個和vocabList等長的向量
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1 # 如果word在vocabList中,其索引對應位置的特徵維度修改為1
        else:
            print("The word:%s is not in my Vocabulary" %word)
    return returnVec


def bagOfWords2Vec(vocabList, inputSet):
    '''詞袋模型'''
    returnVec = [0]*len(vocabList) # 建立一個和vocabList等長的向量
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1 # 如果word在vocabList中,其索引對應位置的特徵維度加1
        else:
            print("The word:%s is not in my Vocabulary" %word)
    return returnVec

接下來是真正的訓練部分.我們知道要計算 P ( c | ω ) ,就需要計算 P ( ω | c ) P ( c ) P ( ω ) ,其中w為一個文字,文字獨一無二,用來計算概率沒有意義,所以其實是計算構建文字的詞條的概率,那麼詞條和文字之間的關係是什麼呢,根據樸素貝葉斯的第一個假設,各詞條之間獨立,則有

P ( ω | c ) = P ( ω 1 | c ) P ( ω 2 | c ) . . . P ( ω m | c )
訓練部分做的就是計算每個詞條的 P ( ω i | c ) ,累計某個分類中的文字內各詞條出現的次數,去除以該類文字的詞條總量即是所求的概率.這個概率去乘以P(c),即該類中文字數量在文字總數中的出現的頻數,就是我們需要的 P ( c | ω i ) .對於一個未知的 ω ,求其 P ( c | ω ) 即是求上式.

def trainNB0(trainMatrix, trainCategory):
    '''
    計算詞表/詞袋中的詞在有辱罵性句子/沒有辱罵性句子的前提下出現的條件概率
    trainMatrix--訓練矩陣,注意!!!是ndarray格式
    trainCategory--訓練向量的標籤列表 注意!!!也是ndarray格式
    '''
    numofDoc = len(trainMatrix) # 用於訓練的句子數
    numofWord = len(trainMatrix[0]) # 用於分類的詞彙數量
    pAbusive = sum(trainCategory)/numofDoc # p(c1)為有辱罵性的句子的總數除以句子總數
    # p1Num = zeros(numofWord); p0Num = zeros(numofWord) # 初始化各單詞的計數
    # p1Cal = 0.0; p0Cal = 0.0
    p1Num = ones(numofWord); p0Num = ones(numofWord) # 初始化各單詞的計數
    p1Cal = 2.0; p0Cal = 2.0
    # 這裡的修改解決了一個問題,在第一版情況下,有大量的詞的條件概率為0,
    # 在利用貝葉斯變換相乘時會導致結果的錯誤.所以把每個詞的初始頻率為
    # 0.5,即在初始情況下,一個詞出現在兩邊的概率相等.
    for i in range(numofDoc):
        if trainCategory[i] == 1:
            p1Num += trainMatrix[i] # p1Num是一個向量,對有辱罵性句子中的各詞出現次數累加
            p1Cal += sum(trainMatrix[i]) # p0Num同樣是向量~
        else:
            p0Num += trainMatrix[i] # 計算有辱罵性句子中詞彙總數
            p0Cal += sum(trainMatrix[i]) #計算無辱罵性~
    # p1 = p1Num/p1Cal # 計算各詞的p(w_i | c1)
    # p0 = p0Num/p0Cal # 計算各詞的p(w_i | c0)
    p1 = log(p1Num / p1Cal)
    p0 = log(p0Num/p0Cal)
    # 第二版解決了另一個問題,即使每個詞的初始概率為0.5,大量詞的概率相乘後,得到的數非常小,可能
    # 導致下溢位,即變成0,將小數相乘轉化為對數相加,能夠有效避免下溢位並且結果的數值雖然發生變化,
    # 但趨勢不變
    return p0, p1, pAbusive
def classifyNB(vec2Classify,p0Vec, p1Vec, pClass1):
    '''根據詞表中提供的條件概率,及侮辱性句子的在資料集中出現的概率,計算一個測試句子是/不是侮辱性句子的概率
    由於vec2classify和p兩個向量需要做元素相乘,
    故vec2classify也需要是ndarray
    但是list和一個array相乘時,numpy會把list轉為array然後做相乘,這是一個trick
    但是最好還是在使用函式的時候加上array()
    '''
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)
    p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
    if p1 > p0:
        return 1
    else: return 0



def testingNB():
    '''便利函式,將功能的測試封裝到一個函式'''
    listOPosts, listClasses = loadDataSet()
    myVocabList = createVocabList(listOPosts)
    trainMat = []
    for line in listOPosts:
        trainMat.append(setOfWords2Vec(myVocabList, line))
    p0V, p1V, pAb = trainNB0(trainMat, listClasses)
    testEntry = ['love', 'my', 'dalmation']
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry)) # 注意轉化為ndarray陣列
    print(testEntry, 'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb))
    testEntry = ['stupid', 'garbage']
    thisDoc = array(setOfWords2Vec(myVocabList,testEntry))
    print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))

testingNB()
['love', 'my', 'dalmation'] classified as:  0
['stupid', 'garbage'] classified as:  1

利用樸素貝葉斯過濾垃圾郵件

過濾垃圾郵件同樣是對垃圾郵件和非垃圾郵件的文字進行詞表製作,然後計算詞表中的各詞的概率.
談一談這裡面測試集和訓練集的問題,從資料集中隨機選取一部分作為訓練集,而剩餘部分作為測試集的過程稱為留存交叉驗證,分類器所需要的概率計算只能從訓練集中的文件來完成.

def splitParse(bigString):
    '''split the phrase to words longer than 2 characters'''
    import re
    regEx = re.compile('\\W*')
    listOfToken = regEx.split(bigString)
    tokenList = [tok.lower() for tok in listOfToken if len(tok) > 2]
    return tokenList


def spamTest():
    docList = [] # 文件列表,每個元素是該文件的劃分詞後的列表
    fullText = [] # 包含所有文件的詞彙,用於製作詞表 這個是幹什麼的呢?
    classList = [] # 即上文的label,用於標記資料集的分類標籤
    for i in range(1,26):
        wordList = splitParse(open('MLIA_self/ch_04/email/spam/%d.txt' % i,encoding='ISO-8859-1').read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(1) #類別標籤為1,即是垃圾郵件
        wordList = splitParse(open('MLIA_self/ch_04/email/ham/%d.txt' % i, encoding='ISO-8859-1').read())

        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)  # 類別標籤為1,即是垃圾郵件
    vocabList = createVocabList(docList)
    trainSet = list(range(50)); testSet = [] # 初始化訓練集和測試集的index
    for i in range(10):
        randIndex = int(random.uniform(0,len(trainSet)))
        testSet.append(trainSet[randIndex])
        del(trainSet[randIndex]) #  這樣刪除不會導致刪了一個之後index就變了麼? 上一行要改
    trainMat = []; trainClasses = []
    for i in trainSet: # 把訓練集和訓練集的標籤匯入
        trainMat.append(setOfWords2Vec(vocabList,docList[i]))
        trainClasses.append(classList[i])
    p0V, p1V, pSpam = trainNB0(array(trainMat),array(trainClasses))
    errorCount = 0.0
    for i in testSet:
        result = classifyNB(array(setOfWords2Vec(vocabList, docList[i])),p0V,p1V,pSpam)
        if result != classList[i]:
            errorCount += 1
    print('The error rate is: %.3f' % float(errorCount / len(testSet)))


spamTest()
The error rate is: 0.000


/home/caid/anaconda3/lib/python3.6/site-packages/ipykernel_launcher.py:5: FutureWarning: split() requires a non-empty pattern match.
  """

在本章的利用貝葉斯處理文字的例子中,詞條切分器和停用詞的設定都值得深入研究,如果遇到文字處理問題時,這一塊還要繼續深入.