贝叶斯分类器

朴素贝叶斯

在前面关于分类的算法中,我们能够对某种数据进行分类,但这种分类的准确性我们无法评估,即一般来说我们不能回答有多大把握认为这种分类是正确的。前面章节中的分类器被称为 惰性学习器,因为当给出训练数据集时,这些分类器只是将它们保存或者记录下来。每次要进行分类时,都需要重新过一次所有训练集。贝叶斯方法 被称为 勤快学习器, 这些分类器会立即分析数据并构建模型。当要对某个实例进行分类是,他会使用训练得到的内部模型。速度较惰性学习器快。

1.1 条件概率与贝叶斯定理

条件概率:

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

贝叶斯定理:

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

这里的所谓贝叶斯定理,其实就是条件概率公式的一个变形。基于朴素贝叶斯,对分类器的构建思路大概是这样的:分类结果=(x_1, x_2, ..., x_n),所有有关信息=D,计算所有的

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

之后,选择其中概率最大的假设,称之为最大后验假设,记为h_{MAP}。此即贝叶斯分类器给出的结果。
上述可以转述为:

h_{MAP} = argmax_{h \subset H}P(h|D)
其中H是所有假设的集合。 h \subset H意味着“对H中的每条假设”。整个公式意味着“对假设和集中的每条假设计算P(h|D)并从中选出概率最大的那条假设”。使用贝叶斯公式,可以转换为:
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实现:

import pandas as pd
import numpy as np
from collections import Counter as ct


'''
chapter 6概率与朴素贝叶斯
'''


# 训练1:根据给出的得分,用朴素贝叶斯算出他最可能的分类,并给出概率。
# 1. 数据导入
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. 实现思路。对数量的统计,如查看iterest列的等于both的个数:idata.loc[idata['interest'] == 'both'].shape[0]
def bayesclassify(array, model):
    '''
    目标输出max{P(i_1|D), ..., P(i_n|D)}
    在这个项目中,各feature的值的产生是独立的
    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{对某项提案反对的民主党人数}{民主党人数},一般化为:

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

n_c为0时,上述计算会出现问题,公式可以改进为:

p(x|y) = \frac{n_c + mp}{n + m}
其中m是一个称为等效样本容量的常数,其确定方法有很多种,如使用feature可选取值个数来定(yes or no 则为2);P是概率的先验估计,通常假设为均匀分布的概率。

2.1 数值型数据集的贝叶斯应用

前面的应用中,数据的格式是分类的。而实际应用中,很多数据是数值型的。对于数值型数据集,如何使用贝叶斯分类?这里有两种方法:

  • 分区间化为分类型
  • 使用高斯分布算出概率

2.1.1 分区间化为分类型数据

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列的标准差。

其实与分类型数据集的训练是类似的,只是计算概率时使用了高斯分布的结果。

2.2 小项目——印第安糖尿病人的预测

此项目基于一系列指标来判断该位患者是否患有糖尿病。数据集格式如下:


image.png

其中,0~8分别代指(怀孕次数、血糖、血压、三头肌皮脂厚度、血清胰岛素、身体质量指数、年龄,最后是该患者是否患有糖尿病)。

2.2.1 数据集观察与实现逻辑

可以发现所有的数据都数值型,这里要使用高斯分布来计算概率。逻辑如下:

  1. 读取训练集与测试集并化为dataframe。
  2. P(0|D)与P(1|D),其中D为0~7的数值型情况。

实现

import numpy as np
import pandas as pd

# 训练二,基于高斯分布的连续型数据集贝叶斯分类器
# 读取数据集
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为输入数据
    '''
    先计算没有患的概率;再计算患病的概率。
    '''
    # 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设置自由度,这里计算的是样本标准差;不加则计算总体标准差
        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 贝叶斯与非结构化文本

在前面的练习中,所有的数据都是已给定的,即结构化的。而实际上,如新闻、邮件等对象并不能很清晰的通过表格的方式来表达。如何对文本的感情进行倾向的分类是本章的重点。使用工具仍是贝叶斯。

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

大致的原理是,探寻在文本情绪为0/1的前提下,这些词出现频率的概率是多少?通过比较这个条件概率,算出最可能的情感倾向。值得注意的是,这里的出现概率并非是遍历英语中每个词出现在该文本的概率(算力浪费),而是将文档看作无序词袋(bag of words),每个词在文档中出现的概率是什么。

3.1.1 数据导入

本文的数据存在于不同的目录,且未划分测试集与训练集,格式如下:

  • main
    • 停用词表1
    • 停用词表2
    • 数据目录
      • 负面倾向数据集
        • 文件夹0
          • xxxx-xx.txt
            ...
          • xxxx-xx.txt
        • 文件夹1
          • xxxx-xx.txt
            ...
          • xxxx-xx.txt
            ...
        • 文件夹9
          • xxxx-xx.txt
            ...
          • xxxx-xx.txt
      • 正面倾向数据集
        • 文件夹0
          • xxxx-xx.txt
            ...
          • xxxx-xx.txt
        • 文件夹1
          • xxxx-xx.txt
            ...
          • xxxx-xx.txt
            ...
        • 文件夹9
          • xxxx-xx.txt
            ...
          • xxxx-xx.txt
            每段txt文本内容如下(文本名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内容。
os.walk 一下main里的目录情况,得到结果如下:

image.png

程序逻辑如下:


image.png

数据读取python实现:

import os
import numpy as np
import pandas as pd


def getTxT(num):
    '''
    输入这里的num是指要用哪一个文件夹下的txt作测试集。num subset [0, 9]
    返回的是
    1. 训练集:[pos_bag, neg_bag],两者格式为空格分割的str文本
    2. 测试集:[txt1, txt2, ..., txtn],集合了正负两个同一文件夹num内的所有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()
    # 得到训练集traData
    numTra = [i for i in range(2) if i != num]  # 把训练集的文件夹名字拿出来
    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]  # 遍历每个数字文件夹内的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:  # 读取文本内容,并添加进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 分类器构建

得到测试集和训练集后,重点即是对分类器的构建。基本公式:

h_{MAP} = argmax_{h \subset H} P(D|h)P(h)
遍历所有假设并从中选择概率最大的那个,其中P(h)是该假设的概率,这里是先验概率,等于训练集的正反情绪样本个数比例;P(D|h)是看到某个证据的概率,这里既是某个词在该文档中出现的频率,公式如下:
P(w_k|h_i) = \frac{n_k + 1}{n + |Vocabulary|}
其中n_k表示训练集(包括正负)某个元素出现在训练集(分为正负)的频数,n表示所有词出现在训练集(分正负)的总频数(正负训练集的长度),vocabulary表示总的词数。

这里用一个哈希表来表示positive与negetive里各词的条件概率。如P(word_i|like) = 0.003, positive_dict = {'word_i':0.003}。
如此,通过比较pos与neg的大小,即可得出感情倾向。

python实现:

import re
import pandas as pd
import numpy as np
from collections import Counter as ct


   
def bayes(data):  # data的格式应该是list
    # print('开始分类')
    # 训练集得到的概率
    # P(w_i|dislike)  w表示训练集的元素
    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里数值过小会表示为0,改为对数加不影响分类
    # 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


'''
   数据预处理
   在进行构建之前,一个很重要的步骤就是清洗文本,这里我们要用collection包里的coutner函数来进行计算,因此要把训练集和测试集的data都转换为list。这里用正则表达式来做
   test结构:[[txt1, txt2, .., txtn], [txt1, txt2, .., txtn]]
   tra结构:[neg_txt, pos_txt]
'''
test, tra = getTxT(0)
# 数据转化为list并且统计词频
# 测试集
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'))])  # 统计
# 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'))])  # 统计
# 训练集
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('计数完成')
# 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))
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容