@[TOC]
朴素贝叶斯算法
本篇主要介绍朴素贝叶斯算法及应用案例。
以Tatinic数据集为例进行应用,同时介绍数据处理,数据集的划分,算法效果评估等内容。
一、简介
朴素贝叶斯法是基于贝叶斯定理与特征条件独立假设的分类方法 。
最为广泛的两种分类模型是决策树模型(Decision Tree Model)和朴素贝叶斯模型(Naive Bayesian Model,NBM)。
和决策树模型相比,朴素贝叶斯分类器(Naive Bayes Classifier 或 NBC)发源于古典数学理论,有着坚实的数学基础,以及稳定的分类效率。同时,NBC模型所需估计的参数很少,对缺失数据不太敏感,算法也比较简单。
朴素贝叶斯算法(Naive Bayesian algorithm) 是应用最为广泛的分类算法之一。
朴素贝叶斯方法是在贝叶斯算法的基础上进行了相应的简化,即假定给定目标值时属性之间相互条件独立。
也就是说没有哪个属性变量对于决策结果来说占有着较大的比重,也没有哪个属性变量对于决策结果占有着较小的比重。
虽然这个简化方式在一定程度上降低了贝叶斯分类算法的分类效果,但是在实际的应用场景中,极大地简化了贝叶斯方法的复杂性。
二、原理
设有样本数据集D = {d~1~ ,d~2~,···,d~n~ },对应样本集的特征属性集为X={x~1~,x~2~,···,x~d~},类变量为Y={y~1~,y~2~,···,y~m~},即D可以分成y~m~类别。其中x~1~,x~2~,···,x~d~相互随机独立,则Y的先验概率P~prior~ = P(Y),Y的后验概率P~post~ = P(Y|X),则由朴素贝叶斯算法可得,后验概率可以由先验概率P~prior~ = P(Y)、证据P(X)、类条件概率P(X|Y)计算出:
$$ P(X|Y)=\frac{P(Y)P(X|Y)}{P(X)} $$
朴素贝叶斯基于各特征相互独立,在给定类别为y的情况下,上式可以进一步表示为下式:
$$ P(X|Y=y)=\prod^{d}_{i=1}P(x_i|Y=y) $$
由以上两式可以算出后验概率为:
$$ P_{post}=P(Y|X)=\frac{P(y)\prod^{d}_{i=1}P(x_i|Y)}{P(X)} $$
由于P(X)的大小是固定不变的,因此在比较后验概率时,只比较上式的分子部分即可。
因此可以得到一个样本数据属于类别y~i~的朴素贝叶斯计算公式如下:
$$ P(y_i|x_1,x_2,···,x_d)=\frac{p(y_i)\prod^{d}_{j=1}P(x_j|y_i)}{\prod^{d}_{j=1}P(x_j)} $$
注意,一般只比较分子部分,这一点在后面会有一个在Titanic上应用的小讨论
三、原理分析(实例)
这里用一个实际案例来说明比较抽象的公式。
以瓜树的案例来计算说明:
目标:对下例进行分类
计算过程:
那么,我自己的理解就是:
先在训练集中计算标签为0和1的先验概率,再分别计算每个特征在标签为0和1的条件概率。
预测过程中,将预测数据每个特征在标签为1时的条件概率之积与预测为1的概率直接相乘,记为P(1)
再将预测数据每个特征在标签为0时的条件概率之积与预测为0的概率直接相乘,记作P(0)
比较P(1)与P(0),哪个大就预测数据的标签为0或1.
四、在Titanic数据集上的应用
1.数据集介绍
数据有如下特征:
继续观察每个特征下的具体情况:
我们会发现:
Age,Cabin,Embarked存在空值,需要进行处理
继续看整体生存状况
再综合我们的常识可以得到结论:
女性的存活率高于男性Pclass1,2,3的生存率依次降低,这有可能与不同层的乘船人的社会地位,富裕程度有关
年龄上来说,小孩先走,存活率较高
Fare分布差异很大。
Parch 代表同船的父母或子女,SipSp代表同船的兄弟姐妹,这都是两个表现亲人的关系,显然这类因素会对存活率产生影响
那么结合上面的分析的,可以做出如下处理:
对于不重要的特征舍弃掉,简化后面的计算,同时将SibSp和Parch两项合并。
数据中很多为连续型和字符型数据,我们需要进行数值化,离散化处理。
年龄中位数28,缺失值补充为28。并且以25和31为界限分为三类sibsp&parch按照有无分为两类
生还共计342人。其中全体票价和生还票价均值均约为32。
生还者票价高于32的126人
死亡共计549人。其中票价低于32的464人
票价低于32共计680人,死亡率0.68
票价低于64的共计773人,死亡512人 选择以64为分界点
附上此部分分析代码:
# 以下是最初的数据分析
# train_set, category = loadDataset('titanic.csv')
# print(category)
# print(train_set)
# k = [0, 0, 0, 0, 0, 0]
# min = train_set[0][3]
# max = 0
# age = []
# for data in train_set:
# for i in range(len(category)):
# if data[i] is None:
# k[i] += 1
# if data[3] is not None:
# if data[3] < min:
# min = data[3]
# if data[3] > max:
# max = data[3]
# age.append(data[3])
#
# age = sorted(age)
#
# print(category)
# print(k)
# print(min)
# print(max)
# print(age)
# print((len(train_set)-k[3])/2) #357
# print(age[int((len(train_set)-k[3])/2)]) # 28
# print(len(age)/3)
# print(age[297]) # 25
# print(age[297*2]) # 31
# for data in train_set:
# if data[3] is None:
# data[3] = 28
# print(train_set)
# s = 0
# for data in train_set:
# if data[-1] < 64:
# s += 1
#
# print(s)
# 以上是最初的数据分析
2.导入相关库
import csv
import numpy as np
import matplotlib.pyplot as plt
3.读入数据和数据处理
此部分函数实现以下内容:
- 读入数据集
- 删除分析中的不重要特征
- 转换数据格式为float
- 数据离散化:
性别以0,1区分 #此处其实更好的方法是one—hot处理,读者可以自行研究年龄以25,31(60)为界限进行离散 #此处再优化分析时会在此说明
sibsp和parcg加和后以2为界限,离散化
fare以64为界限离散化
def loadDataset(filename):
with open(filename, 'r') as f:
lines = csv.reader(f)
data_set = list(lines)
if filename != 'titanic.csv':
for i in range(len(data_set)):
del(data_set[i][0])
# 整理数据
for i in range(len(data_set)):
del(data_set[i][0])
del(data_set[i][2])
data_set[i][4] += data_set[i][5]
del(data_set[i][5])
del(data_set[i][5])
del(data_set[i][6])
del(data_set[i][-1])
category = data_set[0]
del (data_set[0])
# 转换数据格式
for data in data_set:
data[0] = int(data[0])
data[1] = int(data[1])
if data[3] != '':
data[3] = float(data[3])
else:
data[3] = None
data[4] = float(data[4])
data[5] = float(data[5])
# 补全缺失值 转换记录方式 分类
for data in data_set:
if data[3] is None:
data[3] = 28
# male : 1, female : 0
if data[2] == 'male':
data[2] = 1
else:
data[2] = 0
# age <25 为0, 25<=age<31为1,age>=31为2
if data[3] < 25:
data[3] = 0
elif data[3] >= 21 and data[3] < 60: # 但是测试得60分界准确率最高???!!!
data[3] = 1
else:
data[3] = 2
# sibsp&parcg以2为界限,小于为0,大于为1
if data[4] < 2:
data[4] = 0
else:
data[4] = 1
# fare以64为界限
if data[-1] < 64:
data[-1] = 0
else:
data[-1] = 1
return data_set, category
这一部分读入数据要根据自己手中的数据来写,无法直接应用
4.训练集和测试集的划分
常⽤有hold-out,cross validation,bootstrapping⽅法
hold—out
Hold-Out 是基本的划分方法,字面意思就是“留出来一部分”,即将数据集直接按照一定比例划分。
例如常用的 “2/8” 与 “3/7”,含义为训练集 80% 或 70%,相应的测试集占 20% 或 30%.
Hold-Out 的缺点较为明显,即在验证集上计算的出来的 最后评估指标与原始数据的顺序有很大关系。
import random
import csv
import pandas as pd
def loadDataset(filename, split, trainingSet = [], testSet = []):
with open(filename, 'r') as f:
lines = csv.reader(f)
dataset = list(lines)
for x in range(len(dataset)-1):
if random.random() < split: #将数据集随机划分
trainingSet.append(dataset[x])
else:
testSet.append(dataset[x])
if __name__ == "__main__":
train = []
test = []
loadDataset('', 0.7, train, test)
print(train)
print(test)
train2 = pd.DataFrame(data=train)
train2.to_csv('')
test2 = pd.DataFrame(data=test)
test2.to_csv('')
cross validation
简单交叉验证
将原始数据随机分为两组,一组做为训练集,一组做为验证集,利用训练集训练分类器,然后利用验证集验证模型,记录最后的分类准确率为此分类器的性能指标。
好处:处理简单,只需随机把原始数据分为两组即可
坏处:没有达到交叉的思想,由于是随机的将原始数据分组,所以最后验证集分类准确率的高低与原始数据的分组有很大的关系,得到的结果并不具有说服性。
2-折交叉验证(2-fold Cross Validation,记为2-CV)
做法是将数据集分成两个相等大小的子集,进行两次的分类器训练。在第一次中,一个子集作为训练集,另一个便作为测试集;在第二次训练中,则将训练集与测试集对换,
其并不常用:
主要原因是训练集样本数太少,不足以代表母体样本的分布,导致te测试阶段辨识率容易出现明显落差。
K-折交叉验证(K-fold Cross Validation,记为K-CV)
将原始数据分成K组(一般是均分),将每个子集数据分别做一次验证集,其余的K-1组子集数据作为训练集,这样会得到K个模型,用这K个模型最终的验证集的分类准确率的平均数作为此K-CV下分类器的性能指标。K一般大于等于2,实际操作时一般从3开始取,只有在原始数据集合数据量小的时候才会尝试取2。
应用最多,K-CV可以有效的避免过拟合与欠拟合的发生,最后得到的结果也比较具有说服性。
常见为十折交叉验证
- 将数据集分成十份,轮流将其中9份作为训练数据,1份作为测试数据,进行试验。每次试验都会得出相应的正确率。
- 10次的结果的正确率的平均值作为对算法精度的估计,一般还需要进行多次10折交叉验证(例如10次10折交叉验证),再求其均值,作为对算法准确性的估计
import csv
import pandas as pd
with open('titanic.csv', 'r') as f:
lines = csv.reader(f)
data_set = list(lines)
del(data_set[0])
length = len(data_set)//10
print(length)
data = []
for i in range(10):
data.append(data_set[i*length:(i+1)*length])
data.append(data_set[10*length:])
print(data_set)
print(data)
bootstrapping(自助法)
最常用的一种Bootstrap自助法,假设给定的数据集包含d个样本。该数据集有放回地抽样m次,产生m个样本的训练集。这样原数据样本中的某些样本很可能在该样本集中出现多次。没有进入该训练集的样本最终形成检验集(测试集)。 显然每个样本被选中的概率是1/m,因此未被选中的概率就是(1-1/m),这样一个样本在训练集中没出现的概率就是m次都未被选中的概率,即(1-1/m)^m^。当m趋于无穷大时,这一概率就将趋近于e^-1=0.368,所以留在训练集中的样本大概就占原来数据集的63.2%。
例如:人工样本为1,2,3;只有三个样本,则可以从随机变量X,分布为P(X=k)=1/3, k=1,2,3; 这样的经验分布中用计算机根据上述分布自动产生样本,如产生5个样本:1 2 3 2 1;也可以是:3 3 2 1 1
自助法在数据集较小、难以有效划分训练集和测试集时很有用;此外,自助法能从初始数据集中产生多个不同的训练集,这对集成学习等方法有很大的好处。然而,自助法产生的数据集改变了初始数据集的分布,这会引入估计偏差。因此,在初始数据量足够时,留出法和交叉验证法更常用一些。
#自助法
import numpy as np
#任意设置一个数据集
X = [1, 4, 3, 23, 4, 6, 7, 8, 9, 45, 67, 89, 34, 54, 76, 98, 43, 52]
#通过产生的随机数获得抽取样本的序号
bootstrapping = []
for i in range(len(X)):
bootstrapping.append(np.floor(np.random.random()*len(X)))
#通过序号获得原始数据集中的数据
D_1 = []
for i in range(len(X)):
D_1.append(X[int(bootstrapping[i])])
5.朴素贝叶斯计算
这部分没什么好说的,按照公式计算就好
class NaiveBayes:
def __init__(self):
pass
def train(self, data):
length = len(data)
p = []
for i in range(len(data[0])):
p.append([])
# 计算先验概率
sur = 0
for i in data:
if i[0] == 1:
sur += 1
death = length - sur
p1 = sur/length
p0 = 1 - p1
p[0].append(p1)
p[0].append(p0)
print(p)
# 计算pclass的条件概率
a1 = 0
a0 = 0
b1 = 0
b0 = 0
c1 = 0
c0 = 0
for i in data:
if i[0] == 1 and i[1] == 1:
a1 += 1
elif i[0] == 0 and i[1] == 1:
a0 += 1
elif i[0] == 1 and i[1] == 2:
b1 += 1
elif i[0] == 0 and i[1] == 2:
b0 += 1
elif i[0] == 1 and i[1] == 3:
c1 += 1
elif i[0] == 0 and i[1] == 3:
c0 += 1
p[1].append(a1 / sur)
p[1].append(a0 / death)
p[1].append(b1 / sur)
p[1].append(b0 / death)
p[1].append(c1 / sur)
p[1].append(c0 / death)
# 计算sex的条件概率
m1 = 0
m0 = 0
f1 = 0
f0 = 0
for i in data:
if i[0] == 1 and i[2] == 1:
m1 += 1
elif i[0] == 0 and i[2] == 1:
m0 += 1
elif i[0] == 1 and i[2] == 0:
f1 += 1
elif i[0] == 0 and i[2] == 0:
f0 += 1
p[2].append(m1 / sur)
p[2].append(m0 / death)
p[2].append(f1 / sur)
p[2].append(f0 / death)
# 计算age的条件概率
a1 = 0
a0 = 0
b1 = 0
b0 = 0
c1 = 0
c0 = 0
for i in data:
if i[0] == 1 and i[3] == 1:
a1 += 1
elif i[0] == 0 and i[3] == 1:
a0 += 1
elif i[0] == 1 and i[3] == 2:
b1 += 1
elif i[0] == 0 and i[3] == 2:
b0 += 1
elif i[0] == 1 and i[3] == 3:
c1 += 1
elif i[0] == 0 and i[3] == 3:
c0 += 1
p[3].append(a1 / sur)
p[3].append(a0 / death)
p[3].append(b1 / sur)
p[3].append(b0 / death)
p[3].append(c1 / sur)
p[3].append(c0 / death)
# 计算sibsp&parch条件概率
m1 = 0
m0 = 0
f1 = 0
f0 = 0
for i in data:
if i[0] == 1 and i[4] == 1:
m1 += 1
elif i[0] == 0 and i[4] == 1:
m0 += 1
elif i[0] == 1 and i[4] == 0:
f1 += 1
elif i[0] == 0 and i[4] == 0:
f0 += 1
p[4].append(m1 / sur)
p[4].append(m0 / death)
p[4].append(f1 / sur)
p[4].append(f0 / death)
m1 = 0
m0 = 0
f1 = 0
f0 = 0
for i in data:
if i[0] == 1 and i[5] == 1:
m1 += 1
elif i[0] == 0 and i[5] == 1:
m0 += 1
elif i[0] == 1 and i[5] == 0:
f1 += 1
elif i[0] == 0 and i[5] == 0:
f0 += 1
p[5].append(m1 / sur)
p[5].append(m0 / death)
p[5].append(f1 / sur)
p[5].append(f0 / death)
return p
def predict(self, t_data, p):
result = []
pp = []
for data in t_data:
p1 = p[0][0]
p0 = p[0][1]
# pclass
if data[1] == 1:
p1 *= p[1][0]
p0 *= p[1][1]
elif data[1] == 2:
p1 *= p[1][2]
p0 *= p[1][3]
elif data[1] == 3:
p1 *= p[1][4]
p0 *= p[1][5]
# sex
if data[2] == 1:
p1 *= p[2][0]
p0 *= p[2][1]
else:
p1 *= p[2][2]
p0 *= p[2][3]
# age
if data[3] == 0:
p1 *= p[3][0]
p0 *= p[3][1]
elif data[3] == 1:
p1 *= p[3][2]
p0 *= p[3][3]
else:
p1 *= p[3][4]
p0 *= p[3][5]
#sibsp&parch
if data[4] == 1:
p1 *= p[4][0]
p0 *= p[4][1]
else:
p1 *= p[4][2]
p0 *= p[4][3]
# fare
if data[5] == 1:
p1 *= p[5][0]
p0 *= p[5][1]
else:
p1 *= p[5][2]
p0 *= p[5][3]
pp.append(p1)
if p1 >= p0:
result.append(1)
else:
result.append(0)
return pp, result
6.模型评估1
此处评估标准选取ROC_AUC
先给出混淆矩阵:
ROC 图像:
纵轴为 TPR 真正例率,预测为正且实际为正的样本占所有正例样本的比例。
横轴为 FPR 假正例率,预测为正但实际为负的样本占所有负例样本的比例。
$$ TPR = \frac{TP}{TP+FN} $$
$$ FPR = \frac{FP}{TN+FP} $$
ROC图像分析
- 第一个点,(0,1),即 FPR=0, TPR=1,这意味着 FN(false negative)=0,并且FP(false positive)=0。这意味着分类器很完美,因为它将所有的样本都正确分类。
- 第二个点,(1,0),即 FPR=1,TPR=0,这个分类器是最糟糕的,因为它成功避开了所有的正确答案。
- 第三个点,(0,0),即 FPR=TPR=0,即 FP(false positive)=TP(true positive)=0,此时分类器将所有的样本都预测为负样本(negative)。
- 第四个点(1,1),分类器将所有的样本都预测为正样本。
- 对角线上的点表示分类器将一半的样本猜测为正样本,另外一半的样本猜测为负样本。
因此,ROC 曲线越接近左上角,分类器的性能越好。
怎么画ROC曲线
- 按 Score 从大到小排列
- 依次将每个 Score 设定为阈值,然后这 20 个样本的标签会变化,当它的 score 大于或等于当前阈值时,则为正样本,否则为负样本。
- 这样对每个阈值,可以计算一组 FPR 和 TPR,此例一共可以得到 20 组。
- 当阈值设置为 1 和 0 时, 可以得到 ROC 曲线上的 (0,0) 和 (1,1) 两个点。
怎么计算AUC
$$ AUC = \frac{1}{2}\sum^{m-1}_{i=1}(x_{i+1}-x_i)·(y_i+y_{i+1}) $$
def roc_auc(p, test, result):
tp = 0
tn = 0
for i in test:
if i[0] == 1:
tp += 1
else:
tn += 1
for i in range(len(test)):
test[i] = test[i][0]
p[i] = [p[i]]
p[i].append(test[i])
p[i].append(result[i])
p = sorted(p, reverse=True)
print(p)
tpr = [0]
fpr = [0]
length = len(p)
for i in range(1, length):
if p[i][1] == 1:
fpr.append(fpr[i-1])
tpr.append(tpr[i-1]+1/tp)
elif p[i][1] == 0:
fpr.append(fpr[i-1]+1/tn)
tpr.append(tpr[i-1])
tpr.append(1)
fpr.append(1)
print(fpr)
print(tpr)
auc = 0
for i in range(len(fpr)-1):
auc += (fpr[i+1]-fpr[i])*(tpr[i]+tpr[i+1])
auc = auc/2
print(auc)
fpr = np.array(fpr)
tpr = np.array(tpr)
plt.title("ROC curve of %s (AUC = %.4f)" % ('svm', auc))
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")
plt.plot(fpr, tpr)
plt.show()
7.模型评估2——F1_score
F1分数(F1 Score)中,是统计学中用来衡量二分类模型精确度的一种指标。它同时兼顾了分类模型的精确率和召回率。F1分数可以看作是模型精确率和召回率的一种调和平均,它的最大值是1,最小值是0。
$$ F1 = \frac{2*P*R}{P+R}=\frac{2*TP}{样例总数+TP-TN} $$
tp = 0
tn = 0
for i in range(len(result)):
if test[i][0] == result[i] == 1:
tp += 1
if test[i][0] == result[i] == 0:
tn += 1
print(tp)
print(tn)
f1 = (2*tp)/(len(test)+tp-tn)
print(f1)
roc_auc(pp, test, result)
8.评估结果
准确率:74.5%
F1 : 0.678
9.完整代码
import csv
import numpy as np
import matplotlib.pyplot as plt
# 0PassengerId:乘客的ID 不重要
# 1Survived:乘客是否获救,Key:0=没获救,1=已获救
# 2Pclass:乘客船舱等级(1/2/3三个等级舱位)
# 3Name:乘客姓名 不重要
# 4Sex:性别
# 5Age:年龄
# 6SibSp:乘客在船上的兄弟姐妹/配偶数量
# 7Parch:乘客在船上的父母/孩子数量
# 8Ticket:船票号 不重要
# 9Fare:船票价
# 10Cabin:客舱号码 不重要
# 11Embarked:登船的港口 不重要
# 数据分析得: 年龄中位数28,缺失值补充为28。并且以25和31为界限分为三类
# sibsp&parch按照有无分为两类
# 生还共计342人。其中全体票价和生还票价均值均约为32。
# 生还者票价高于32的126人
# 死亡共计549人。其中票价低于32的464人
# 票价低于32共计680人,死亡率0.68
# 票价低于64的共计773人,死亡512人 选择以64为分界点
def loadDataset(filename):
with open(filename, 'r') as f:
lines = csv.reader(f)
data_set = list(lines)
if filename != 'titanic.csv':
for i in range(len(data_set)):
del(data_set[i][0])
# 整理数据
for i in range(len(data_set)):
del(data_set[i][0])
del(data_set[i][2])
data_set[i][4] += data_set[i][5]
del(data_set[i][5])
del(data_set[i][5])
del(data_set[i][6])
del(data_set[i][-1])
category = data_set[0]
del (data_set[0])
# 转换数据格式
for data in data_set:
data[0] = int(data[0])
data[1] = int(data[1])
if data[3] != '':
data[3] = float(data[3])
else:
data[3] = None
data[4] = float(data[4])
data[5] = float(data[5])
# 补全缺失值 转换记录方式 分类
for data in data_set:
if data[3] is None:
data[3] = 28
# male : 1, female : 0
if data[2] == 'male':
data[2] = 1
else:
data[2] = 0
# age <25 为0, 25<=age<31为1,age>=31为2
if data[3] < 25:
data[3] = 0
elif data[3] >= 21 and data[3] < 60: # 但是测试得60分界准确率最高???!!!
data[3] = 1
else:
data[3] = 2
# sibsp&parcg以2为界限,小于为0,大于为1
if data[4] < 2:
data[4] = 0
else:
data[4] = 1
# fare以64为界限
if data[-1] < 64:
data[-1] = 0
else:
data[-1] = 1
return data_set, category
class NaiveBayes:
def __init__(self):
pass
def train(self, data):
length = len(data)
p = []
for i in range(len(data[0])):
p.append([])
# 计算先验概率
sur = 0
for i in data:
if i[0] == 1:
sur += 1
death = length - sur
p1 = sur/length
p0 = 1 - p1
p[0].append(p1)
p[0].append(p0)
print(p)
# 计算pclass的条件概率
a1 = 0
a0 = 0
b1 = 0
b0 = 0
c1 = 0
c0 = 0
for i in data:
if i[0] == 1 and i[1] == 1:
a1 += 1
elif i[0] == 0 and i[1] == 1:
a0 += 1
elif i[0] == 1 and i[1] == 2:
b1 += 1
elif i[0] == 0 and i[1] == 2:
b0 += 1
elif i[0] == 1 and i[1] == 3:
c1 += 1
elif i[0] == 0 and i[1] == 3:
c0 += 1
p[1].append(a1 / sur)
p[1].append(a0 / death)
p[1].append(b1 / sur)
p[1].append(b0 / death)
p[1].append(c1 / sur)
p[1].append(c0 / death)
# 计算sex的条件概率
m1 = 0
m0 = 0
f1 = 0
f0 = 0
for i in data:
if i[0] == 1 and i[2] == 1:
m1 += 1
elif i[0] == 0 and i[2] == 1:
m0 += 1
elif i[0] == 1 and i[2] == 0:
f1 += 1
elif i[0] == 0 and i[2] == 0:
f0 += 1
p[2].append(m1 / sur)
p[2].append(m0 / death)
p[2].append(f1 / sur)
p[2].append(f0 / death)
# 计算age的条件概率
a1 = 0
a0 = 0
b1 = 0
b0 = 0
c1 = 0
c0 = 0
for i in data:
if i[0] == 1 and i[3] == 1:
a1 += 1
elif i[0] == 0 and i[3] == 1:
a0 += 1
elif i[0] == 1 and i[3] == 2:
b1 += 1
elif i[0] == 0 and i[3] == 2:
b0 += 1
elif i[0] == 1 and i[3] == 3:
c1 += 1
elif i[0] == 0 and i[3] == 3:
c0 += 1
p[3].append(a1 / sur)
p[3].append(a0 / death)
p[3].append(b1 / sur)
p[3].append(b0 / death)
p[3].append(c1 / sur)
p[3].append(c0 / death)
# 计算sibsp&parch条件概率
m1 = 0
m0 = 0
f1 = 0
f0 = 0
for i in data:
if i[0] == 1 and i[4] == 1:
m1 += 1
elif i[0] == 0 and i[4] == 1:
m0 += 1
elif i[0] == 1 and i[4] == 0:
f1 += 1
elif i[0] == 0 and i[4] == 0:
f0 += 1
p[4].append(m1 / sur)
p[4].append(m0 / death)
p[4].append(f1 / sur)
p[4].append(f0 / death)
m1 = 0
m0 = 0
f1 = 0
f0 = 0
for i in data:
if i[0] == 1 and i[5] == 1:
m1 += 1
elif i[0] == 0 and i[5] == 1:
m0 += 1
elif i[0] == 1 and i[5] == 0:
f1 += 1
elif i[0] == 0 and i[5] == 0:
f0 += 1
p[5].append(m1 / sur)
p[5].append(m0 / death)
p[5].append(f1 / sur)
p[5].append(f0 / death)
return p
def predict(self, t_data, p):
result = []
pp = []
for data in t_data:
p1 = p[0][0]
p0 = p[0][1]
# pclass
if data[1] == 1:
p1 *= p[1][0]
p0 *= p[1][1]
elif data[1] == 2:
p1 *= p[1][2]
p0 *= p[1][3]
elif data[1] == 3:
p1 *= p[1][4]
p0 *= p[1][5]
# sex
if data[2] == 1:
p1 *= p[2][0]
p0 *= p[2][1]
else:
p1 *= p[2][2]
p0 *= p[2][3]
# age
if data[3] == 0:
p1 *= p[3][0]
p0 *= p[3][1]
elif data[3] == 1:
p1 *= p[3][2]
p0 *= p[3][3]
else:
p1 *= p[3][4]
p0 *= p[3][5]
#sibsp&parch
if data[4] == 1:
p1 *= p[4][0]
p0 *= p[4][1]
else:
p1 *= p[4][2]
p0 *= p[4][3]
# fare
if data[5] == 1:
p1 *= p[5][0]
p0 *= p[5][1]
else:
p1 *= p[5][2]
p0 *= p[5][3]
pp.append(p1)
if p1 >= p0:
result.append(1)
else:
result.append(0)
return pp, result
def roc_auc(p, test, result):
tp = 0
tn = 0
for i in test:
if i[0] == 1:
tp += 1
else:
tn += 1
for i in range(len(test)):
test[i] = test[i][0]
p[i] = [p[i]]
p[i].append(test[i])
p[i].append(result[i])
p = sorted(p, reverse=True)
print(p)
tpr = [0]
fpr = [0]
length = len(p)
for i in range(1, length):
if p[i][1] == 1:
fpr.append(fpr[i-1])
tpr.append(tpr[i-1]+1/tp)
elif p[i][1] == 0:
fpr.append(fpr[i-1]+1/tn)
tpr.append(tpr[i-1])
tpr.append(1)
fpr.append(1)
print(fpr)
print(tpr)
auc = 0
for i in range(len(fpr)-1):
auc += (fpr[i+1]-fpr[i])*(tpr[i]+tpr[i+1])
auc = auc/2
print(auc)
fpr = np.array(fpr)
tpr = np.array(tpr)
plt.title("ROC curve of %s (AUC = %.4f)" % ('svm', auc))
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")
plt.plot(fpr, tpr)
plt.show()
if __name__ == "__main__":
# 以下是最初的数据分析
# train_set, category = loadDataset('titanic.csv')
# print(category)
# print(train_set)
# k = [0, 0, 0, 0, 0, 0]
# min = train_set[0][3]
# max = 0
# age = []
# for data in train_set:
# for i in range(len(category)):
# if data[i] is None:
# k[i] += 1
# if data[3] is not None:
# if data[3] < min:
# min = data[3]
# if data[3] > max:
# max = data[3]
# age.append(data[3])
#
# age = sorted(age)
#
# print(category)
# print(k)
# print(min)
# print(max)
# print(age)
# print((len(train_set)-k[3])/2) #357
# print(age[int((len(train_set)-k[3])/2)]) # 28
# print(len(age)/3)
# print(age[297]) # 25
# print(age[297*2]) # 31
# for data in train_set:
# if data[3] is None:
# data[3] = 28
# print(train_set)
# s = 0
# for data in train_set:
# if data[-1] < 64:
# s += 1
#
# print(s)
# 以上是最初的数据分析
train_set, category = loadDataset('titanic_train.csv')
test_set, category = loadDataset('titanic_test.csv')
print(category)
print(train_set)
bayes = NaiveBayes()
p = bayes.train(train_set)
print(p)
pp, result = bayes.predict(test_set, p)
print(result)
count = 0
for i in range(len(result)):
if test_set[i][0] == result[i]:
count += 1
accuracy = count/len(test_set)
print(accuracy)
# # 十折交叉验证
# data_set, category = loadDataset('titanic.csv')
#
# length = len(data_set) // 10
# print(length)
# data = []
# for i in range(10):
# data.append(data_set[i * length:(i + 1) * length])
# data.append(data_set[10 * length:])
#
# sum = 0
#
# for i in range(9):
# p = bayes.train(data[i])
# print(p)
#
# pp, result = bayes.predict(data[9], p)
# print(result)
# count = 0
# for i in range(len(result)):
# if test_set[i][0] == result[i]:
# count += 1
# sum += count/len(data[9])
#
# print(sum/10)
# 自助法
data_set, category = loadDataset('titanic.csv')
bootstrapping = []
for i in range(len(data_set)):
bootstrapping.append(np.floor(np.random.random()*len(data_set)))
test = []
for i in range(len(data_set)):
test.append(data_set[int(bootstrapping[i])])
p = bayes.train(data_set)
print(p)
pp, result = bayes.predict(test, p)
print(result)
count = 0
for i in range(len(result)):
if test[i][0] == result[i]:
count += 1
accuracy = count / len(test)
print(accuracy)
#基于自助法计算F1-score
tp = 0
tn = 0
for i in range(len(result)):
if test[i][0] == result[i] == 1:
tp += 1
if test[i][0] == result[i] == 0:
tn += 1
print(tp)
print(tn)
f1 = (2*tp)/(len(test)+tp-tn)
print(f1)
roc_auc(pp, test, result)
五、模型优化与提升
- 性别数值化更好的方法是one—hot处理
- 年龄离散化的标准。后续再测试的过程中发现,年龄的离散化标准对模型的准确率影响极大,这一点可以在探索后进行优化进而提升模型效果
- 对于贝叶斯的概率计算时,原方法是只计算分子进行比较。但是我们可以换一种方法,计算完整的概率值,然后与0.5进行比较,然后分类。这种方法再结合更加合适的年龄划分标准可以是本地的准确率达到90%以上,在kaggle上也可以达到80%以上
这几种优化思路提供给读者自行探索尝试。
import csv
import numpy as np
import matplotlib.pyplot as plt
# 0PassengerId:乘客的ID 不重要
# 1Survived:乘客是否获救,Key:0=没获救,1=已获救
# 2Pclass:乘客船舱等级(1/2/3三个等级舱位)
# 3Name:乘客姓名 不重要
# 4Sex:性别
# 5Age:年龄
# 6SibSp:乘客在船上的兄弟姐妹/配偶数量
# 7Parch:乘客在船上的父母/孩子数量
# 8Ticket:船票号 不重要
# 9Fare:船票价
# 10Cabin:客舱号码 不重要
# 11Embarked:登船的港口 不重要
# 数据分析得: 年龄中位数28,缺失值补充为28。并且以25和31为界限分为三类
# sibsp&parch按照有无分为两类
# 生还共计342人。其中全体票价和生还票价均值均约为32。
# 生还者票价高于32的126人
# 死亡共计549人。其中票价低于32的464人
# 票价低于32共计680人,死亡率0.68
# 票价低于64的共计773人,死亡512人 选择以64为分界点
def loadDataset(filename):
with open(filename, 'r') as f:
lines = csv.reader(f)
data_set = list(lines)
if filename != 'data/data43004/titanic.csv':
for i in range(len(data_set)):
del(data_set[i][0])
# 整理数据
for i in range(len(data_set)):
del(data_set[i][0])
del(data_set[i][2])
data_set[i][4] += data_set[i][5]
del(data_set[i][5])
del(data_set[i][5])
del(data_set[i][6])
del(data_set[i][-1])
category = data_set[0]
del (data_set[0])
# 转换数据格式
for data in data_set:
data[0] = int(data[0])
data[1] = int(data[1])
if data[3] != '':
data[3] = float(data[3])
else:
data[3] = None
data[4] = float(data[4])
data[5] = float(data[5])
# 补全缺失值 转换记录方式 分类
for data in data_set:
if data[3] is None:
data[3] = 28
# male : 1, female : 0
if data[2] == 'male':
data[2] = 1
else:
data[2] = 0
# age <25 为0, 25<=age<31为1,age>=31为2
if data[3] < 25:
data[3] = 0
elif data[3] >= 21 and data[3] < 60: # 但是测试得60分界准确率最高???!!!
data[3] = 1
else:
data[3] = 2
# sibsp&parcg以2为界限,小于为0,大于为1
if data[4] < 2:
data[4] = 0
else:
data[4] = 1
# fare以64为界限
if data[-1] < 64:
data[-1] = 0
else:
data[-1] = 1
return data_set, category
class NaiveBayes:
def __init__(self):
pass
def train(self, data):
length = len(data)
p = []
for i in range(len(data[0])):
p.append([])
# 计算先验概率
sur = 0
for i in data:
if i[0] == 1:
sur += 1
death = length - sur
p1 = sur/length
p0 = 1 - p1
p[0].append(p1)
p[0].append(p0)
print(p)
# 计算pclass的条件概率
a1 = 0
a0 = 0
b1 = 0
b0 = 0
c1 = 0
c0 = 0
for i in data:
if i[0] == 1 and i[1] == 1:
a1 += 1
elif i[0] == 0 and i[1] == 1:
a0 += 1
elif i[0] == 1 and i[1] == 2:
b1 += 1
elif i[0] == 0 and i[1] == 2:
b0 += 1
elif i[0] == 1 and i[1] == 3:
c1 += 1
elif i[0] == 0 and i[1] == 3:
c0 += 1
p[1].append(a1 / sur)
p[1].append(a0 / death)
p[1].append(b1 / sur)
p[1].append(b0 / death)
p[1].append(c1 / sur)
p[1].append(c0 / death)
# 计算sex的条件概率
m1 = 0
m0 = 0
f1 = 0
f0 = 0
for i in data:
if i[0] == 1 and i[2] == 1:
m1 += 1
elif i[0] == 0 and i[2] == 1:
m0 += 1
elif i[0] == 1 and i[2] == 0:
f1 += 1
elif i[0] == 0 and i[2] == 0:
f0 += 1
p[2].append(m1 / sur)
p[2].append(m0 / death)
p[2].append(f1 / sur)
p[2].append(f0 / death)
# 计算age的条件概率
a1 = 0
a0 = 0
b1 = 0
b0 = 0
c1 = 0
c0 = 0
for i in data:
if i[0] == 1 and i[3] == 1:
a1 += 1
elif i[0] == 0 and i[3] == 1:
a0 += 1
elif i[0] == 1 and i[3] == 2:
b1 += 1
elif i[0] == 0 and i[3] == 2:
b0 += 1
elif i[0] == 1 and i[3] == 3:
c1 += 1
elif i[0] == 0 and i[3] == 3:
c0 += 1
p[3].append(a1 / sur)
p[3].append(a0 / death)
p[3].append(b1 / sur)
p[3].append(b0 / death)
p[3].append(c1 / sur)
p[3].append(c0 / death)
# 计算sibsp&parch条件概率
m1 = 0
m0 = 0
f1 = 0
f0 = 0
for i in data:
if i[0] == 1 and i[4] == 1:
m1 += 1
elif i[0] == 0 and i[4] == 1:
m0 += 1
elif i[0] == 1 and i[4] == 0:
f1 += 1
elif i[0] == 0 and i[4] == 0:
f0 += 1
p[4].append(m1 / sur)
p[4].append(m0 / death)
p[4].append(f1 / sur)
p[4].append(f0 / death)
m1 = 0
m0 = 0
f1 = 0
f0 = 0
for i in data:
if i[0] == 1 and i[5] == 1:
m1 += 1
elif i[0] == 0 and i[5] == 1:
m0 += 1
elif i[0] == 1 and i[5] == 0:
f1 += 1
elif i[0] == 0 and i[5] == 0:
f0 += 1
p[5].append(m1 / sur)
p[5].append(m0 / death)
p[5].append(f1 / sur)
p[5].append(f0 / death)
return p
def predict(self, t_data, p):
result = []
pp = []
for data in t_data:
p1 = p[0][0]
p0 = p[0][1]
# pclass
if data[1] == 1:
p1 *= p[1][0]
p0 *= p[1][1]
elif data[1] == 2:
p1 *= p[1][2]
p0 *= p[1][3]
elif data[1] == 3:
p1 *= p[1][4]
p0 *= p[1][5]
# sex
if data[2] == 1:
p1 *= p[2][0]
p0 *= p[2][1]
else:
p1 *= p[2][2]
p0 *= p[2][3]
# age
if data[3] == 0:
p1 *= p[3][0]
p0 *= p[3][1]
elif data[3] == 1:
p1 *= p[3][2]
p0 *= p[3][3]
else:
p1 *= p[3][4]
p0 *= p[3][5]
#sibsp&parch
if data[4] == 1:
p1 *= p[4][0]
p0 *= p[4][1]
else:
p1 *= p[4][2]
p0 *= p[4][3]
# fare
if data[5] == 1:
p1 *= p[5][0]
p0 *= p[5][1]
else:
p1 *= p[5][2]
p0 *= p[5][3]
pp.append(p1)
if p1 >= p0:
result.append(1)
else:
result.append(0)
return pp, result
def roc_auc(p, test, result):
tp = 0
tn = 0
for i in test:
if i[0] == 1:
tp += 1
else:
tn += 1
for i in range(len(test)):
test[i] = test[i][0]
p[i] = [p[i]]
p[i].append(test[i])
p[i].append(result[i])
p = sorted(p, reverse=True)
print(p)
tpr = [0]
fpr = [0]
length = len(p)
for i in range(1, length):
if p[i][1] == 1:
fpr.append(fpr[i-1])
tpr.append(tpr[i-1]+1/tp)
elif p[i][1] == 0:
fpr.append(fpr[i-1]+1/tn)
tpr.append(tpr[i-1])
tpr.append(1)
fpr.append(1)
print(fpr)
print(tpr)
auc = 0
for i in range(len(fpr)-1):
auc += (fpr[i+1]-fpr[i])*(tpr[i]+tpr[i+1])
auc = auc/2
print(auc)
fpr = np.array(fpr)
tpr = np.array(tpr)
plt.title("ROC curve of %s (AUC = %.4f)" % ('svm', auc))
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")
plt.plot(fpr, tpr)
plt.show()
if __name__ == "__main__":
# 以下是最初的数据分析
# train_set, category = loadDataset('titanic.csv')
# print(category)
# print(train_set)
# k = [0, 0, 0, 0, 0, 0]
# min = train_set[0][3]
# max = 0
# age = []
# for data in train_set:
# for i in range(len(category)):
# if data[i] is None:
# k[i] += 1
# if data[3] is not None:
# if data[3] < min:
# min = data[3]
# if data[3] > max:
# max = data[3]
# age.append(data[3])
#
# age = sorted(age)
#
# print(category)
# print(k)
# print(min)
# print(max)
# print(age)
# print((len(train_set)-k[3])/2) #357
# print(age[int((len(train_set)-k[3])/2)]) # 28
# print(len(age)/3)
# print(age[297]) # 25
# print(age[297*2]) # 31
# for data in train_set:
# if data[3] is None:
# data[3] = 28
# print(train_set)
# s = 0
# for data in train_set:
# if data[-1] < 64:
# s += 1
#
# print(s)
# 以上是最初的数据分析
train_set, category = loadDataset('data/data43004/titanic_train.csv')
test_set, category = loadDataset('data/data43004/titanic_test.csv')
print(category)
print(train_set)
bayes = NaiveBayes()
p = bayes.train(train_set)
print(p)
pp, result = bayes.predict(test_set, p)
print(result)
count = 0
for i in range(len(result)):
if test_set[i][0] == result[i]:
count += 1
accuracy = count/len(test_set)
print(accuracy)
# # 十折交叉验证
# data_set, category = loadDataset('titanic.csv')
#
# length = len(data_set) // 10
# print(length)
# data = []
# for i in range(10):
# data.append(data_set[i * length:(i + 1) * length])
# data.append(data_set[10 * length:])
#
# sum = 0
#
# for i in range(9):
# p = bayes.train(data[i])
# print(p)
#
# pp, result = bayes.predict(data[9], p)
# print(result)
# count = 0
# for i in range(len(result)):
# if test_set[i][0] == result[i]:
# count += 1
# sum += count/len(data[9])
#
# print(sum/10)
# 自助法
data_set, category = loadDataset('data/data43004/titanic.csv')
bootstrapping = []
for i in range(len(data_set)):
bootstrapping.append(np.floor(np.random.random()*len(data_set)))
test = []
for i in range(len(data_set)):
test.append(data_set[int(bootstrapping[i])])
p = bayes.train(data_set)
print(p)
pp, result = bayes.predict(test, p)
print(result)
count = 0
for i in range(len(result)):
if test[i][0] == result[i]:
count += 1
accuracy = count / len(test)
print(accuracy)
#基于自助法计算F1-score
tp = 0
tn = 0
for i in range(len(result)):
if test[i][0] == result[i] == 1:
tp += 1
if test[i][0] == result[i] == 0:
tn += 1
print(tp)
print(tn)
f1 = (2*tp)/(len(test)+tp-tn)
print(f1)
roc_auc(pp, test, result)