機器學習-樸素貝葉斯算法

貝葉斯定理

w是由待測數據的所有屬性組成的向量。p(c|x)表示,在數據爲x時,屬於c類的概率。
p(cw)=p(wc)p(c)p(w) p(c|w)=\frac{p(w|c)p(c)}{p(w)}

如果數據的目標變量最後有兩個結果,則需要分別計算p(c1|x)p(c2|x)取最大的值爲分類的結果
p(c1w)=p(wc1)p(c1)p(w)p(c2w)=p(wc2)p(c2)p(w) p(c_{1}|w)=\frac{p(w|c_{1})p(c_{1})}{p(w)}、 p(c_{2}|w)=\frac{p(w|c_{2})p(c_{2})}{p(w)}

算法的目的就在於找到使p最大的 cic_{i} 。由於只需要比較兩個概率的大小,則分母p(w)可以不用算,並不影響結果。那 p(wc0)p(c0)p(w|c_{0})p(c_{0}) 又如何計算呢?一條數據w其實包含很多屬性w=w1,w2,w3,...,wn.以p(wc0)p(c0)p(w|c_{0})p(c_{0})爲例:

p(c0) 表示分類結果爲c0的概率:
p(c0)=c0 p(c_{0})=\frac{數據集中屬於c_{0}類別的數據條數}{數據集的總數}

p(wc0)p(w|c_{0}) == p(w1,w2,w3,...,wnc0)p(w_{1},w_{2},w_{3},...,w_{n}|c_{0})。樸素貝葉斯分類假設所有屬性之間是獨立的,互不影響。那麼就滿足如下關係:
p(w1,w2,w3,...,wnc0)=p(w1c0)p(w2c0)p(w3c0)...p(wnc0) p(w_{1},w_{2},w_{3},...,w_{n}|c_{0}) = p(w_{1}|c{0})p(w_{2}|c{0})p(w_{3}|c{0})...p(w_{n}|c{0}) p(w1c0)=c0w1c0 p(w_{1}|c{0})=\frac{在c_{0}類別的數據中單詞w_{1}出現的次數}{屬於c_{0}類別的單詞總數}

至此,已經計算出了足夠數據來計算出p(wc1)p(c1)p(w|c_{1})p(c_{1}),用這些概率可以給新的數據分類。如果此時有數據 w=w1,w3,w5,那需要分別算出兩個概率:
p(c0w1,w3,w5)=>p(w1c0)p(w3c0)p(w5c0)p(c0) p(c_{0}|w_{1},w_{3},w_{5})=>p(w_{1}|c{0})p(w_{3}|c{0})p(w_{5}|c{0})p(c_{0}) p(c1w1,w3,w5)=>p(w1c1)p(w3c0)p(w5c1)p(c1) p(c_{1}|w_{1},w_{3},w_{5})=>p(w_{1}|c{1})p(w_{3}|c{0})p(w_{5}|c{1})p(c_{1})
比較大小,找到最大的概率,最大概率的cic_{i}就是分類的結果

算法實現

收集數據

1、從文本文件中讀取數據,並分割成每個單詞,放到一個list中。每個文件一個list,最後是一個二維的list:

[[‘my’, ‘dog’, ‘has’, ‘flea’, ‘problems’, ‘help’, ‘please’],
[‘maybe’, ‘not’, ‘take’, ‘him’, ‘to’, ‘dog’, ‘park’, ‘stupid’]]

def loadDataSet():
    """
    創建數據集
    :return: 文檔列表 docList, 所屬類別classVec
    """
    docList = [['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]  # 1 is abusive, 0 not
    return docList, classVec

2、遍歷上例的二維表,找到所有出現的單詞,使用set()去重。這個集合就作爲數據集的屬性.上例的詞彙表爲:

[‘steak’, ‘dog’, ‘problems’, ‘so’, ‘buying’, ‘my’, ‘how’, ‘licks’, ‘dalmation’, ‘take’, ‘food’, ‘maybe’, ‘stop’, ‘posting’, ‘him’, ‘garbage’, ‘has’, ‘stupid’, ‘park’, ‘ate’, ‘mr’, ‘not’, ‘love’, ‘help’, ‘worthless’, ‘flea’, ‘please’, ‘quit’, ‘to’, ‘is’, ‘I’, ‘cute’]

def createVocabList(docList):
    """構造詞彙表,統計所有文本中的所有單詞
    :return list 去重的詞彙表
    """
    vocalSet = set([])
    for line in docList:
        vocalSet = vocalSet | set(line)     # 集合求並集操作
    return list(vocalSet)

3、將每個文件的單詞列表轉換爲向量。遍歷文件中的每個單詞,如果出現在詞彙表中則爲1,否則爲0

[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1]

def setOfWords2Vec(vocabList, inputSet):
    """將輸入數據轉換爲向量.存在這個單詞記爲1,不存在則記爲0"""
    returnVec = [0] * len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1
    return returnVec

訓練算法

訓練算法就是計算一系列概率的過程。要預測一個文本,需要計算以下概率:

  • p(c0)p(c_{0})p(c1)p(c_{1})
  • p(wic0)p(w_{i}|c_{0})p(wic1)p(w_{i}|c_{1})

下面的代碼計算出了這些概率,其中p0是一個列表,其中記錄了每一個p(wic0)p(w_{i}|c_{0})的值。p1同理。pc1表示p(c1)p(c_{1})p(c0)p(c_{0}) 可以用 1-pc1 得到

p0: [[0.04166667 0.04166667 0.04166667 0.04166667 …]]
p1: [[0. 0.10526316 0. 0. …]]
pc: 0.5

def trainNB0(trainMatrix, trainCategory):
    """分類器訓練函數"""
    numberOfAttr = len(trainMatrix[0])
    numbrOfDoc = len(trainMatrix)
    p0 = np.zeros((1, numberOfAttr))                # p(wi|c0)
    p1 = np.zeros((1, numberOfAttr))                # p(wi|c1)
    p0Total = 0.0
    p1Total = 0.0
    pc1 = float(sum(trainCategory)) / numbrOfDoc          # p(c1)

    for i in range(numbrOfDoc):
        if trainCategory[i] == 0:
            p0 += trainMatrix[i]                            # 統計先驗概率c0下,每個單詞出現的次數
            p0Total += sum(trainMatrix[i])
        else:
            p1 += trainMatrix[i]                            # 統計先驗概率c1下,每個單詞出現的次數
            p1Total += sum(trainMatrix[i])

    p0 = p0 / p0Total           # 用c0下每個單詞出現的次數,分別除以c0下的總數==> p(wi|c0)
    p1 = p1 / p1Total           # p(wi|c1)

    return p0, p1, pc1

分類

根據之前的理論。如果數據有三個單詞 w=w1,w3,w5,那需要分別算出兩個概率:
p(c0w1,w3,w5)=>p(w1c0)p(w3c0)p(w5c0)p(c0) p(c_{0}|w_{1},w_{3},w_{5})=>p(w_{1}|c{0})p(w_{3}|c{0})p(w_{5}|c{0})p(c_{0}) p(c1w1,w3,w5)=>p(w1c1)p(w3c0)p(w5c1)p(c1) p(c_{1}|w_{1},w_{3},w_{5})=>p(w_{1}|c{1})p(w_{3}|c{0})p(w_{5}|c{1})p(c_{1})

優化代碼

1、書上說,對於 p(w1c0)p(w2c0)p(w3c0)p(w_{1}|c{0})p(w_{2}|c{0})p(w_{3}|c{0}) 如果其中任何一個概率爲0,則總概率爲零,所以把所有單詞出現的次數初始化爲1。(其實不改也行,但是代碼中inputData * p0已經過濾出了所有非零元素)

2、 概率都是很小的數,如果直接以小數運算會帶來很大的誤差。書上採用了對數替代直接的小數運算。本來的概率是這樣算的
p(w1c0)p(w3c0)p(w5c0)p(c0)==count(w1c0)count(c0)count(w3c0)count(c0)count(w5c0)count(c0)p(c0) p(w_{1}|c{0})p(w_{3}|c{0})p(w_{5}|c{0})p(c_{0}) == \frac{count(w_{1}|c_{0})}{count(c{0})}\frac{count(w_{3}|c_{0})}{count(c{0})}\frac{count(w_{5}|c_{0})}{count(c{0})}p(c_{0})
現在使用對數,In(fx)並不會影響f(x)的單調性,所以計算的結果可以直接比較大小,不會影響分類結果。計算方式如下:
In(p(w1c0)p(w3c0)p(w5c0)p(c0))==In(count(w1c0)count(c0))+In(count(w3c0)count(c0))+In(count(w5c0)count(c0))+In(p(c0)) In(p(w_{1}|c{0})p(w_{3}|c{0})p(w_{5}|c{0})p(c_{0})) == In(\frac{count(w_{1}|c_{0})}{count(c{0})})+In(\frac{count(w_{3}|c_{0})}{count(c{0})})+In(\frac{count(w_{5}|c_{0})}{count(c{0})}) + In(p(c_{0}))

優化後的訓練代碼如下:

def trainNB1(trainMatrix, trainCategory):
    """分類器訓練函數"""
    numberOfAttr = len(trainMatrix[0])
    numbrOfDoc = len(trainMatrix)
    p0 = np.ones((1, numberOfAttr))                # p(wi|c0)
    p1 = np.ones((1, numberOfAttr))                # p(wi|c1)
    p0Total = 2.0                                   # 不唯一
    p1Total = 2.0
    pc1 = float(sum(trainCategory)) / numbrOfDoc          # p(c1)

    for i in range(numbrOfDoc):
        if trainCategory[i] == 0:
            p0 += trainMatrix[i]
            p0Total += sum(trainMatrix[i])
        else:
            p1 += trainMatrix[i]
            p1Total += sum(trainMatrix[i])

    p0 = np.log(p0 / p0Total)
    p1 = np.log(p1 / p1Total)

    return p0, p1, pc1

分類的代碼如下:

def classifyNB(inputData, p0, p1, pc1):
    """使用計算得到的概率分類"""
    prob0 = np.sum(inputData * p0) + np.log(1-pc1)
    prob1 = np.sum(inputData * p1) + np.log(pc1)
    if prob0 > prob1:
        return 0
    else:
        return 1

測試代碼:

def testNB():
    """測試函數"""
    docList, classVec = loadDataSet()
    vocabList = createVocabList(docList)

    trainMat = []                       # 由0/1組成的數據集 : [[0,1,0,0,1,....],[0,0,0,0,0,1,...]]
    for doc in docList:
        trainMat.append(setOfWords2Vec(vocabList, doc))
    p0, p1, pc1 = trainNB0(trainMat, classVec)

    testData = ['love', 'my', 'dalmation']
    thisDoc = setOfWords2Vec(vocabList, testData)
    print("分類結果是:", classifyNB(thisDoc, p0, p1, pc1))

    testData = ['stupid', 'garbage']
    thisDoc = setOfWords2Vec(vocabList, testData)
    print("分類結果是:", classifyNB(thisDoc, p0, p1, pc1))

詞袋模型

上面的代碼中,文件中出現的單詞,記爲1,否則爲0。這種方式叫詞集模型(set-of-words model)。得到的是如下的向量:

[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1]

但是同一個單詞在文檔中可能多次出現,在向量中記錄單詞出現的次數的方式叫做詞袋模型(bag-of-words model)。得到的向量可能是這樣的:

[0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 7, 0, 0, 2]

要實現詞袋模型只需要改動很少量的代碼:

def bagOfWords2Vec(vocabList, inputSet):
    """[詞袋模型]將輸入數據轉換爲向量.存在這個單詞記爲1,不存在則記爲0"""
    returnVec = [0] * len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1
    return returnVec

案例:過濾垃圾郵件

def textParse(bigString):
    """將字符串返回成單詞列表
    1. 以空白字符作爲分隔符
    2. 排除長度小於2的單詞,他可能沒有實際意義
    3. 所有單詞轉換爲小寫
    """
    import re
    listOfWords = re.split(r'\W*', bigString)
    return [word.lower() for word in listOfWords if len(word) > 2]


def spamTest():
    """測試算法。使用交叉驗證"""
    docList, classList, fullText = [], [], []
    # 1. 解析文本文件。一個文件解析成一個list,所有文件保存爲一個二維list
    for i in range(1, 26):
        spam = open("dataset/email/spam/%d.txt" % i)
        wordList = textParse(spam.read())
        docList.append(wordList)
        classList.append(1)

        ham = open("dataset/email/ham/%d.txt" % i)
        wordList = textParse(ham.read())
        docList.append(wordList)
        classList.append(0)

    # 2.格式化
    vocabList = createVocabList(docList)     # 創建詞彙表

    # 3. 隨機挑選10個測試數據(可能沒有10個)
    trainSet = list(range(50))            # 記錄了所有用於訓練的數據集的下標
    testSet = []                    # 記錄了所有用於測試的數據集的下標
    for i in range(10):
        randIndex = int(random.uniform(0, len(trainSet)))
        testSet.append(trainSet[randIndex])
        del trainSet[randIndex]

    # 4. 訓練
    trainMatrix, trainCategory = [], []
    for i in range(len(trainSet)):
        trainMatrix.append(bagOfWords2Vec(vocabList, docList[trainSet[i]]))
        trainCategory.append(classList[trainSet[i]])
    p0, p1, pc1 = trainNB1(trainMatrix, trainCategory)

    # 5. 測試
    testMatrix, testCategory, error = [], [], 0
    for i in range(len(testSet)):
        line = bagOfWords2Vec(vocabList, docList[testSet[i]])
        result = classifyNB(line, p0, p1, pc1)
        if result != classList[testSet[i]]:
            error += 1
    print("錯誤率爲:", (float(error)/len(testSet)))
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章