机器学习实验-贝叶斯分类器

山东大学软件学院机器学习基础课程实验报告

一、前期准备

鸢尾花数据集

iris以鸢尾花的特征作为数据来源,常用在分类操作中。该数据集由3种不同类型的鸢尾花的各50个样本数据构成。其中的一个种类与另外两个种类是线性可分离的,后两个种类是非线性可分离的。

该数据集包含了4个属性:

& Sepal.Length(花萼长度),单位是cm;

& Sepal.Width(花萼宽度),单位是cm;

& Petal.Length(花瓣长度),单位是cm;

& Petal.Width(花瓣宽度),单位是cm;

种类:Iris Setosa(山鸢尾)、Iris Versicolour(杂色鸢尾),以及Iris Virginica(维吉尼亚鸢尾)

数据集可以从网上下载

需要手动分割训练集和数据集。我将五分之四的数据集作为训练集对贝叶斯分类器进行训练,将剩余数据集作为测试集,采用训练好的贝叶斯分类器模型对其进行预测。训练集与测试集的数据是随机选取的。

贝叶斯分类器

下面只是写一个目录,只把用到的朴素贝叶斯分类器的核心思想记录了一下,其它详见西瓜书。

贝叶斯准则
基于最小错误率的贝叶斯决策
极大似然估计
朴素贝叶斯分类器

简化:“属性条件独立性假设”

分类过程:

  1. 估计类先验概率
    • 拉普拉斯修正 “平滑”
  2. 估计类条件概率
    • 离散属性:拉普拉斯修正 “平滑”
    • 连续属性:高斯函数
  3. 估计后验概率
    • 后验概率 = 类先验概率*所有的类条件概率
    • 后验概率最大的即为所属类别
    • 常通过取对数的方式将“连乘”转化为“连加”,以避免数值下溢
半朴素贝叶斯分类器

二、实验过程

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绘图工具

①以Speciesx轴,使用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.Lengthx轴,以Petal.Lengthy轴绘制回归图,对比以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))

五、心得体会

​ 通过本次实验,我对贝叶斯分类器有了更深刻的了解,清楚了朴素贝叶斯分类的基本流程,加深了对机器学习基本方法的理解与应用,提高了自身的代码技能和分析并解决问题的能力,为之后的实验和学习奠定了基础,受益匪浅。

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值