李航,統計學習方法- 樸素貝葉斯:

 

'''
數據集:Mnist
訓練集數量:60000
測試集數量:10000
------------------------------
運行結果:
    正確率:84.3%
    運行時長:103s
'''

import numpy as np
import time

def loadData(fileName):
    '''
    加載文件
    :param fileName:要加載的文件路徑
    :return: 數據集和標籤集
    '''
    #存放數據及標記
    dataArr = []; labelArr = []
    #讀取文件
    fr = open(fileName)
    #遍歷文件中的每一行
    for line in fr.readlines():
        #獲取當前行,並按“,”切割成字段放入列表中
        #strip:去掉每行字符串首尾指定的字符(默認空格或換行符)
        #split:按照指定的字符將字符串切割成每個字段,返回列表形式
        curLine = line.strip().split(',')
        #將每行中除標記外的數據放入數據集中(curLine[0]爲標記信息)
        #在放入的同時將原先字符串形式的數據轉換爲整型
        #此外將數據進行了二值化處理,大於128的轉換成1,小於的轉換成0,方便後續計算
        dataArr.append([int(int(num) > 128) for num in curLine[1:]])
        #將標記信息放入標記集中
        #放入的同時將標記轉換爲整型
        labelArr.append(int(curLine[0]))
    #返回數據集和標記
    return dataArr, labelArr

def NaiveBayes(Py, Px_y, x):
    '''
    通過樸素貝葉斯進行概率估計
    :param Py: 先驗概率分佈
    :param Px_y: 條件概率分佈
    :param x: 要估計的樣本x
    :return: 返回所有label的估計概率
    '''
    #設置特徵數目
    featrueNum = 784
    #設置類別數目
    classNum = 10
    #建立存放所有標記的估計概率數組
    P = [0] * classNum
    #對於每一個類別,單獨估計其概率
    for i in range(classNum):
        #初始化sum爲0,sum爲求和項。
        #在訓練過程中對概率進行了log處理,所以這裏原先應當是連乘所有概率,最後比較哪個概率最大
        #但是當使用log處理時,連乘變成了累加,所以使用sum
        sum = 0
        #獲取每一個條件概率值,進行累加
        for j in range(featrueNum):
            sum += Px_y[i][j][x[j]]
        #最後再和先驗概率相加(也就是式4.7中的先驗概率乘以後頭那些東西,乘法因爲log全變成了加法)
        P[i] = sum + Py[i]

    #max(P):找到概率最大值
    #P.index(max(P)):找到該概率最大值對應的所有(索引值和標籤值相等)
    return P.index(max(P))


def test(Py, Px_y, testDataArr, testLabelArr):
    '''
    對測試集進行測試
    :param Py: 先驗概率分佈
    :param Px_y: 條件概率分佈
    :param testDataArr: 測試集數據
    :param testLabelArr: 測試集標記
    :return: 準確率
    '''
    #錯誤值計數
    errorCnt = 0
    #循環遍歷測試集中的每一個樣本
    for i in range(len(testDataArr)):
        #獲取預測值
        presict = NaiveBayes(Py, Px_y, testDataArr[i])
        #與答案進行比較
        if presict != testLabelArr[i]:
            #若錯誤  錯誤值計數加1
            errorCnt += 1
    #返回準確率
    return 1 - (errorCnt / len(testDataArr))


def getAllProbability(trainDataArr, trainLabelArr):
    '''
    通過訓練集計算先驗概率分佈和條件概率分佈
    :param trainDataArr: 訓練數據集
    :param trainLabelArr: 訓練標記集
    :return: 先驗概率分佈和條件概率分佈
    '''
    #設置樣本特診數目,數據集中手寫圖片爲28*28,轉換爲向量是784維。
    # (我們的數據集已經從圖像轉換成784維的形式了,CSV格式內就是)
    featureNum = 784
    #設置類別數目,0-9共十個類別
    classNum = 10

    #初始化先驗概率分佈存放數組,後續計算得到的P(Y = 0)放在Py[0]中,以此類推
    #數據長度爲10行1列
    Py = np.zeros((classNum, 1))
    #對每個類別進行一次循環,分別計算它們的先驗概率分佈
    #計算公式爲書中"4.2節 樸素貝葉斯法的參數估計 公式4.8"
    for i in range(classNum):
        #下方式子拆開分析
        #np.mat(trainLabelArr) == i:將標籤轉換爲矩陣形式,裏面的每一位與i比較,若相等,該位變爲Ture,反之False
        #np.sum(np.mat(trainLabelArr) == i):計算上一步得到的矩陣中Ture的個數,進行求和(直觀上就是找所有label中有多少個
        #爲i的標記,求得4.8式P(Y = Ck)中的分子)
        #np.sum(np.mat(trainLabelArr) == i)) + 1:參考“4.2.3節 貝葉斯估計”,例如若數據集總不存在y=1的標記,也就是說
        #手寫數據集中沒有1這張圖,那麼如果不加1,由於沒有y=1,所以分子就會變成0,那麼在最後求後驗概率時這一項就變成了0,再
        #和條件概率乘,結果同樣爲0,不允許存在這種情況,所以分子加1,分母加上K(K爲標籤可取的值數量,這裏有10個數,取值爲10)
        #參考公式4.11
        #(len(trainLabelArr) + 10):標籤集的總長度+10.
        #((np.sum(np.mat(trainLabelArr) == i)) + 1) / (len(trainLabelArr) + 10):最後求得的先驗概率
        Py[i] = ((np.sum(np.mat(trainLabelArr) == i)) + 1) / (len(trainLabelArr) + 10)
    #轉換爲log對數形式
    #log書中沒有寫到,但是實際中需要考慮到,原因是這樣:
    #最後求後驗概率估計的時候,形式是各項的相乘(“4.1 樸素貝葉斯法的學習” 式4.7),這裏存在兩個問題:1.某一項爲0時,結果爲0.
    #這個問題通過分子和分母加上一個相應的數可以排除,前面已經做好了處理。2.如果特診特別多(例如在這裏,需要連乘的項目有784個特徵
    #加一個先驗概率分佈一共795項相乘,所有數都是0-1之間,結果一定是一個很小的接近0的數。)理論上可以通過結果的大小值判斷, 但在
    #程序運行中很可能會向下溢出無法比較,因爲值太小了。所以人爲把值進行log處理。log在定義域內是一個遞增函數,也就是說log(x)中,
    #x越大,log也就越大,單調性和原數據保持一致。所以加上log對結果沒有影響。此外連乘項通過log以後,可以變成各項累加,簡化了計算。
    #在似然函數中通常會使用log的方式進行處理(至於此書中爲什麼沒涉及,我也不知道)
    Py = np.log(Py)

    #計算條件概率 Px_y=P(X=x|Y = y)
    #計算條件概率分成了兩個步驟,下方第一個大for循環用於累加,參考書中“4.2.3 貝葉斯估計 式4.10”,下方第一個大for循環內部是
    #用於計算式4.10的分子,至於分子的+1以及分母的計算在下方第二個大For內
    #初始化爲全0矩陣,用於存放所有情況下的條件概率
    Px_y = np.zeros((classNum, featureNum, 2))
    #對標記集進行遍歷
    for i in range(len(trainLabelArr)):
        #獲取當前循環所使用的標記
        label = trainLabelArr[i]
        #獲取當前要處理的樣本
        x = trainDataArr[i]
        #對該樣本的每一維特診進行遍歷
        for j in range(featureNum):
            #在矩陣中對應位置加1
            #這裏還沒有計算條件概率,先把所有數累加,全加完以後,在後續步驟中再求對應的條件概率
            Px_y[label][j][x[j]] += 1


    #第二個大for,計算式4.10的分母,以及分子和分母之間的除法
    #循環每一個標記(共10個)
    for label in range(classNum):
        #循環每一個標記對應的每一個特徵
        for j in range(featureNum):
            #獲取y=label,第j個特診爲0的個數
            Px_y0 = Px_y[label][j][0]
            #獲取y=label,第j個特診爲1的個數
            Px_y1 = Px_y[label][j][1]
            #對式4.10的分子和分母進行相除,再除之前依據貝葉斯估計,分母需要加上2(爲每個特徵可取值個數)
            #分別計算對於y= label,x第j個特徵爲0和1的條件概率分佈
            Px_y[label][j][0] = np.log((Px_y0 + 1) / (Px_y0 + Px_y1 + 2))
            Px_y[label][j][1] = np.log((Px_y1 + 1) / (Px_y0 + Px_y1 + 2))

    #返回先驗概率分佈和條件概率分佈
    return Py, Px_y


if __name__ == "__main__":
    start = time.time()
    # 獲取訓練集
    print('start read transSet')
    trainDataArr, trainLabelArr = loadData('../Mnist/mnist_train.csv')

    # 獲取測試集
    print('start read testSet')
    testDataArr, testLabelArr = loadData('../Mnist/mnist_test.csv')

    #開始訓練,學習先驗概率分佈和條件概率分佈
    print('start to train')
    Py, Px_y = getAllProbability(trainDataArr, trainLabelArr)

    #使用習得的先驗概率分佈和條件概率分佈對測試集進行測試
    print('start to test')
    accuracy = test(Py, Px_y, testDataArr, testLabelArr)

    #打印準確率
    print('the accuracy is:', accuracy)
    #打印時間
    print('time span:', time.time() -start)

 

 

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