今天我们来看看另外一种分类方式——基于概率论的朴素贝叶斯
1、补充概率论知识——条件概率
假装所有人概率论都很6,条件概率什么的简单。重点是一个叫“先验概率”的,用正常人听得懂的话说就是,以往的数据都可以参考,可以用和这个数据集里不同点的“相似”程度来分类。这里的“相似”和之前的k均值、决策树不一样。k均值是“相差”小,决策树是根据不同的特征递归下降分类判断。而贝叶斯的“相似”指的是根据用“贝叶斯统计”推断出来的相似程度最大的那个分类作为预测分类。好吧,“贝叶斯统计”简单地说就是:我想通过特征去分类,直观上做不到,但我可以用贝叶斯公式,即用条件概率算出某特征与某分类同时成立的概率,再将它除以该特征的概率即可以得到“通过该特征用先验概率判断为某分类”的概率,数学公式大概长这样:
再提一下“朴素”吧,个人理解是这些点之间没有关联,即最“朴素”的办法。
2、朴素贝叶斯分类的文本分类器
《机器学习实战》书上这个例子,大概意思是,比如一个文档,通过里面一些词组出现在“侮辱集合”和“正常集合”里的频率来预测这个文档或者这句话的类别(侮辱类还是正常类)
先创建bayes.py文件,并引入numpy以备不时之需
from numpy import *
第一步,先创建loadDataSet()函数加载数据
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
postingList是不同类型的序列,classVec是标签
接着写createVocabList(dataSet)用于将待检测分类的文本通过set类型生成元素不重复的序列
def createVocabList(dataSet): vocabSet = set([]) # 创建一个空集 for document in dataSet: vocabSet = vocabSet | set(document) # 创造两个集合的并集 return list(vocabSet)
接着,创建setOfWords2Vec(vocalList,inputSet)用于将在待分类inputSet中出现的vocalList集合里的元素的数量对应自增。
def setOfWords2Vec(vocalList, inputSet): returnVec = [0] * len(vocalList) # 创建一个其中所含元素都为0的向量 for word in inputSet: if word in vocalList: returnVec[vocalList.index(word)] = 1 else: print("the word: %s is not in my Vocabulary!" % word) return returnVec
在命令行中测试:
>>> from numpy import *
>>> import bayes
>>> listOPosts,listClasses=bayes.loadDataSet()
>>> myVocabList=bayes.createVocabList(listOPosts)
>>> myVocabList
['dalmation', 'how', 'flea', 'problems', 'ate', 'to', 'steak', 'has', 'is', 'take',
'stupid', 'love', 'please', 'food', 'posting', 'cute', 'maybe', 'my', 'not', 'park',
'licks', 'stop', 'mr', 'dog', 'quit', 'garbage', 'so', 'I', 'him', 'help', 'buying'
, 'worthless']
然后我们来通过词向量计算概率,为了便于理解,借用书上第60页的伪代码:
计算每个类别中的文档数目:
对每篇训练文档:
对每个类别:
如果词条出现在文档中->增加该词条的计数值
增加所有词条的计数值
对每个类别:
对每个词条:
将该词条的数目除以总词条数目得到条件概率
返回每个类别的条件概率
于是,我们继续在bayes.py中书写朴素贝叶斯分类器训练函数 trainNB0(trainMatrix,trainCategory)
# 朴素贝叶斯分类器训练函数 # trainMatrix 文档矩阵 # trainCategory 由每篇文档类别标签所构成的向量 def trainNB0(trainMatrix, trainCategory): numTrainDocs = len(trainMatrix) # 文档矩阵行数 numWords = len(trainMatrix[0]) # 文档矩阵列数 pAbusive = sum(trainCategory) / float(numTrainDocs) # 侮辱率,即侮辱词汇占总词数的比例 # 初始化概率 p0Num = zeros(numWords) p1Num = 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[i]) # 对每个元素做除法 p1Vect = p1Num / p1Denom p0Vect = p0Num / p0Denom return p0Vect, p1Vect, pAbusive
在命令行中测试如下:
>>> from numpy import *
>>> from imp import reload
>>> reload(bayes)
<module 'bayes' from 'C:\\Users\\dell\\PycharmProjects\\untitled\\bayes.py'>
>>> listOPosts,listClasses=bayes.loadDataSet()
>>> myVocabList=bayes.createVocabList(listOPosts)
>>> trainMat=[]
>>> for postinDoc in listOPosts:
... trainMat.append(bayes.setOfWords2Vec(myVocabList,postinDoc))
...
>>> p0V,p1V,pAb=bayes.trainNB0(trainMat,listClasses)
>>> pAb
0.5
>>> p0V
array([0.04166667, 0.04166667, 0.04166667, 0.04166667, 0.04166667,
0.04166667, 0.04166667, 0.04166667, 0.04166667, 0. ,
0. , 0.04166667, 0.04166667, 0. , 0. ,
0.04166667, 0. , 0.125 , 0. , 0. ,
0.04166667, 0.04166667, 0.04166667, 0.04166667, 0. ,
0. , 0.04166667, 0.04166667, 0.08333333, 0.04166667,
0. , 0. ])
>>> p1V
array([0. , 0. , 0. , 0. , 0. ,
0.05263158, 0. , 0. , 0. , 0.05263158,
0.15789474, 0. , 0. , 0.05263158, 0.05263158,
0. , 0.05263158, 0. , 0.05263158, 0.05263158,
0. , 0.05263158, 0. , 0.10526316, 0.05263158,
0.05263158, 0. , 0. , 0.05263158, 0. ,
0.05263158, 0.10526316])
结果正确。
现在我们遇到了两个问题:
(1)如果一个概率的结果为零,则其乘积也是零
(2)太多小数相乘会造成下溢出
针对第一个问题,我们将所有词的出现数初始化为1,并将坟墓初始化为2.
将bayes.py中的trainNB0()的第四行和第五行改为:
p0Num = ones(numWords) p1Num = ones(numWords) p0Denom = 2.0 p1Denom = 2.0
针对第二个问题,我们运用对数工具解决,修改return前的两行代码如下:
p1Vect = log(p1Num / p1Denom) p0Vect = log(p0Num / p0Denom)
接着书写朴素贝叶斯分类函数
# 参数解释 # vec2Classify 要分类的向量 # p0Vec, p1Vec, pClass1 使用函数trainNB0()计算得到的三个概率 def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1): # 使用NumPy的数组来计算两个向量相乘的结果 # 这里跌相乘是指对应元素的相乘,即两个向量中的第i个对应相乘,i=1....n p1 = sum(vec2Classify * p1Vec) + log(pClass1) p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1) if p1 > p0: return 1 else: return 0 # 遍历函数(convenience function)节省调用之前代码的时间 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))
然后接着在命令行中测试:
>>> reload(bayes)
<module 'bayes' from 'C:\\Users\\dell\\PycharmProjects\\untitled\\bayes.py'>
>>> bayes.testingNB()
['love', 'my', 'dalmation'] classified as: 0
['stupid', 'garbage'] classified as: 1
结果正确。
最后,我们拓展一下思路。我们现在只是将存在与否用0、1分别表示,即词集;但是我们在实际需要中往往需要统计频数,即需要记录每个词出现的次数,即词袋,下面简单列出朴素贝叶斯词袋模型的代码:
# 词集模型:每个词的出现与否作为一个特征 # 词袋模型:每个词出现的次数 # 文档词袋模型 def bagOfWordsVecMV(vocabList, inputSet): returnVec = [0] * len(vocabList) for word in inputSet: if word in vocabList: # 和setOfWords2Vec()的唯一不同之处: # 每当遇到一个单词时,它会增加词向量中的对应值 # 而不只是将对应的数值设为1 returnVec[vocabList.index(word)] += 1 return returnVec
感兴趣的话,就可以利用这个词袋模型替代之前的词集模型进行类似“垃圾邮件过滤”的工作了,就不再赘述了。
欢迎看到最后,发现错误欢迎指正。