《机器学习实战》这本书其实对机器学习算法的讲解不是很仔细(或者说不太适合数学基础薄弱的人)对程序的解释也不是很详细
数学基础薄弱的人可能看着程序觉得一头雾水(比如我)
在朴素贝叶斯算法这一节中,如果不懂条件概率,就很难理解算法,甚至根本弄不懂程序是用来做什么的
我特意把大二的概率论与数理统计拿出来复习了一下,总算是把这一章看懂了
条件概率,数学上用P(A|B)来表示,意思是在B事件发生的情况下A事件发生的概率,这句话很绕口,但是结合书中的一个例子就很容易理解了
假设100件产品中有5件是不合格产品,而五件不合格产品中又有3件是次品,2件是废品
设A事件是在100件产品中抽到次品的概率,很容易计算得到P(A) = 3/100
设B事件是在100件产品中抽到不合格产品的概率,也很容易计算得到P(B) = 5/100 = 1/20
那么P(A|B)的意思套用上面的定义就是在抽到不合格产品的情况下,抽到次品的概率,这下就很好理解了,不合格产品中有3件次品,很容易就能计算出P(A|B) = 3/5
计算公式为:P(A|B) = P(AB)/P(B)
P(AB)的意思是,AB两件事情同时发送的概率
拿上面的例子举例,P(AB)就是抽到一件即不合格又是次品的产品,即事件A和事件B交集的概率P(A∪B),由于次品属于合格品的一部分即A∪B = A
很容易计算得P(AB) =P(A)= 3/100
对于相互独立的事件(即多个事情的发生互相之间没有影响)可以用公式P(AB) = P(A)*P(B)来计算
而对于互相不独立的事件,需要用P(A∪B)来计算,比如上述例子,当事件A(抽到一件次品)发生,事件B(这件产品是不合格品)发生的概率是1,所以AB不独立
计算P(AB)的公式还有P(AB) = P(B)*P(A|B) = P(A)*P(B|A)
条件概率的计算公式如下P(A|B) = P(AB)/P(B),用上面的例子检验,P(AB) = 3/100,P(B) = 1/20,通过公式计算得P(A|B) = P(AB)/P(B) = (3/100)/(1/20) = 3/5
由以条件概率的计算公式和P(B)*P(A|B) = P(A)*P(B|A)可以得到贝叶斯公式
P(A|B) = P(B|A)*P(A)/P(B)
本书中的第一个例子是文本分类
这里我们的问题是,给出一个文本将文本分类两类(侮辱性文本1)(非侮辱性文本0)
设是侮辱性词汇的事件为C1,不是侮辱性词汇的事件C2,一个句子由(词1,词2,词3......)组成的事件为W
需要求P(C1|W),即给出一个句子,求该句子属于侮辱性词汇的条件概率
P(C2|W),即给出一个句子,求该句子属于非侮辱性词汇的条件概率
用贝叶斯公式可以得到P(C1|W) = P(W|C1)*P(C1)/P(W)
将文本转换成由多个词组组成的向量W1,W2,W3.....,并且假设这个向量出现的概率是互相独立的,那么P(W|C1) = P(W1|C1)*P(W2|C1)*P(W3|C3).........
由于在实际情况中,各个词组之间的出现概率不是互相独立的,比如am 出现在i之前的概率比其他情况大,而这里不考虑这一点,所以称之为朴素贝叶斯
训练集如下:
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代表正常言论
首先将训练集的文本中所有不重复的单词提取出来用于计算每句话的向量
def createVocabList(dataSet):
vocabSet = set([])
for document in dataSet:
vocabSet = vocabSet | set(document)
return list(vocabSet)
将文本通过这个词组集转换成(01)向量,这里构建的向量的长度即词组集的长度,所表征的是词组集中的每个词在给出的文本中是否出现
朴素贝叶斯分类器通常有两种实现方式,一种基于伯努利模型实现,一种基于多项式模型的实现,这里采用前一中方式.这种方式不考虑词在文档中出现的次数,只考虑出现与否
def setOfWords2Vcc(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
然后计算给出一个分类(属于侮辱性词汇和非侮辱性词汇)词汇出现的概率,即P(Wi|Cj)(j = 1代表侮辱性词汇,j=0代表非侮辱性词汇,i代表词汇编号)
def trainNB0(trainMatrix,trainCategory):
numTrainDocs = len(trainMatrix)
numWords = len(trainMatrix[0])
pAbusive = sum(trainCategory)/float(numTrainDocs)
p0Num = numpy.zeros(numWords)
p1Num = numpy.zeros(numWords)
p0Denom = 0.0
p1Denom = 0.0
for i in range(numTrainDocs):
if trainCategory[i] == 1:
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[0])
p1Vect = p1Num/p1Denom
p0Vect = p0Num/p0Denom
return p0Vect,p1Vect,pAbusive
在计算P(W|Ci)时,需要通过P(W1|Ci)*P(W2|Ci)*P(W3|Ci).......来计算,而只要其中一个词汇出现的概率是0那么整个概率都会变成0,为了降低这种影响,可以将所有词的出现数初始化为1,将分母初始化为2,所以将一下代码
p0Num = numpy.zeros(numWords)
p1Num = numpy.zeros(numWords)
p0Denom = 0.0
p1Denom = 0.0
改为
p0Num = numpy.ones(numWords)
p1Num = numpy.ones(numWords)
p0Denom = 2.0
p1Denom = 2.0
另一个问题是下一处,太多很小的数相乘时,程序会下溢出或得不到正确答案(比如四舍五入变成0),利用对数函数的性质ln(a*b) = ln(a)+ln(b)将乘法化为加法可以解决这一问题
p1Vect = p1Num/p1Denom
p0Vect = p0Num/p0Denom
改为
p1Vect = log(p1Num/p1Denom)
p0Vect = log(p0Num/p0Denom)
分别计算P(C1|W)和P(C2|W),概率较大的一个即是我们需要的分类
def classfyNB(vec2Classify,p0Vec,p1Vec,pClass1):
p1 = sum(vec2Classify*p1Vec)+log(pClass1)
p0 = sum(vec2Classify*p0Vec)+log(pClass1)
if(p1>p0):
return 1
else:
return 0
测试程序
def test():
listOposts,listClasses = loadDataSet()
myVocabList = createVocabList(listOposts)
trainMat = []
for postinDoc in listOposts:
trainMat.append(setOfWords2Vcc(myVocabList,postinDoc))
p0V,p1V,pAb = trainNB0(trainMat,listClasses)
a = ['love','my','dalmation']
b = ['stupid','garbage']
thisdoc = setOfWords2Vcc(myVocabList,a)
thisdoc2 = setOfWords2Vcc(myVocabList,b)
print(classfyNB(thisdoc,p0V,p1V,pAb),classfyNB(thisdoc2,p0V,p1V,pAb))
结果
0 1