6. 朴素贝叶斯算法实现垃圾分类实战详解

1 介绍

分类预测中,以概率论作为基础的算法较少,而朴素贝叶斯便是其中一种,它实现简单、预测分类的效率还很高,因此使用频率也很高。

由于它基于概率论,因此这里首先来了解一下概率论的基础。

如果事件A发送的概率表示为P(A)P(A),事件B发生的概率为P(B)P(B),那么事件A、B同时发生的概率我们就可以表示为P(AB)P(AB)

在该基础之上,如果我想要求当事件B发生之后A发生的概率为多少,就被称为条件概率,其计算公式为:P(AB)=P(AB)P(B)P(A \mid B)=\frac{P(AB)}{P(B)}

有了B事件发生之后A事件发生的概率公式,那么如果求P(BA)P(B|A)呢?也就是在A事件发生之后的B事件发生的概率?

这便是贝叶斯定理,其推导过程如下:

P(BA)=P(AB)P(A)P(B \mid A)=\frac{P(AB)}{P(A)}

P(AB)=P(AB)×P(B)P(AB)=P(A\mid B)\times P(B)

结合上面两个式子得到完整的贝叶斯定理:

P(BA)=P(AB)P(A)=P(AB)×P(B)P(A)P(B \mid A) = \frac{P(AB)}{P(A)}=\frac{P(A \mid B)\times P(B)}{P(A)}

其中P(A)P(A)P(B)P(B) 这种已知的概率,称为先验概率,它是通过过往经验和分析得到的概率,比如抛硬币我们会认为其概率就是0.5。

当事件发生之后求的反向概率,则被称为后验概率,比如上式中的P(BA)P(B \mid A) 就是通过P(A)P(A)P(B)P(B)先验概率求出来的反向条件概率。

而朴素贝叶斯的原理就是将贝叶斯原理与条件独立结合而成的算法:

P(类别 \mid 特征) = \frac{P(特征 \mid 类别)\times P(类别)}{P(特征)}

相当于就是将A换为了特征、B换为了类别。

那么我们现在就可以通过这个公式求出当特征概率已知的情况下,它类别概率是什么,这不正好满足我们分类的需求嘛!

其中右边类别的概率、特征的概率、特征在类别下的概率都是已知的。

朴素贝叶斯中的朴素,含义就是假设预测的各个属性之间相互独立、每个属性独立的对分类结果产生影响。

比如判断一朵花的类型有叶长、花瓣数量两个特征,那么前面我们预测的方式都是结合这两者一起来预测花朵的类型,而在朴素贝叶斯中,就认为这两个特征是独立的影响类别判断、而不会将其结合在一起判断。

这样做使得其变得简单而简洁,但自然的,在某些情况下就会牺牲一定的分类准确度。

2 朴素贝叶斯算法实现

有了上面的基础,下面我们就可以来介绍朴素贝叶斯算法的python代码实现逻辑了。

流程如下:

  1. 假设X=\left\{x_1,x_2, \dots ,x_n\right\}为训练数据,而xix_i是训练数据的特征值。
  2. 假设Y=\left\{y_1,y_2,\dots,y_m \right\}为类别集合
  3. 计算每种类别在特征值条件下的概率:P(y_1\mid x),P(y_2\mid x),\dots,P(y_m\mid x)
  4. 寻找P(y_1\mid x),P(y_2\mid x),\dots,P(y_m\mid x)中最大的概率P(ykx)P(y_k\mid x),则x就属于类别yky_k

而根据前面朴素贝叶斯算法原理,这里比较所有的P(yix)P(y_i\mid x)大小,实际上比较的是P(类别_i \mid 特征),由于根据上面的转换公式,它等于:\frac{P(特征 \mid 类别)\times P(类别)}{P(特征)}

所有的P(特征)分母值都相同,所以相当于就是比较分子P(特征 \mid 类别)\times P(类别)的乘积大小。

那么如何计算得到这两个值呢?这就需要用到极大似然估计法贝叶斯估计法了。

为了更加直观的看到效果,这里先给出一组测试数据:

def gen_data():
    # 生成示例数据
    data = {
        "x": ["g", "g", "r", "r", "b", "g", "g", "r", "b", "b", "g","r", "g", "r", "b"],
        "y": ["m", "s", "l", "s", "m", "s", "m", "s", "m", "l", "l", "s", "m", "m", "l"],
        "labels": ["A", "A", "A", "A","A", "A", "A", "A", "B", "B", "B", "B", "B", "B", "B"],
    }
    return data

这里的测试数据中,x、y为两个特征值,也就是X={x,y}X=\{x,y\}。labels则是一个类别,也就是Y={labels}Y=\{labels\}

2.1 极大似然估计

所谓极大似然估计,就是根据过往经验,推断出最有可能造成某个结果的参数值。

比如A箱子里面有999个白球、一个黑球,B箱子里面有999个黑球、一个白球。

那么当我说我抽出了要给白球时,你自然而然的就会认为我是从A箱子里面抽出来的,因为A箱子里面抽出白球的概率实在是太大了。

回到这里,我们求P(类别)时,可以认为它的概率就是它在训练数据中所有类别的占比,比如这里训练数据中的labels为其中一个类别,由于数据总共有15条,其中A占8条,B占7条,那么就能求出各自的概率分别为815\frac{8}{15}715\frac{7}{15}

此时对应的python代码为:

def get_P_labels(labels):
    labels = list(labels)  # 转换为 list 类型
    P_label = {}  # 设置空字典用于存入 label 的概率
    for label in labels:
        # 统计 label 标签在标签集中出现的次数再除以总长度
        P_label[label] = labels.count(label) / float(
            len(labels)
        )  # p = count(y) / count(Y)
    return P_label

而对于P(特征 \mid 类别)来说,要求的实际上就是在某种类别情况下,某个特征出现的概率。

比如属于A标签的8个数据中,对于X特征来说,g就占了4个,所以对于A标签、X特征来说,g的概率就是48\frac{4}{8}

对应的python代码示例求A标签三个特征值的概率如下:

def get_A_X_fea_lab(data):
    ret={
        'r':0,
        'g':0,
        'b':0
    }
    for i in range(0,8):
        ret[data['x'][i]]+=1
    ret['r']/=8
    ret['g']/=8
    ret['b']/=8
    return ret

ret=get_A_X_fea_lab(data)
print(ret)

结果:

image.png

2.2 贝叶斯估计

在最大似然估计中,一旦某个类别缺少某个特征,就会出现概率为0的情况。

比如当上方A标签内恰好没有X特征的b特征值,那么在b特征下A的概率就直接成为了0,着显然偏离的很厉害,毕竟这些信息都应该成为分类的标准。

而解决这样问题的方式就是采用贝叶斯估计,其底层逻辑其实就是为每个特征的特征值都加上下限,保证其不可能为0。

比如还是A标签的8个X特征数据中,g占了4个,按最大似然估计值就是48\frac{4}{8},那么按贝叶斯估计就是4+18+13=511\frac{4+1}{8+1*3}=\frac{5}{11}

这里分子加的1可以用变量λ\lambda表示,此时分母加的131*3就变成了λk\lambda*k,这里的k实际上就是X特征内值的类型个数有三个:r、g、b,所以值为3。

这便是贝叶斯估计,它可以保证所有概率计算结果都不可能为0,当λ\lambda的值为0时,就是最大似然估计,一般我们取λ\lambda的值为1,此时又被称为拉普拉斯平滑

此时可以将上面的oython代码改写为:

def get_A_X_fea_lab(data):
    ret={
        'r':0,
        'g':0,
        'b':0
    }
    for i in range(0,8):
        ret[data['x'][i]]+=1
    ret['r']=(ret['r']+1)/(8+3)
    ret['g']=(ret['g']+1)/(8+3)
    ret['b']=(ret['b']+1)/(8+3)
    return ret

ret=get_A_X_fea_lab(data)
print(ret)

2.3 常见模型

在实际数据中,我们可以根据特征的数据类型不同,使用不同的模型来计算先验概率,主要分为:多项式模型、伯努利模型、高斯模型。

其中多项式模型常用于特征值离散的情况,比如上面的示例中特征值就是离散的。

伯努利与多项式模型一样,也适用于离散模型,但不同之处在于,此时每个特征的取值只能为1或0。

最后高斯模型则是用于处理连续的特征变量,因为一旦连续,就代表其类型无限多。

比如体温范围可能就是一个连续的值,那么我们就需要离散化处理,比如将36.1~36.2区间作为一个散点值,此时就又回到了上面的极大似然估计,一旦不存在某个区间的体温,那么就会导致其概率为0、从而影响分类,即使采用贝叶斯估计,也会导致其所加的值没有现实意义、仍无法表达“连续性趋势”与“邻近值的相似性”。

高斯模型就可以很好的解决这个问题:

P(xiy=ck)=12πσk,i2exp((xiμk,i)22σk,i2) P(x_i \mid y=c_k) = \frac{1}{\sqrt{2\pi\sigma_{k,i}^2}} \exp\left( -\frac{(x_i - \mu_{k,i})^2}{2\sigma_{k,i}^2} \right)

  • xix_i:第 i 个特征的值;
  • y=cky=c_k:第 k 个类别;
  • μk,i\mu_{k,i}:特征 xix_i 在类别 ckc_k 下的均值;
  • σk,i2\sigma_{k,i}^2:特征 xix_i 在类别 ckc_k 下的方差。

高斯模型假设这些连续的特征数据是服从高斯分布的,那么将对应的值代入该公式中,我们就能直接得到该值对应的概率。

3 垃圾邮件分类

最后,根据上面所学内容,我们来实战一下垃圾邮件的分类,数据集可以在这里下载:email_data

解压后存在两个文件夹,其中data目录下为数据、full目录下为索引:

image.png

这里的spam代表垃圾邮件,ham代表正常邮件,后面的路径便是对应的邮件数据文件。

因此我们可以用0代表垃圾文件、1代表正常文件,用于后面的分类预测。

但由于数据量比较大,总共有6万多条,所以这里为了加快速度,只取随机1000条数据进行试验。

3.1 数据预处理

首先读出数据、随机取1000行:

import pandas as pd

pd.set_option("future.no_silent_downcasting", True)
data = pd.read_table(
    "email_data/full/index", sep=r"\s+", header=None, names=["label", "path"]
)
# 将spam和ham替换为0和1
data["label"] = data["label"].replace(["spam", "ham"], [0, 1])
# 将path中的".."替换为"email_data"
data["path"] = data["path"].replace(regex=r"\.\.", value="email_data")
# 随机取出1000行数据
data = data.sample(n=1000, random_state=12)
print(data.head())
print(data.count())

为了方便读取文件,这里不仅仅将"spam"、"ham"更换为了0和1,还将后面的路径更换为了实际的目录email_data。

效果如下:

image.png

有了数据集之后,下一步我们需要去除掉邮件中的某些字符:非中文字符,比如标点符号、英文字符、数字、网站链接等等。

以及还要去除掉停用词,可以点击这里下载:stopwords,所谓停用词,指的是非实意词、没有实际作用的词语,解决不好理解的可以下载这个文件、看看里面的内容你大概就知道什么意思了。

作者:余识
全部文章:0
会员文章:0
总阅读量:0
c/c++pythonrustJavaScriptwindowslinux