朴素贝叶斯
朴素贝叶斯是一种基于概率论的分类方法
条件概率公式
P(Y|X)表示,X条件下Y的概率。
举例说明:假设夫妻双方血型,丈夫为O型,妻子为AB型血。两人结合生下的孩子是什么血型?
丈夫血型 | 妻子血型 | 孩子的血型 |
---|---|---|
aa | AB | ? |
P(A型血|(aa,AB))表示孩子是A型血的概率,其中(丈夫为O型,妻子为AB型血即为条件)
其中P(X|Y)在各特征独立且权重一样的的情况下可以拆分成:
注意X为特征向量,可以有多个特征。如下
x1 | x2 | x3 | x4 | y |
---|---|---|---|---|
0 | 1 | 1 | 0 | yes |
0 | 2 | 0 | 0 | yes |
1 | 1 | 1 | 0 | no |
2 | 1 | 1 | 0 | yes |
X表示每一行的(x1,x2,x3,x4), Y表示每一行的y
这就很清楚了,
左边表示x1,x2…xn同时满足,右边则是拆分满足x1的情况,再满足x2等等。所以上述等式成立是显而易见的了。
根据以上的概率知识,只要获得给定X的情况下,P(Y|X)概率最大的,那个Y就是我们需要的分类了。
算法实现步骤
- 收集数据(初始化数据)
- 获取训练集中,所有不同的词列表(多少个不同的词)
- 转化为训练集矩阵形式
- 训练算法,计算不同的独立词中各种分类情况下的概率
- 给定某一串文字,进行分类得出结果
下面以文本分类作为例子进行说明(以在线社区的留言板为例):
具体实现
def testingNB():
listOPosts, listClasses = loadDataSet() #初始化数据集及分类集
myVocabList = createVocabList(listOPosts) # 获得训练集中,所有不同的词列表即词汇表
# 转化训练集为矩阵
trainMat = []
for postinDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
p0V, p1V, pAb = AlgorithmUtil.trainNBO(array(trainMat), array(listClasses))
# testEntry = ['love', 'my', 'dalmation']
testEntry = ['dog', 'stupid', 'him']
thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
print testEntry,'classified as: ',AlgorithmUtil.classifyNB(thisDoc,p0V,p1V,pAb)
==========以下为一些子函数
#初始化训练数据
def loadDataSet():
postingLis = [['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 postingLis, classVec
上面为初始训练集。当然实际情况不可能这么简单,这么少的。当然这不影响算法的实现步骤。
#获得包含在所有文档中出现的不重复词的列表(也就是获取词汇表)
def createVocabList(dataSet):
#参数说明,dataSet可以看作上一函数中的postingLis,就是初始训练集
vocabSet = set([])
for document in dataSet:
vocabSet = vocabSet | set(document) #并集
return list(vocabSet)
#转化为词向量,向量的每一元素为1或0,分别表示词汇表中的单词在输人文档中是否出现
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
#朴素贝叶斯分类器训练函数(只有俩类的情况)
def trainNBO(trainMatrix,trainCategory):
#参数说明,trainMatrix
numTrainDocs = len(trainMatrix) #行
numWords = len(trainMatrix[0]) #列
pAbusive = sum(trainCategory) / float(numTrainDocs) #整个训练集中,侮辱性言论的比例,或者概率。P(Y),即P(侮辱性言论)
p0Num = ones(numWords); p1Num = ones(numWords)
# 1 代表侮辱性文字,0代表正常言论
#p0Num表示训练集中,正常言论里面包含每一个词的个数。是一个向量(包括词汇表中每一个词)
#p1Num表示训练集中,侮辱性言论里面包含每一个词的个数是一个向量(包括词汇表中每一个词)
p0Denom = 2.0; p1Denom = 2.0
#p0Denom表示训练集中,正常言论里面总的词汇数(有可能重复)
#p1Denom表示训练集中,侮辱性言论里面总的词汇数(有可能重复)
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) #概率 分类1时,各词出现的概率
p0Vect = np.log(p0Num/p0Denom) #概率 分类0时,各词出现的概率
#加log,是为一些数学到考虑,下面会提到
return p0Vect, p1Vect, pAbusive
为什么p0Num,p1Num初始化为[1,1,….,1],不为[0,0,…,0]呢?
为什么p0Denom,p1Denom初始化为2呢?
是因为:利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率。从上面的函数我们可以看到,其实求到的是每一个词汇对应类别的概率。如果其中一个概率值为0,那么最后的乘积也为0。所以为降低 这种影响,可以将所有词的出现数初始化为1,并将分母初始化为2(分母不能为0吧)。这不影响结果,因为我们是要比较哪种分类的概率大
为什么要加得到的概率要log?
是因为:同样到要计算多个概率的乘积以获得文档属于某个类别的概率。由于大部分因子都非常小,所以程序会下溢出或者 得到不正确的答案。(很多很小到数相乘,四舍五入后会=0)。log不影响结果,因为我们是要比较哪种分类的概率大,log大的,还是大,趋势一致。(这应该好理解吧。。。)其实后面分解也会有用到。
#具体分类
def classifyNB(vec2Classify, pOVec, plVec, pClassl):
#参数说明
#vec2Classify 测试文本(转换为向量后的)
#pOVec 训练集中,类别为0即正常言论里,各词的概率
#plVec 训练集中,类别为1即侮辱言论里,各词的概率
#pClassl 训练集中,侮辱言论的概率
pl = sum(vec2Classify * plVec) + log(pClassl)
p0 = sum(vec2Classify * pOVec) + log(1.0 - pClassl)
if pl > p0:
return 1
else: return 0
上面pl = sum(vec2Classify * plVec) + log(pClassl)的含义:
P(Y|X)=P(X|Y)P(Y)P(X)
根据 P(X|Y)=P((x1,x2...xn)|Y)=P((x1)|Y)∗P((x2)|Y)∗...P((xn)|Y)
因为X是相同的,分母相同,取log,只要比较分子的大小即可。
log一下,就得到了(简单,自己推导一下)