朴素贝叶斯
在前面关于分类的算法中,我们能够对某种数据进行分类,但这种分类的准确性我们无法评估,即一般来说我们不能回答有多大把握认为这种分类是正确的。前面章节中的分类器被称为 惰性学习器,因为当给出训练数据集时,这些分类器只是将它们保存或者记录下来。每次要进行分类时,都需要重新过一次所有训练集。贝叶斯方法 被称为 勤快学习器, 这些分类器会立即分析数据并构建模型。当要对某个实例进行分类是,他会使用训练得到的内部模型。速度较惰性学习器快。
1.1 条件概率与贝叶斯定理
条件概率:
贝叶斯定理:
这里的所谓贝叶斯定理,其实就是条件概率公式的一个变形。基于朴素贝叶斯,对分类器的构建思路大概是这样的:分类结果,所有有关信息=D,计算所有的
之后,选择其中概率最大的假设,称之为最大后验假设,记为。此即贝叶斯分类器给出的结果。
上述可以转述为:
其中H是所有假设的集合。意味着“对H中的每条假设”。整个公式意味着“对假设和集中的每条假设计算P(h|D)并从中选出概率最大的那条假设”。使用贝叶斯公式,可以转换为:
由于公式中的分母P(D)是不变的,因此计算分母并得到最大值在除以P(D)即是原公式的最大值。
补充,全概率公式:
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时,它就会住到朴素贝叶斯的计算过程,不管其他值是什么都无济于事。 这样算出来的概率往往是真实概率的偏低估计。
以一个共和党人及民主党人投票的例子为例。,一般化为:
当为0时,上述计算会出现问题,公式可以改进为:
其中m是一个称为等效样本容量的常数,其确定方法有很多种,如使用feature可选取值个数来定(yes or no 则为2);P是概率的先验估计,通常假设为均匀分布的概率。
2.1 数值型数据集的贝叶斯应用
前面的应用中,数据的格式是分类的。而实际应用中,很多数据是数值型的。对于数值型数据集,如何使用贝叶斯分类?这里有两种方法:
- 分区间化为分类型
- 使用高斯分布算出概率
2.1.1 分区间化为分类型数据
pass
2.1.2 利用高斯分布计算概率
利用公式:
其中其实就是i列的均值;
是i列的标准差。
其实与分类型数据集的训练是类似的,只是计算概率时使用了高斯分布的结果。
2.2 小项目——印第安糖尿病人的预测
此项目基于一系列指标来判断该位患者是否患有糖尿病。数据集格式如下:

其中,0~8分别代指(怀孕次数、血糖、血压、三头肌皮脂厚度、血清胰岛素、身体质量指数、年龄,最后是该患者是否患有糖尿病)。
2.2.1 数据集观察与实现逻辑
可以发现所有的数据都数值型,这里要使用高斯分布来计算概率。逻辑如下:
- 读取训练集与测试集并化为dataframe。
- 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 贝叶斯与非结构化文本
在前面的练习中,所有的数据都是已给定的,即结构化的。而实际上,如新闻、邮件等对象并不能很清晰的通过表格的方式来表达。如何对文本的感情进行倾向的分类是本章的重点。使用工具仍是贝叶斯。
大致的原理是,探寻在文本情绪为0/1的前提下,这些词出现频率的概率是多少?通过比较这个条件概率,算出最可能的情感倾向。值得注意的是,这里的出现概率并非是遍历英语中每个词出现在该文本的概率(算力浪费),而是将文档看作无序词袋(bag of words),每个词在文档中出现的概率是什么。
3.1.1 数据导入
本文的数据存在于不同的目录,且未划分测试集与训练集,格式如下:
- main
- 停用词表1
- 停用词表2
- 数据目录
- 负面倾向数据集
- 文件夹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
- 正面倾向数据集
- 文件夹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文本内容如下(文本名cv000_29416.txt):
- xxxx-xx.txt
- 文件夹0
- 负面倾向数据集
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里的目录情况,得到结果如下:

程序逻辑如下:

数据读取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 分类器构建
得到测试集和训练集后,重点即是对分类器的构建。基本公式:
遍历所有假设并从中选择概率最大的那个,其中P(h)是该假设的概率,这里是先验概率,等于训练集的正反情绪样本个数比例;P(D|h)是看到某个证据的概率,这里既是某个词在该文档中出现的频率,公式如下:
其中表示训练集(包括正负)某个元素出现在训练集(分为正负)的频数,n表示所有词出现在训练集(分正负)的总频数(正负训练集的长度),vocabulary表示总的词数。
这里用一个哈希表来表示positive与negetive里各词的条件概率。如, 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))