樸素貝葉斯
在前面關(guān)于分類的算法中,我們能夠?qū)δ撤N數(shù)據(jù)進行分類,但這種分類的準確性我們無法評估,即一般來說我們不能回答有多大把握認為這種分類是正確的。前面章節(jié)中的分類器被稱為 惰性學(xué)習(xí)器,因為當給出訓(xùn)練數(shù)據(jù)集時,這些分類器只是將它們保存或者記錄下來。每次要進行分類時,都需要重新過一次所有訓(xùn)練集。貝葉斯方法 被稱為 勤快學(xué)習(xí)器, 這些分類器會立即分析數(shù)據(jù)并構(gòu)建模型。當要對某個實例進行分類是,他會使用訓(xùn)練得到的內(nèi)部模型。速度較惰性學(xué)習(xí)器快。
1.1 條件概率與貝葉斯定理
條件概率:
貝葉斯定理:
這里的所謂貝葉斯定理,其實就是條件概率公式的一個變形。基于樸素貝葉斯,對分類器的構(gòu)建思路大概是這樣的:分類結(jié)果,所有有關(guān)信息=D,計算所有的
之后,選擇其中概率最大的假設(shè),稱之為最大后驗假設(shè),記為。此即貝葉斯分類器給出的結(jié)果。
上述可以轉(zhuǎn)述為:
其中H是所有假設(shè)的集合。意味著“對H中的每條假設(shè)”。整個公式意味著“對假設(shè)和集中的每條假設(shè)計算P(h|D)并從中選出概率最大的那條假設(shè)”。使用貝葉斯公式,可以轉(zhuǎn)換為:
由于公式中的分母P(D)是不變的,因此計算分母并得到最大值在除以P(D)即是原公式的最大值。
補充,全概率公式:
python實現(xiàn):
import pandas as pd
import numpy as np
from collections import Counter as ct
'''
chapter 6概率與樸素貝葉斯
'''
# 訓(xùn)練1:根據(jù)給出的得分,用樸素貝葉斯算出他最可能的分類,并給出概率。
# 1. 數(shù)據(jù)導(dǎo)入
with open('i.txt', 'r') as data1:
oridata = [i.rstrip().split('\t') for i in data1.readlines()]
colname = oridata[0]
idata = pd.DataFrame(oridata[1:], columns=colname)
# print(idata)
# 2. 實現(xiàn)思路。對數(shù)量的統(tǒng)計,如查看iterest列的等于both的個數(shù):idata.loc[idata['interest'] == 'both'].shape[0]
def bayesclassify(array, model):
'''
目標輸出max{P(i_1|D), ..., P(i_n|D)}
在這個項目中,各feature的值的產(chǎn)生是獨立的
P(i_n|D) = P(i_n|(feature1, feature2, ... , featureN)) = P(feature1|i_n)*...*P(featureN|i_n)*P(i_n) / +
(P(featrue1)*...*P(featuren))
其中
P(feature1|i_n) = P(i_n交feature1) / P(i_n)
'''
# 2.1 分子計算
num = 1
arrayandcolname = list(zip(colname, array))
# print(list(arrayandcolname))
for i in arrayandcolname:
# print(type(idata.loc[(idata['%s' % i[0]] == i[1]) & (idata['model'] == model)].shape[0])) # & 兩邊用小括號
num = num * ((idata.loc[(idata['%s' % i[0]] == i[1]) & (idata['model'] == model)].shape[0] / idata.shape[0]) /
(idata.loc[idata['model'] == model].shape[0] / idata.shape[0])) # 計算出分子,除P(i_n)外
# print('num更新', num)
num = num * (idata.loc[idata['model'] == model].shape[0] / idata.shape[0])
# print('upper is %s' % num)
# 2.2 分母計算
dem = 1
for j in arrayandcolname:
dem = dem * (idata.loc[idata['%s' % j[0]] == j[1]].shape[0] / idata.shape[0])
# print('dem更新', dem)
# print('dem is %s' % dem)
# print('%s P is %s' % (model, num / dem))
return num / dem
def classify(array): # 遍歷可能的i_n, 用bayes算出推薦的可能性。
tuijian = 'null'
score = 0
for i in list(idata['model'].unique()):
if bayesclassify(array, i) > score:
tuijian = I
print('推薦%s' % tuijian)
return None
classify(['health', 'moderate', 'moderate', 'yes'])
1.2 樸素貝葉斯的問題及解決
當某個概率為0時,它就會住到樸素貝葉斯的計算過程,不管其他值是什么都無濟于事。 這樣算出來的概率往往是真實概率的偏低估計。
以一個共和黨人及民主黨人投票的例子為例。,一般化為:
當為0時,上述計算會出現(xiàn)問題,公式可以改進為:
其中m是一個稱為等效樣本容量的常數(shù),其確定方法有很多種,如使用feature可選取值個數(shù)來定(yes or no 則為2);P是概率的先驗估計,通常假設(shè)為均勻分布的概率。
2.1 數(shù)值型數(shù)據(jù)集的貝葉斯應(yīng)用
前面的應(yīng)用中,數(shù)據(jù)的格式是分類的。而實際應(yīng)用中,很多數(shù)據(jù)是數(shù)值型的。對于數(shù)值型數(shù)據(jù)集,如何使用貝葉斯分類?這里有兩種方法:
- 分區(qū)間化為分類型
- 使用高斯分布算出概率
2.1.1 分區(qū)間化為分類型數(shù)據(jù)
pass
2.1.2 利用高斯分布計算概率
利用公式:
其中其實就是i列的均值;
是i列的標準差。
其實與分類型數(shù)據(jù)集的訓(xùn)練是類似的,只是計算概率時使用了高斯分布的結(jié)果。
2.2 小項目——印第安糖尿病人的預(yù)測
此項目基于一系列指標來判斷該位患者是否患有糖尿病。數(shù)據(jù)集格式如下:

其中,0~8分別代指(懷孕次數(shù)、血糖、血壓、三頭肌皮脂厚度、血清胰島素、身體質(zhì)量指數(shù)、年齡,最后是該患者是否患有糖尿病)。
2.2.1 數(shù)據(jù)集觀察與實現(xiàn)邏輯
可以發(fā)現(xiàn)所有的數(shù)據(jù)都數(shù)值型,這里要使用高斯分布來計算概率。邏輯如下:
- 讀取訓(xùn)練集與測試集并化為dataframe。
- P(0|D)與P(1|D),其中D為0~7的數(shù)值型情況。
實現(xiàn)
import numpy as np
import pandas as pd
# 訓(xùn)練二,基于高斯分布的連續(xù)型數(shù)據(jù)集貝葉斯分類器
# 讀取數(shù)據(jù)集
listn = [i for i in range(1, 11)]
datalist = []
datalist2 = []
for i in listn:
if i <= 9:
with open('pima/pima-0%s' % i, 'r') as data:
tempdata = [i.rstrip().split('\t') for i in data.readlines()]
datalist.extend(tempdata)
data.close()
else:
with open('pima/pima-%s' % i, 'r') as data:
tempdata = [i.rstrip().split('\t') for i in data.readlines()]
datalist.extend(tempdata)
data.close()
for i in listn:
if i <= 9:
with open('pimaSmall/pimaSmall-0%s' % i, 'r') as data:
tempdata = [i.rstrip().split('\t') for i in data.readlines()]
datalist2.extend(tempdata)
data.close()
else:
with open('pimaSmall/pimaSmall-%s' % i, 'r') as data:
tempdata = [i.rstrip().split('\t') for i in data.readlines()]
datalist2.extend(tempdata)
data.close()
trainningdata = pd.DataFrame(datalist, dtype=np.float)
testdata = pd.DataFrame(datalist2, dtype=np.float)
# print(trainningdata)
# print('-------------')
# print(testdata)
# 分類器
def bayesClassifier(data): # data為輸入數(shù)據(jù)
'''
先計算沒有患的概率;再計算患病的概率。
'''
# 1. P(0|D) = P(0D)/P(D) = P(D|0)P(0)/P(D) = P(D_1|0)*...*P(D_n|0)P(0)/P(D)
# 先計算連乘條件概率部分,這里使用dataframe遍歷columnsname的方法來取得均值、標準差,并計算概率
columnsname = trainningdata.columns.values.tolist()
pd1PlusTopdn0 = 1
pd1PlusTopdn1 = 1
for i in columnsname[:-1]: # 這里是-1而不是全部是因為最后一列為是否患有病而非D
uij = np.mean(trainningdata[I])
sdij = np.std(trainningdata[i], ddof=1) # ddof設(shè)置自由度,這里計算的是樣本標準差;不加則計算總體標準差
pd1PlusTopdn0 *= (1 / (np.sqrt(2 * np.pi) * sdij)) * (np.e ** (-(data[i] - uij) ** 2 / (2 * sdij ** 2)))
pd1PlusTopdn1 *= (1 / (np.sqrt(2 * np.pi) * sdij)) * (np.e ** (-(data[i] - uij) ** 2 / (2 * sdij ** 2)))
if pd1PlusTopdn0 > pd1PlusTopdn1:
return 0
else:
return 1
# 用測試集進行測試
counter = 0
totalN = testdata.shape[0]
for i in range(testdata.shape[0]):
dataT = [np.float(i) for i in list(testdata.loc[i, :])]
dataTd = dataT[:-1]
dataCl = dataT[-1]
if dataCl == bayesClassifier(dataTd):
counter += 1
print('準確率', counter/totalN)
3.1 貝葉斯與非結(jié)構(gòu)化文本
在前面的練習(xí)中,所有的數(shù)據(jù)都是已給定的,即結(jié)構(gòu)化的。而實際上,如新聞、郵件等對象并不能很清晰的通過表格的方式來表達。如何對文本的感情進行傾向的分類是本章的重點。使用工具仍是貝葉斯。
大致的原理是,探尋在文本情緒為0/1的前提下,這些詞出現(xiàn)頻率的概率是多少?通過比較這個條件概率,算出最可能的情感傾向。值得注意的是,這里的出現(xiàn)概率并非是遍歷英語中每個詞出現(xiàn)在該文本的概率(算力浪費),而是將文檔看作無序詞袋(bag of words),每個詞在文檔中出現(xiàn)的概率是什么。
3.1.1 數(shù)據(jù)導(dǎo)入
本文的數(shù)據(jù)存在于不同的目錄,且未劃分測試集與訓(xùn)練集,格式如下:
- main
- 停用詞表1
- 停用詞表2
- 數(shù)據(jù)目錄
- 負面傾向數(shù)據(jù)集
- 文件夾0
- xxxx-xx.txt
... - xxxx-xx.txt
- xxxx-xx.txt
- 文件夾1
- xxxx-xx.txt
... - xxxx-xx.txt
...
- xxxx-xx.txt
- 文件夾9
- xxxx-xx.txt
... - xxxx-xx.txt
- xxxx-xx.txt
- 文件夾0
- 正面傾向數(shù)據(jù)集
- 文件夾0
- xxxx-xx.txt
... - xxxx-xx.txt
- xxxx-xx.txt
- 文件夾1
- xxxx-xx.txt
... - xxxx-xx.txt
...
- xxxx-xx.txt
- 文件夾9
- xxxx-xx.txt
... - xxxx-xx.txt
每段txt文本內(nèi)容如下(文本名cv000_29416.txt):
- xxxx-xx.txt
- 文件夾0
- 負面傾向數(shù)據(jù)集
plot : two teen couples go to a church party , drink and then drive .
they get into an accident .
one of the guys dies , but his girlfriend continues to see him in her life , and has nightmares .
what's the deal ?
...
where's joblo coming from ?
a nightmare of elm street 3 ( 7/10 ) - blair witch 2 ( 7/10 ) - the crow ( 9/10 ) - the crow : salvation ( 4/10 ) - lost highway ( 10/10 ) - memento ( 10/10 ) - the others ( 9/10 ) - stir of echoes ( 8/10 )
這里使用 十折交叉驗證 的方法來計算分類準確度,并用 混淆矩陣 來進行表示。使用 os 模塊來得到不同傾向下的txt內(nèi)容。
os.walk 一下main里的目錄情況,得到結(jié)果如下:

程序邏輯如下:

數(shù)據(jù)讀取python實現(xiàn):
import os
import numpy as np
import pandas as pd
def getTxT(num):
'''
輸入這里的num是指要用哪一個文件夾下的txt作測試集。num subset [0, 9]
返回的是
1. 訓(xùn)練集:[pos_bag, neg_bag],兩者格式為空格分割的str文本
2. 測試集:[txt1, txt2, ..., txtn],集合了正負兩個同一文件夾num內(nèi)的所有txt文本
'''
dir = '/Users/zarathustra/Desktop/pg2dm-python-master/data/ch7/review_polarity_buckets/txt_sentoken'
dirlist = list(os.walk(dir)) # 得到txt_sentoken下所有的文件路徑
# 得到測試集testData
testdir = [dirlist[1][0] + '/' + str(num), dirlist[12][0] + '/' + str(num)]
testData = [[], []]
neg_test_list = list(os.walk(testdir[0]))[0][2]
for j in neg_test_list:
neg_dir = dir + '/' + 'neg' + '/' + '%s' % num + '/' + j
with open(neg_dir, 'r') as negdata:
testData[0].append(negdata.read())
negdata.close()
pos_test_list = list(os.walk(testdir[1]))[0][2]
for j in pos_test_list:
pos_dir = dir + '/' + 'pos' + '/' + '%s' % num + '/' + j
with open(pos_dir, 'r') as posdata:
testData[1].append(posdata.read())
posdata.close()
# 得到訓(xùn)練集traData
numTra = [i for i in range(2) if i != num] # 把訓(xùn)練集的文件夾名字拿出來
traData = [[], []]
for i in numTra:
neg_tra_list = dirlist[1][0] # 前半段地址和測試集的一樣
temp1 = neg_tra_list + '/' + '%s' % i
neg_txt_list = list(os.walk(temp1))[0][2] # 遍歷每個數(shù)字文件夾內(nèi)的txt名字,為遍歷并添加到tradata準備
for j in neg_txt_list:
neg_tra_dir = neg_tra_list + '/' + '%s' % i + '/' + j # 拿到這個txt的絕對路徑
with open(neg_tra_dir, 'r') as tradata: # 讀取文本內(nèi)容,并添加進tradata里
traData[0].append(tradata.read()) # 避免算力浪費,先做list,再joinlist里的元素
print('拿到', '%s' % j)
tradata.close()
pos_tra_list = dirlist[12][0]
pos_txt_list = list(os.walk(pos_tra_list + '/' + '%s' % i))[0][2]
for j in pos_txt_list:
pos_tra_dir = pos_tra_list + '/' + '%s' % i + '/' + j
with open(pos_tra_dir, 'r') as tradata:
traData[1].append(tradata.read())
print('添加成功')
tradata.close()
traData[0] = '\t'.join(traData[0]) # join一下
traData[1] = '\t'.join(traData[1])
return testData, traData
3.2 分類器構(gòu)建
得到測試集和訓(xùn)練集后,重點即是對分類器的構(gòu)建?;竟剑?/p>
遍歷所有假設(shè)并從中選擇概率最大的那個,其中P(h)是該假設(shè)的概率,這里是先驗概率,等于訓(xùn)練集的正反情緒樣本個數(shù)比例;P(D|h)是看到某個證據(jù)的概率,這里既是某個詞在該文檔中出現(xiàn)的頻率,公式如下:
其中表示訓(xùn)練集(包括正負)某個元素出現(xiàn)在訓(xùn)練集(分為正負)的頻數(shù),n表示所有詞出現(xiàn)在訓(xùn)練集(分正負)的總頻數(shù)(正負訓(xùn)練集的長度),vocabulary表示總的詞數(shù)。
這里用一個哈希表來表示positive與negetive里各詞的條件概率。如, positive_dict = {'word_i':0.003}。
如此,通過比較pos與neg的大小,即可得出感情傾向。
python實現(xiàn):
import re
import pandas as pd
import numpy as np
from collections import Counter as ct
def bayes(data): # data的格式應(yīng)該是list
# print('開始分類')
# 訓(xùn)練集得到的概率
# P(w_i|dislike) w表示訓(xùn)練集的元素
bayesDict_neg = {}
bayesDict_pos = {}
# print('uni', tra_negandpos_list_uni)
for i in tra_negandpos_list_uni:
# print('i' ,i)
dislike_score = (tra_neg_counter.get(i, 0) + 1) / (len(tra_neg_list) + len(tra_negandpos_counter))
bayesDict_neg['%s' % i] = dislike_score
# P(w_i|like)
like_score = 0.5
for i in tra_negandpos_list_uni:
like_score = (tra_pos_counter.get(i, 0) + 1) / (len(tra_pos_list) + len(tra_negandpos_counter))
bayesDict_pos['%s' % i] = like_score
# print(bayesDict_neg)
# print(bayesDict_pos)
# P(D_i|dislike) D表示測試集
disscore = np.log(0.5)
for i in data:
# print('i', i)
# print(bayesDict_neg['battlefield'])
disscore += np.log(bayesDict_neg.get(i, 1)) # 這里使用+而非乘法是因為python里數(shù)值過小會表示為0,改為對數(shù)加不影響分類
# P(D_i|like)
likescore = np.log(0.5)
for i in data:
likescore += np.log(bayesDict_pos.get(i, 1))
print('neg %s, pos %s' % (disscore, likescore), disscore < likescore)
return disscore < likescore
'''
數(shù)據(jù)預(yù)處理
在進行構(gòu)建之前,一個很重要的步驟就是清洗文本,這里我們要用collection包里的coutner函數(shù)來進行計算,因此要把訓(xùn)練集和測試集的data都轉(zhuǎn)換為list。這里用正則表達式來做
test結(jié)構(gòu):[[txt1, txt2, .., txtn], [txt1, txt2, .., txtn]]
tra結(jié)構(gòu):[neg_txt, pos_txt]
'''
test, tra = getTxT(0)
# 數(shù)據(jù)轉(zhuǎn)化為list并且統(tǒng)計詞頻
# 測試集
test_neg_list = []
test_pos_list = []
test_neg_counter = []
test_pos_counter = []
# neg
for i in test[0]:
test_neg_list.append(re.sub(u'[^0-9a-zA-Z]+', '\t', i).split('\t')) # 清理非需要字符
# test_neg_counter.append([ct(re.sub(u'[^0-9a-zA-Z]', '\t', i).split('\t'))]) # 統(tǒng)計
# pos
for i in test[1]:
test_pos_list.append(re.sub(u'[^0-9a-zA-Z]+', '\t', i).split('\t')) # 清理非需要字符
# test_pos_counter.append([ct(re.sub(u'[^0-9a-zA-Z]', '\t', i).split('\t'))]) # 統(tǒng)計
# 訓(xùn)練集
tra_neg_list = re.sub(u'[^0-9a-zA-Z]+', '\t', tra[0]).split('\t')
tra_pos_list = re.sub(u'[^0-9a-zA-Z]+', '\t', tra[1]).split('\t')
# print(tra_neg_list)
tra_neg_counter = ct(tra_neg_list)
tra_pos_counter = ct(tra_pos_list)
# print(tra_neg_counter)
# print(tra_pos_counter)
# print('計數(shù)完成')
# print(tra_pos_list.extend(tra_neg_list))
# print(tra_neg_list)
tra_negandpos_list = tra_neg_list
tra_negandpos_list.extend(tra_pos_list)
# print(tra_negandpos_list)
# print('找交集完成', tra_negandpos_list)
tra_negandpos_counter = ct(tra_negandpos_list)
tra_negandpos_list_uni = list(set(tra_negandpos_list))
print(len(tra_negandpos_counter), len(tra_negandpos_list_uni))
# bayes(test_neg_list[1])
# test
counter = 0
for i in test_neg_list: # 分別使用neg測試集和pos測試集來做
# print('%s' % i)
# print(i)
if not bayes(i):
counter += 1
print(counter / len(test_neg_list))