1. Naive Bayes
朴素贝叶斯法是基于 贝叶斯定理 和 特征条件独立假设 的分类方法,它没有迭代学习参数的过程,而是通过参数估计的方式,估计先验概率和条件概率这两个参数,从而得到分类器。在代码中,学习过程就是统计样本数据,获取参数估计时所得表达式在样本中的取值,从而确定分类器。
1.1 模型
1.1.1 模型概述
朴素贝叶斯的模型如下所示
y
=
arg
max
c
k
P
(
Y
=
c
k
)
∏
j
P
(
X
(
j
)
=
x
(
j
)
∣
Y
=
c
k
)
y=\arg\max_{c_k}P(Y=c_k)\prod_jP(X^{(j)}=x^{(j)}|Y=c_k)
y=argckmaxP(Y=ck)j∏P(X(j)=x(j)∣Y=ck)
模型的含义是,计算数据
x
x
x 属于
c
1
,
c
2
,
…
c_1,c_2,\dots
c1,c2,… 类的概率
p
1
,
p
2
,
…
p_1,p_2,\dots
p1,p2,…,选择概率最大的类,为
x
x
x 的类
1.1.2 公式推导
模型的目的是在已知数据
x
x
x 时,求出它属于类别
y
=
c
k
y=c_k
y=ck 的概率,取其中概率最大者为
x
x
x 类别,即求出下述概率中最大者
P
(
Y
=
c
k
∣
X
=
x
)
P(Y=c_k|X=x)
P(Y=ck∣X=x)
由贝叶斯定理可以有以下变换
P
(
Y
=
c
k
∣
X
=
x
)
=
P
(
X
=
x
,
Y
=
c
k
)
P
(
X
=
x
)
=
P
(
X
=
x
∣
Y
=
c
k
)
P
(
Y
=
c
k
)
P
(
X
=
x
)
\begin{aligned} P(Y=c_k|X=x)&=\frac{P(X=x,Y=c_k)}{P(X=x)}\\ &=\frac{P(X=x|Y=c_k)P(Y=c_k)}{P(X=x)} \end{aligned}
P(Y=ck∣X=x)=P(X=x)P(X=x,Y=ck)=P(X=x)P(X=x∣Y=ck)P(Y=ck)
显然,
P
(
X
=
x
)
P(X=x)
P(X=x) 对于任何一个类别
c
k
c_k
ck 其概率都是一样的,所以它并不影响最终分类的效果,所以可以省略它以简化模型,即等价于取下式中概率最大者
P
(
X
=
x
∣
Y
=
c
k
)
P
(
Y
=
c
k
)
P(X=x|Y=c_k)P(Y=c_k)
P(X=x∣Y=ck)P(Y=ck)
又因为数据
x
x
x 是一个特征向量,其中含有许多个特征,具体的,
x
=
(
x
(
1
)
,
x
(
2
)
…
)
x=(x^{(1)},x^{(2)}\dots)
x=(x(1),x(2)…),再根据我们的特征条件独立假设,即
P
(
X
=
x
)
=
P
(
X
(
1
)
=
x
(
1
)
)
P
(
X
(
2
)
=
x
(
2
)
)
…
P(X=x)=P(X^{(1)}=x^{(1)})P(X^{(2)}=x^{(2)})\dots
P(X=x)=P(X(1)=x(1))P(X(2)=x(2))…,用连乘符号表示,即有
P
(
X
=
x
)
=
∏
j
P
(
X
(
j
)
=
x
(
j
)
)
P(X=x)=\prod\limits_jP(X^{(j)}=x^{(j)})
P(X=x)=j∏P(X(j)=x(j)),将其代入,即可得模型如下
y
=
arg
max
c
k
P
(
Y
=
c
k
)
∏
j
P
(
X
(
j
)
=
x
(
j
)
∣
Y
=
c
k
)
y=\arg\max_{c_k}P(Y=c_k)\prod_jP(X^{(j)}=x^{(j)}|Y=c_k)
y=argckmaxP(Y=ck)j∏P(X(j)=x(j)∣Y=ck)
1.2 策略
贝叶斯算法的策略是 后验概率最大化,即求出 x x x 属于每一个类的概率,选择概率最大的类为 x x x 的类,这等价于 0 − 1 0-1 0−1 损失函数时的期望风险最小化。通俗的说,就是计算一系列的值,得到 x x x 的类别是 c 1 c_1 c1 的概率为 p 1 p_1 p1,是 c 2 c_2 c2 的概率为 p 2 p_2 p2, … \dots …,其中概率最大的是 c k c_k ck,其概率为 p k p_k pk,也就是说 x x x 的类别是 c k c_k ck 的可能性是最大的,因此认为 x x x 的类别是 c k c_k ck
1.3 算法
贝叶斯分类的求取模型参数的方法是 参数估计,通过 极大似然估计 或 贝叶斯估计 来确定模型中参数的值,显然从模型中可看到,有两个需要估计的参数
- 先验概率 P ( Y = c k ) P(Y=c_k) P(Y=ck)
- 条件概率 ∏ j P ( X ( j ) = x ( j ) ∣ Y = c k ) \prod\limits_jP(X^{(j)}=x^{(j)}|Y=c_k) j∏P(X(j)=x(j)∣Y=ck)
1.3.1 极大似然估计 Maximum Likelihood Estimate
1.3.1.1 MLE 简介
MLE 是一种参数估计方法,它的应用场景如下
- 我们已经知道了总体所服从的分布,但是该分布中参数的值,我们不知道
- 我们有一批样本,它们来自总体,是独立同分布 ( i . i . d ) (i.i.d) (i.i.d) 的
MLE 要做的,就是从样本出发,估计出总体中参数的值。具体的,抽取到样本
x
i
x_i
xi 的概率是
P
(
X
i
=
x
i
)
P(X_i=x_i)
P(Xi=xi),这个概率受到要估计的总体参数
θ
\theta
θ 的影响,我们将其记做
P
(
X
i
=
x
i
∣
θ
)
P(X_i=x_i|\theta)
P(Xi=xi∣θ)。我们一共抽取了
n
n
n 个样本
x
1
,
x
2
,
…
,
x
n
x_1,x_2,\dots,x_n
x1,x2,…,xn,由于它们独立同分布,那么恰好抽中这些样本的概率如下
P
(
X
1
=
x
1
,
X
2
=
x
2
,
…
,
X
n
=
x
n
)
=
∏
i
=
1
n
P
(
X
i
=
x
i
∣
θ
)
P(X_1=x_1,X_2=x_2,\dots,X_n=x_n)=\prod\limits_{i=1}^nP(X_i=x_i|\theta)
P(X1=x1,X2=x2,…,Xn=xn)=i=1∏nP(Xi=xi∣θ)
这个概率就是我们的似然函数
L
(
θ
)
=
∏
i
=
1
n
P
(
X
i
=
x
i
∣
θ
)
L(\theta)=\prod\limits_{i=1}^nP(X_i=x_i|\theta)
L(θ)=i=1∏nP(Xi=xi∣θ)
likelihood 的意思是 可能性,所以极大似然估计就是指极大可能估计,那么,什么是 极大可能 呢?MLE 的想法是,总体中存在那么多的个体,为什么就刚好抽中这些个体呢?这是否意味着抽中这些个体是 极大可能 的呢?那么,我们所要估计的参数
θ
\theta
θ 的真实值,是否就是使得我们抽中这些个体的概率比抽中其他个体的概率更大呢?
因此,MLE 所估计的
θ
\theta
θ,就是使得似然函数取得最大值时的
θ
\theta
θ,也就是使得从总体中抽中我们所获取的这些个体的概率取得最大值时的
θ
\theta
θ。因此,要计算
θ
\theta
θ,我们就要求出使似然函数取最大值时的点,然后解出
θ
\theta
θ。用极大似然估计进行参数估计的步骤可以概括如下
- 写出似然函数 L ( θ ) = ∏ i = 1 n P ( X i = x i ∣ θ ) L(\theta)=\prod\limits_{i=1}^nP(X_i=x_i|\theta) L(θ)=i=1∏nP(Xi=xi∣θ)
- 对似然函数取对数,这主要是为了方便计算,尤其是在计算机计算时,可以有效避免溢出 log L ( θ ) = ∑ i = 1 n log P ( X i = x i ∣ θ ) \log L(\theta)=\sum\limits_{i=1}^n\log P(X_i=x_i|\theta) logL(θ)=i=1∑nlogP(Xi=xi∣θ)
- 对 θ \theta θ 求导,令其等于 0 0 0,即 d ( log L ( θ ) ) d θ = 0 \frac{d(\log L(\theta))}{d\theta}=0 dθd(logL(θ))=0,此时似然函数达到最值
- 解出第 3 步中的等式,即求得 θ \theta θ 值
1.3.1.2 估计先验概率
首先给出参数估计的结果如下
P
(
Y
=
c
k
)
=
∑
i
=
1
N
I
(
y
i
=
c
k
)
N
,
k
=
1
,
2
,
…
,
N
P(Y=c_k)=\frac{\sum\limits_{i=1}^NI(y_i=c_k)}{N},k=1,2,\dots,N
P(Y=ck)=Ni=1∑NI(yi=ck),k=1,2,…,N
简单说就是训练数据中类别为
c
k
c_k
ck 的数据量占训练集中总数据量的比例,下面给出极大似然估计的过程。
记
P
(
Y
=
c
1
)
=
p
P(Y=c_1)=p
P(Y=c1)=p,则
Y
Y
Y 取其他值的概率为
1
−
p
1-p
1−p,一共
n
n
n 条数据,假设其中有
d
d
d 条数据的类别为
c
1
c_1
c1,则为其他类别的数据有
n
−
d
n-d
n−d 条,那么可以写出似然函数如下
L
(
p
)
=
p
d
(
1
−
p
)
(
n
−
d
)
L(p)=p^d(1-p)^{(n-d)}
L(p)=pd(1−p)(n−d)
取对数有
ln
L
(
p
)
=
d
ln
p
+
(
n
−
d
)
ln
(
1
−
p
)
\ln L(p)=d\ln p + (n-d)\ln (1-p)
lnL(p)=dlnp+(n−d)ln(1−p)
对
p
p
p 求导,令其等于
0
0
0 有
d
(
ln
L
(
p
)
)
d
p
=
d
/
p
−
(
n
−
d
)
/
(
1
−
p
)
=
0
\begin{aligned} \frac{d(\ln L(p))}{dp}&=d/p-(n-d)/(1-p)\\ &=0 \end{aligned}
dpd(lnL(p))=d/p−(n−d)/(1−p)=0
即解方程
d
p
=
n
−
d
1
−
p
\frac{d}{p}=\frac{n-d}{1-p}
pd=1−pn−d
得到
p
=
d
n
p=\frac{d}{n}
p=nd
由于训练集中类别为
c
1
c_1
c1 的数据数量可表示为
d
=
∑
i
=
1
N
I
(
y
i
=
c
1
)
d=\sum\limits_{i=1}^NI(y_i=c_1)
d=i=1∑NI(yi=c1),即
P
(
Y
=
c
1
)
P(Y=c_1)
P(Y=c1) 参数估计的结果如下
P
(
Y
=
c
1
)
=
∑
i
=
1
N
I
(
y
i
=
c
1
)
N
P(Y=c_1)=\frac{\sum\limits_{i=1}^NI(y_i=c_1)}{N}
P(Y=c1)=Ni=1∑NI(yi=c1)
同理,其他类别的先验概率也按此方法估计,最终得到估计结果如下
P
(
Y
=
c
k
)
=
∑
i
=
1
N
I
(
y
i
=
c
k
)
N
,
k
=
1
,
2
,
…
,
N
P(Y=c_k)=\frac{\sum\limits_{i=1}^NI(y_i=c_k)}{N},k=1,2,\dots,N
P(Y=ck)=Ni=1∑NI(yi=ck),k=1,2,…,N
1.3.1.3 估计条件概率
和先验概率的估计过程类似,就不再给出推导过程,直接给出参数估计的结果如下
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
I
(
y
i
=
c
k
)
P(X^{(j)}=a_{jl}|Y=c_k)=\frac{\sum\limits_{i=1}^NI(x_i^{(j)}=a_{jl},y_i=c_k)}{\sum\limits_{i=1}^NI(y_i=c_k)}
P(X(j)=ajl∣Y=ck)=i=1∑NI(yi=ck)i=1∑NI(xi(j)=ajl,yi=ck)
1.3.2 贝叶斯估计 Bayesian Estimation
极大似然估计存在的一个问题是有可能出现估计出概率值为 0 0 0 的情况,例如丢硬币全丢出来正面。这种时候就会影响到后验概率的计算,因此需要引入贝叶斯估计来解决这个问题。贝叶斯估计考虑先验知识的影响,也就是说,虽然丢硬币全部是正面,但是我们知道,这是不合理的,所以要根据我们已经知道的知识对结果做一些修正。具体的,对两个概率的贝叶斯估计结果如下
- 先验概率
P λ ( Y = c k ) = ∑ i = 1 N I ( y i = c k ) + λ N + K λ P_\lambda(Y=c_k)=\frac{\sum\limits_{i=1}^NI(y_i=c_k)+\lambda}{N+K\lambda} Pλ(Y=ck)=N+Kλi=1∑NI(yi=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 I ( y i = c k ) + S j λ P_\lambda(X^{(j)}=a_{jl}|Y=c_k)=\frac{\sum\limits_{i=1}^NI(x_i^{(j)}=a_{jl},y_i=c_k)+\lambda}{\sum\limits_{i=1}^NI(y_i=c_k)+S_j\lambda} Pλ(X(j)=ajl∣Y=ck)=i=1∑NI(yi=ck)+Sjλi=1∑NI(xi(j)=ajl,yi=ck)+λ
注: λ ≥ 0 \lambda\geq 0 λ≥0,当 λ = 0 \lambda=0 λ=0 时,就相当于极大似然估计,当 λ = 1 \lambda=1 λ=1 时,被称为 拉普拉斯平滑 Laplacian Smoothing
2. 实现 Naive Bayes
2.1 用极大似然估计的 Naive Bayes
2.1.1 代码实现
import numpy as np
from collections import defaultdict, Counter
class NaiveBayesMLE:
"""使用极大似然估计的朴素贝叶斯"""
def __init__(self):
"""
self.py: 即先验概率 p(y), 是一个一维字典, key 为类别 y, val 为 p(Y=y).
默认为 0, 即数据 x 类别为 y 的概率
self.pxy: 即条件概率 p(x|y), 是一个三维字典, 具体的, pxy[y][i][v] 为 p(x_i=v|Y=y).
默认为 0, 即已知数据 x 类别为 y 的前提下, x 第 i 个属性取值为 v 的条件概率
"""
self.py = defaultdict(lambda: 0)
self.pxy = defaultdict(lambda: defaultdict(lambda: defaultdict(lambda: 0)))
def fit(self, X, Y):
"""
训练方法: NaiveBayes 的训练, 就是学习模型的参数, 即先验概率 p(y) 和条件概率 p(x|y)
Args:
X(ndarray): 训练数据的特征矩阵
Y(ndarray): 训练数据的真实类别向量
"""
# 1. 计算先验概率
y_count = Counter(Y)
for y in y_count:
self.py[y] = y_count[y] / len(X)
# 2. 计算条件概率
for x, y in zip(X, Y):
# 2.1 计算在确定 y 的前提下, 每个属性各种取值的数量
for idx, val in enumerate(x):
self.pxy[y][idx][val] += 1
# 2.2 根据数量计算条件概率
for y in y_count:
for idx in pxy[y]:
for val in pxy[y][idx]:
self.pxy[y][idx][val] /= y_count[y]
def predict(self, X):
"""
预测方法: 根据训练好的模型,预测测试数据的类别
Args:
X(ndarray): 测试数据的属性矩阵
Returns:
所有测试数据预测类别组成的向量
"""
return apply_along_axis(self._predict, axis=-1, arr=X)
def _predict(self, x):
"""
辅助方法: 使用当前模型,预测某一数据 x 的类别 y
Args:
x(ndarray): 一条数据的属性向量
Returns:
预测结果,即类别 y
"""
# 1. 取出标签集合
labels = list(self.py.keys())
probilities = [] # probilities 是一个列表, 第 i 个值代表 x 属于第 i 类的概率
# 2. 计算 x 属于每种标签的概率
for y in labels:
probility = self.py[y]
for idx, val in enumerate(x):
probility *= self.pxy[y][idx][val]
probilities.append(probility)
# 3. 选择可能性最大的标签返回
return labels[np.argmax(probilities)]
2.1.2 代码中的 API 和语法
- defaultdict
defaultdict 是 collections 中的一个类,作用是对于字典中不存在的键值对,查找时返回一个默认值。它接收一个函数作为参数,这个函数决定了当键值对缺失时返回什么。以 defaultdict(lambda: 0) 为例,当键值对缺失时,它就会返回一个 0 0 0 - Counter
Counter 也是 collections 中的一个类,它可以有多种方式实例一个对象,实例对象的效果就是,创建一个字典,key 是输入中的一个成分,val 是成分在输入中出现的频次。例如 Counter(Y) 就统计了标签向量 Y Y Y 中各个标签出现的频次 - 字典的 keys 方法
对一个字典实例调用 keys 方法可以去除该字典中所有的 key
2.2 用贝叶斯估计的 Naive Bayes
贝叶斯估计的 Naive Bayes 和极大似然估计的 Naive Bayes 主要在两个部分有所不同,即 __init__() 方法和 fit() 方法。前者主要是需要再接收一个参数 λ \lambda λ,后者则是根据贝叶斯估计的结果统计参数,下面给出这两个不同的部分代码
2.2.1 __init__() 方法
该方法主要是需要再多获取一个超参数 λ \lambda λ,代码如下
def __init__(self, lamda=1):
"""
self.py: 即先验概率 p(y), 是一个一维字典, key 为类别 y, val 为 p(Y=y).
默认为 0, 即数据 x 类别为 y 的概率
self.pxy: 即条件概率 p(x|y), 是一个三维字典, 具体的, pxy[y][i][v] 为 p(x_i=v|Y=y).
默认为 0, 即已知数据 x 类别为 y 的前提下, x 第 i 个属性取值为 v 的条件概率
self.lamda: 即参数 $\lambda$, 因为与 python 中的关键字 lambda 重复, 所以此处写作 lamda 以作区分
"""
self.py = defaultdict(lambda: 0)
self.pxy = defaultdict(lambda: defaultdict(lambda: defaultdict(lambda: 0)))
self.lamda = lamda
2.2.2 fit() 方法
根据参数估计的结果,修改计算两个参数值的步骤即可,代码如下
def fit(self, X, Y):
"""
训练方法: NaiveBayes 的训练, 就是学习模型的参数, 即先验概率 p(y) 和条件概率 p(x|y)
Args:
X(ndarray): 训练数据的特征矩阵
Y(ndarray): 训练数据的真实类别向量
"""
# 1. 计算先验概率
y_count = Counter(Y)
for y in y_count:
self.py[y] = (y_count[y] + self.lamda) / (len(X) + self.lamda * len(y_count)) # 变化一: 计算先验概率
# 2. 计算条件概率
for x, y in zip(X, Y):
# 2.1 计算在确定 y 的前提下, 每个属性各种取值的数量
for idx, val in enumerate(x):
self.pxy[y][idx][val] += 1
# 2.2 根据数量计算条件概率
for y in y_count:
for idx in pxy[y]:
for val in pxy[y][idx]:
self.pxy[y][idx][val] += self.lamda # 变化二: 计算条件概率
self.pxy[y][idx][val] /= y_count[y] + self.lamda * len(X[0])