'''
數據集: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)