实验1.使用sklearn的GaussianNB、BernoulliNB、MultinomialNB完成肿瘤预测任务
实验内容:
- 使用GaussianNB、BernoulliNB、MultinomialNB完成肿瘤预测
- 计算各自十折交叉验证的精度、查准率、查全率、F1值
- 根据精度、查准率、查全率、F1值的实际意义以及四个值的对比阐述三个算法在肿瘤预测中的表现对比
1. 读取数据集
python
import pandas as pd
import numpy as np
data = pd.read_csv('./breast-cancer.csv')
data = data.replace({'B': 1, 'M': -1})
data = data.values
data_x = data[:,2:]
data_y = data[:,1:2].ravel()
2. 导入模型
python
from sklearn.naive_bayes import GaussianNB
from sklearn.naive_bayes import MultinomialNB
from sklearn.naive_bayes import BernoulliNB
from sklearn.model_selection import cross_val_predict
from sklearn.metrics import accuracy_score
from sklearn.metrics import precision_score
from sklearn.metrics import recall_score
from sklearn.metrics import f1_score
3. 计算十折交叉验证下,GaussianNB、BernoulliNB、MultinomialNB的精度、查准率、查全率、F1值
python
model = GaussianNB()
prediction = cross_val_predict(model, data_x, data_y, cv=10)
acc = accuracy_score(data_y, prediction)
precision = precision_score(data_y, prediction)
recall = recall_score(data_y, prediction)
f = f1_score(data_y, prediction)
print("GaussianNB在测试集上的四项指标")
print("精度:",acc)
print("查准率:",precision)
print("查全率:",recall)
print("f1值:",f)
model12 = BernoulliNB()
prediction12 = cross_val_predict(model12, data_x, data_y, cv=10)
acc12 = accuracy_score(data_y, prediction12)
precision12 = precision_score(data_y, prediction12)
recall12 = recall_score(data_y, prediction12)
f12 = f1_score(data_y, prediction12)
print("BernoulliNB在测试集上的四项指标")
print("精度:",acc12)
print("查准率:",precision12)
print("查全率:",recall12)
print("f1值:",f12)
model13 = MultinomialNB()
prediction13 = cross_val_predict(model13, data_x, data_y, cv=10)
acc13 = accuracy_score(data_y, prediction13)
precision13 = precision_score(data_y, prediction13)
recall13 = recall_score(data_y, prediction13)
f13 = f1_score(data_y, prediction13)
print("MultinomialNB在测试集上的四项指标")
print("精度:",acc13)
print("查准率:",precision13)
print("查全率:",recall13)
print("f1值:",f13)
实验2.实现一个高斯朴素贝叶斯分类器
实验内容:
- 实现高斯朴素贝叶斯分类器
- 计算模型的查准率,查全率,F1值
我们要实现一个可以处理连续特征的,服从高斯分布的朴素贝叶斯分类器
符号
给定训练集 T T T
T = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , ⋅ ⋅ ⋅ , ( x N , y N ) } T = \{(x_1, y_1), (x_2, y_2), ···, (x_N, y_N)\} T={(x1,y1),(x2,y2),⋅⋅⋅,(xN,yN)}
其中, x x x 为样本的特征, y y y 是该样本对应的标记,下标表示对应的是第几个样本,上标表示第几个特征。训练集 T T T 内一共 ∣ T ∣ = N \vert T \vert = N ∣T∣=N 个样本。
假设我们的任务是处理 K K K 类分类任务,记类标记分别为 c 1 , c 2 , . . . , c k c_1, c_2, ..., c_k c1,c2,...,ck 。
目标
我们的目标是对样本进行分类,这里我们用概率的方法,求 P ( Y = c k ∣ X = x ) , k = 1 , 2 , . . . , K P(Y = c_k \mid X = x), \ k = 1, 2, ..., K P(Y=ck∣X=x), k=1,2,...,K 中最大的那个概率对应的 k k k 是哪个,也就是,给定样本 x x x ,模型认为它是哪个类别的概率最大。
原理
由贝叶斯公式:
P ( Y = c k ∣ X = x ) = P ( Y = c k , X = x ) P ( X = x ) = P ( X = x ∣ Y = c k ) P ( Y = c k ) ∑ k P ( X = x ∣ Y = c k ) P ( Y = c k ) \begin{aligned} P(Y = c_k \mid X = x) &= \frac{P(Y = c_k, X = x)}{P(X = x)} \\ &= \frac{P(X = x \mid Y = c_k)P(Y = c_k)}{\sum_kP(X = x \mid Y = c_k)P(Y = c_k)} \\ \end{aligned} P(Y=ck∣X=x)=P(X=x)P(Y=ck,X=x)=∑kP(X=x∣Y=ck)P(Y=ck)P(X=x∣Y=ck)P(Y=ck)
这里,我们要求 K K K 个概率中最大的那个,而这 K K K 个概率的分母都相同,我们可以忽略分母部分,比较分子部分的大小,也就是比较 先验概率 P ( Y = c k ) P(Y = c_k) P(Y=ck) 和 似然 P ( X = x ∣ Y = c k ) P(X = x \mid Y = c_k) P(X=x∣Y=ck) 的乘积。
通过先验概率分布
P ( Y = c k ) , k = 1 , 2 , . . . , K P(Y = c_k), \ k = 1, 2, ..., K P(Y=ck), k=1,2,...,K
和条件概率分布
P ( X = x ∣ Y = c k ) = P ( X ( 1 ) = x ( 1 ) , ⋅ ⋅ ⋅ , X ( n ) = x ( n ) ∣ Y = c k ) , k = 1 , 2 , . . . , K P(X = x \mid Y = c_k) = P(X^{(1)} = x^{(1)}, ···, X^{(n)} = x^{(n)} \mid Y = c_k), \ k = 1, 2, ..., K P(X=x∣Y=ck)=P(X(1)=x(1),⋅⋅⋅,X(n)=x(n)∣Y=ck), k=1,2,...,K
我们就可以得到联合概率分布 P ( X = x , Y = c k ) P(X = x, Y = c_k) P(X=x,Y=ck) 。
1. 先验概率 P ( Y = c k ) P(Y = c_k) P(Y=ck) :
先验概率的求解很简单,只要统计训练集中类别 k k k 出现的概率即可。
P ( Y = c k ) = n u m b e r o f c k N P(Y = c_k) = \frac{\mathrm{number} \ \mathrm{of}\ c_k}{N} P(Y=ck)=Nnumber of ck
2. 似然 P ( X = x ∣ Y = c k ) P(X = x \mid Y = c_k) P(X=x∣Y=ck) :
求解这个条件概率比较复杂,这里我们要假设特征之间相互独立,可得
P ( X = x ∣ Y = c k ) = ∏ j = 1 n P ( X ( j ) = x ( j ) ∣ Y = c k ) P(X = x \mid Y = c_k) = \prod^n_{j=1}P(X^{(j)}=x^{(j)} \mid Y = c_k) P(X=x∣Y=ck)=j=1∏nP(X(j)=x(j)∣Y=ck)
其中, x ( j ) x^{(j)} x(j) 表示样本 x x x 的第 j j j 个特征。
这样,复杂的条件概率就转换为了多个特征条件概率的乘积。
3. 特征 j j j 的条件概率 P ( X ( j ) = x ( j ) ∣ Y = c k ) P(X^{(j)}=x^{(j)} \mid Y = c_k) P(X(j)=x(j)∣Y=ck) :
因为我们处理的特征都是连续型特征,一般我们假设这些特征服从正态分布。
当 Y = c k Y = c_k Y=ck 时, X ( j ) = a j l X^{(j)} = a_{jl} X(j)=ajl 的概率可由下面的公式计算得到:
P ( X ( j ) = a j l ∣ Y = c k ) = 1 2 π σ c k , j 2 exp ( − ( a j l − μ c k , j ) 2 2 σ c k , j 2 ) P(X^{(j)} = a_{jl} \mid Y = c_k) = \frac{1}{\sqrt{2 \pi \sigma^2_{c_k,j}}} \exp{\bigg( - \frac{(a_{jl} - \mu_{c_k,j})^2}{2 \sigma^2_{c_k,j}} \bigg)} P(X(j)=ajl∣Y=ck)=2πσck,j2 1exp(−2σck,j2(ajl−μck,j)2)
这里 μ c k , j \mu_{c_k,j} μck,j 和 σ c k , j 2 \sigma^2_{c_k,j} σck,j2 分别表示当 Y = c k Y = c_k Y=ck 时,第 j j j 个特征的均值和方差,这个均值和方差都是通过训练集的样本计算出来的。
因为正态分布只需要两个参数(均值和方差)就可以确定,对于特征 j j j 我们要估计 K K K 个类别的均值和方差,所以特征 j j j 的参数共有 2 K 2K 2K个。
综上
朴素贝叶斯分类器可以表示为:
y = arg max c k P ( Y = c k ) ∏ j P ( X ( j ) = x ( j ) ∣ Y = c k ) y = \mathop{\arg\max}_{c_k} P(Y = c_k) \prod_j P(X^{(j)} = x^{(j)} \mid Y = c_k) y=argmaxckP(Y=ck)j∏P(X(j)=x(j)∣Y=ck)
实现
实现的时候会遇到数值问题,在上面的条件概率连乘中,如果有几个概率值很小,它们的连乘就会导致下溢,解决方案就是将其改写为连加的形式。
首先,我们的目标是:
y = arg max c k P ( Y = c k ) ∏ j P ( X ( j ) = x ( j ) ∣ Y = c k ) y = \mathop{\arg\max}_{c_k} P(Y = c_k) \prod_j P(X^{(j)} = x^{(j)} \mid Y = c_k) y=argmaxckP(Y=ck)j∏P(X(j)=x(j)∣Y=ck)
比较这 K K K 个数值的大小,然后取最大的那个数对应的 k k k。
为了解决可能出现的下溢问题,我们对上面的式子取对数,因为是对 K K K 项都取对数,不会改变单调性,所以取对数是不影响它们之间的大小关系的。
那目标就变成了:
y = arg max c k [ log P ( Y = c k ) ∏ j P ( X ( j ) = x ( j ) ∣ Y = c k ) ] = arg max c k [ log P ( y = c k ) + ∑ j log P ( X ( j ) = x ( j ) ∣ Y = c k ) ] \begin{aligned} y &= \mathop{\arg\max}{c_k} \big[ \log^{ \ P(Y = c_k) \prod_j P(X^{(j)} = x^{(j)} \mid Y = c_k)} \big] \\ &= \mathop{\arg\max}{c_k} \big[ \log^{ \ P(y = c_k)} + \sum_j \log^{ \ P(X^{(j)} = x^{(j)} \mid Y = c_k)} \big] \end{aligned} y=argmaxck[log P(Y=ck)∏jP(X(j)=x(j)∣Y=ck)]=argmaxck[log P(y=ck)+j∑log P(X(j)=x(j)∣Y=ck)]
在求条件概率的时候,也进行变换:
log P ( X ( j ) = x ( j ) ∣ Y = c k ) = log [ 1 2 π σ c k , j 2 exp ( − ( a j l − μ c k , j ) 2 2 σ c k , j 2 ) ] = log 1 2 π σ c k , j 2 + log exp ( − ( a j l − μ c k , j ) 2 2 σ c k , j 2 ) = − 1 2 log 2 π σ c k , j 2 − 1 2 ( a j l − μ c k , j ) 2 σ c k , j 2 \begin{aligned} \log^{ \ P(X^{(j)} = x^{(j)} \mid Y = c_k)} &= \log^{ \ \bigg[\frac{1}{\sqrt{2 \pi \sigma^2_{c_k,j}}} \exp{\bigg(- \frac{(a_{jl} - \mu_{c_k,j})^2}{2 \sigma^2_{c_k,j}}\bigg)}\bigg]}\\ &= \log^{ \frac{1}{\sqrt{2 \pi \sigma^2_{c_k,j}}} } + \log^{ \exp{\bigg(- \frac{(a_{jl} - \mu_{c_k,j})^2}{2 \sigma^2_{c_k,j}}\bigg)} }\\ &= - \frac{1}{2} \log^{2 \pi \sigma^2_{c_k,j}} - \frac{1}{2} \frac{(a_{jl} - \mu_{c_k,j})^2}{\sigma^2_{c_k,j}} \end{aligned} log P(X(j)=x(j)∣Y=ck)=log [2πσck,j2 1exp(−2σck,j2(ajl−μck,j)2)]=log2πσck,j2 1+logexp(−2σck,j2(ajl−μck,j)2)=−21log2πσck,j2−21σck,j2(ajl−μck,j)2
所以,高斯朴素贝叶斯就可以变形为:
y = arg max c k [ log P ( y = c k ) + ∑ j ( − 1 2 log 2 π σ c k , j 2 − 1 2 ( a j l − μ c k , j ) 2 σ c k , j 2 ) ] y = \mathop{\arg\max}{c_k} \bigg[ \log^{ \ P(y = c_k)} + \sum_j \big( - \frac{1}{2} \log^{2 \pi \sigma^2{c_k,j}} - \frac{1}{2} \frac{(a_{jl} - \mu_{c_k,j})^2}{\sigma^2_{c_k,j}} \big) \bigg] y=argmaxck[log P(y=ck)+j∑(−21log2πσck,j2−21σck,j2(ajl−μck,j)2)]
上式就是我们需要求的,我们要求出 K K K 个值,然后求最大的那个对应的 k k k。
1. 导入数据集
python
import pandas as pd
import numpy as np
data = pd.read_csv('./breast-cancer.csv')
data = data.replace({'B': 1, 'M': -1})
data = data.values
data_x = data[:,2:]
data_y = data[:,1:2].ravel()
2. 划分数据集
python
from sklearn.model_selection import train_test_split
trainX, testX, trainY, testY = train_test_split(data_x, data_y, test_size = 0.4, random_state = 32)
3. 实现高斯朴素贝叶斯
接下来我们开始实现高斯朴素贝叶斯,我们以类的形式实现这个高斯朴素贝叶斯。因为朴素贝叶斯是懒惰学习,所以这个模型只有在预测的时候,会进行大量的运算。
python
class myGaussianNB:
'''
处理连续特征的高斯朴素贝叶斯
'''
def __init__(self):
'''
初始化四个字典
self.label_mapping 类标记 与 下标(int)
self.probability_of_y 类标记 与 先验概率(float)
self.mean 类标记 与 均值(np.ndarray)
self.var 类标记 与 方差(np.ndarray)
'''
self.label_mapping = dict()
self.probability_of_y = dict()
self.mean = dict()
self.var = dict()
def _clear(self):
'''
为了防止一个实例反复的调用fit方法,我们需要每次调用fit前,将之前学习到的参数删除掉
'''
self.label_mapping.clear()
self.probability_of_y.clear()
self.mean.clear()
self.var.clear()
def fit(self, trainX, trainY):
'''
这里,我们要根据trainY内的类标记,针对每类,计算这类的先验概率,以及这类训练样本每个特征的均值和方差
Parameters
----------
trainX: np.ndarray, 训练样本的特征, 维度:(样本数, 特征数)
trainY: np.ndarray, 训练样本的标记, 维度:(样本数, )
'''
# 先调用_clear
self._clear()
# 获取类标记
labels = np.unique(trainY)
# 添加类标记与下标的映射关系
self.label_mapping = {label: index for index, label in enumerate(labels)}
# 遍历每个类
for label in labels:
# 取出为label这类的所有训练样本,存为 x
x = trainX[trainY == label, :]
# 计算先验概率,用 x 的样本个数除以训练样本总个数,存储到 self.probability_of_y 中,键为 label,值为先验概率
self.probability_of_y[label] = len(x) / len(trainY)
# 对 x 的每列求均值,使用 keepdims = True 保持维度,存储到 self.mean 中,键为 label,值为每列的均值组成的一个二维 np.ndarray
self.mean[label] = np.mean(x, axis=0, keepdims=True)
# 这句话是debug用的,如果不满足下面的条件,会直接跳出
assert self.mean[label].shape == (1, trainX.shape[1])
# 对 x 的每列求方差,使用 keepdims = True 保持维度,存储到 self.var 中,键为 label,值为每列的方差组成的一个二维 np.ndarray
self.var[label] = np.var(x, axis=0, keepdims=True)
# debug
assert self.var[label].shape == (1, trainX.shape[1])
# 平滑,因为方差在公式的分母部分,我们要加一个很小的数,防止除以0
self.var[label] += 1e-9 * np.var(trainX, axis = 0).max()
def predict(self, testX):
'''
给定测试样本,预测测试样本的类标记,这里我们要实现化简后的公式
Parameters
----------
testX: np.ndarray, 测试的特征, 维度:(测试样本数, 特征数)
Returns
----------
prediction: np.ndarray, 预测结果, 维度:(测试样本数, )
'''
# 初始化一个空矩阵 results,存储每个样本属于每个类的概率,维度是 (测试样本数,类别数),每行表示一个样本,每列表示一个特征
results = np.empty((testX.shape[0], len(self.probability_of_y)))
# 初始化一个列表 labels,按 self.label_mapping 的映射关系存储所有的标记,一会儿会在下面的循环内部完成存储
labels = [0] * len(self.probability_of_y)
# 遍历当前的类,label为类标记,index为下标,我们将每个样本预测出来的这个 label 的概率,存到 results 中的第 index 列
for label, index in self.label_mapping.items():
# 先验概率存为 py
py = self.probability_of_y[label]
# 使用变换后的公式,计算所有特征的条件概率之和,存为sum_of_conditional_probability
sum_of_conditional_probability = -0.5 * np.sum(np.log(2 * np.pi * self.var[label])) \
- 0.5 * np.sum(((testX - self.mean[label]) ** 2) / self.var[label], axis=1)
# debug
assert sum_of_conditional_probability.shape == (len(testX), )
# 使用变换后的公式,将 条件概率 与 log先验概率 相加,存为result,维度应该是 (测试样本数, )
result = sum_of_conditional_probability + np.log(py)
# debug
assert result.shape == (len(testX), )
# 将所有测试样本属于当前这类的概率,存入到results中
results[:, index] = result
# 将当前的label,按index顺序放入到labels中
labels[index] = label
# 将labels转换为np.ndarray
np_labels = np.array(labels)
# 循环结束后,就计算出了给定测试样本,当前样本属于这类的概率的近似值,存放在了results中,每行对应一个样本,每列对应一个特征
# 我们要求每行的最大值对应的下标,也就是求每个样本,概率值最大的那个下标是什么,结果存入max_prob_index中
max_prob_index = np.argmax(results, axis=1)
# debug
assert max_prob_index.shape == (len(testX), )
# 现在得到了每个样本最大概率对应的下标,我们需要把这个下标变成 np_labels 中的标记
# 使用上面小技巧中的第五点求解
prediction = np_labels[max_prob_index]
# debug
assert prediction.shape == (len(testX), )
# 返回预测结果
return prediction
python
# 测试样例
from sklearn.metrics import accuracy_score
model2 = myGaussianNB()
model2.fit(trainX, trainY)
accuracy_score(testY, model2.predict(testX)) # 0.9254385964912281
4. 计算其他的指标
python
# YOUR CODE HERE
prediction2 = model2.predict(testX)
precision2 = precision_score(testY, prediction2)
recall2 = recall_score(testY, prediction2)
f2 = f1_score(testY, prediction2)
print("查准率:",precision2)
print("查全率:",recall2)
print("f1值:",f2)
实验3.:实现带有拉普拉斯修正的朴素贝叶斯
实验内容:
- 叙述拉普拉斯修正的作用
- 使用给定的数据集
- 给出实现的代码,要有详细的注释
- 给出模型评价指标的结果
1. 叙述拉普拉斯修正的作用
回答:
在朴素贝叶斯分类器中,拉普拉斯修正主要用于解决概率估计中的零概率问题。当在训练数据中出现一个特征值而在测试数据中没有相应特征值时,朴素贝叶斯分类器的条件概率计算中可能会出现零概率,这会导致整个后验概率为零,影响分类器的性能。
拉普拉斯修正通过在概率估计的分子和分母中添加一个小的常数,解决了零概率的问题,确保每个特征值都有一个非零的概率。
2. 拉普拉斯修正的具体介绍
在训练集中总共的分类数,用 N 表示;di 属性可能的取值数用 Ni 表示,因此原来的先验概率 P© 的计算公式由:
P© = Dc / D
被拉普拉斯修正为
P© = (Dc + 1) / (D + N)
类的条件概率由P(xi|c) = Dc,xi / Dc
被拉普拉斯修正为
P(xi|c) = (Dc,xi + 1) / (Dc + Ni)
3. 数据集的介绍
Balance Scale Data Set
离散特征的三分类数据集
Attribute Information:
1. Class Name: 3 (L(左边重), B(天平平衡), R(右边重))
2. Left-Weight: 5 (1, 2, 3, 4, 5)
3. Left-Distance: 5 (1, 2, 3, 4, 5)
4. Right-Weight: 5 (1, 2, 3, 4, 5)
5. Right-Distance: 5 (1, 2, 3, 4, 5)
4. 导入数据集及处理
python
import numpy as np
data = np.loadtxt("./balance-scale.data", str, unpack = True)
print(data)
#数据处理:分类结果数值化,B->0, L->-1, R->1
dicts = {'R': 1, 'L': -1, 'B': 0}
# print(dicts['R'])
results = []
for i in data:
tmp = []
for j in i:
if j == ',':
continue
if j == 'B' or j == 'R' or j == 'L':
tmp.append(dicts[j])
else:
tmp.append(int(j))
results.append(tmp)
data = np.array(results) # 处理后的数据集
datax = data[ : , 1 : ]
datay = data[ : , 0]
print(datax.shape)
print(datay.shape)
5. 划分数据集
测试集30%,训练集70%
python
from sklearn.model_selection import train_test_split
trainX, testX, trainY, testY = train_test_split(datax, datay, test_size = 0.3, random_state = 32)
6. 实现带拉普拉斯修正的朴素贝叶斯
python
class myGaussianNB:
def __init__(self):
'''
初始化四个字典
self.label_mapping 类标记 与 下标(int)
self.probability_of_y 类标记 与 先验概率(float)
self.probability_of_attributes 类标记 与 每个属性的类条件概率
'''
# your code
self.label_mapping = dict()
self.probability_of_y = dict()
self.probability_of_attributes = dict()
self.mean_of_attributes = dict()
def _clear(self):
'''
为了防止一个实例反复的调用fit方法,我们需要每次调用fit前,将之前学习到的参数删除掉
'''
# your code
self.label_mapping.clear()
self.probability_of_y.clear()
self.probability_of_attributes.clear()
def fit(self, trainX, trainY):
self._clear()
# 获取类标
unique_labels, counts = np.unique(trainY, return_counts=True)
total_samples = len(trainY)
# 遍历每个类
for label, count in zip(unique_labels, counts):
# 类标映射下标
self.label_mapping[label] = len(self.label_mapping)
# 先验概率
self.probability_of_y[label] = count / total_samples
label_indices = np.where(trainY == label)[0]
label_data = trainX[label_indices, :]
# 计算每个特征的频率
feature_frequencies = []
for feature_values in label_data.T:
unique_values, value_counts = np.unique(feature_values, return_counts=True)
feature_frequency = dict(zip(unique_values, value_counts / count))
feature_frequencies.append(feature_frequency)
self.mean_of_attributes[label] = feature_frequencies
def predict(self, testX):
predictions = []
for sample in testX:
max_probability = float('-inf')
predicted_label = None
# 遍历当前类, 预测概率
for label, index in self.label_mapping.items():
prior_prob = np.log(self.probability_of_y[label])
likelihood = 0
# 计算类条件概率
for feature, value in enumerate(sample):
if value in self.mean_of_attributes[label][feature]:
likelihood += np.log(self.mean_of_attributes[label][feature][value])
else:
likelihood += float('-inf')
# 条件概率加 log(先验概率)
posterior_prob = prior_prob + likelihood
if posterior_prob > max_probability:
max_probability = posterior_prob
predicted_label = label
predictions.append(predicted_label)
# lebels转换为np.ndarray
return np.array(predictions)
7. 模型评价指标的计算
python
from sklearn.metrics import accuracy_score
from sklearn.metrics import precision_score
from sklearn.metrics import recall_score
from sklearn.metrics import f1_score
model = myGaussianNB()
model.fit(trainX, trainY)
prediction = model.predict(testX)
# 多分类任务 your code
acc3 = accuracy_score(testY, prediction)
precision3 = precision_score(testY, prediction, average='weighted')
recall3 = recall_score(testY, prediction, average='weighted')
f3 = f1_score(testY, prediction, average= 'weighted')
print("精度:", acc3)
print("查准率:",precision2)
print("查全率:",recall2)
print("f1值:",f2)