机器学习之用Python从零实现贝叶斯分类器

原帖在这里,好久没看到这么清晰的机器学习的教程,所以自己也来试试了,选取的数据是Iris的数据,结果准确率很高,平均在94%左右.如果没有特别声明,一下属性一般指的是植物的宽度之类的数据,种类指的是不同种类的植物.

关于朴素贝叶斯

朴素贝叶斯算法是一个直观的方法,使用每个属性归属于某个类的概率来做预测。你可以使用这种监督性学习方法,对一个预测性建模问题进行概率建模。
给定一个类,朴素贝叶斯假设每个属性归属于此类的概率独立于其余所有属性,从而简化了概率的计算。这种强假定产生了一个快速、有效的方法。
给定一个属性值,其属于某个类的概率叫做条件概率。对于一个给定的类值,将每个属性的条件概率相乘,便得到一个数据样本属于某个类的概率。
我们可以通过计算样本归属于每个类的概率,然后选择具有最高概率的类来做预测。
通常,我们使用分类数据来描述朴素贝叶斯,因为这样容易通过比率来描述、计算。一个符合我们目的、比较有用的算法需要支持数值属性,同时假设每一个数值属性服从正态分布(分布在一个钟形曲线上),这又是一个强假设,但是依然能够给出一个健壮的结果。

根据花萼和花瓣的长度和宽度预测它的属性

本文使用的测试问题是”安德森鸢尾花卉”的属性问题,IRIS数据集一共包括150个数据集,分为三类,每类50个数据,每个数据包含四个属性,分别是花萼和花瓣的长度和宽度,最后1条记录是属性值,0,1,2分别代表三种不同的植物
下面是iris数据集的一个样本

1
2
3
4
5
5.1,3.5,1.4,0.2,0
4.9,3.0,1.4,0.2,0
4.7,3.2,1.3,0.2,0
4.6,3.1,1.5,0.2,0
5.0,3.6,1.4,0.2,0

基本做法和链接的那篇文章一样

朴素贝叶斯算法教程

教程分为如下几步:
1.处理数据:从CSV文件中载入数据,然后划分为训练集和测试集。
2.提取数据特征:提取训练数据集的属性特征,以便我们计算概率并做出预测。
3.单一预测:使用数据集的特征生成单个预测。
4.多重预测:基于给定测试数据集和一个已提取特征的训练数据集生成预测。
5.评估精度:评估对于测试数据集的预测精度作为预测正确率。
6.合并代码:使用所有代码呈现一个完整的、独立的朴素贝叶斯算法的实现。

1.处理数据

1
2
3
4
5
6
7
8
9
10
11
def load_data(filename, ratio=0.67):
data_set = []
train_set = []
fp = open(filename, "r")
for line in fp:
data_set.append([float(x) for x in line.replace("\n", "").split(',')])
limit = int(len(data_set) * ratio)
while len(train_set) < limit:
index = random.randrange(len(data_set))
train_set.append(data_set.pop(index))
return data_set, train_set

当然也可以在read的时候取随机

1
2
3
4
5
6
7
8
9
10
11
12
def load_data(filename, ratio=0.67):
test_set = []
train_set = []
fp = open(filename, "r")
for line in fp:
flag = random.randrange(1,101)
if flag in range(1, int(ratio * 100) + 1):
train_set.append([float(x) for x in line.replace("\n", "").split(',')])
else:
test_set.append([float(x) for x in line.replace("\n", "").split(',')])
print len(train_set), len(test_set)
return test_set, train_set

再按类别生成key-value对应的数据

1
2
3
4
5
6
def separateByClass(dataset):
result = defaultdict(list)
for i in range(len(dataset)):
classType = int(dataset[i][-1])
result[classType].append(dataset[i][:-1])
return result

计算均值还有方差以及概率密度函数

1
2
3
4
5
6
7
8
9
10
def mean(numbers):
return sum(numbers) / float(len(numbers))
def stdev(numbers):
return math.sqrt(sum([pow(x - mean(numbers), 2) for x in numbers]) / float(len(numbers) - 1))
# 生成高斯函数并计算某一个值的概率
def calNumberProbability(mean, std, y):
exponent = math.exp(-(math.pow(y - mean, 2)/(2*math.pow(std, 2))))
return (1 / (math.sqrt(2*math.pi) * std)) * exponent

训练模型,生成每个属性对应的平均值还有标准差

1
2
3
4
5
6
def createMeanStdPair(result):
summarise = defaultdict(list)
for key in result:
for type in zip(*result[key]):
summarise[key].append([mean(type), stdev(type)])
return summarise

预测

准备工作都已经做完了,开始正事了,首先要说明,上面的步骤应该用训练数据去培训,下面用测试数据去生成结果.

1
2
3
4
5
6
7
8
9
10
11
def labelOfVector(summarise, input_vector):
summarise_prob = {}
for key in summarise:
#需要把初始概率写在循环里面,因为每个类别初始的概率都是1
init_prob = 1
for i in range(len(summarise[key])):
mean, std = summarise[key][i]
probability = calNumberProbability(mean, std, input_vector[i])
init_prob *= probability
summarise_prob[key] = init_prob
return sorted(summarise_prob.items(),key=lambda x:x[1])[-1][0]

计算精度

1
2
3
4
5
6
7
8
9
10
11
def main(summarise, test_set):
# 初始正确的数目
count = 0
sum_vector = 0
for key in test_set:
sum_vector += len(test_set[key])
for vector in test_set[key]:
label = labelOfVector(summarise, vector)
if label == key:
count += 1
print "测试数据一共{0}条,正确率为{1}%".format(sum_vector, (count / float(sum_vector)) * 100)

运行程序,得到结果

1
测试数据一共48条,正确率为95.8333333333%

不同种类的植物的区分度很大,且各个属性之间可看作是独立的.

下面我们来看看用现成的scikit-learn包怎么做.官方文档
测试的问题是“皮马印第安人糖尿病问题”。

这个问题包括768个对于皮马印第安患者的医疗观测细节,记录所描述的瞬时测量取自诸如患者的年纪,怀孕和血液检查的次数。所有患者都是21岁以上(含21岁)的女性,所有属性都是数值型,而且属性的单位各不相同。
每一个记录归属于一个类,这个类指明以测量时间为止,患者是否是在5年之内感染的糖尿病。如果是,则为1,否则为0。
机器学习文献中已经多次研究了这个标准数据集,好的预测精度为70%-76%。下面是pima-indians.data.csv文件中的一个样本,了解一下我们将要使用的数据。

1
2
3
4
5
6,148,72,35,0,33.6,0.627,50,1
1,85,66,29,0,26.6,0.351,31,0
8,183,64,0,0,23.3,0.672,32,1
1,89,66,23,94,28.1,0.167,21,0
0,137,40,35,168,43.1,2.288,33,1

基本方式和上面类似,我们也用高斯,不知道翻译成什么,直接用Gaussian Naive Bayes代替吧

1.处理数据

这里要注意数据都要处理成array格式的
步骤和上面的类似,先把初始数据处理成train_datatest_data.有两点不同需要注意一下.

  1. 将测量数据和类别数据分开
  2. 将数据用numpy处理成array格式的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    def load_data(filename, ratio=0.67):
    test_set = []
    train_set = []
    train_features = []
    test_features = []
    fp = open(filename, "r")
    for line in fp:
    flag = random.randrange(1,101)
    if flag in range(1, int(ratio * 100) + 1):
    train_set.append([float(x) for x in line.replace("\n", "").split(',')[:-1]])
    train_features.append(int(line.replace("\n", "").split(',')[-1]))
    else:
    test_set.append([float(x) for x in line.replace("\n", "").split(',')[:-1]])
    test_features.append(int(line.replace("\n", "").split(',')[-1]))
    return np.array(test_set), np.array(train_set), np.array(train_features), np.array(test_features)

2.建立模型

1
2
3
4
5
from sklearn.naive_bayes import GaussianNB
gnb = GaussianNB()
y_pred = gnb.fit(train_set, train_features).predict(test_set)
print("split %d rows into train=%d and test=%d rows \nAccuracy: %.2f%%"\
% (len(test_set) + train_set.shape[0], train_set.shape[0], test_set.shape[0], (test_features == y_pred).sum() * 100 / float(test_set.shape[0])))

输出结果如下:

1
2
split 768 rows into train=524 and test=244 rows
Accuracy: 79.92%

不得不说有scikit-learn包以后简单了很多, T T.