python学智能算法(十三)|机器学习朴素贝叶斯方法进阶-简单二元分类

引言

前述学习进程中,已经学习了拉普拉斯平滑公式计算条件概率的简单应用,文章链接为:
python学智能算法(十二)|机器学习朴素贝叶斯方法初步-拉普拉斯平滑计算条件概率

在此基础上,今天更进一步,联系一个简单二元分类的项目。

项目介绍

简单二元分类,就是把数据分成两种样本,完成区分即可。

参数定义

开展项目之前,先来定义几个参数:

先验概率P(y):

P ( y ) = ∑ j = 1 j = n y j ∑ y P(y)=\frac{\sum_{j=1}^{j=n}y_{j}}{\sum_{}^{}y_{}} P(y)=∑y∑j=1j=nyj这里的yj代表特定样本,y代表任何样本,先验概率的意思就是特定样本yj占所有样本的比例。

条件概率P(xj|y):

P ( x j ∣ y ) = ∑ y 1 j m x j + 1 ∑ i = 1 n y i + y . s h a p e [ 1 ] P(x_{j}|y)=\frac{\sum_{y_1}^{j_m}x_{j}+1}{\sum_{i=1}^{n}y_{i}+y.shape[1]} P(xj∣y)=∑i=1nyi+y.shape[1]∑y1jmxj+1这里的xj代表特定样本中特征xj出现的次数,yi代表特定样本,y.shape[1]是指每一个样本所拥有的特征数。条件概率使用了拉普拉斯平滑公式。

具体的,如果给定矩阵X和标签数组y:

假设有3个样本,2个特征

样本数就是行数,特征数是列数

X = np.array([

1, 0\], # 样本1 \[1, 1\], # 样本2 \[0, 1\] # 样本3 \]) y = np.array(\[0, 1, 1\])

对于y=0,只有样本[1,0],对应的:

先验概率P(y=0)=1/3,y=0表示标签为0的第一个样本,这里计算的先验概率就是标签为0的第一个样本占所有样本的比例。

条件概率P(1|y)=(1+1)/(1+2)=0.67,P(1|y)表示标签为0的第一个样本中,特征1所占的条件概率,由于只有一个样本,每个样本的特征数都是2,所以分母上是1+2,这样计算得到的条件概率就是0.67。

可以这样理解,矩阵的行数就是样本数,矩阵的列数就是特征数。条件概率只在属于特定标签的范围内计算。

有时候标签也会被称作类别,即特定类别中计算条件概率。

拉普拉斯平滑公式

拉普拉斯平滑公式定义的目的是解决传统的条件概率计算结果为0的问题。

比如,对于正在计算的条件概率P(xj|y),在标签或者类别y下特征xj如果从来没有出现,那如果按照下式计算结果就是0:
P ( x j ∣ y ) = ∑ y 1 y m x j ∑ i = 1 n y i P(x_{j}|y)=\frac{\sum_{y_1}^{y_m}x_{j}}{\sum_{i=1}^{n}y_{i}} P(xj∣y)=∑i=1nyi∑y1ymxj

具体公式推导以后再开展学习,此处只需要记住条件概率P(xj|y)的定义采用了拉普拉斯平滑公式。

对数后验概率

对数后验概率:
l n P ( y ∣ x ) = l n P ( y ) + ∑ j = 1 n x j ⋅ P ( x j ∣ y ) lnP(y|x)=lnP(y)+{\sum_{j=1}^{n}x_{j}·P(x_{j}|y)} lnP(y∣x)=lnP(y)+j=1∑nxj⋅P(xj∣y)对数后验概率实际上可以细分为两部分:

对数先验概率: l n P ( x j ∣ y ) = l n P ( y ) lnP(x_{j}|y)=lnP(y) lnP(xj∣y)=lnP(y)

对数条件概率之和: s u m x j P ( x j ∣ y ) = ∑ j = 1 n x j ⋅ P ( x j ∣ y ) sumx_{j}P(x_{j}|y)={\sum_{j=1}^{n}x_{j}·P(x_{j}|y)} sumxjP(xj∣y)=j=1∑nxj⋅P(xj∣y)

所以,对数后验概率实际上是对数先验概率和对数条件概率之和。

具体的,如果给定矩阵X和标签数组y:

假设有3个样本,2个特征

样本数就是行数,特征数是列数

X = np.array([

1, 0\], # 样本1 \[1, 1\], # 样本2 \[0, 1\] # 样本3 \]) y = np.array(\[0, 1, 1\])

对于y=1,有2个样本[1,1]和[0,1],对应的先验概率P(y=1)=2/3,y=1表示标签为1的第2个样本,这里计算的先验概率就是标签为1的第一个样本占所有样本的比例。

条件概率P(1|y)=(1+1)/(1+2)=0.67,P(1|y)表示标签为1的2个样本中,特征1所占的条件概率。由于有2个样本,每个样本都有特征1,按照拉普拉斯平滑公式先计算条件概率,首先得到分子[1+1,1+1+1=[2,3],此外由于每个样本的特征数都是2,所以分母上是2+2求和后的[4,4],这样计算得到的条件概率就是[2/4,3/4]=[0.5,0.75]。

接下来就可以计算:

对数先验概率:lnP(1|y)=ln0.67=-0.4005

对数条件概率:

lnP(x1=1|y=1)=ln(0.5)=-0.6931,

lnP(x2=1|y=1)=ln(0.75)=-0.2877

对数后验概率要对每一个样本都开展:

样本[1,0],lnP(1|y)+sumx_{j}P(x_{j}|y)=-0.4005+1*-0.6931+0*-0.2877=-1.0936;

样本[1,1],lnP(1|y)+sumx_{j}P(x_{j}|y)=-0.4005+1*-0.6931+1*-0.2877=-1.3813;

样本[0,1],lnP(1|y)+sumx_{j}P(x_{j}|y)=-0.4005+0*-0.6931+1*-0.2877=-0.6882。

对数后验概率需要将每一个样本都代入计算,不管样本属于哪一个类别。

代码

首先引入库:

python 复制代码
# 引入 numpy模块
import numpy as np

然后需要定义一个大类:

python 复制代码
# 定义类
class NaiveBayes:

这个类里面分了很多子函数,先看第一个:

python 复制代码
    def __init__(self):
        """初始化朴素贝叶斯分类器"""
        # classes参数用于存储类别标签
        self.classes = None
        # prior参数用于存储存储先验概率 P(y)
        self.prior = None
        # conditional参数用于存储条件概率 P(x|y)
        self.conditional = None

这里的_ini_(self)函数定义了几个变量名,用于存储数据。

第二个函数:

python 复制代码
    # 定义子函数fit用于训练
    def fit(self, X, y):
        """
        训练朴素贝叶斯分类器
        X: 训练数据特征,形状为 [样本数, 特征数]
        y: 训练数据标签,形状为 [样本数]
        """
        # n_samples为样本X的行数,n_features为样本X的列数
        n_samples, n_features = X.shape
        # np.unique()是一个合并同类项的函数,classes可以获得y具体的类
        self.classes = np.unique(y)
        # n_classes存储了classes的类的具体数量
        n_classes = len(self.classes)

        # 初始化先验概率和条件概率
        # prior是一个纯0矩阵,矩阵为n_classes大小的一维数组
        self.prior = np.zeros(n_classes)
        # conditional是一个n_classes行n_features列的纯0矩阵
        self.conditional = np.zeros((n_classes, n_features))

此处定义的fit(self, X, y)函数用于训练,实际上此处还没有开始训练,依然是定义了大量的常数,唯一对输入变量进行作用的执行是使用np.unique(y)对标签数组y合并同类项。

然后第三个函数:

python 复制代码
       # 计算先验概率和条件概率
        # i代表枚举函数classes中各个类的位置索引,c代表具体的类别
        for i, c in enumerate(self.classes):
            # 计算属于类别 c 的样本
            # X_c存储的结果是True和False,当y中的数据等于c时,取True,否则取False
            X_c = X[y == c]
            print(f"类别 {c} 的样本:\n{X_c}")
            # 计算类别 c 的先验概率:类别 c 的样本数 / 总样本数
            # X_c.shape[0]是X_c的行数
            self.prior[i] = X_c.shape[0] / float(n_samples)
            print(f"self.prior {i} =:\n{self.prior[i]}")
            # 计算条件概率:每个特征在类别 c 下出现的频率
            self.conditional[i] = (X_c.sum(axis=0) + 1) / (X_c.shape[0] + n_features)  # 拉普拉斯平滑
            print(f"self.conditional{i} =:\n{self.conditional[i]}")

此处定义的i, c in enumerate(self.classes)函数使用for循环+枚举的形式获得了先验概率self.prior[i] = X_c.shape[0] / float(n_samples)和拉普拉斯平滑条件概率:self.conditional[i] = (X_c.sum(axis=0) + 1) / (X_c.shape[0] + n_features)

之后是第四个函数:

python 复制代码
   def predict(self, X):
        """
        对输入数据进行预测
        X: 待预测数据特征,形状为 [样本数, 特征数]
        返回预测结果,形状为 [样本数]
        """
        # 对每个样本计算后验概率,选择后验概率最大的类别
        return [self._predict(x) for x in X]

这里实际上只有一个输出和调用,调用了第五个子函数:

python 复制代码
   def _predict(self, x):
        """对单个样本进行预测"""
        # 计算每个类别的后验概率对数
        # posteriors是一个空矩阵,用于存储计算数据
        posteriors = []
        # for循环类别的位置索引和类别标签
        for i, c in enumerate(self.classes):
            # 先计算对数先验概率
            prior = np.log(self.prior[i])
            # 计算条件概率对数之和
            conditional = np.sum(np.log(self.conditional[i]) * x)
            # 获得对数后验概率
            posterior = prior + conditional
            # 顿出所有的对数后验概率
            posteriors.append(posterior)

        # 返回后验概率最大的类别索引
        return self.classes[np.argmax(posteriors)]

实际上在理解了前面的参数定义后,后半段会比较好理解,在每一个类别下,计算所有样本的对数后验概率,取最大的后验概率对应的类别为预测值。

之后就比较简单,输入训练参数和待预测数据:

python 复制代码
# 示例用法
if __name__ == "__main__":
    # 创建简单的训练数据
    X = np.array([[1, 1], [1, 0], [0, 1], [0, 0]])
    y = np.array([1, 1, 0, 0])

    # 初始化并训练模型
    nb = NaiveBayes()
    nb.fit(X, y)

    # 进行预测
    test_X = np.array([[1, 0], [0, 1]])
    predictions = nb.predict(test_X)

    print("预测结果:", predictions)  # 输出应该是 [1, 0]

完整代码为:

python 复制代码
# 引入 numpy模块
import numpy as np

# 定义类
class NaiveBayes:
    # 定义子函数__init__(self)存储数据
    def __init__(self):
        """初始化朴素贝叶斯分类器"""
        # classes参数用于存储类别标签
        self.classes = None
        # prior参数用于存储存储先验概率 P(y)
        self.prior = None
        # conditional参数用于存储存储条件概率 P(x|y)
        self.conditional = None

    # 定义子函数fit用于训练
    def fit(self, X, y):
        """
        训练朴素贝叶斯分类器
        X: 训练数据特征,形状为 [样本数, 特征数]
        y: 训练数据标签,形状为 [样本数]
        """
        # n_samples为样本X的行数,n_features为样本X的列数
        n_samples, n_features = X.shape
        # np.unique()是一个合并同类项的函数,classes可以获得y具体的类
        self.classes = np.unique(y)
        # n_classes存储了classes的类的具体数量
        n_classes = len(self.classes)

        # 初始化先验概率和条件概率
        # prior是一个纯0矩阵,矩阵为n_classes大小的一维数组
        self.prior = np.zeros(n_classes)
        # conditional是一个n_classes行n_features列的纯0矩阵
        self.conditional = np.zeros((n_classes, n_features))

        # 计算先验概率和条件概率
        # i代表枚举函数classes中各个类的位置索引,c代表具体的类别
        for i, c in enumerate(self.classes):
            # 计算属于类别 c 的样本
            # X_c存储的结果是True和False,当y中的数据等于c时,取True,否则取False
            X_c = X[y == c]
            print(f"类别 {c} 的样本:\n{X_c}")
            # 计算类别 c 的先验概率:类别 c 的样本数 / 总样本数
            # X_c.shape[0]是X_c的行数
            self.prior[i] = X_c.shape[0] / float(n_samples)
            print(f"self.prior {i} =:\n{self.prior[i]}")
            # 计算条件概率:每个特征在类别 c 下出现的频率
            self.conditional[i] = (X_c.sum(axis=0) + 1) / (X_c.shape[0] + n_features)  # 拉普拉斯平滑
            print(f"self.conditional{i} =:\n{self.conditional[i]}")

    def predict(self, X):
        """
        对输入数据进行预测
        X: 待预测数据特征,形状为 [样本数, 特征数]
        返回预测结果,形状为 [样本数]
        """
        # 对每个样本计算后验概率,选择后验概率最大的类别
        return [self._predict(x) for x in X]

    def _predict(self, x):
        """对单个样本进行预测"""
        # 计算每个类别的后验概率对数
        # posteriors是一个空矩阵,用于存储计算数据
        posteriors = []
        # for循环类别的位置索引和类别标签
        for i, c in enumerate(self.classes):
            # 先计算对数先验概率
            prior = np.log(self.prior[i])
            # 计算条件概率对数之和
            conditional = np.sum(np.log(self.conditional[i]) * x)
            # 获得对数后验概率
            posterior = prior + conditional
            # 顿出所有的对数后验概率
            posteriors.append(posterior)

        # 返回后验概率最大的类别索引
        return self.classes[np.argmax(posteriors)]


# 示例用法
if __name__ == "__main__":
    # 创建简单的训练数据
    X = np.array([[1, 1], [1, 0], [0, 1], [0, 0]])
    y = np.array([1, 1, 0, 0])

    # 初始化并训练模型
    nb = NaiveBayes()
    nb.fit(X, y)

    # 进行预测
    test_X = np.array([[1, 0], [0, 1]])
    predictions = nb.predict(test_X)

    print("预测结果:", predictions)  # 输出应该是 [1, 0]

实际输出为:

类别 0 的样本:

\[0 1

0 0\]

self.prior 0 =:

0.5

self.conditional0 =:

0.25 0.5

类别 1 的样本:

\[1 1

1 0\]

self.prior 1 =:

0.5

self.conditional1 =:

0.75 0.5

预测结果: [1, 0]

总结

学习了使用朴素贝叶斯方法实现简单二元分类的基本操作技巧。

相关推荐
周圣贤1 小时前
九尾狐编程语言新算法“超维时空演算体”
开发语言·算法
pianmian12 小时前
arcpy数据分析自动化(3)
python
CaracalTiger2 小时前
HTTP 协议的基本概念(请求/响应流程、状态码、Header、方法)问题解决方案大全
开发语言·网络·python·深度学习·网络协议·http·pip
随缘而动,随遇而安3 小时前
第八十二篇 大数据开发基础:树形数据结构深度解析与实战指南(附创新生活案例)
大数据·开发语言·数据结构
2401_888567003 小时前
Mac电脑-人工智能图像处理-降噪-Topaz Photo AI
图像处理·人工智能
phoenix@Capricornus3 小时前
主成分分析(PCA)例题——给定协方差矩阵
线性代数·矩阵
棱镜研途3 小时前
学习笔记丨数字信号处理(DSP)的应用——图像处理篇
图像处理·人工智能·信号处理
Web3_Daisy4 小时前
Solana 一键冷分仓机制解析:如何低成本实现代币控盘打散?
大数据·人工智能·web3·区块链
杭州泽沃电子科技有限公司4 小时前
母线槽接头过热隐患难防?在线测温方案实时守护电力安全
网络·人工智能·安全