朴素贝叶斯—概率模型
写在前面 : 我本科是学医的(0数学基础),刚开始搞这些东西的是和都是硬背,磨蹭了半年只学会各种框架搭积木,后来意识到这样不行遂从头开始翻机器学习的书和深度学习的经典论文,开始一点点理解算法本质,这时候才感觉到搭建模型的过程其实还是很有趣的,找到方法就容易多了。和广大转行者共勉同行吧
朴素贝叶斯分类器
Naive Bayes模型为基础的概率模型,最最最明显的特点就是进行分类时会输出样本属于每个类别的概率,而不是像KNN或者Decision Tree那样非黑即白的硬分类。
搞机器学习算法不学概率论无异于谈恋爱不结婚——都是耍流氓。所以推荐同步看一下概率论与数理统计。
贝叶斯条件概率分布:
p
(
a
∣
b
)
=
p
(
b
∣
a
)
p
(
a
)
p
(
b
)
p(a|b) =\frac{p(b|a)p(a)}{p(b)}
p(a∣b)=p(b)p(b∣a)p(a)
朴素贝叶斯的两个假设:
- 所有特征之间相互独立(这也是naive的含义)
- 所有特征同等重要
- 优点:在数据比较少得情况下仍有效果,可以处理多分类问题。
- 缺点:对于输入得数据准备的方式比较敏感。
- 适用数据类型:标称型数据
Naive Bayes的一般过程
- 收集数据:本文用RSS源
- 准备数据:需要数值型或布尔型数据
- 分析数据:有大量特征时,绘制特征作用不大,用直方图效果更好
- 训练算法:计算不同的独立特征的条件概率
- 测试算法:计算错误率
- 使用算法:是用朴素贝叶斯分类器,一个常见应用场景是文档分类器
下面思考一个文本分类的例子
所有代码模块都是Python代码
1.首先,创造一个文档集来代替真实文本数据,对于其中每个文档都进行标注(0代表正常文本,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
2.第一步已经获得了初始标注好的数据,接下来要做的事情就是对数据进行分析,预处理。这一步要做的几件是
- 2-1.将所有词汇整理,提取出一个vocabulary,包含所有不重复的词汇
def createVocabList(dataSet):
vocabSet = set([]) # 创造一个空集
for document in dataSet:
vocabSet = vocabSet | set(document) # 求并集,换句话说就是将文本去重
return list(vocabSet)
- 2-2.以2-1创造的vocabulary为模板,创建文本向量,元素下标(或者index)对应vocabulary
- 可以用词集模型,每个元素都为0-1,0表示没出现,1表示出现过。
def setOfWords2Vec(vocabList, inputSet):
"""词集模型
input:vocabList 词汇表
inputSet 输入文档
output: 与词汇表对应的词汇向量,一个0-1矩阵"""
returnVec = [0] * len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] = 1
else:
print('this word: %s is not in my Vocabulary!' % word)
return returnVec
- 也可有用词袋模型,每个元素为0-n,表示该下标对应的单词出现的频率
def bagOfwords2VecMN(vocabList, inputSet):
"""词袋模型
input:vocabList 词汇表
inputSet 输入文档
output: 与词汇表对应的词汇向量,一个单词频数矩阵"""
returnVec = [0] * len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1
return returnVec
第二步的目的主要是对文本进行向量化,因为分类的是和数值方便计算。类似于one-hot编码
3.训练算法:从词向量计算概率(重头戏)
-
先来理解一下模型。
首先,我们的算法模型是离散概率分布,**其中 X X X表示特征随机变量, C C C表示类别随机变量,要计算分类概率,公式为:
p ( 类 别 i ∣ 特 征 ) = p ( 特 征 ∣ 类 别 i ) p ( 类 别 i ) p ( 特 征 ) p(类别_i|特征) =\frac{p(特征|类别_i)p(类别_i)}{p(特征)} p(类别i∣特征)=p(特征)p(特征∣类别i)p(类别i),用数学符号表示就是 p ( C i ∣ X ) = p ( X ∣ C i ) p ( C i ) p ( X ) p(C_i|X) =\frac{p(X|C_i)p(C_i)}{p(X)} p(Ci∣X)=p(X)p(X∣Ci)p(Ci) —(1)根据全概率公式可以得到: P ( X ) = P ( x 1 ) P ( x 2 ) P ( x 3 ) . . . P ( x n ) P(X) = P(x_1)P(x_2)P(x_3)...P(x_n) P(X)=P(x1)P(x2)P(x3)...P(xn) —(2)
通过(1)(2)我们可以通过先验概率 P ( C i ) P(C_i) P(Ci),特征概率 P ( X ) P(X) P(X),和条件概率 p ( X ∣ C i ) p(X|C_i) p(X∣Ci)计算得到后验概率 P ( C i ∣ X ) P(C_i|X) P(Ci∣X),这样正式我们想要输出的结果
-
算法流程:
- (1) 先将先验概率 p ( C ) p(C) p(C)和不同类别C_i的条件下所有 P ( x i ∣ C i ) P(x_i|C_i) P(xi∣Ci)计算出来
- (2) 用朴素贝叶斯公式来计算后验概率输出结果
# 2.训练算法:从词向量计算概率
def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix)
numWords = len(trainMatrix[0])
pAbusive = sum(trainCategory) / float(numTrainDocs)
p0Num = ones(numWords); p1Num = ones(numWords) # 初始化p(0)和p(1)的词频数
p0Denom = 2.; p1Denom = 2. # 初始化文档的总词数
for i in range(numTrainDocs): # 遍历所有文档,记录累加随机变量x=1,x=0的词频数和文档总词数
if trainCategory[i] == 1:
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
p1Vect = log(p1Num / p1Denom)
p0Vect = log(p0Num / p0Denom)
return p0Vect, p1Vect, pAbusive
# 朴素贝叶斯分类函数
def classifyNB(vec2Classify, p0Vect, p1Vect, pClass1):
p1 = sum(vec2Classify * p1Vect) + log(pClass1)
p0 = sum(vec2Classify * p0Vect) + log(1. - pClass1)
return 1 if p1 > p0 else 0
这里需要强调一下:
第一,朴素贝叶斯之所以naive,是因为我们假设所有的词汇都是独立的特征,也就是说每个词出现的频率不会受其他词的影响,这一点肯定不成立,这样假设的好处是可以减少样本量(因为独立特征可以共用同一套样本),而且在实际应用过程中效果还是不错的。
第二,因为概率很小,所以计算全概率时,所有特征概率相乘会出现数值下溢问题(得到的结果为0),解决这个问题的方法就是对概率进行对数化
到这里算法构建已经完成了,下一步我们算法进行测试,跑下面的代码:
def testingNB():
listOposts, listClasses = loadDataSet()
myVocabList = createVocabList(listOposts)
trainMat = []
for postinDoc in listOposts:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
p0v, p1v, pAb = trainNB0(array(trainMat), array(listClasses))
testEntry = ['love', 'my', 'dalmation']
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as:', classifyNB(thisDoc, p0v, p1v, pAb))
testEntry = ['stupid', 'garbage']
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as: ', classifyNB(thisDoc, p0v, p1v, pAb))
得到结果