朴素贝叶斯 以及西瓜数据集的特征工程
朴素贝叶斯的基本思想
- 先验概率: 通过已知事实, 推论出未知事务出现的概率
例如: 通过硬币有两面, 推论出抛一次硬币可能出现正面的概率 - 后验概率: 通过结果, 推论出导致该结果的原因的概率
例如: 通过硬币抛出来之后是正面, 需要你来判断该硬币被你做手法了的概率
关于先验概率 与 后验概率的理解: https://www.cnblogs.com/yemanxiaozu/p/7680761.html
- 贝叶斯公式:
- 其中, B 是 该事件最后的结果, A 是表示导致该结果的原因, 再结合我们之前对后验概率的理解, 可以知道, 贝叶斯公式的结果就是导致结果B出现的可能性
- 在机器学习中, 对于一个需要预测的数据, 我们知道的是它的各种已知信息相当于公式中的A, 而我们要求的是最后对他的各个分类的概率, 这个分类就相当于公式中的 B, 于是 我们的公式可以表示为:
- 机器学习—朴素贝叶斯: 对于上述的贝叶斯公式, 其中已知的 A 可以看作该数据的特征, 而我们假定该数据的各个特征是独立的, 所以对于p(特征|类别), 我们可以分解为 p(特征1|类别) * p(特征2|类别) * p(特征3|类别) … 这就是朴素的意思, 而对于需预测的数据来说, 我们p(特征), p(类别) 是很容易通过大量数据得到的已知值
- 引用西瓜书上说的:
其中ck表示第k 个分类, xj 表示第j个特征值, Y是该数据最终的分类结果
- 因此 我们需要训练的是什么呢:
每个特征属于每个分类的概率 - 通过被预测数据的特征, 从训练出来的分类器中查找每个特征在每个分类的概率相乘(朴素)
- 得到该数据最终属于每个分类的概率, 选出概率最大的为其最后的分类
- 引用西瓜书上说的:
- 本文算法代码只讨论二分类情况
朴素贝叶斯分类的过程
- 数据获取: 任意方式获取
- 数据分析: 得到其独立同分布的特征, 应用朴素这一条件
- 训练过程:
- 若是离散值: 获取每一个特征在每一个分类下的可能性
- 若是连续值: 假定其符合正态分布, 计算其一维正态分布值:
- 预测过程: 获取数据各个特征, 计算其属于每个分类的概率, 选出最大概率作为其分类结果
- 模型评估: 本章采用 留出法, k折交叉验证法(留一法), 自助法, 结果为
[真正例, 假正例, 真负例, 假负例]- 性能评估: 准确率
数据集:
1,青绿,蜷缩,浊响,清晰,凹陷,硬滑,0.697,0.46,是
2,乌黑,蜷缩,沉闷,清晰,凹陷,硬滑,0.774,0.376,是
3,乌黑,蜷缩,浊响,清晰,凹陷,硬滑,0.634,0.264,是
4,青绿,蜷缩,沉闷,清晰,凹陷,硬滑,0.608,0.318,是
5,浅白,蜷缩,浊响,清晰,凹陷,硬滑,0.556,0.215,是
6,青绿,稍蜷,浊响,清晰,稍凹,软粘,0.403,0.237,是
7,乌黑,稍蜷,浊响,稍糊,稍凹,软粘,0.481,0.149,是
8,乌黑,稍蜷,浊响,清晰,稍凹,硬滑,0.437,0.211,是
9,乌黑,稍蜷,沉闷,稍糊,稍凹,硬滑,0.666,0.091,否
10,青绿,硬挺,清脆,清晰,平坦,软粘,0.243,0.267,否
11,浅白,硬挺,清脆,模糊,平坦,硬滑,0.245,0.057,否
12,浅白,蜷缩,浊响,模糊,平坦,软粘,0.343,0.099,否
13,青绿,稍蜷,浊响,稍糊,凹陷,硬滑,0.639,0.161,否
14,浅白,稍蜷,沉闷,稍糊,凹陷,硬滑,0.657,0.198,否
15,乌黑,稍蜷,浊响,清晰,稍凹,软粘,0.36,0.37,否
16,浅白,蜷缩,浊响,模糊,平坦,硬滑,0.593,0.042,否
17,青绿,蜷缩,沉闷,稍糊,稍凹,硬滑,0.719,0.103,否
- 特征包含了离散值与连续值, 最后是数据标签
- 可看到连续值已经处于 (0,1)之间了, 因此不需要归一化处理
版本1
- 这个版本只完成了最基础的西瓜数据集的朴素贝叶斯分类
- 写的时候发现每次预测都要训练, 然后, 想到把训练与预测分类, 就可以多次使用分类器了, 然后有了版本2
记录一下代码有多烂:
import numpy as np
import math
def label_count(dataset):
"""
计算每个分类结果的概率
:param dataset:[ [feat], [feat], [feat], ... , [label], ]
:return: p(label) : {label: prob, }
"""
label = {
}
for i in dataset:
if i[-1] not in label.keys():
label[i[-1]] = 0
label[i[-1]] += 1
return label
def feat_count(dataset, item, label, labels):
"""
计算某个特征向量被分类为label的概率
:param dataset:
:param labels 每种分类的概率
:param item: 特征向量
:return:
"""
feat_num = np.array([0 for _ in item], np.float32)
for i in range(len(item)):
if isinstance(item[i], (float, int)):
set_i = np.array([j[i] for j in dataset])
u = set_i.mean()
std = set_i.std()
feat_num[i] = (1/(math.sqrt(2*math.pi)*std)) * math.pow(math.e, -1*(feat_num[i] - u)**2 / (2*(std**2)))
else:
for j in dataset:
if item[i] == j[i] and j[-1] == label:
feat_num[i] += 1
feat_num[i] /= labels[label]
return feat_num
def classification(item, dataset):
"""
返回属于每一种分类的可能性
:param item:
:param dataset:
:return:
"""
labels = label_count(dataset)
data_num = len(dataset)
ans = {
}
for label in labels:
feat_num = feat_count(dataset, item, label, labels)
ans[label] = 1
for i in feat_num:
ans[label] *= i
ans[label] *= float(labels[label]) / float(data_num)
return sorted(ans.items(), key=lambda j: j[1], reverse=True)
def fun(i):
i = i.strip()
try:
i = float(i)
except ValueError:
pass
return i
def read_data(path):
dataset = []
with open(path, 'r', encoding='utf8') as f:
for line in f.readlines():
split_ = line.strip().split(',')[1:]
dataset.append([i for i in map(fun, split_)])
return dataset
if __name__ == '__main__':
path = "xigua.txt"
dataset = read_data(path)
pre_data = '青绿,蜷缩,浊响,清晰,凹陷,硬滑,0.697, 0.460'
item = [i for i in pre_data.strip().split(',')]
item = [i for i in map(fun, item)]
print(item)
l = classification(item, dataset)
print(l)
结果:
评估
把数据集放进去, 分出测试集与训练集, 进行评估即可
结果:
[真正例, 假正例, 真负例, 假负例]
版本2
- 这个版本把训练过程 与测试过程分开了
- 加入了拉普拉斯假设: 即把为0的特征进行+1, 避免了该分类可能性为0
- 最后给每个特征概率的结果相乘改成了: 最后求log后相加, 避免了特征很多的时候出现的大误差
- 写的时候想到了是不是可以用矩阵运算来避免遍历过程, 以及面向对象.于是出现了版本3
训练
- 训练返回结果的数据结构:
- 离散型, feat_pos_count, feat_neg_count, 表示分类结果为正例和负例的计数
{
特征1:{value1: count, value2: count},
特征2:{value1: count, value2: count},
…
} - 连续型, numer_feat
{
连续型特征: {‘u’: 平均数, ‘std’: 标准差},
…
} - pos_prob: 正例占总数的可能性, 负例直接 1-pos_prob
- 离散型, feat_pos_count, feat_neg_count, 表示分类结果为正例和负例的计数
- 训练过程就是计数而已, 很简单
def n_attribute(u, std, x):
return (1/(sqrt(2*pi)*std)) * pow(e, -1*(x - u)**2 / (2*(std**2)))
def train_bayesian_2_classify(dataset):
"""
2分类器, 1--正, 0--负
训练贝叶斯分类器, 即得到每个特征值的条件概率
:param dataset:最后一列是label 其他作为特征值
:return:
"""
# 初始化每个特征有一个计数,避免最后概率为0. 拉普拉斯校准
feat_num = len(dataset[0])-1
feat_pos_count = [dict() for _ in range(feat_num)]
feat_neg_count = [dict() for _ in range(feat_num)]
# 获取正负例数
pos_num = 0.0
neg_num = 0.0
data_num = len(dataset)
numer_feat = []
for i in range(feat_num):
if isinstance(dataset[0][i], (float,)):
numer_feat.append(i)
for i in range(data_num):
for j in range(feat_num):
if j not in numer_feat:
if dataset[