利用朴素贝叶斯实现森林分类,不明白朴素贝叶斯的同学推荐去看看西瓜书,本文的实现方法和书上一致。PS:转载请注明本文链接。
1.数据集下载地址
2.相关资源
我的另一篇博文是利用BP神经网络实现相同任务,有兴趣的可以去看看。
3.代码运行结果
先放一个代码运行完的结果,证明这个代码是可以运行的。
4.实现代码
# 导入包
import pandas as pd
import numpy as np
import time
def load_datasets(filepath):
"""
功能:
读取csv文件数据并进行分割,分割为特征集和标签集
输入参数:
filepath - 数据文件路径
返回值:
feature - 特征集,一个m*n矩阵,m为样本数,n为特征维度
label - 标签集,一个m*1的矩阵,m为样本数,1为标签维度,值为0,1,2,3
"""
dataset = pd.read_csv(filepath) # 读取csv文件
dataset['class'] = dataset['class'].map({"s ": 0, "h ": 1, "d ": 2, "o ": 3}) # 将字符转换为数值,注意字符后面的空格不能去掉,这是一个坑
label = dataset.loc[:, ['class']] # 读取标签,数据类型为DataFrame
label = np.array(label) # 转换为array
feature = dataset.iloc[:, 1:dataset.shape[1]] # 将特征数据分割出来
feature = np.array(feature) # 转换为array
return feature, label # (198, 27) (198, 1)
def random_dataset(train_x, train_y, test_x, test_y, seed):
"""
功能:
打乱训练集和测试集数据,使数据同分布
输入参数:
train_x - 训练特征集,一个m1*n矩阵,m1为训练样本数,n为特征维度
train_y - 训练标签集,一个m1*1阵,m1为训练样本数
test_x - 测试特征集,一个m2*n矩阵,m2为测试样本数,n为特征维度
test_y - 测试标签集,一个m2*1矩阵,m2为测试样本数
seed - 程序随机种子
返回值:
new_train_x - 打乱后的训练特征集,一个m1*n矩阵,m1为训练样本数,n为特征维度
new_train_y - 打乱后的训练标签集,一个m1*1阵,m1为训练样本数
new_test_x - 打乱后的测试特征集,一个m2*n矩阵,m2为测试样本数,n为特征维度
new_test_y - 打乱后的测试标签集,一个m2*1矩阵,m2为测试样本数
"""
if seed > 0:
np.random.seed(seed) # 随机种子大于0,就打乱数据集,设置随机种子
m = train_x.shape[0] # 训练样本数
new_x = np.concatenate((train_x, test_x), axis=0) # 将特征集进行拼接
new_y = np.concatenate((train_y, test_y), axis=0) # 将标签集进行拼接
per = np.random.permutation(new_x.shape[0]) # 打乱特征集行号
new_x = new_x[per, :] # 获取打乱后的特征数据
new_y = new_y[per, :] # 获取打乱后的标签数据
new_train_x, new_test_x = np.vsplit(new_x, [m]) # 按原训练样本数进行分割
new_train_y, new_test_y = np.vsplit(new_y, [m]) # 按原训练样本数进行分割
else:
new_train_x, new_train_y, new_test_x, new_test_y = train_x, train_y, test_x, test_y # 随机种子小于零就不打乱数据集
return new_train_x, new_train_y, new_test_x, new_test_y
def get_class_prior_probability(labels):
"""
功能:
计算类先验概率,频率代替概率
输入参数:
labels - 标签集,m*1矩阵,m为样本数
返回值:
calss_prior_probability - 各类先验概率,字典
"""
labels = labels.T[0, :].tolist() # 转换为list类型
labelset = set(labels) # 去重
class_prior_probability = {} # 存入label的概率
for label in labelset:
class_prior_probability = labels.count(label) / float(len(labels)) # 类先验概率公式:Pi = count(yi) / count(Y)
return class_prior_probability
def get_class_feature_mean_std(features, labels):
"""
功能:
计算类条件概率,求各特征的高斯密度函数中的均值和标准差
输入参数:
features - 特征集,m*n矩阵,m为样本数,n为特征维数
labels - 标签集,m*1矩阵,m为样本数
返回值:
mean_std_dict - c*2*n均值和标准差矩阵字典,c为类别数,2为均值和标准差,n为特征维数,键为类别
"""
# 特征集按类分割
labels_set = set(labels.T[0, :].tolist()) # 提取标签集中不重复元素
features_dict = {} # 特征集分类字典
for label in labels_set:
index = np.where(labels == label) # 找到标签集中index元素的所有索引
features_dict = features[index, :][0, :, :] # 将对应样本的特征矩阵存入字典
# 计算均值和标准差
mean_std_dict = {} # 各类均值和标准差字典
for key in features_dict.keys():
features_split = features_dict[key] # 取出各类特征矩阵
mean_std = np.zeros((2, features.shape[1])) # 初始化均值和标准差矩阵
for index in range(features_split.shape[1]):
mean_std[0][index] = np.mean(features_split[:, index]) # 求均值
mean_std[1][index] = np.std(features_split[:, index]) # 求标准差
mean_std_dict[key] = mean_std # 将均值和标准差存入字典
return mean_std_dict
def get_class_posterior_probability(features, class_prior_probability, mean_std_dict):
"""
功能:
计算类后验概率
输入参数:
features - 特征集,m*n矩阵,m为样本数,n为特征维数
class_prior_probability - 各类先验概率,列表
mean_std_dict - c*2*n均值和标准差矩阵字典,c为类别数,2为均值和标准差,n为特征维数,键为类别
返回值:
cpp_dict - c*m*1类后验概率矩阵字典,键为类别,c为类别数,m为样本数
"""
cpp_dict = {} # 类后验概率字典
for key in mean_std_dict.keys():
mean_std = mean_std_dict[key] # 取出第k类均值和标准差矩阵
# 计算样本联合密度函数
fx = (1 / (np.sqrt(2 * np.pi) * mean_std[1, :])) * np.exp(
-((features - mean_std[0, :]) ** 2) / (2 * mean_std[1, :] ** 2)) # 计算样本特征高斯密度
ccp = np.ones((fx.shape[0], 1)) # 初始化类条件概率
for index in range(fx.shape[1]):
ccp = ccp * (fx[:, index].reshape(ccp.shape[0], -1)) # 类条件概率等于样本联合密度函数,联合密度函数为各边缘密度函数之积
# 计算类后验概率
cpp = class_prior_probability[key] * ccp
cpp_dict[key] = cpp
return cpp_dict
def evaluation(cpp_dict, labels):
"""
功能:
评估预测结果
输入参数:
cpp_dict - c*m*1类后验概率矩阵字典,键为类别,c为类别数,m为样本数
labels - 标签集,一个m*1的矩阵,m为样本数,1为标签维度,值为0,1,2,3
返回值:
acc - 准确率
class_pre - 预测类别
"""
cpp = np.zeros((cpp_dict[0].shape[0], len(cpp_dict.keys()))) # 零初始化类后验概率矩阵
for key in cpp_dict.keys():
cpp[:, key] = tuple(cpp_dict[key]) # 将类后验概率字典读入矩阵
cpp_one_hot = list(map(lambda x: x == max(x), cpp)) * np.ones(shape=cpp.shape) # 为方便计算准确率,将类后验概率进行one-hot编码
labels_one_hot = np.eye(4)[labels].reshape(cpp_one_hot.shape[0], 4) # 为方便计算准确率,将标签进行one-hot编码
acc = np.sum(cpp_one_hot * labels_one_hot) / cpp_one_hot.shape[0] # 计算准确率
class_pre = pd.Series(cpp_one_hot.argmax(axis=1).tolist()).map({0: 's', 1: 'h', 2: 'd', 3: 'o'}) # 将数字类别标签转化为字符类别标签
return acc, class_pre
if __name__ == '__main__':
start = time.time() # 记录当前时间
# 训练,测试文件路径
TrainingFilePath = 'training.csv'
TestingFilePath = 'testing.csv'
# 加载数据
train_x, train_y = load_datasets(TrainingFilePath)
test_x, test_y = load_datasets(TestingFilePath)
# 打乱数据集,使训练集和测试集同分布
# seed = [0,1] average_acc = 0.8642,seed = 9 average_acc = 0.8738
# 经过初步测试,打乱数据集对平均准确率基本无影响(一般会使训练准确率下降,测试准确率上升)
# train_x, train_y, test_x, test_y = random_dataset(train_x, train_y, test_x, test_y, 9)
# 计算类先验概率
class_prior_probability = get_class_prior_probability(train_y)
# 计算训练集各类均值和标准差
mean_std_dict = get_class_feature_mean_std(train_x, train_y)
print("****************************训练集评估****************************")
# 计算样本类后验概率
train_cpp_dict = get_class_posterior_probability(train_x, class_prior_probability, mean_std_dict)
# 计算预测准确率和预测类别,进行评估
train_acc, train_class_pre = evaluation(train_cpp_dict, train_y)
print("训练集准确率:" + str(train_acc))
# print("训练集预测类别:\n" + str(train_class_pre))
print("****************************测试集评估****************************")
# 计算样本类后验概率
test_cpp_dict = get_class_posterior_probability(test_x, class_prior_probability, mean_std_dict)
# 计算预测准确率和预测类别,进行评估
test_acc, test_class_pre = evaluation(test_cpp_dict, test_y)
print("测试集准确率:" + str(test_acc))
# print("测试集预测类别:\n" + str(test_class_pre))
print("*****************************总体评估*****************************")
average_acc = train_acc * train_x.shape[0] / (train_x.shape[0] + test_x.shape[0]) \
+ test_acc * test_x.shape[0] / (train_x.shape[0] + test_x.shape[0])
print("平均准确率:" + str(average_acc))
print("程序运行时间为:" + str(time.time() - start))