目录
1 基础理论
朴素贝叶斯分类器是一种基于贝叶斯定理和特征条件独立假设的概率分类算法。
1.1 贝叶斯定理
贝叶斯定理是关于随机事件的概率推断的基本定理,表示了在已知先验概率的情况下,如何根据新的观测数据来更新我们对事件概率的信念。其数学表达式为:
其中,
- 是在观测到事件 B 后事件 A 发生的概率(后验概率);
- 是在事件 A 发生的情况下观测到事件 B 的概率;
- 和 分别是事件 A 和事件 B 的先验概率
1.2 贝叶斯分类器的分类准则
给定一个输入样本 ,朴素贝叶斯分类器通过计算每个类别 的后验概率 ,并选择具有最大后验概率的类别作为预测结果。根据贝叶斯定理:
其中,
- 是在给定观测数据 的情况下类别 的后验概率
- 是在类别 下观测到数据 的概率(似然)
- 是类别 的先验概率, 是观测数据 的边际概率
贝叶斯分类准则:
❏ 如果P(c1|x, y) > P(c2|x, y),那么属于类别c1;
❏ 如果P(c1|x, y) < P(c2|x, y),那么属于类别c2。
1.3 朴素贝叶斯的特征条件独立假设
在朴素贝叶斯分类器中,假设已知类别的所有特征相互独立,即每个特征独立地对分类结果发生影响。
2 应用 -- 文本分类
以社区的线上留言板为例:为了不影响社区的发展,我们要屏蔽侮辱性的言论,构建一个快速过滤器,如果某条留言使用了负面或者侮辱性的语言,那么就将该留言标识为内容不当。对此问题建立两个类别:侮辱类和非侮辱类,使用1和0分别表示。
2.1 将文本转换为词向量
- 创建一些测试文本
def loadDataSet():
postingList = [['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],
['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'],
['stop', 'posting', 'stupid', 'worthless', 'garbage'],
['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'],
['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']]
classVec = [0, 1, 0, 1, 0, 1] # 1:表示侮辱性文字, 0:表示正常言论
return postingList, classVec
- 创建一个【包含所有文本中出现的不重复词】的词汇列表
def createVocabList(dataSet):
vocabSet = set([])
for document in dataSet:
# 将词条列表输给set构造函数,set就会返回一个不重复词表
vocabSet = vocabSet | set(document) # 取两个集合的并集
return list(vocabSet)
- 将某文档根据词汇列表转换为词向量
def setOfWords2Vec(vocabList, inputSet):
returnVec = [0] * len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] = 1
else:
print("the word: %s is not in my Vocabulary!" % word)
return returnVec
- 测试效果
这个例子中,词向量中的数值个数与词汇表中的词个数相同的。 上述结果说明,测试文档内有32个不重复的词,所以形成的文档向量是32维的。
2.2 基于词向量计算条件概率,并构建分类器
- 基于以下公式计算条件概率:
- 具体如何计算?
- 计算概率p(ci):通过类别 i(侮辱性留言或非侮辱性留言)中文档数除以总的文档数
- 计算p(w|ci),这里要用到朴素贝叶斯假设。将w展开可以将上述概率写作p(w0, w1, w2..wN|ci),根据特征条件独立假设可得 p(w0, w1, w2..wN|ci) = p(w0|ci)p(w1|ci)p(w2|ci)...p(wN|ci)
- 构建朴素贝叶斯分类器计算概率:
def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix)
numWords = len(trainMatrix[0])
pAbusive = sum(trainCategory) / float(numTrainDocs)
# 初始化概率
p0Num = np.ones(numWords)
p1Num = np.ones(numWords)
p0Denom = 2.0
p1Denom = 2.0
for i in range(numTrainDocs):
if trainCategory[i] == 1:
# 向量相加
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
# 对每个元素做除法。取对数
p1Vect = np.log(p1Num / p1Denom)
p0Vect = np.log(p0Num / p0Denom)
return p0Vect, p1Vect, pAbusive
- 比较概率大小进行朴素贝叶斯分类
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
p1 = sum(vec2Classify * p1Vec) + np.log(pClass1) # element-wise mult
p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
- 测试
# 测试函数
def bayesTest():
list0Posts, listClasses = loadDataSet()
myVocabList = createVocabList(list0Posts)
trainMat = []
for postInDoc in list0Posts:
trainMat.append(setOfWords2Vec(myVocabList, postInDoc))
p0V, p1V, pAb = trainNB0(trainMat, listClasses)
print("p0V:{}".format(p0V))
print("p1V:{}".format(p1V))
print("pAb:{}".format(pAb))
testEntry = ['love', 'my', 'dalmation']
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))
testEntry = ['stupid', 'garbage']
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))
- 测试结果
2.3 实现朴素贝叶斯过程中需要考虑的问题
- 利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率,即计算p(w0|1)p(w1|1)p(w2|1)。如果其中一个概率值为0,那么最后的乘积也为0。为降低这种影响,将所有词的出现数初始化为1,并将分母初始化为2(拉普拉斯法修正)。
- 由于太多很小的数相乘造成下溢出问题,一种解决办法是对乘积取自然对数,通过求对数可以避免下溢出或者浮点数舍入导致的错误,而且不会有任何损失。
下图给出函数f(x)与ln(f(x))的曲线:
检查这两条曲线会发现它们在相同区域内同时增加或者减少,并且在相同点上取到极值。它们的取值虽然不同,但不影响最终结果。