基础知识
朴素贝叶斯分类器是基于贝叶斯决策理论的分类模型,首先来了解一下贝叶斯决策理论和概率论的相关知识。
贝叶斯决策理论
概率决策是贝叶斯决策理论的核心思想。在执行分类任务的时候我们无法保证分类的结果总是正确的,贝叶斯决策理论的目的就是基于已有的相关概率来做出使得误判损失最低的决策。
对于一个多分类任务,假定样本总共有
m
m
m 类:
c
1
,
c
2
,
.
.
.
,
c
m
{c_1, c_2,...,c_m}
c1,c2,...,cm,我们定义将真实类别为
j
j
j 的样本误判为
i
i
i 产生的损失为
λ
i
j
\lambda_{ij}
λij,后验概率
P
(
c
i
∣
x
)
P(c_i|\boldsymbol{x})
P(ci∣x) 表示将特征为
x
\boldsymbol{x}
x 的样本分类为
c
i
c_i
ci 的概率。那么根据后验概率我们可以得到分类产生的期望损失(或者叫做样本
x
\boldsymbol{x}
x 的条件风险):
R
(
c
i
∣
x
)
=
∑
j
=
1
m
λ
i
j
P
(
c
j
∣
x
)
(1)
R(c_i|\boldsymbol{x})=\sum_{j=1}^{m}\lambda_{ij}P(c_j|\boldsymbol{x}) \tag{1}
R(ci∣x)=j=1∑mλijP(cj∣x)(1)贝叶斯判定准则就是对于每个样本选择使上式达到最小的类别
c
c
c,从而使得整体的风险最小。如果定义误判损失
λ
i
j
\lambda_{ij}
λij如下:
λ
i
j
=
{
0
,
if
i
=
j
1
,
otherwise
(2)
\lambda_{i j}=\left\{\begin{array}{ll}{0,} & {\text { if } i=j} \\ {1,} & {\text { otherwise }}\end{array}\right. \tag{2}
λij={0,1, if i=j otherwise (2)那么根据式(1)很容易得到
R
(
c
∣
x
)
=
1
−
P
(
c
∣
x
)
(3)
R(c|\boldsymbol{x})=1-P(c|\boldsymbol{x}) \tag{3}
R(c∣x)=1−P(c∣x)(3)那么贝叶斯决策准则就可以转化为,对于样本
x
\boldsymbol{x}
x,选择使得
P
(
c
∣
x
)
P(c|\boldsymbol{x})
P(c∣x) 最大的类别
c
c
c 作为样本
x
\boldsymbol{x}
x 的类别。但通常我们无法直接得到
P
(
c
∣
x
)
P(c|\boldsymbol{x})
P(c∣x),所以需要根据一些先验知识从训练样本中估计得到。
先验概率和条件概率
从贝叶斯定理可以得到
P
(
c
∣
x
)
=
P
(
c
)
P
(
x
∣
c
)
P
(
x
)
(4)
P(c|\boldsymbol{x})=\frac{P(c)P(\boldsymbol{x}|c)}{P(\boldsymbol{x})} \tag{4}
P(c∣x)=P(x)P(c)P(x∣c)(4)式中
P
(
c
)
P(c)
P(c) 表示类别
c
c
c 的先验概率,
P
(
x
∣
c
)
P(\boldsymbol{x}|c)
P(x∣c) 表示样本特征
x
\boldsymbol{x}
x 相对于类别
c
c
c 的条件概率(也称为似然),
P
(
x
)
P(\boldsymbol{x})
P(x) 对于所有类样本均相同,因此与类别无关。因此通过式(4)求最大后验
P
(
c
∣
x
)
P(c|\boldsymbol{x})
P(c∣x) 就转化为了通过训练样本估计
P
(
x
∣
c
)
P(\boldsymbol{x}|c)
P(x∣c) 和
P
(
c
)
P(c)
P(c) 。
对于
P
(
c
)
P(c)
P(c),假设训练集足够大,并且所有样本独立同分布,那么根据大数定律就可以用样本集中
c
c
c 类样本出现的频率(即所占的比例)来表示
P
(
c
)
P(c)
P(c)。对于
P
(
x
∣
c
)
P(\boldsymbol{x}|c)
P(x∣c),由于
x
\boldsymbol{x}
x 是一个向量,因此
P
(
x
∣
c
)
P(\boldsymbol{x}|c)
P(x∣c) 是一个联合概率,直接从有限的样本中得到这个概率是很难的,因此在估计
P
(
x
∣
c
)
P(\boldsymbol{x}|c)
P(x∣c) 时又有了一个假设:各属性特征之间是独立的。这样就可以得到
P
(
x
∣
c
)
=
∏
i
=
1
d
P
(
x
i
∣
c
)
(5)
P\left(\boldsymbol{x}|c\right)=\prod_{i=1}^{d} P\left(x_i |c\right) \tag{5}
P(x∣c)=i=1∏dP(xi∣c)(5)其中
x
i
x_i
xi 表示样本在第
i
i
i 个特征上的取值,
d
d
d 表示特征维数。
朴素贝叶斯分类器
总结一下,朴素贝叶斯分类器是基于以下两个假设的:
- 所有样本独立同分布;
- 样本的各个特征之间也是相互独立的。
基于这两个假设,式(4)可以写成如下形式:
P
(
c
∣
x
)
=
P
(
c
)
P
(
x
∣
c
)
P
(
x
)
=
P
(
c
)
P
(
x
)
∏
i
=
1
d
P
(
x
i
∣
c
)
(6)
P(c | \boldsymbol{x})=\frac{P(c) P(\boldsymbol{x} | c)}{P(\boldsymbol{x})}=\frac{P(c)}{P(\boldsymbol{x})} \prod_{i=1}^{d} P\left(x_{i} | c\right) \tag{6}
P(c∣x)=P(x)P(c)P(x∣c)=P(x)P(c)i=1∏dP(xi∣c)(6)朴素贝叶斯分类器的目标就是对于输入样本
x
\boldsymbol{x}
x,求式(6)取得最大值对应的
c
c
c,又由于
P
(
x
)
P(\boldsymbol{x})
P(x) 与类别无关,因此朴素贝叶斯分类器的目标如下:
h
n
b
(
x
)
=
arg
max
c
∈
Y
P
(
c
)
∏
i
=
1
d
P
(
x
i
∣
c
)
(7)
h_{n b}(\boldsymbol{x})=\underset{c \in \mathcal{Y}}{\arg \max } P(c) \prod_{i=1}^{d} P\left(x_{i} | c\right) \tag{7}
hnb(x)=c∈YargmaxP(c)i=1∏dP(xi∣c)(7)式中
Y
\mathcal{Y}
Y 表示类别集合。
在大数定律的前提下,类的先验概率可以用类别的样本比例来表示
P
(
c
)
=
∣
D
c
∣
∣
D
∣
(8)
P(c)=\frac{|D_c|}{|D|} \tag{8}
P(c)=∣D∣∣Dc∣(8)
∣
D
∣
|D|
∣D∣ 表示训练样本数,
∣
D
c
∣
|D_c|
∣Dc∣ 表示训练集中
c
c
c 类样本的数量。估计
P
(
x
i
∣
c
)
P(x_{i} | c)
P(xi∣c) 分特征取值离散和连续两种情况,对于离散取值的特征,
P
(
x
i
∣
c
)
P(x_{i} | c)
P(xi∣c) 可以通过下面的方式估计:
P
(
x
i
∣
c
)
=
∣
D
c
,
x
i
∣
∣
D
c
∣
(9)
P\left(x_{i} | c\right)=\frac{|D_{c, x_{i}}|}{|D_{c}|} \tag{9}
P(xi∣c)=∣Dc∣∣Dc,xi∣(9)
∣
D
c
,
x
i
∣
|D_{c, x_{i}}|
∣Dc,xi∣ 表示
c
c
c 类样本中第
i
i
i 个特征取值为
x
i
x_i
xi 的样本数量。当特征取值连续的时候可以用概率密度函数来表示
P
(
x
i
∣
c
)
P(x_{i} | c)
P(xi∣c) ,假定其服从正态分布:
P
(
x
i
∣
c
)
∼
N
(
μ
c
,
i
,
σ
c
,
i
2
)
P\left(x_{i} | c\right) \sim \mathcal{N}\left(\mu_{c, i}, \sigma_{c, i}^{2}\right)
P(xi∣c)∼N(μc,i,σc,i2),
μ
c
,
i
\mu_{c, i}
μc,i 和
σ
c
,
i
\sigma_{c, i}
σc,i 分别表示
c
c
c 类样本中特征
i
i
i 的均值和标准差,则
p
(
x
i
∣
c
)
=
1
2
π
σ
c
,
i
exp
(
−
(
x
i
−
μ
c
,
i
)
2
2
σ
c
,
i
2
)
(10)
p\left(x_{i} | c\right)=\frac{1}{\sqrt{2 \pi} \sigma_{c, i}} \exp \left(-\frac{\left(x_{i}-\mu_{c, i}\right)^{2}}{2 \sigma_{c, i}^{2}}\right) \tag{10}
p(xi∣c)=2πσc,i1exp(−2σc,i2(xi−μc,i)2)(10)
这样朴素贝叶斯的数学分析和推导部分就基本结束了。接下来通过垃圾邮件分类的例子来实践一下。
朴素贝叶斯垃圾邮件分类器
这里使用的数据集来自《机器学习实战》一书,代码也是根据书中代码修改。数据集由两个文件夹下的文本文件构成,文件夹的名称表明邮件的类别(spam/ham),每个文本文都是一个邮件的内容。利用朴素贝叶斯对邮件进行分类主要分为以下几步:
- 读取数据集。将每个邮件的内容读取为一个由单词组成的向量,正常邮件类别标记为1,垃圾邮件类别标记为0;
- 得到所有样本中出现的不重复词列表;
- 划分训练集和测试集,并根据不重复词列表将每个样本转化为数值向量;
- 从训练样本中计算出先验概率 P ( c ) P(c) P(c) 和条件概率 P ( x ∣ c ) P\left(x| c\right) P(x∣c);
- 根据式(7)对测试样本进行分类。
在得到不重复词向量vocabList
之后,对每个样本进行数值转化的函数如下:
# 根据vocabList将句子转换为向量表示
def Word2Vec(vocabList, wordList):
wordVec = [0] * len(vocabList)
for word in wordList:
if word in vocabList: # 对出现在句子中的vocabList中的元素进行计数
wordVec[vocabList.index(word)] += 1
return wordVec
具体来说,对于每个原始样本,返回一个和vocabList
相同长度的向量,其中每个元素为vocabList
当前索引处的单词在样本中出现的次数。
此外,具体实现的时候有两个问题需要注意:(1) 利用式(5) 求
P
(
x
∣
c
)
P\left(\boldsymbol{x}| c\right)
P(x∣c) 的时候如果其中一个
P
(
x
i
∣
c
)
=
0
P\left(x_i| c\right) = 0
P(xi∣c)=0,那么最终计算得到的
P
(
x
∣
c
)
P\left(\boldsymbol{x}| c\right)
P(x∣c) 也为0,这样显然不太合理,会将条件概率较大的特征也忽略,因此在实际计算
P
(
c
)
P(c)
P(c) 和
P
(
x
∣
c
)
P\left(\boldsymbol{x}| c\right)
P(x∣c) 我们通常使用“拉普拉斯修正”来进行平滑处理:
P
(
c
)
=
∣
D
c
∣
+
1
∣
D
∣
+
N
(11)
P(c)=\frac{|D_c| + 1}{|D| + N} \tag{11}
P(c)=∣D∣+N∣Dc∣+1(11)
P
(
x
i
∣
c
)
=
∣
D
c
,
x
i
∣
+
1
∣
D
c
∣
+
N
i
(12)
P\left(x_{i} | c\right)=\frac{|D_{c, x_{i}}| + 1}{|D_{c}| + N_i} \tag{12}
P(xi∣c)=∣Dc∣+Ni∣Dc,xi∣+1(12) 式中
N
N
N 表式
D
D
D 中可能的类别数,
N
i
N_i
Ni 表示第
i
i
i 个属性的可能取值。
(2) 另一个问题是利用式(5) 求
P
(
x
∣
c
)
P\left(\boldsymbol{x}| c\right)
P(x∣c) 的时候连乘容易造成下溢出,实际应用中可以通过求对数来避免:
L
L
(
c
)
=
log
P
(
x
∣
c
)
=
∑
i
=
1
d
log
P
(
x
i
∣
c
)
(13)
LL(c)=\log{P\left(\boldsymbol{x}|c\right)}=\sum_{i=1}^{d} \log{P\left(x_i |c\right)} \tag{13}
LL(c)=logP(x∣c)=i=1∑dlogP(xi∣c)(13) 这样做的数学合理性在于函数
f
(
x
)
f(x)
f(x) 和
log
f
(
x
)
\log{f(x)}
logf(x) 的增区间和减区间相同,并且极大值点也相同。
训练函数的代码如下,代码中使用了2代替式(11)和(12)中的
N
N
N 和
N
i
N_i
Ni。
def trainNB(trainingSet, trainingClasses):
trainLength = trainingSet.shape[0]
numWords = trainingSet.shape[1]
pAbusive = np.sum(trainingClasses) / trainLength # 类别为1的概率 p(c_i=1)
p0Num = np.ones(numWords)
p1Num = np.ones(numWords)
p0Denom = 2
p1Denom = 2
for i in range(trainLength):
if trainingClasses[i] == 1:
p1Num += trainingSet[i, :]
p1Denom += np.sum(trainingSet[i, :])
else:
p0Num += trainingSet[i ,:]
p0Denom += np.sum(trainingSet[i, :])
p1Vec = np.log(p1Num / p1Denom) # 每个类别的条件概率
p0Vec = np.log(p0Num / p0Denom)
return p0Vec, p1Vec, pAbusive
对于测试样本testVec
,对其分类的函数如下:
def classifyNB(testVec, p0Vec, p1Vec, pClass1):
p0 = np.sum(testVec * p0Vec) + np.log(1 - pClass1)
p1 = np.sum(testVec * p1Vec) + np.log(pClass1)
if p0 > p1:
return 0
else:
return 1
数据集和完整的代码可以在我的github上下载。
sklearn.naive_bayes实现语句情绪识别
sklearn.naive_bayes是sklearn包中朴素贝叶斯分类器的模块,利用它可以方便快捷的实现朴素贝叶斯分类。sklearn.naive_bayes包含了三个不同的分类器:GaussianNB、MultinomialNB以及BernoulliNB,它们的使用场景稍有差别:GaussianNB通常用于特征为连续值的分类任务,MultinomialNB通常用于离散特征值的分类任务,如果特征值是二元离散值或者稀疏的多元离散值可以考虑BernoulliNB,更详细的介绍可以参考官方文档。
Sentiment Labelled Sentences是来源于UCI机器学习数据库的数据,数据内容为来自imdb.com 、amazon.com、yelp.com网站的带类别标签的评论语句,类别标签为句子的情绪,1表示积极的情绪,0表示消极的情绪。部分样本如下所示:
可以看到样本都是以字符串形式存储的,因此首先需要将字符串格式的数据转化为数值特征,sklearn中提供了现成的转换工具,这里我们使用sklearn.feature_extraction.text.TfidfVectorizer进行转化,它可以将文本数据转化为TF-IDF特征矩阵,之后就可以进行训练了。完整的实现代码如下:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
'''
@Date : 2019/10/8
@Author : Rezero
用朴素贝叶斯方法进行情绪分类,数据集:sentiment labelled sentences
'''
import os
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import classification_report
def loadDataSet(path):
fileList = os.listdir(path)
dataSet = []
labels = []
for file in fileList:
with open(path + file) as fr:
for line in fr.readlines():
dataSet.append(line.split('\t')[0])
labels.append(int(line.strip().split('\t')[1]))
return dataSet, labels
if __name__ == "__main__":
dataSet, labels = loadDataSet('sentences/')
trainX, testX, trainY, testY = train_test_split(dataSet, labels, test_size=0.15, random_state=20) # 划分训练集和测试集
tvec = TfidfVectorizer()
trainX = tvec.fit_transform(trainX)
testX = tvec.transform(testX)
mnb = MultinomialNB() # 多项式朴素贝叶斯
mnb.fit(trainX, trainY)
predict_Y = mnb.predict(testX)
error = np.sum(np.abs(predict_Y - testY)) / len(predict_Y)
print(error)
# print(classification_report(testY, predict_Y))
函数的参数使用了默认配置,最后在测试集上得到的误差率为14.9%。
总结
朴素贝叶斯分类器使用较为简单,在信息检索领域很常用,但是因为通常处理文本类数据,特征转换、数据清洗、去除高频词等预处理过程很重要。
参考资料
《机器学习》(周志华)
《机器学习实战》(Peter Harrington)
https://www.cnblogs.com/mengnan/p/9307648.html