山东大学软件学院机器学习基础课程实验报告
文章目录
一、前期准备
鸢尾花数据集
iris以鸢尾花的特征作为数据来源,常用在分类操作中。该数据集由3种不同类型的鸢尾花的各50个样本数据构成。其中的一个种类与另外两个种类是线性可分离的,后两个种类是非线性可分离的。
该数据集包含了4个属性:
& Sepal.Length(花萼长度),单位是cm;
& Sepal.Width(花萼宽度),单位是cm;
& Petal.Length(花瓣长度),单位是cm;
& Petal.Width(花瓣宽度),单位是cm;
种类:Iris Setosa(山鸢尾)、Iris Versicolour(杂色鸢尾),以及Iris Virginica(维吉尼亚鸢尾)
数据集可以从网上下载
需要手动分割训练集和数据集。我将五分之四的数据集作为训练集对贝叶斯分类器进行训练,将剩余数据集作为测试集,采用训练好的贝叶斯分类器模型对其进行预测。训练集与测试集的数据是随机选取的。
贝叶斯分类器
下面只是写一个目录,只把用到的朴素贝叶斯分类器的核心思想记录了一下,其它详见西瓜书。
贝叶斯准则
基于最小错误率的贝叶斯决策
极大似然估计
朴素贝叶斯分类器
简化:“属性条件独立性假设”
分类过程:
- 估计类先验概率
- 拉普拉斯修正 “平滑”
- 估计类条件概率
- 离散属性:拉普拉斯修正 “平滑”
- 连续属性:高斯函数
- 估计后验概率
- 后验概率 = 类先验概率*所有的类条件概率
- 后验概率最大的即为所属类别
- 常通过取对数的方式将“连乘”转化为“连加”,以避免数值下溢
半朴素贝叶斯分类器
二、实验过程
1.分割数据集
数据集是从网上下载的.csv
文件,直接放在了项目里。
首先以4 : 1(训练集:测试集)的比例分割数据集:
因为每个类别都有50条数据,我初始是按照每个类别随机取10条数据作为测试集的想法做的,代码如下:
def split_dataset(data: pd.DataFrame):
"""
分割训练集和测试集
:param data: iris dataset
:return: trainSet, testSet
"""
# 随机采样 每个品种各取1/5作为测试集
test_index = random.sample(range(50), 10)
test_index.extend(random.sample(range(50, 100), 10))
test_index.extend(random.sample(range(100, 150), 10))
print("测试集包括:")
print(test_index)
testSet = data.iloc[test_index]
train_index = list(range(150))
for index in test_index:
train_index.remove(index)
trainSet = data.iloc[train_index]
return trainSet, testSet
后来考虑了一下,为了使随机采样更具随机性,我选择了直接随机取样。把随机采样改为:
test_index = random.sample(range(150), 30)
2.计算类先验概率
# 类先验概率
# 拉普拉斯修正
num = train.shape[0]
prior_0 = np.log((train.loc[train.Species == Species[0]].shape[0]+1)/(num+3))
prior_1 = np.log((train.loc[train.Species == Species[1]].shape[0]+1)/(num+3))
prior_2 = np.log((train.loc[train.Species == Species[2]].shape[0]+1)/(num+3))
在这里,采用了拉普拉斯修正,进行“平滑”处理。
同时,为了避免数值下溢,对数值进行取对数处理。
使用train.loc[train.Species == Species[0]]
获得训练集中品种为"setosa"
的数据,使用.shape[0]
取得行数。
3.计算所有列的类条件概率
首先,因为鸢尾花数据集都是连续数据,所以要用高斯函数计算类条件概率,函数代码如下:
def continuous_attr_p(value, mean, var):
"""
:param value: xi
:param mean: 均值
:param var: 方差
:return: 类条件概率p
"""
p = 1 / ((2 * PI * var) ** 0.5) * math.exp(-(value - mean)**2 / (2 * var))
# print("p:")
# print(p)
return p
计算所有类条件概率的思路:根据物种类型,取出训练集中同属于一类的数据。对于这些数据中的每一列,计算它们的均值和方差,代入测试集数据x该列的值,计算类条件概率,判断这些数据属于哪个物种类型,将每列的类条件概率存放在定义好的字典中。属于"setosa"就存放在storage0中,以此类推。为了避免数值下溢,对数值进行取对数处理。
Species = ["setosa", "versicolor", "virginica"]
定义字典的代码:
storage0 = {"Sepal.Length": 0, "Sepal.Width": 0, "Petal.Length": 0, "Petal.Width": 0}
storage1 = {"Sepal.Length": 0, "Sepal.Width": 0, "Petal.Length": 0, "Petal.Width": 0}
storage2 = {"Sepal.Length": 0, "Sepal.Width": 0, "Petal.Length": 0, "Petal.Width": 0}
该思路实现的代码为:
for spes in Species:
train_dic = train.loc[train.Species == spes]
for column in train_dic.columns:
if column != 'Species': # 去除预测标签的影响
mean = np.mean(train_dic[column])
var = np.var(train_dic[column])
p = np.log(continuous_attr_p(x[column], mean, var))
if spes == Species[0]:
storage0[column] = p
elif spes == Species[1]:
storage1[column] = p
elif spes == Species[2]:
storage2[column] = p
4.计算后验概率
定义一个后验概率数组,存放三个品种的后验概率。
post_p = np.zeros(3, np.float64)
post_p[0] = prior_0
post_p[1] = prior_1
post_p[2] = prior_2
因为先验概率和所有的类条件概率都取了对数,所以后验概率=类先验概率+所有的类条件概率之和。
for column in train.columns:
if column != 'Species':
post_p[0] = post_p[0] + storage0[column]
post_p[1] = post_p[1] + storage1[column]
post_p[2] = post_p[2] + storage2[column]
返回三者中的最大值的索引,索引与Species数组相对应,得到的即为该测试数据所属的类别:
post_p_i = np.argmax(post_p, axis=0)
print("post_p_i", post_p_i)
return post_p_i
主函数
贝叶斯分类器的实现
Data = pd.read_csv('iris.csv')
# 划分数据集
train_set, test_set = split_dataset(Data)
# 存放分类器对测试集的分类结果
classify_result = []
for index, row in test_set.iterrows():
classify_result.append(Species[classifier(row, train_set)])
# 将分类结果添加到测试集中
test_set.insert(test_set.shape[1], 'forecast', classify_result)
可视化方式
使用seaborn
绘图工具
①以Species
为x
轴,使用sns.countplot()
绘制条形图,显示训练集中各物种的个数:
colors = sns.color_palette('pastel')
sns.countplot(x="Species", palette='pastel', data=train_set)
plt.show()
②统计测试集中分类正确和分类错误的数目:
# 分类正确的数目
df1 = test_set[['Species']]
df2 = test_set[['forecast']]
df = pd.concat([df1, df2], axis=1)
df['result'] = np.where(df['Species'] == df['forecast'], 'true', 'false')
colors = sns.color_palette('pastel')
sns.countplot(x="result", palette='pastel', data=df)
plt.show()
③以Sepal.Length
为x
轴,以Petal.Length
为y
轴绘制回归图,对比以Species
列为分类标准和以forecast
列为分类标准的图像的差异。
sns.lmplot(x='Sepal.Length', y='Petal.Length', hue='Species',
data=test_set, markers=['*', 'o', '+'])
plt.show()
sns.lmplot(x='Sepal.Length', y='Petal.Length', hue='forecast',
data=test_set, markers=['*', 'o', '+'])
plt.show()
评估方式
采用准确率:分类正确的样本数/总样本数进行评估
TP = 0
for index, row in comparison.iterrows():
if row["Species"] == row['forecast']:
TP += 1
Accuracy = TP / 30
print('正确率: {:.2%}'.format(Accuracy))
三、输出截屏
注:下列输出结果为使用jupyter
运行后得到的结果。
1.测试集选取
随机选取的测试集标号:
2.训练集各物种个数
可视化显示训练集中各物种的个数:
3.各测试数据计算出的后验概率与种类选取
输出后验概率:post_p[0]为setosa
物种的后验概率,post_p[1]为versicolor
物种的后验概率,post_p[2]为virginica
物种的后验概率。取三者的最大值作post_p_i,即预测的类别,0为setosa
物种,1为versicolor
物种,2为virginica
物种。
下图仅为部分数据:
4.测试集正确分类与贝叶斯分类结果对比
①统计测试集中分类正确和分类错误的数目:
②
应该得到的正确分类:
实际上得到的结果:
5.正确率
四、全部代码
import numpy as np
import random
import math
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
# from torcheval.metrics import functional as TMF
PI = math.pi
Species = ["setosa", "versicolor", "virginica"]
def split_dataset(data: pd.DataFrame):
"""
分割训练集和测试集
:param data: iris dataset
:return: trainSet, testSet
"""
# 随机采样
test_index = random.sample(range(150), 30)
print("测试集包括:")
print(test_index)
testSet = data.iloc[test_index]
train_index = list(range(150))
for index in test_index:
train_index.remove(index)
trainSet = data.iloc[train_index]
return trainSet, testSet
def continuous_attr_p(value, mean, var):
"""
:param value: xi
:param mean: 均值
:param var: 方差
:return: 类条件概率p
"""
p = 1 / ((2 * PI * var) ** 0.5) * math.exp(-(value - mean) ** 2 / (2 * var))
# print("p:")
# print(p)
return p
storage0 = {"Sepal.Length": 0, "Sepal.Width": 0, "Petal.Length": 0, "Petal.Width": 0}
storage1 = {"Sepal.Length": 0, "Sepal.Width": 0, "Petal.Length": 0, "Petal.Width": 0}
storage2 = {"Sepal.Length": 0, "Sepal.Width": 0, "Petal.Length": 0, "Petal.Width": 0}
def classifier(x, train: pd.DataFrame):
"""
朴素贝叶斯分类
鸢尾花数据集都是连续数据
:param x: 测试集的一条数据
:param train: 训练集模型
:return: 预测结果
"""
# 类先验概率
# 拉普拉斯修正
num = train.shape[0]
prior_0 = np.log((train.loc[train.Species == Species[0]].shape[0] + 1) / (num + 3))
prior_1 = np.log((train.loc[train.Species == Species[1]].shape[0] + 1) / (num + 3))
prior_2 = np.log((train.loc[train.Species == Species[2]].shape[0] + 1) / (num + 3))
# 计算所有列的类条件概率
for spes in Species:
train_dic = train.loc[train.Species == spes]
for column in train_dic.columns:
if column != 'Species': # 去除预测标签的影响
mean = np.mean(train_dic[column])
var = np.var(train_dic[column])
p = np.log(continuous_attr_p(x[column], mean, var))
if spes == Species[0]:
storage0[column] = p
elif spes == Species[1]:
storage1[column] = p
elif spes == Species[2]:
storage2[column] = p
post_p = np.zeros(3, np.float64)
post_p[0] = prior_0
post_p[1] = prior_1
post_p[2] = prior_2
for column in train.columns:
if column != 'Species':
post_p[0] = post_p[0] + storage0[column]
post_p[1] = post_p[1] + storage1[column]
post_p[2] = post_p[2] + storage2[column]
print("post_p[0]", post_p[0])
print("post_p[1]", post_p[1])
print("post_p[2]", post_p[2])
post_p_i = np.argmax(post_p, axis=0)
print("post_p_i", post_p_i)
return post_p_i
if __name__ == '__main__':
Data = pd.read_csv('iris.csv')
# 划分数据集
train_set, test_set = split_dataset(Data)
# 显示训练集物种分布情况
# a=[]
# for index, row in train_set.iterrows():
# if row["Species"] == Species[0]:
# a.append(1)
# elif row["Species"] == Species[1]:
# a.append(2)
# else:
# a.append(3)
# train_set.insert(train_set.shape[1], 'digit',a)
colors = sns.color_palette('pastel')
sns.countplot(x="Species", palette='pastel', data=train_set)
plt.show()
# 存放分类器对测试集的分类结果
classify_result = []
for index, row in test_set.iterrows():
classify_result.append(Species[classifier(row, train_set)])
# 将分类结果添加到测试集中
test_set.insert(test_set.shape[1], 'forecast', classify_result)
sns.lmplot(x='Sepal.Length', y='Petal.Length', hue='Species',
data=test_set, markers=['*', 'o', '+'])
plt.show()
sns.lmplot(x='Sepal.Length', y='Petal.Length', hue='forecast',
data=test_set, markers=['*', 'o', '+'])
plt.show()
# 分类正确的数目
df1 = test_set[['Species']]
df2 = test_set[['forecast']]
df = pd.concat([df1, df2], axis=1)
df['result'] = np.where(df['Species'] == df['forecast'], 'true', 'false')
colors = sns.color_palette('pastel')
sns.countplot(x="result", palette='pastel', data=df)
plt.show()
TP = 0
for index, row in test_set.iterrows():
if row["Species"] == row['forecast']:
TP += 1
Accuracy = TP / 30
print('正确率: {:.2%}'.format(Accuracy))
五、心得体会
通过本次实验,我对贝叶斯分类器有了更深刻的了解,清楚了朴素贝叶斯分类的基本流程,加深了对机器学习基本方法的理解与应用,提高了自身的代码技能和分析并解决问题的能力,为之后的实验和学习奠定了基础,受益匪浅。