貝葉斯分類器

樸素貝葉斯

在前面關(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 條件概率與貝葉斯定理

條件概率:

P(h|D) = \frac{P(hD)}{P(D)}

貝葉斯定理:

P(h|D) = \frac{P(D|h)P(h)}{P(D)}

這里的所謂貝葉斯定理,其實就是條件概率公式的一個變形。基于樸素貝葉斯,對分類器的構(gòu)建思路大概是這樣的:分類結(jié)果=(x_1, x_2, ..., x_n),所有有關(guān)信息=D,計算所有的

P(x_i|D) = \frac{P(D|x_i)P(x_i)}{P(D)}

之后,選擇其中概率最大的假設(shè),稱之為最大后驗假設(shè),記為h_{MAP}。此即貝葉斯分類器給出的結(jié)果。
上述可以轉(zhuǎn)述為:

h_{MAP} = argmax_{h \subset H}P(h|D)
其中H是所有假設(shè)的集合。 h \subset H意味著“對H中的每條假設(shè)”。整個公式意味著“對假設(shè)和集中的每條假設(shè)計算P(h|D)并從中選出概率最大的那條假設(shè)”。使用貝葉斯公式,可以轉(zhuǎn)換為:
h_{MAP} = arg max_{h \subset H}\frac{P(D|h)P(h)}{P(D)}

由于公式中的分母P(D)是不變的,因此計算分母并得到最大值在除以P(D)即是原公式的最大值。
補充,全概率公式:

P(B) = P(B|A)P(A) + P(B|\overline{A})P(\overline{A})

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時,它就會住到樸素貝葉斯的計算過程,不管其他值是什么都無濟于事。 這樣算出來的概率往往是真實概率的偏低估計。
以一個共和黨人及民主黨人投票的例子為例。P(s = no| Democrat) = \frac{對某項提案反對的民主黨人數(shù)}{民主黨人數(shù)},一般化為:

p(x|y) = \frac{n_c}{n}

n_c為0時,上述計算會出現(xiàn)問題,公式可以改進為:

p(x|y) = \frac{n_c + mp}{n + m}
其中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 利用高斯分布計算概率

利用公式:

P(x_i|y_j) = \frac{1}{\sqrt{2 \pi} \sigma_{ij}} e^{\frac{-(x_i - \mu_{ij})^2}{2 \sigma_{ij}^2}}
其中\mu_{ij}其實就是i列的均值;\sigma_{ij}是i列的標準差。

其實與分類型數(shù)據(jù)集的訓(xùn)練是類似的,只是計算概率時使用了高斯分布的結(jié)果。

2.2 小項目——印第安糖尿病人的預(yù)測

此項目基于一系列指標來判斷該位患者是否患有糖尿病。數(shù)據(jù)集格式如下:


image.png

其中,0~8分別代指(懷孕次數(shù)、血糖、血壓、三頭肌皮脂厚度、血清胰島素、身體質(zhì)量指數(shù)、年齡,最后是該患者是否患有糖尿病)。

2.2.1 數(shù)據(jù)集觀察與實現(xiàn)邏輯

可以發(fā)現(xiàn)所有的數(shù)據(jù)都數(shù)值型,這里要使用高斯分布來計算概率。邏輯如下:

  1. 讀取訓(xùn)練集與測試集并化為dataframe。
  2. 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)化的。而實際上,如新聞、郵件等對象并不能很清晰的通過表格的方式來表達。如何對文本的感情進行傾向的分類是本章的重點。使用工具仍是貝葉斯。

h_{MAP} = argmax_{h \subset H} P(D|h)P(h)

大致的原理是,探尋在文本情緒為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
        • 文件夾1
          • xxxx-xx.txt
            ...
          • xxxx-xx.txt
            ...
        • 文件夾9
          • xxxx-xx.txt
            ...
          • xxxx-xx.txt
      • 正面傾向數(shù)據(jù)集
        • 文件夾0
          • xxxx-xx.txt
            ...
          • xxxx-xx.txt
        • 文件夾1
          • xxxx-xx.txt
            ...
          • xxxx-xx.txt
            ...
        • 文件夾9
          • xxxx-xx.txt
            ...
          • xxxx-xx.txt
            每段txt文本內(nèi)容如下(文本名cv000_29416.txt):

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é)果如下:

image.png

程序邏輯如下:


image.png

數(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>

h_{MAP} = argmax_{h \subset H} P(D|h)P(h)
遍歷所有假設(shè)并從中選擇概率最大的那個,其中P(h)是該假設(shè)的概率,這里是先驗概率,等于訓(xùn)練集的正反情緒樣本個數(shù)比例;P(D|h)是看到某個證據(jù)的概率,這里既是某個詞在該文檔中出現(xiàn)的頻率,公式如下:
P(w_k|h_i) = \frac{n_k + 1}{n + |Vocabulary|}
其中n_k表示訓(xùn)練集(包括正負)某個元素出現(xiàn)在訓(xùn)練集(分為正負)的頻數(shù),n表示所有詞出現(xiàn)在訓(xùn)練集(分正負)的總頻數(shù)(正負訓(xùn)練集的長度),vocabulary表示總的詞數(shù)。

這里用一個哈希表來表示positive與negetive里各詞的條件概率。如P(word_i|like) = 0.003, 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))
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容