机器学习算法三——基于概率论的分类方法:朴素贝叶斯(1)

若要求分类器给出“该数据实例属于哪一类”的明确答案,可能会产生错误结果。这时,可以要求分类器给出一个最优的类别猜测结果,同时给出这个猜测的概率估计值。

“朴素”:整个形式化过程只做最原始、最简单的假设。

一、基于贝叶斯决策理论的分类方法

朴素贝叶斯(贝叶斯决策理论的一部分)
优点:在数据较少的情况下仍然有效,可以处理多类别问题。
缺点:对于输入数据的准备方式较为敏感。
适用数据类型:标称型数据。

贝叶斯决策理论的核心思想:选择具有最高概率的决策。

二、条件概率
条件概率:
P(gray|bucketB) = P(gray and bucketB)/P(bucketB)

贝叶斯准则(交换条件概率中的条件与结果):
p(a|x) = p(x|a) p(a) / p(x)

三、使用条件概率来分类
p(a1 |x,y) 和 p(a2 |x,y):给定某个由x、y表示的数据点,那么该数据点来自类别a1的概率和来自类别a2的概率。
注意,这不同于 p(x,y| a1) 。
但可以使用贝叶斯准则来交换概率中条件与结果:
p (ai | x,y) = p (x,y | ai) p(ai) / p(x,y)
使用这些定义,可以定义贝叶斯准则为:
如果 P(a1 |x, y) > P(a2 |x, y) , 那么类别属于a1
如果 P(a1 |x, y) < P(a2 |x, y) , 那么类别属于a2

四、使用朴素贝叶斯进行文档分类
我们可以观察文档中出现的词,并把每个词的出现或者不出现作为一个特征,这样得到的特征数目就会跟词汇表中的词目一样多。
朴素贝叶斯的一般过程
(1)收集数据:可用任何方法。本章使用RSS源。
(2)准备数据:数值型或布尔型数据。
(3)分析数据:有大量特征时,直方图效果更好。
(4)训练算法:计算不同的独立特征的条件概率。
(5)测试算法:计算错误率。
(6)使用算法:一个常见的朴素贝叶斯应用是文档分类。
可以在任意的分类场景中使用朴素贝叶斯分类器,不一定非要是文本。

假设词汇表中有M个单词,若每个特征需要N个样本,则对于包含M个特征的词汇表将需要NM个样本。可见,所需的样本数随特征数目的增大而迅速增长。

假设一:若特征之间相互独立,那么样本数就可从NM减少到M*N。
统计意义上的独立:一个特征或者单词出现的可能性与它和其他单词相邻没有关系。(e.g.bacon出现在unhealthy后面与delicious后面的概率相同)————>朴素

假设二:每个特征同等重要。
然而,1000个单词的留言板可能只需看10-20个特征就足以做出判断了。

尽管上述假设存在一些小的瑕疵,但朴素贝叶斯的实际效果却很好。

五、使用python进行文本分类
要从文本中获取特征:(1)拆分文本;(2)一个文本片段——>一个词条向量,1表示词条出现在文档中,0表示未出现。
:社区留言板屏蔽侮辱性言论
构建快速过滤器:留言使用了负面或者侮辱性语言——>标识为内容不当。
类别:侮辱类和非侮辱类(用1和0分别表示)
1、准备数据:从文本中构建词向量
将句子转换为向量:(1)考虑所有文档中出现的所有单词;(2)决定将哪些词纳入词汇表;(3)将每一篇文档转换为词汇表上的向量。

##词表到向量的转换函数
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 #返回词条集合和类别标签集合

#创建一个包含在所有文档中出现的不重复词的列表
def createVocabList(dataSet):
    vocabSet = set([]) #创建一个空集,set()会返回一个不重复词表
    for document in dataSet:
        vocabSet = vocabSet | set(document) #创建两个集合的并集
    return list(vocabSet)

def setOfWords2Vec(vocabList, inputSet): #输入参数(词汇表,某文档)
    returnVec = [0]*len(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 #01分别表示词汇表的单词在文档中未出现和出现
 
def main():
    listOPosts, listClasses = loadDataSet()
    myVocabList = createVocabList(listOPosts)
    print(myVocabList)
    print(setOfWords2Vec(myVocabList, listOPosts[0]))
    print(setOfWords2Vec(myVocabList, listOPosts[2]))

if __name__ == "__main__":
    main()

运行结果:

>>>['is', 'please', 'problems', 'maybe', 'garbage', 'quit', 'mr', 'dalmation', 'posting', 'my', 'him', 'worthless', 'not', 'buying', 'to', 'help', 'steak', 'stop', 'has', 'take', 'ate', 'dog', 'love', 'how', 'I', 'flea', 'so', 'licks', 'stupid', 'food', 'cute', 'park']
[0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
[1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0]

检查上述词表,确认没有重复出现的单词(注:目前该词表还没有排序)。
标签序列分别代表了单词表中的词在第一句话和第三句话中是否出现。

2、训练算法:从词向量计算概率
现在已经知道一个词是否出现在一篇文档中,也知道该文档所属的类别。
现在我们重写贝叶斯准则,将之前的x、y替换为w,粗体w表示这是一个向量,即它由多个数值组成
p(ai | w) = p(w | ai) p(ai)/p(w)

函数的伪代码如下:

计算每个类别中的文档数目
对每篇训练文档:
    对每个类别:
    	如果词条出现文档中——>增加该词条的计数值
    	增加所有词条的计数值
    对每个类别:
    	对每个词条:
    		将该词条的数目除以总词条数目得到条件概率
    返回每个类别的条件概率

以下代码将文档集转换成词汇表的0、1序列(比如共10篇文档,则转换成10个词汇表的0、1序列,分别表示单词是否在文档中出现)。返回的p0Vect对应每个单词在非侮辱性文档中出现的概率,p1Vect对应每个单词在侮辱性文档中出现的概率.
pAbusive:训练集中侮辱性文档的概率
p0Num:矩阵,统计词汇表中每一个单词在非侮辱性文档中出现的总次数
p1Num:矩阵,统计词汇表中每一个单词在侮辱性文档中出现的总次数
p0Denom:非侮辱性文档的单词总数
p1Denom:侮辱性文档的单词总数

#朴素贝叶斯分类器训练函数
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]  #将其对应的词汇表矩阵累加到p1Num,值越大的位置对应的单词侮辱性越强
            p1Denom += sum(trainMatrix[i]) #侮辱性文档的单词总数
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    p1Vect = p1Num/p1Denom #change to log() #对每个元素做除法
    p0Vect = p0Num/p0Denom #非侮辱性单词在非侮辱性文档单词总数中出现的概率
    return p0Vect,p1Vect,pAbusive
    
def main():
    listOPosts, listClasses = loadDataSet()
    myVocabList = createVocabList(listOPosts) #包含所有词的列表
    print(myVocabList)
    trainMat = []
    for postinDoc in listOPosts:
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))#词汇表中的词是否在文档中出现的标签列表
    p0V,p1V,pAb= trainNB0(trainMat,listClasses)
    print(p0V)
    print(p1V)
    print(pAb)

运行结果如下:

>>>=============== RESTART: E:\Master\test\action\bayes\bayes.py ===============
['garbage', 'my', 'food', 'stop', 'how', 'worthless', 'steak', 'dalmation', 'to', 'park', 'take', 'quit', 'him', 'I', 'buying', 'has', 'so', 'help', 'love', 'stupid', 'problems', 'posting', 'cute', 'is', 'licks', 'mr', 'dog', 'flea', 'ate', 'maybe', 'not', 'please']
[ 0.          0.125       0.          0.04166667  0.04166667  0.
  0.04166667  0.04166667  0.04166667  0.          0.          0.
  0.08333333  0.04166667  0.          0.04166667  0.04166667  0.04166667
  0.04166667  0.          0.04166667  0.          0.04166667  0.04166667
  0.04166667  0.04166667  0.04166667  0.04166667  0.04166667  0.          0.
  0.04166667]
[ 0.05263158  0.          0.05263158  0.05263158  0.          0.10526316
  0.          0.          0.05263158  0.05263158  0.05263158  0.05263158
  0.05263158  0.          0.05263158  0.          0.          0.          0.
  0.15789474  0.          0.05263158  0.          0.          0.          0.
  0.10526316  0.          0.          0.05263158  0.05263158  0.        ]
0.5

对照结果,我们看到词汇表中第一个词是garbage,它在类别0中从未出现,在类别1中出现1次,对应的概率分别为0和0.05263158,计算正确。
发现在p1倒数13的位置概率最大为0.15789474,查单词表知其对应stupid,说明stupid是最能表征类别1(侮辱性文档类)的单词。
使用该函数进行分类之前,还需解决函数中的一些缺陷。

3、测试算法:根据现实情况修改分类器
利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率,即计算p(w0|1)p(w1|1)p(w2|1)。如果其中一个概率值为0,那么最后乘积也为0。为降低这种影响,可以将所有词的出现次数初始化为1,并将分母初始化为2。将trainNB0()的第4行和第5行修改为:

p0Num = ones(numWords) ; p1Num = ones(numWords)
p0Denom = 2.0;   p1Denom = 2.0

另一个问题是下溢出。当计算乘积p(w0|ci)p(w1|ci)p(w2|ci)…p(wN|ci)时,由于大部分因子都很小,所以程序会下溢出或者得到不正确的答案,(可以用python尝试相乘许多很小的数,最后四舍五入会得到0)。于是可以通过求对数避免下溢出或者浮点数舍入导致的错误(ln(a*b) = ln(a) + ln(b))。通过修改return前的两行代码:

p1Vect = log(p1Num/p1Denom) #change to log() 
p0Vect = log(p0Num/p0Denom)

以下代码中,第一个函数的4个输入参数为:要分类的向量vec2Classify以及函数trainNB0()返回的三个概率,最后返回大概率对应的类别标签。第二个函数是一个便利函数,封装所有操作,节省输入代码的时间。

#朴素贝叶斯分类函数
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1): #(要分类的向量,以及trainNB0得到的三个概率)
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)  #对应元素相乘,求和,将该值加到类别的对数概率上
    p0 = sum(vec2Classify * p0Vec) + log(1.0 - 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(array(trainMat),array(listClasses))
    testEntry = ['love','my','dalmation'] #测试词条
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry)) #返回词汇表的01序列
    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))

运行结果为:

>>>['love', 'my', 'dalmation'] classified as: 0
['stupid', 'garbage'] classified as: 1

接下来,对代码做些修改,使分类器工作得更好。

4、准备数据:文档词袋模型
词集模型:将每个词的出现与否作为一个特征。
词袋模型:如果一个词在文档中出现不止一次,这可能意味着包含该词是否出现在文档中所不能表达的某种信息。
在词袋中,每个单词可以出现多次,而在词集中,每个词只能出现一次。
为适应词袋模型,需要对函数setOfWords2Vec()稍加修改,将修改后的函数称为bagOfWords2Vec(),唯一的不同就是每当遇到一个单词时,它会增加词向量中的对应值,而不只是将对应的数值设为1。

def bagOfWords2VecMN(vocabList, inputSet): #输入参数(词汇表,某文档)
    returnVec = [0]*len(vocabList)  #创建一个其中所含元素都为0的向量
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1
    return returnVec #统计每个单词出现的次数

现在分类器已经构建好了,下面我们将利用该分类器来过滤垃圾邮件。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值