实验内容:
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()
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
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)
实验内容:
我们要实现一个可以处理连续特征的,服从高斯分布的朴素贝叶斯分类器
给定训练集 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) 。
先验概率的求解很简单,只要统计训练集中类别 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
求解这个条件概率比较复杂,这里我们要假设特征之间相互独立,可得
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 个特征。
这样,复杂的条件概率就转换为了多个特征条件概率的乘积。
因为我们处理的特征都是连续型特征,一般我们假设这些特征服从正态分布。
当 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,j21exp(−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,j21exp(−2σck,j2(ajl−μck,j)2)]=log2πσck,j21+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。
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()
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)
接下来我们开始实现高斯朴素贝叶斯,我们以类的形式实现这个高斯朴素贝叶斯。因为朴素贝叶斯是懒惰学习,所以这个模型只有在预测的时候,会进行大量的运算。
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
# 测试样例
from sklearn.metrics import accuracy_score
model2 = myGaussianNB()
model2.fit(trainX, trainY)
accuracy_score(testY, model2.predict(testX)) # 0.9254385964912281
# 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)
实验内容:
回答:
在朴素贝叶斯分类器中,拉普拉斯修正主要用于解决概率估计中的零概率问题。当在训练数据中出现一个特征值而在测试数据中没有相应特征值时,朴素贝叶斯分类器的条件概率计算中可能会出现零概率,这会导致整个后验概率为零,影响分类器的性能。
拉普拉斯修正通过在概率估计的分子和分母中添加一个小的常数,解决了零概率的问题,确保每个特征值都有一个非零的概率。
在训练集中总共的分类数,用 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)
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)
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)
from sklearn.model_selection import train_test_split
trainX, testX, trainY, testY = train_test_split(datax, datay, test_size = 0.3, random_state = 32)
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)
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)