1. 程式人生 > >機器學習實戰讀書筆記(四):樸素貝葉斯演算法

機器學習實戰讀書筆記(四):樸素貝葉斯演算法

樸素貝葉斯
優點: 在資料較少的情況下仍然有效 可以處理多類別問題
缺點:對輸入的資料的準備方式較為敏感
適用資料型別:標稱型資料
p1(x,y)>p2(x,y) 那麼類別是1
p2(x,y)>p1(x,y) 那麼類別是2
貝葉斯決策的核心是選擇具有最高概率的決策
樸素貝葉斯分類器通常有兩種方式 : 伯努利模型 和 多項式模型
這裡採用伯努利實現方式 該實現不考慮詞在文件中出現的次數 只考慮是否出現
某種意義上相當於假設詞是等權重的

from numpy import *


# 詞表到向量的轉換函式
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] # 1代表侮辱詞彙 # 0代表正常言論
    return postingList,classVec

# 建立詞彙表
def createVocabList(dataSet):
    # 建立詞彙集合 空集 初始為空集
    vocabSet = set([])
    for document in dataSet:
        # 操作符| 用於取兩個集合的並集
        # vocabSet|set(document) 兩個詞彙集合取並集
        # 將每篇文章中的新詞加入到詞彙集
        # set集合所有元素不重複
        vocabSet = vocabSet|set(document)
        # 將詞彙集轉換為list
    return list(vocabSet)

# 詞集模型 只考慮單詞是否出現
# vocabList:詞彙表
# inputSet:某個文件向量
def setofWords2Vec(vocabList,inputSet):
    # 建立一個其中所含元素都為0的向量
    returnVec = [0]*len(vocabList)
    # 輸入集合中的詞彙如果出現在詞彙表中 定影向量位置設定為1
    for word in inputSet:
        if word in vocabList:
            # vocabList.index(word) 找到word所在的索引值
            # 因為vocabList是從set集合轉換過來所以每次只改變一個0
            returnVec[vocabList.index(word)] = 1
        else:
            print('The word: %s is not in my Vocabulary!'%word)
    return returnVec

# 樸素貝葉斯詞袋模型
# 每個詞是否出現作為一個特徵 這可以作為詞集模型 伯努利模型
# 如果一個詞不僅出現一次 是這個詞是否出現不能表達的資訊 被稱為 詞袋模型 多項式模型
# 對setofWords2Vec()進行修改

def bagofWords2VecMN(vocabList, inputSet):
    returnVec = [0]*len(vocabList)
    for word in inputSet:
        if word in inputSet:
            # 該詞彙出現自動加一
            returnVec[vocabList.index(word)] += 1
    return returnVec


# 樸素貝葉斯分類器訓練函式
# trainMatrix 詞向量資料集
# trainCategory 資料集對應的類別標籤

# p0Vect:詞彙表中各個單詞在正常言論中的類條件概率密度
# p1Vect:詞彙表中各個單詞在侮辱性言論中的類條件概率密度
# pAbusive:侮辱性言論在整個資料集中的比例

def trainNB0(trainMatrix,trainCategory):
    # 訓練集總條數
    numTrainDocs = len(trainMatrix)
    # 訓練集中的所有不重複單詞總數
    numWords = len(trainMatrix[0])
    # 初始化概率 計算屬於侮辱性文件 class = 1 的概率,p(1)
    # p(0) = 1-p(1) 二分類 多於兩類需要修改
    # 侮辱類佔總數的比例
    pAbusive = sum(trainCategory)/float(numTrainDocs)
    # 正常言論的類條件概率密度p(某單詞|正常言論)=p0Num/p0Denom
    p0Num = zeros(numWords)
    # 侮辱性言論的類條件概率密度 p(某單詞|侮辱性言論)=p1Num/p1Denom
    p1Num = zeros(numWords)
    # 初始化分母置為0
    p0Denom = 0.0
    p1Denom = 0.0
    for i in range(numTrainDocs):
        # 以下是向量相加
        if trainCategory[i] == 1:
            # 如果該篇文章是侮辱性的
            # 統計侮辱類所有文件中的各個單詞總數
            p1Num += trainMatrix[i]
            # p1Denom侮辱類總單詞數
            p1Denom += sum(trainMatrix[i])
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])

    # 詞彙表中的單詞在侮辱性言論文件中的類條件概率
    p1Vect = p1Num/p1Denom
    # 詞彙表中的單詞在正常性言論文件中的類條件概率
    p0Vect = p0Num/p0Denom
    return p0Vect , p1Vect , pAbusive


# 利用貝葉斯分類器對文件進行分類時,要計算多個概率的乘積以獲取某個文件屬於某個類別的概率
# 即計算p(w0|1)*p(w1|1)p(w2|1)
# 若其中有一個概率值為0,最後乘積為零 為降低這種影響
# 可以把所有詞的出現初始化為1,並將分母初始化為2
# 大部分相乘因子很小,所以程式會下溢位,四捨五入得到0,所以對乘積取自然對數
# ln(a*b) = ln(a)+ln(b) 避免下溢位或者浮點數舍入導致錯誤 同時採用自然對數不會有任何損失
# 修改trainNB0

def trainNB1(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix)
    numWords = len(trainMatrix[0])
    pAbusive = sum(trainCategory) / float(numTrainDocs)
    # 修改初試值
    p0Num = ones(numWords)
    p1Num = ones(numWords)
    p0Denom = 2.0
    p1Denom = 2.0
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    # 取自然對數
    p1Vect = log(p1Num / p1Denom)
    p0Vect = log(p0Num / p0Denom)
    return p0Vect, p1Vect, pAbusive


# 樸素貝葉斯分類函式
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    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 postinDoc in listOPosts:
        # 資料轉換後存入資料訓練矩陣trainMat中
        trainMat.append(setofWords2Vec(myVocabList, postinDoc))
    # 訓練分類器
    p0V, p1V, pAb = trainNB1(array(trainMat),array(listClasses))
    testEntry = ['love','my','dalmation']
    thisDoc = array(setofWords2Vec(myVocabList, testEntry))
    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))

listOPosts , listClasses = loadDataSet()
myVocabList = createVocabList(listOPosts)

# 檔案解析及完整的垃圾郵件測試函式
# 1
# print(myVocabList)
# print(setofWords2Vec(myVocabList,listOPosts[0]))
# print(setofWords2Vec(myVocabList,listOPosts[3]))

# 2
# trainMat = []
# for postinDoc in listOPosts:
#     trainMat.append(setofWords2Vec(myVocabList, postinDoc))
# p0V, p1V, pAb = trainNBO(trainMat, listClasses)
# print(pAb)
# print(p0V)
# print(p1V)


# 3
testingNB()

# 4
# 使用樸素貝葉斯對電子郵件進行分類

# 準備資料 切分文字
mySent = 'This book is the best book on python or M.L. I have ever laid eyes upon.'
import re
regEx = re.compile('\W')
listOgTokens = regEx.split(mySent)

# 去除空字串
# list1 = [tok for tok in listOgTokens if len(tok) > 0]

# 去除空字元串同時全部轉化為小寫
# tok.upper()
# tok.lower()
# list2 = [tok.lower() for tok in listOgTokens if len(tok) > 0]

# print(listOgTokens)
# print(list1)
# print(list2)


# emailText = open('email/ham/6.txt').read()
# listOgTokens = regEx.split(emailText)
# listText = [tok.lower() for tok in listOgTokens if len(tok) > 2]
# print(listText)

# 測試演算法 使用樸素貝葉斯進行交叉驗證
# 留存交叉驗證: 隨機選擇資料的一部分作為訓練集,剩餘部分作為測試集的過程
# 檔案解析及完整的垃圾函式測試函式
def textParse(bigString):
    import re
    listOgTokens = re.split(r'\W',bigString)
    return [tok.lower() for tok in listOgTokens if len(tok) >2]

def spamTest():
    # 詞彙列表 1*n列 1維陣列
    docList = []
    # 結果列表 1*n列 1維陣列 0:正常 1:垃圾郵件
    classList = []
    # 二維陣列 存放郵件 和結果列表一一對應
    fullText = []
    # 填充三個列表
    for i in range(1,26):
        wordList = textParse(open('email/spam/%d.txt'%i).read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(1)
        wordList = textParse(open('email/ham/%d.txt'%i).read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)
    # 詞彙列表去重取並集
    vocabList = createVocabList(docList)
    # 為每封郵件編號
    trainingSet = list(range(50))
    testSet = []

    # 50個郵件隨機分成2組,一組10個,一組40個 用於測試和訓練
    for i in range(10):
        # 隨機選取10個下標放入到testSet內
        # 從訓練下標集合中刪除
        randIndex = int(random.uniform(0,len(trainingSet)))
        testSet.append(trainingSet[randIndex])
        del(trainingSet[randIndex])
    # 根據trainingSet組成trainMat矩陣 為2維
    trainMat = []
    # 訓練郵件結果
    trainClasses = []
    for docIndex in trainingSet:
        trainMat.append(setofWords2Vec(vocabList, docList[docIndex]))
        trainClasses.append(classList[docIndex])
    p0V, p1V, pSpam = trainNB0(array(trainMat),array(trainClasses))
    errorCount = 0
    for docIndex in testSet:
        wordVector = setofWords2Vec(vocabList,docList[docIndex])
        if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
            errorCount += 1
    print('the error rate is: ',float(errorCount)/len(testSet))


spamTest()