众所周知,朴素贝叶斯是一种简单但是非常强大的线性分类器。它在垃圾邮件分类,疾病诊断中都取得了很大的成功。它只所以称为朴素,是因为它假设特征之间是相互独立的,但是在现实生活中,这种假设基本上是不成立的。那么即使是在假设不成立的条件下,它依然表现的很好,尤其是在小规模样本的情况下。但是,如果每个特征之间有很强的关联性和非线性的分类问题会导致朴素贝叶斯模型有很差的分类效果。
其基本思想就是,给出一个分类问题,对于待求项,属于哪个分类的概率最大,那这个待求项就属于哪个分类。
根据贝叶斯定理,对一个分类问题,给定样本特征x,样本属于类别y的概率是
下面通过实现朴素贝叶斯文本分类的代码来看:
根据上面的算法流程,在这里实现一个句子极性划分的例子。所谓句子极性是指,句子所表达的情感色彩,例如积极/消极,这里(书里)使用的是侮辱性/非侮辱性。其实是什么类别不重要,只要给定有标签的训练数据,就可以得到分类模型。
导入数据并完成初始化:
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]
return postingList,classVec
#得到词汇表
def createVocabList(dataSet):
vocabSet = set([]) #set是不重复集
for document in dataSet:
vocabSet = vocabSet | set(document) #对每个文档求得的结果求并,去重复的
return list(vocabSet)#转换成list
#根据词汇表构成0向量,对每个文档每个词对应向量中的位置赋值为1
def setOfWords2Vec(vocabList, inputSet):
returnVec = [0]*len(vocabList)#构建vocabList长度的0向量
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 trainNB0(trainMatrix,trainCategory):
numTrainDocs = len(trainMatrix)# 6
numWords = len(trainMatrix[0])#得到词汇表的长度32
pAbusive = sum(trainCategory)/float(numTrainDocs)#(0+1+0+1+0+1)/6=0.5
p0Num = np.ones(numWords)#长度为numWords32全为1的数组
p1Num = np.ones(numWords)#计算频数初始化为1
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 = math.log(p1Num/p1Denom) #注意
p0Vect = math.log(p0Num/p0Denom) #注意
return p0Vect,p1Vect,pAbusive#返回各类对应特征的条件概率向量
#和各类的先验概率
分类:
def getTrainMatrix():
listOPosts,listClasses = loadDataSet()
myVocabList = createVocabList(listOPosts)
trainMat = []
for postinDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList,postinDoc))
return trainMat,listClasses
def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
p1 = sum(vec2Classify * p1Vec) + math.log(pClass1)#注意
p0 = sum(vec2Classify * p0Vec) + math.log(1-pClass1)#注意
if p1 > p0:
return 1
else:
return 0
def testingNB():#流程展示
listOPosts,listClasses = loadDataSet()#加载数据
myVocabList = createVocabList(listOPosts)#建立词汇表
trainMat = []
for postinDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList,postinDoc))
p0V,p1V,pAb = trainNB0(trainMat,listClasses)#训练
#测试
testEntry = ['love','my','dalmation']
thisDoc = setOfWords2Vec(myVocabList,testEntry)
print (testEntry,'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb))
注意:上述代码中标有注意的地方,是公式中概率连乘变成了对数概率相加,两者是正相关的。此举可以在数学上证明不会影响分类结果,且在实际计算中,避免了因概率因子远小于1而连乘造成的下溢出。