朴素贝叶斯原理python实现以及垃圾邮件检测应用

朴素贝叶斯

前导知识

贝叶斯公式

P ( Y ∣ X ) = P ( X , Y ) P ( X ) = P ( X ∣ Y ) P ( Y ) P ( X ) P(Y|X) =\frac{P(X,Y)}{P(X)} =\frac{P(X|Y)P(Y)}{P(X)} P(YX)=P(X)P(X,Y)=P(X)P(XY)P(Y)

全概率公式

P ( X ) = P ( X ∣ Y 1 ) P ( Y 1 ) + P ( X ∣ Y 2 ) P ( Y 2 ) + . . . P ( X ∣ Y n ) P ( Y N ) = ∑ i = 1 n P ( X ∣ Y i ) P ( Y i ) P(X) =P(X|Y_1)P(Y_1)+P(X|Y_2)P(Y_2)+...P(X|Y_n)P(Y_N)=\sum_{i=1}^n{P(X|Y_i)}{P(Y_i)} P(X)=P(XY1)P(Y1)+P(XY2)P(Y2)+...P(XYn)P(YN)=i=1nP(XYi)P(Yi)

算法描述

定义

朴素贝叶斯法是基贝叶斯定理特征条件独立假设分类方法

就是一个分类方法

解决的问题

朴素贝叶斯还是解决的分类问题

我们有一个数据集 T T T如下
T = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . ( x N , y N ) } T=\{(x_1,y_1),(x_2,y_2),...(x_N,y_N)\} T={(x1,y1),(x2,y2),...(xN,yN)}
其中

  • x ∈ R n x\in{R^n} xRn ,即实例 x x x n n n维的向量

  • y ∈ { c 1 , c 2 , . . . , c k } y\in{\{c_1,c_2,...,c_k\}} y{c1,c2,...,ck}

我们现在给一个新的实例 x = ( x ( 1 ) , x ( 2 ) , . . . x ( n ) ) x=(x^{(1)},x^{(2)},...x^{(n)}) x=(x(1),x(2),...x(n))

那么该实例该归属哪类?

基本准则

朴素贝叶斯法的目是基于一个模型,对于给定的输入 x = ( x ( 1 ) , x ( 2 ) , . . . x ( n ) ) x=(x^{(1)},x^{(2)},...x^{(n)}) x=(x(1),x(2),...x(n)),利用贝叶斯定理求出后验概率 P ( Y = c k ∣ X = x ) P(Y=c_k|X=x) P(Y=ckX=x)最大的分类 c k c_k ck,最后将这个后验概率最大的分类 c k c_k ck当作是输入 x x x所属的类别

利用这个准则得到最终的决策函数,也就是分类器
f ( x ) = a r g m a x c k P ( Y = c k ∣ X = x ) f(x) =\underset{c_k}{argmax}P(Y=c_k|X=x) f(x)=ckargmaxP(Y=ckX=x)
所以朴素贝叶斯的准则就是后验概率最大化

后验概率最大化的含义

后验概率最大化能作为我们的基本准则是因为它和期望风险最小化是等价的,而期望风险最小化是机器学习的一个基本准则

其等价关系推导可参考:链接

这个等价关系不影响朴素贝叶斯的学习

基本方法

朴素贝叶斯的基本方法是利用样本的输入\输出,学习到 X X X Y Y Y的联合概率分布 P ( X , Y ) P(X,Y) P(X,Y),最后由联合概率分布求出后验概率 P ( Y ∣ X ) P(Y|X) P(YX),再使用这个后验概率分布对输入 X X X进行预测

为什么求出联合概率分布 P ( X , Y ) P(X,Y) P(X,Y)就可以求出 P ( Y ∣ X ) P(Y|X) P(YX)了呢?请看贝叶斯公式
P ( Y ∣ X ) = P ( X , Y ) P ( X ) = P ( X ∣ Y ) P ( Y ) P ( X ) P(Y|X) =\frac{P(X,Y)}{P(X)} =\frac{P(X|Y)P(Y)}{P(X)} P(YX)=P(X)P(X,Y)=P(X)P(XY)P(Y)

联合概率分布:包含多个条件同时成立的概率

条件概率:一个事件在另一个事件已经发生的情况下发生的概率

公式推导

根据贝叶斯公式我们有
P ( Y = c k ∣ X = x ) = P ( X = x ∣ Y = c k ) P ( Y = c k ) P ( X = x ) (1) P(Y=c_k|X=x)=\frac{P(X=x|Y=c_k)P(Y=c_k)}{P(X=x)}\tag{1} P(Y=ckX=x)=P(X=x)P(X=xY=ck)P(Y=ck)(1)

观察公式右边现在还不能直接通过样本可求,所以需要对其进行转换

分子部分变换

我们先看分子部分 P ( X = x ∣ Y = c k ) P ( Y = c k ) {P(X=x|Y=c_k)P(Y=c_k)} P(X=xY=ck)P(Y=ck),这部分是由求联合概率分布 P ( X = x , Y = c k ) P(X=x,Y=c_k) P(X=x,Y=ck)转化来的

其中 P ( Y = c k ) P(Y=c_k) P(Y=ck)是先验概率分布,表示每个类的概率,是可以很方便从样本中计算估计出来的
P ( Y = c k ) ,   k = 1 , 2... K (1.1) P(Y=c_k),\ k=1,2...K\tag{1.1} P(Y=ck), k=1,2...K(1.1)

P ( X = x ∣ Y = c k ) P(X=x|Y=c_k) P(X=xY=ck)是条件概率分布,表示对于给定的每个类,再给一个 X X X,他是 x x x的概率,原则上也是可以根据样本估计出来的,其中 X = x X=x X=x X ( 1 ) = x ( 1 ) , . . . X ( n ) = x ( n ) X^{(1)}=x^{(1)},...X^{(n)}=x^{(n)} X(1)=x(1),...X(n)=x(n)的简化,于是有下面等式
P ( X = x ∣ Y = c k ) = P ( X ( 1 ) = x ( 1 ) , . . . X ( n ) = x ( n ) ∣ Y = c k ) ,   k = 1 , 2... K (1.2) P(X=x|Y=c_k) = P(X^{(1)}=x^{(1)},...X^{(n)}=x^{(n)}|Y=c_k)\tag{1.2},\ k=1,2...K P(X=xY=ck)=P(X(1)=x(1),...X(n)=x(n)Y=ck), k=1,2...K(1.2)

Y = c k Y=c_k Y=ck的发生概率,再乘以在 Y = c k Y=c_k Y=ck条件下 X = x X=x X=x发生的概率,等于 Y = c k Y=c_k Y=ck X = x X=x X=x同时发生的概率,也就是联合概率

特征条件独立假设

为什么上面说 P ( X = x ∣ Y = c k ) P(X=x|Y=c_k) P(X=xY=ck)原则可算呢?

观察公式 ( 1.2 ) (1.2) (1.2),每个实例 x x x n n n个特征(维度),我们假设每个维度 x ( j ) x^{(j)} x(j) S j S_j Sj种可能的取值,那么 x x x就有 ∏ j = 1 n S j \prod_{j=1}^n{S_j} j=1nSj种取值(每个维度 S j S_j Sj种)

我们 c k c_k ck一共有 K K K种取值

所以概率 P ( X = x ∣ Y = c k ) P(X=x|Y=c_k) P(X=xY=ck)一共要计算出 K ∏ j = 1 n S j K\prod_{j=1}^n{S_j} Kj=1nSj种才能更好应对各种组合的输入X的预测

如果 n n n S j S_j Sj比较大,那么 K ∏ j = 1 n S j K\prod_{j=1}^n{S_j} Kj=1nSj就会非常大,这时候无论是计算量还是样本的需求量都特别大

所以朴素贝叶斯的朴素,就是做了一个特征条件独立假设,也就是假设实例 x x x的每个特征都是独立,这是一个非常强的假设

此时由概率论可知,当 a a a b b b相互独立的时候有
P ( a b ) = P ( a ) P ( b ) P ( a b ∣ c ) = P ( a ∣ c ) P ( b ∣ c ) P(ab) = P(a)P(b)\\ P(ab|c) =P(a|c)P(b|c) P(ab)=P(a)P(b)P(abc)=P(ac)P(bc)

则可将 P ( X = x ∣ Y = c k ) P(X=x|Y=c_k) P(X=xY=ck)做如下转换
P ( X = x ∣ Y = c k ) = P ( X ( 1 ) = x ( 1 ) , . . . X ( n ) = x ( n ) ∣ Y = c k ) = ∏ j = 1 n P ( X ( j ) = x ( j ) ∣ Y = c k ) (1.3) P(X=x|Y=c_k)=P(X^{(1)}=x^{(1)},...X^{(n)}=x^{(n)}|Y=c_k)=\prod_{j=1}^n{P(X^{(j)}=x^{(j)}|Y=c_k)}\tag{1.3} P(X=xY=ck)=P(X(1)=x(1),...X(n)=x(n)Y=ck)=j=1nP(X(j)=x(j)Y=ck)(1.3)
此时我们要求的概率种数由 K ∏ j = 1 n S j K\prod_{j=1}^n{S_j} Kj=1nSj变成了 K ∑ j = 1 n S j K\sum_{j=1}^n{S_j} Kj=1nSj种,大大减少

即使不假设,原则上也是可以求联合概率分布的,只是复杂度比较大。所以这个算法的可行性是有的

变换结果

经过分子的变换我们将公式 ( 1 ) (1) (1)变换如下
P ( Y = c k ∣ X = x ) = P ( Y = c k ) ∏ j = 1 n P ( X ( j ) = x ( j ) ∣ Y = c k ) P ( X = x ) (2) P(Y=c_k|X=x)=\frac{P(Y=c_k)\prod_{j=1}^n{P(X^{(j)}=x^{(j)}|Y=c_k)}}{P(X=x)}\tag{2} P(Y=ckX=x)=P(X=x)P(Y=ck)j=1nP(X(j)=x(j)Y=ck)(2)

分母部分变换

输入也是有概率的,也是可以从样本估算的,神奇不

由全概率公式我们可以将分母变换如下
P ( X = x ) = ∑ i = 1 K P ( X = x ∣ Y = c i ) P ( Y = c i ) (2.1) P(X=x)=\sum_{i=1}^KP(X=x|Y=c_i)P(Y=c_i)\tag{2.1} P(X=x)=i=1KP(X=xY=ci)P(Y=ci)(2.1)

理解:对于样本中任意一个数据,其类别为 c i c_i ci并且其实例为某个给定的 x x x的概率为 P ( X = x ∣ Y = c i ) P ( Y = c i ) P(X=x|Y=c_i)P(Y=c_i) P(X=xY=ci)P(Y=ci),那么这个任意数据的实例为这个给定的 x x x的概率为 ∑ i = 1 K P ( X = x ∣ Y = c i ) P ( Y = c i ) \sum_{i=1}^KP(X=x|Y=c_i)P(Y=c_i) i=1KP(X=xY=ci)P(Y=ci),从而估算出我们任意给一个数据其实例为给定的 x x x的概率为 ∑ i = 1 K P ( X = x ∣ Y = c i ) P ( Y = c i ) \sum_{i=1}^KP(X=x|Y=c_i)P(Y=c_i) i=1KP(X=xY=ci)P(Y=ci)

具体可以去理解一下全概率公式

再根据公式 ( 1.3 ) (1.3) (1.3),最终将分母转化如下
P ( X = x ) = ∑ i = 1 K P ( Y = c i ) ∏ j = 1 n P ( X ( j ) = x ( j ) ∣ Y = c i ) (2.2) P(X=x)=\sum_{i=1}^KP(Y=c_i)\prod_{j=1}^n{P(X^{(j)}=x^{(j)}|Y=c_i)}\tag{2.2} P(X=x)=i=1KP(Y=ci)j=1nP(X(j)=x(j)Y=ci)(2.2)

变换结果

最终变换得到的公式如下,此时等式右边所有参数可以通过样本估计
P ( Y = c k ∣ X = x ) = P ( Y = c k ) ∏ j = 1 n P ( X ( j ) = x ( j ) ∣ Y = c k ) ∑ i = 1 K P ( Y = c i ) ∏ j = 1 n P ( X ( j ) = x ( j ) ∣ Y = c i ) (3) P(Y=c_k|X=x)=\frac{P(Y=c_k)\prod_{j=1}^n{P(X^{(j)}=x^{(j)}|Y=c_k)}}{\sum_{i=1}^KP(Y=c_i)\prod_{j=1}^n{P(X^{(j)}=x^{(j)}|Y=c_i)}}\tag{3} P(Y=ckX=x)=i=1KP(Y=ci)j=1nP(X(j)=x(j)Y=ci)P(Y=ck)j=1nP(X(j)=x(j)Y=ck)(3)

分类器

我们已经得到了后验概率 P ( Y = c k ∣ X = x ) P(Y=c_k|X=x) P(Y=ckX=x)的基本公式 ( 3 ) (3) (3)了,但是要生成分类器,还有最后一步,就是基本准则中提到的选取使后验概率最大的 c k c_k ck作为输入所属的类别,于是我们得到分类器的基本形式
y = f ( x ) = a r g m a x c k P ( Y = c k ) ∏ j = 1 n P ( X ( j ) = x ( j ) ∣ Y = c k ) ∑ i = 1 K P ( Y = c i ) ∏ j = 1 n P ( X ( j ) = x ( j ) ∣ Y = c i ) (4) y=f(x)=\underset{c_k}{argmax}\frac{P(Y=c_k)\prod_{j=1}^n{P(X^{(j)}=x^{(j)}|Y=c_k)}}{\sum_{i=1}^KP(Y=c_i)\prod_{j=1}^n{P(X^{(j)}=x^{(j)}|Y=c_i)}}\tag{4} y=f(x)=ckargmaxi=1KP(Y=ci)j=1nP(X(j)=x(j)Y=ci)P(Y=ck)j=1nP(X(j)=x(j)Y=ck)(4)
注意到我公式(4)的分母,对于我们考虑的每个参数 c k c_k ck其计算结果都是一样的,因为它是把所有 c c c的取值计算结果做一个求和,而不像分子那样只和当前考虑的 c k c_k ck有关。

由于我么比较的是不同 c k c_k ck情况下等式右边的值的大小,所以可以直接不算分母,因为比较过程中可以约掉,则最终分类器如下
y = f ( x ) = a r g m a x c k P ( Y = c k ) ∏ j = 1 n P ( X ( j ) = x ( j ) ∣ Y = c k ) (6) y=f(x)=\underset{c_k}{argmax}{P(Y=c_k)\prod_{j=1}^n{P(X^{(j)}=x^{(j)}|Y=c_k)}}\tag{6} y=f(x)=ckargmaxP(Y=ck)j=1nP(X(j)=x(j)Y=ck)(6)

虽然我们最终不用真正求出后验概率,但是思想还是利用后验概率比较得出结果

分类器的理解:每个特征对一个实例的类别都有一个贡献,通过样本可以把贡献求出,最后根据所有特征对不同类别的贡献得出不同的计算结果

参数估计

所谓的参数估计就是通过样本估计先验概率 P ( Y = c k ) P(Y=c_k) P(Y=ck)以及条件概率 P ( X ( j ) = x ( j ) ∣ Y = c k ) P(X^{(j)}=x^{(j)}|Y=c_k) P(X(j)=x(j)Y=ck),从而计算公式(6)

估计先验概率

P ( Y = c k ) P(Y=c_k) P(Y=ck)概率很简单,设数据量为 N N N,利用指示函数 I I I
P ( Y = c k ) = ∑ i = 1 N ( I ( y i = c k ) ) N ,    k = 1 , 2 , . . . K (7) P(Y=c_k)=\frac{\sum_{i=1}^N(I(y_i=c_k))}{N},\ \ k=1,2,...K\tag{7} P(Y=ck)=Ni=1N(I(yi=ck)),  k=1,2,...K(7)

估计条件概率

对于 P ( X ( j ) = x ( j ) ∣ Y = c k ) P(X^{(j)}=x^{(j)}|Y=c_k) P(X(j)=x(j)Y=ck),我们依然可以得出一个估计的通式,后面将具体的 x x x带入即可

设第 j j j个特征 x ( j ) x^{(j)} x(j)可能取值一共有 S j S_j Sj种,并且其集合为 { a j 1 , a j 2 , . . . , a j S j } \{a_{j1},a_{j2},...,a_{jS_j}\} {aj1,aj2,...,ajSj}

则条件概率 P ( X ( j ) = a j l ∣ Y = c k ) P(X^{(j)}=a_{jl}|Y=c_k) P(X(j)=ajlY=ck)的估计为
P ( X ( j ) = a j l ∣ Y = c k ) = ∑ i = 1 N I ( x i ( j ) = a j l , y i = c k ) ∑ i = 1 N ( y i = c k ) j = 1 , 2 , . . . , n ; l = 1 , 2 , . . . S j ; k = 1 , 2 , . . . K (8) P(X^{(j)}=a_{jl}|Y=c_k)=\frac{\sum_{i=1}^NI(x_i^{(j)}=a_{jl},y_i=c_k)}{\sum_{i=1}^N(y_i=c_k)}\\ j=1,2,...,n;l=1,2,...S_j;k=1,2,...K\tag{8} P(X(j)=ajlY=ck)=i=1N(yi=ck)i=1NI(xi(j)=ajl,yi=ck)j=1,2,...,n;l=1,2,...Sj;k=1,2,...K(8)
类为 c k c_k ck并且其第 j j j个特征的特征值等于 a j l a_{jl} ajl的样本数除以类为 c k c_k ck的样本总数,(因为一个样本第 j j j个特征只有一个值,其实真正除以的是类为 c k c_k ck的第 j j j个特征特征值总数),即为在类为 c k c_k ck条件下,第 j j j个特征为 a j l a_{jl} ajl的概率

这里的条件概率是只针对一个特征的条件概率,不用考虑其他特征

在通式(8)求出来之后我们就可以针对特定的输入 x x x,对输入 x x x的的每一个特征求出条件概率

最终算法

  • **输入:**训练数据集 T = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . ( x N , y N ) } T=\{(x_1,y_1),(x_2,y_2),...(x_N,y_N)\} T={(x1,y1),(x2,y2),...(xN,yN)};实例 x x x

    其中

    • x i = ( x i ( 1 ) , x i ( 2 ) , . . . , x i ( n ) ) T x_i=(x_i^{(1)},x_i^{(2)},...,x_i^{(n)})^T xi=(xi(1),xi(2),...,xi(n))T
    • x i ( j ) x_i^{(j)} xi(j)是第 i i i个样本的第 j j j个特征,每个特征有 S j S_j Sj种取值, x i ( j ) ∈ { a j 1 , a j 2 , . . . a j S j } x_i^{(j)}\in\{a_{j1},a_{j2},...a_{jS_j}\} xi(j){aj1,aj2,...ajSj} a j l a_{jl} ajl是第 j j j个特征所有可能取值中第 l l l个值, j = 1 , 2 , . . . , n j=1,2,...,n j=1,2,...,n l = 1 , 2 , . . . , S j l=1,2,...,S_j l=1,2,...,Sj
    • y i ∈ c 1 , c 2 , . . . c K y_i\in{c_1,c_2,...c_K} yic1,c2,...cK
  • **输出:**实例 x x x的分类

  • 过程:

    1. 先根据样本计算出先验概率以及每个特征的条件概率

      得到样本时先计算一遍,后面就不用再计算了

      P ( Y = c k ) = ∑ i = 1 N ( I ( y i = c k ) ) N ,    k = 1 , 2 , . . . K P(Y=c_k)=\frac{\sum_{i=1}^N(I(y_i=c_k))}{N},\ \ k=1,2,...K P(Y=ck)=Ni=1N(I(yi=ck)),  k=1,2,...K

      P ( X ( j ) = a j l ∣ Y = c k ) = ∑ i = 1 N I ( x i ( j ) = a j l , y i = c k ) ∑ i = 1 N ( y i = c k ) j = 1 , 2 , . . . , n ; l = 1 , 2 , . . . S j ; k = 1 , 2 , . . . K P(X^{(j)}=a_{jl}|Y=c_k)=\frac{\sum_{i=1}^NI(x_i^{(j)}=a_{jl},y_i=c_k)}{\sum_{i=1}^N(y_i=c_k)}\\ j=1,2,...,n;l=1,2,...S_j;k=1,2,...K P(X(j)=ajlY=ck)=i=1N(yi=ck)i=1NI(xi(j)=ajl,yi=ck)j=1,2,...,n;l=1,2,...Sj;k=1,2,...K

    2. 对于给定的实例 x = ( x ( 1 ) , x ( 2 ) , . . . , x ( n ) ) T x=(x^{(1)},x^{(2)},...,x^{(n)})^T x=(x(1),x(2),...,x(n))T,根据1.计算的结果计算出
      P ( Y = c k ) ∏ j = 1 n P ( X ( j ) = x ( j ) ∣ Y = c k ) ,   k = 1 , 2 , . . . , K {P(Y=c_k)\prod_{j=1}^n{P(X^{(j)}=x^{(j)}|Y=c_k)}},\ k=1,2,...,K P(Y=ck)j=1nP(X(j)=x(j)Y=ck), k=1,2,...,K

    3. 确定实例 x x x的类
      y = f ( x ) = a r g m a x c k P ( Y = c k ) ∏ j = 1 n P ( X ( j ) = x ( j ) ∣ Y = c k ) y=f(x)=\underset{c_k}{argmax}{P(Y=c_k)\prod_{j=1}^n{P(X^{(j)}=x^{(j)}|Y=c_k)}} y=f(x)=ckargmaxP(Y=ck)j=1nP(X(j)=x(j)Y=ck)

举例

用下表的训练数据学习一个朴素贝叶斯的分类器并确定 x = ( 2 , S ) T x=(2,S)^T x=(2,S)T的类标 y y y

其中特征 X ( 1 ) X^{(1)} X(1)的取值集合为 A 1 = { 1 , 2 , 3 } A_1=\{1,2,3\} A1={1,2,3},特征 X ( 2 ) X^{(2)} X(2)的取值集合为 A 2 = { S , M , L } A2=\{S,M,L\} A2={S,M,L} Y ∈ { 1 , − 1 } Y\in\{1,-1\} Y{1,1}

为了表格便于查看,这里将样本竖着排列

123456789101112131415
X ( 1 ) X^{(1)} X(1)111112222233333
X ( 2 ) X^{(2)} X(2)SMMSSSMMLLLMMLL
Y Y Y-1-111-1-1-11111111-1

求先验概率
P ( Y = 1 ) = 9 15 p ( Y = − 1 ) = 6 15 P(Y=1)=\frac{9}{15}\\ p(Y=-1)=\frac{6}{15} P(Y=1)=159p(Y=1)=156
求条件概率

这里为了方便就不把所有条件概率都求出来了,只求需要使用的

P ( X ( 1 ) = 2 ∣ Y = 1 ) = 3 9 P ( X ( 2 ) = S ∣ Y = 1 ) = 1 9 P ( X ( 1 ) = 2 ∣ Y = − 1 ) = 2 6 P ( X ( 2 ) = S ∣ Y = − 1 ) = 3 6 \begin{aligned} P(X^{(1)}=2|Y=1)&=\frac{3}{9}\\ P(X^{(2)}=S|Y=1)&=\frac{1}{9}\\ P(X^{(1)}=2|Y=-1)&=\frac{2}{6}\\ P(X^{(2)}=S|Y=-1)&=\frac{3}{6}\\ \end{aligned} P(X(1)=2∣Y=1)P(X(2)=SY=1)P(X(1)=2∣Y=1)P(X(2)=SY=1)=93=91=62=63

利用给定的 x = ( 2 , S ) T x=(2,S)^T x=(2,S)T,针对每个 Y = c k Y=c_k Y=ck计算
P ( Y = 1 ) P ( X ( 1 ) = 2 ∣ Y = 1 ) P ( X ( 2 ) = S ∣ Y = 1 ) = 1 45 P ( Y = − 1 ) P ( X ( 1 ) = 2 ∣ Y = − 1 ) P ( X ( 2 ) = S ∣ Y = − 1 ) = 1 15 \begin{aligned} P(Y=1)P(X^{(1)}=2|Y=1)P(X^{(2)}=S|Y=1)&=\frac{1}{45}\\ P(Y=-1)P(X^{(1)}=2|Y=-1)P(X^{(2)}=S|Y=-1)&=\frac{1}{15} \end{aligned} P(Y=1)P(X(1)=2∣Y=1)P(X(2)=SY=1)P(Y=1)P(X(1)=2∣Y=1)P(X(2)=SY=1)=451=151
因为 Y = − 1 Y=-1 Y=1时计算结果最大,所以取 y = − 1 y=-1 y=1,即预测 x = ( 2 , S ) T x=(2,S)^T x=(2,S)T对应的类 y y y为-1

贝叶斯估计

我们观察一下公式
y = f ( x ) = a r g m a x c k P ( Y = c k ) ∏ j = 1 n P ( X ( j ) = x ( j ) ∣ Y = c k ) y=f(x)=\underset{c_k}{argmax}{P(Y=c_k)\prod_{j=1}^n{P(X^{(j)}=x^{(j)}|Y=c_k)}} y=f(x)=ckargmaxP(Y=ck)j=1nP(X(j)=x(j)Y=ck)
对于某个特征值 x ( j ) x^{(j)} x(j),如果这个 x ( j ) x^{(j)} x(j)在样本中没有观察到,那么有一个条件就会等于0,从而导致右边计算值就变成0

但只因这个特征没观察到就确定该实例的类为 c k c_k ck可能性为0,而否定其他特征的贡献,这不应该

这就是所谓的过拟合现象,所以我们在估计的时候要为随机变量的取值加上一个正数 λ > 0 \lambda>0 λ>0对其进行修正,这就是贝叶斯估计,如下
P λ ( X ( j ) = a j l ∣ Y = c k ) = ∑ i = 1 N I ( x i ( j ) = a j l , y i = c k ) + λ ∑ i = 1 N ( y i = c k ) + S j λ P_{\lambda}(X^{(j)}=a_{jl}|Y=c_k)=\frac{\sum_{i=1}^NI(x_i^{(j)}=a_{jl},y_i=c_k)+\lambda}{\sum_{i=1}^N(y_i=c_k)+S_j\lambda} Pλ(X(j)=ajlY=ck)=i=1N(yi=ck)+Sjλi=1NI(xi(j)=ajl,yi=ck)+λ
λ = 1 \lambda=1 λ=1时,称为拉普拉斯平滑,拉普拉斯平滑就是为了解决出现概率为0的情况

下面加 S j λ S_j\lambda Sjλ也是因为要使 ∑ l = 1 S j P ( X ( j ) = a j l ∣ Y = c k ) = 1 \sum_{l=1}^{S_j}P(X^{(j)}=a_{jl}|Y=c_k)=1 l=1SjP(X(j)=ajlY=ck)=1​,才能算一种概率分布;在 Y = c k Y=c_k Y=ck的条件下, X ( j ) X^{(j)} X(j)的取值就 S j S_j Sj种,那么每一种的概率都应该大于0并且加起来等于1

但是实际实现过程中, S j S_j Sj是根据样本得出的,所以这里加起来等于1不保证,但这不重要,我们的目的只是为了防止条件概率为0,求出的概率有点小误差也是能接受的

同样我们对先验概率也做修正,虽然先验概率不会出现0的情况,因为先验概率是不受输入 x x x的影响的
P λ ( Y = c k ) = ∑ i = 1 N I ( y i = c k ) + λ N + K λ P_\lambda(Y=c_k)=\frac{\sum_{i=1}^{N}I(y_i=c_k)+\lambda}{N+K\lambda} Pλ(Y=ck)=N+Kλi=1NI(yi=ck)+λ

python实现

构造一个贝叶斯分页器,其构造时传入训练数据和标签方可自动训练

用这个跑一下上述例子,也就是《统计学习方法》里面的朴素贝叶斯例子

from collections import Counter

import numpy as np
import pandas as pd


'''
朴素贝叶斯分类器
输出空间由构造函数的labels得出,所以不会预测出训练集不存在的类型
而输入空间由训练的values得出,如果预测的输入的某个特征值在values中不存在,则用拉普拉斯修正
训练过程中也用拉普拉斯训练
'''
class NaiveBayes:
    '''
    构造函数
    :param values: 训练实例
    :param values: 训练标签
    '''

    def __init__(self, values, labels):
        self.values = np.array(values)
        self.labels = np.array(labels)
        self.labels_space = set(self.labels)  # 不重复的标签值
        self.lam = 1  # 拉普拉斯修正
        self.train()  # 开始训练

    def train(self):
        # 变量初始化
        sample_len = self.values.shape[0]  # 样本数
        feature_len = self.values.shape[1]  # 特征数
        pri_p = {}  # 先验概率,key是类,value是值
        con_p = {}  # 条件概率,key是:(类别,特征,特征值)
        label_num_dict = dict(Counter(self.labels))  # 各种标签的数量
        self.label_num_dict = label_num_dict
        # 计算每个维度的取值总数,供拉普拉斯修正使用
        S = {}
        for j in range(feature_len):
            S[j] = len(set(self.values[:, j]))
        self.S = S

        # 经过修正的先验概率
        # 先计算数量再除以总数得到概率,避免精度损失过大
        for key in label_num_dict:
            pri_p[key] = (label_num_dict[key] + self.lam) / (sample_len + len(self.labels_space) * self.lam)

        # 计算条件概率,经过修正
        con_p_mol_dict = {}  # 某种类下某个特征某个特征值的数量
        for i in range(sample_len):
            for j in range(feature_len):
                key = (self.labels[i], j, self.values[i][j])
                if key in con_p_mol_dict:
                    con_p_mol_dict[key] += 1
                else:
                    con_p_mol_dict[key] = 1

        for key in con_p_mol_dict:
            con_p[key] = (con_p_mol_dict[key] + self.lam) / (label_num_dict[key[0]] + self.lam * S[key[1]])

        # 保存到self种
        self.pri_p = pri_p
        self.con_p = con_p

    '''
    朴素贝叶斯预测
    :param value:预测的实例
    '''

    def predict(self, value):
        res = {}  # 每个类计算的比较公式结果
        for label in self.labels_space:
            tmp = self.pri_p[label]  # 初始化为先验概率
            # 条件概率连乘
            for j in range(len(value)):
                # 构造条件概率的key
                key = (label, j, value[j])
                # 对于不存在的条件概率,需要使用拉普拉斯修正
                if key not in self.con_p:
                    tmp *= self.lam / (self.label_num_dict[key[0]] + self.lam * self.S[key[1]])
                else:
                    tmp *= self.con_p[key]

            res[label] = tmp

        # 排序返回概率最大的
        # print('计算结果:', res)
        return sorted(res.items(), key=lambda x: x[1], reverse=True)[0][0]

#
# if __name__ == '__main__':
#     df = pd.DataFrame(pd.read_excel('./data/data_in_book.xlsx'))
#     values = df.values[:, :-1]
#     labels = df.values[:, -1]
#     nb = NaiveBayes(values, labels)
#     print("[2,'S']的预测的类别是:{}".format(nb.predict([2,'S'])))

结果和书里面的一样

计算结果: {1: 0.0326797385620915, -1: 0.06100217864923746}
[2,'S']的预测的类别是:-1

朴素贝叶斯的应用-邮件分类实现

在学习过程中一直看到这个邮件分类的例子,在这里找个数据用python预测一下顺便检测一下上面实现的分类器

背景介绍

这里的邮件分类值指的是划分邮件为正常邮件与垃圾邮件。

垃圾邮件是一类相对容易识别的邮件类型,具有一些显著的文本特征。

  • 语法或拼写错误。骚扰邮件常常书写不严谨,经常会出现单词拼写错误,语法错误,汉字可能出现错别字的现象。
  • 诱导性词汇。骚扰邮件经常会出现很多的诱导性词汇,比如“中奖”等词汇,诱导用户点击相关链接或者访问相关网站。
  • 发件人地址不明。发件人邮件地址不明,不属于机构内部后缀,一般陌生后缀的发件人邮箱地址常常属于骚扰邮件的范畴

比如下面就是一些垃圾邮件的内容

As a valued customer, I am pleased to advise you that following recent review of your Mob No. you are awarded with a £1500 Bonus Prize, call 09066364589

Urgent UR awarded a complimentary trip to EuroDisinc Trav, Aco&Entry41 Or £1000. To claim txt DIS to 87121 18+6*£1.50(moreFrmMob. ShrAcomOrSglSuplt)10, LS1 3AJ

Did you hear about the new "Divorce Barbie"? It comes with all of Ken's stuff!

问题描述

我们现在有许多的邮件,已经被分类成垃圾邮件或者普通邮件

现希望通过对这些邮件数据进行训练,得出对垃圾邮件进行分类的贝叶斯分类器

ham	Go until jurong point, crazy.. Available only in bugis n great world la e buffet... Cine there got amore wat...
ham	Ok lar... Joking wif u oni...
spam	Free entry in 2 a wkly comp to win FA Cup final tkts 21st May 2005. Text FA to 87121 to receive entry question(std txt rate)T&C's apply 08452810075over18's
ham	U dun say so early hor... U c already then say...
ham	Nah I don't think he goes to usf, he lives around here though
spam	FreeMsg Hey there darling it's been 3 week's now and no word back! I'd like some 
...

解决方案

分类是什么

针对上面的数据集我们可以知道,分类自然就是ham以及spam,也就是垃圾邮件或者普通邮件

实例是什么

实例,也就是我们的特征向量 x = ( x ( 1 ) , x ( 2 ) , . . . x ( n ) ) x=(x^{(1)},x^{(2)},...x^{(n)}) x=(x(1),x(2),...x(n)),我们需要构造出实例才能进行训练

诈一眼看过去,每个样本(邮件)好像可以使用邮件内容作为该邮件的特征向量,也就是每个词语的位置是一个特征,每个词语是一个特征值,比如说对于第二封邮件其特征为

x = ( 'Ok', 'lar...', 'Joking', 'wif', 'u', 'oni...' )

但是这样会有两个很明显的问题

  1. 一个致命的问题是这样每个特征向量长度不等,不可进行朴素贝叶斯训练
  2. 其次,每个位置出现的词的取值可能性根本无法预测,也就是我们通过样本求出的 a j l a_{jl} ajl很不靠谱

换个思路想一下,我们判断垃圾邮件有一种方法是通过里面的有无垃圾、错误词语判断一封邮件是不是垃圾邮件

那么我们可以将所有邮件样本的所有词语求一个集合,得到一个语料表

将语料表当作每个特征向量的特征,而每个特征特征值只有两个: 0和1;0表示该邮件有该词语,1表示该邮件没有出现该词语

可以思考一下为啥用有无,而不用数量作为特征值

举个例子,假如邮件样本只有以下词语

['I' , 'like', 'python', 'money', 'click' , 'call', 'V' , 'me' , '50']

那么

  • 普通邮件 ‘I like python’ 对应的实例应该是 x = ( 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 ) x=(1,1,1,0,0,0,0,0,0) x=(1,1,1,0,0,0,0,0,0),类别是’ham’

  • 垃圾邮件’v me 50’对应的实例是 x = ( 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 ) x=(0,0,0,0,0,0,1,1,1) x=(0,0,0,0,0,0,1,1,1),类别是’spam’

算法流程

  • 获取所有邮件数据
  • 对每封邮件的词语进行清洗拆分并转换成小写
  • 对所有邮件内容求并集得出语料表
  • 对每一封邮件进行处理,根据语料表和邮件内容得出每个样本(邮件)的特征向量
  • 利用朴素贝叶斯进行交叉验证

puthon代码

就不封装函数了,看的时候不用跳来跳去

# _*_ coding:utf-8 _*_
import re
import random

import numpy as np
from naive_bayes import NaiveBayes

'''读取数据'''
f = open('./data/email_data.txt', encoding='UTF-8')
email_list = []  # 存放每个邮件的分词
labels = []  # 存放每个邮件的分类
vocabulary_list = set([])  # 语料库
while True:
    # 处理一行,使用特殊字符分割字符串并转换成列表
    line = f.readline()
    if not line:
        break
    line =re.split('\W+', line)
    for i in range(len(line)):
        line[i] = line[i].lower()
    # 只保存标签正确的邮件
    if line[0] == 'ham' or line[0] == 'spam':
        email_list.append(line[1:])
        labels.append(line[0])
        vocabulary_list = vocabulary_list | set(line[1:])  # 更新语料库
vocabulary_list = list(vocabulary_list)  # 变回集合

'''对每封邮件根据语料库生成特征向量'''
values = []  # 特征向量
for i in range(len(email_list)):
    vec = [0] * len(vocabulary_list)  # 特征向量初始化为词料表大小
    for word in email_list[i]:
        if word in vocabulary_list:
            vec[vocabulary_list.index(word)] = 1
    values.append(vec)

values = np.array(values)
labels = np.array(labels)
'''交叉验证,70%训练集,30%测试集'''
# 随机特征向量分成两组
# 先将下标切分,后面方便将特征向量和标签切分
train_index = list(range(len(values)))
test_index = []
test_len = int(len(values) * .3)  # 需要测试集的大小
for i in range(test_len):
    rand = int(random.uniform(0, len(train_index)))
    test_index.append(train_index[rand])
    del train_index[rand]  # 将训练移入测试集的下标从训练下标删除

train_values = values[train_index]
train_labels = labels[train_index]
test_values = values[test_index]
test_labels = labels[test_index]

'''训练并计算准确率'''
nb = NaiveBayes(train_values, train_labels)
test_len = len(test_values)
res_labels = []  # 返回集合
for i in range(test_len):
    res_labels.append(nb.predict(test_values[i]))


correct_rate = 0.0
correct_num = 0
for i in range(test_len):
    if test_labels[i] == res_labels[i]:
        correct_num += 1
correct_rate = correct_num * 1.0 / test_len
print('垃圾邮件分类准确率为:{}',correct_rate)

预测结果

结果还是挺高的有木有!!!

垃圾邮件分类准确率为:{} 0.9844497607655502

Process finished with exit code 0

数据集地址

可以到我github拿

github地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值