机器学习实战-朴素贝叶斯

朴素贝叶斯(naive Bayes)法是是基于贝叶斯定理 和特征条件独立假设的分类方法,对于给定的训练数据集,首先基于特征条件独立假设学习输入/输出的联合分布概率;然后基于此模型,对给定的输入x,再利用贝叶斯定理求出其后验概率最大的输出y。
朴素贝叶斯以自变量之间的独立(条件特征独立)性和连续变量的正态性假设为前提,就会导致算法精度在某种程度上受影响。
朴素贝叶斯数学表达是下面这个贝叶斯公式:
在这里插入图片描述实际应用中可写成:
在这里插入图片描述
一、条件概率
条件概率(Condittional probability),就是指在事件B发生的情况下,事件A发生的概率,用P(A|B)来表示。
在这里插入图片描述
在这里插入图片描述二、全概率公式
全概率公式是指:如果事件 Y=y1,Y=y2,…,Y=yn 可构成一个完备事件组,即它们两两互不相容,其和为全集。则对于事件 X=x 有:
在这里插入图片描述朴素贝叶斯分类器通常有两种实现方式:一种基于贝努利模型实现,该实现方式中并考虑词在文档中出现的次数,只考虑出不出现,因此这个意义上相当于假设词是等权重的。一种基于多项式模型实现,考虑词在文档中的出现次数。
三、使用朴素贝叶斯进行文档分类
(1)使用Python进行文本分类
要从文本获得特征,需要首先拆分文本。具体如何做呢?这里的特征是来自文本的词条(token),一个词条是字符的任意组合。可以把词条相象成单词,也可以使用非单词词条,如URL,IP地址或者任意其他字符串。然后将每一个文本片段表示为一个词条向量,其中值为1表示词条出现在文档中,0表示词条未出现。
以在线社区留言为例。为了不影响社区的发展,我们要屏蔽侮辱性的言论,所以要构建一个快速过滤器,如果某条留言使用了负面或者侮辱性的语言,那么就将该留言标志为内容不当。过滤这类内容是一个很常见的需求。对此问题建立两个类型:侮辱类和非侮辱类,使用1和0分别表示。
(2)准备数据:从文本中构建词向量

  • 集合的操作
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述在这里插入图片描述
    具体可参考:https://www.cnblogs.com/sunyang945/p/7859962.html

     import numpy as np
    
     def loadDataSet(): #导入数据 #假设数据为最简单的6篇文章,每篇文章大概7~8个词汇左右,如下
     """
    
     :return:
      postingList - 实验样本切分的词条
     classVec - 类别标签向量
     """
     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] #对应上述6篇文章的分类结果,1为侮辱性,0为非侮辱性
     return postingList,classVec
    
     def createVocabList(dataSet):# 将所有文章中的词汇取并集汇总
         """
         将切分的实验样本词条整理成不重复的词条列表,也就是词汇表
         :param dataSet:整理的样本数据集
         :return:
          vocabSet - 返回不重复的词条列表,也就是词汇表
         """
         vocabSet = set([]) # 定义一个set(set存储的内容无重复)
         for document in dataSet:# 遍历导入的dataset数据,将所有词汇取并集存储至vocabSet中
             vocabSet = vocabSet | set(document) # | 符号为取并集,即获得所有文章的词汇表
         return list(vocabSet) #该函数输入参数为词汇表及某篇文章,输出为文档向量,向量每个元素为1或0,分别表示词汇表中的单词在输入文档中是否出现;
     
     def setOfWords2Vec(vocabList, inputSet):
         """
         根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0
         :param vocabList: createVocabList返回的列表
         :param inputSet:切分的词条列表
         :return:
          returnVec - 文档向量,词集模型
         """
         returnVec = [0]*len(vocabList) #构建一个0向量;
         for word in inputSet: # 遍历词汇表,如果文档中出现了词汇表中单词,则将输出的文档向量中对应值设为1,旨在计算各词汇出现的次数;
             if word in vocabList:
                 returnVec[vocabList.index(word)] = 1#因为上一段代码里,给的文章例子里的单词都是不重复的,如果有重复的单词的话,这段代码改写为 #returnVec[vocabList.index(word)] += 1更为合适;
           	  else: print("the word: %s is not in my Vocabulary!" % word)
     	 return returnVec#返回向量化后的某篇文章
     
     def trainNB0(trainMatrix,trainCategory):
         """
     
     :param trainMatrix: 训练文档矩阵,即setOfWords2Vec返回的returnVec构成的矩阵
     :param trainCategory:训练类别标签向量,即loadDataSet返回的classVec
     :return:
     p0Vect - 侮辱类的条件概率数组
     p1Vect - 非侮辱类的条件概率数组
     pAbusive - 文档属于侮辱类的概率
     """
     numTrainDocs = len(trainMatrix) #计算有多少篇文章
     numWords = len(trainMatrix[0]) #计算第一篇文档的词汇数
     pAbusive = sum(trainCategory)/float(numTrainDocs) #计算p(c_1),p(c_0)=1-p(c_1)
     p0Num = np.zeros(numWords) #构建一个空矩阵,用来计算非侮辱性文章中词汇数
     p1Num = np.zeros(numWords) #构建一个空矩阵,用来计算侮辱性文章中词汇数
     p0Denom = 0.0
     p1Denom = 0.0
     for i in range(numTrainDocs): #遍历每一篇文章,来求P(w|c)
         if trainCategory[i] == 1: #判断该文章是否为侮辱性文章
             p1Num += trainMatrix[i] #累计每个词汇出现的次数
             p1Denom += sum(trainMatrix[i]) #计算所有该类别下不同词汇出现的总次数
         else:#如果该文章为非侮辱性文章
             p0Num += trainMatrix[i]
             p0Denom += sum(trainMatrix[i])
     p1Vect = p1Num/p1Denom #计算每个词汇出现的概率P(w_i|c_1)
     p0Vect = p0Num/p0Denom #计算每个词汇出现的概率P(w_i|c_0)
     return p0Vect,p1Vect,pAbusive
    
       if __name__ == '__main__':
     	dataSet,classVec = loadDataSet()
     	vocabList = createVocabList(dataSet)
     	trainMatrix =[]
    	 for pl in dataSet:
        		 trainMatrix.append(setOfWords2Vec(vocabList, pl))
         p0Vect, p1Vect, pAbusive =trainNB0(trainMatrix, classVec)
         print(p0Vect)
         print(p1Vect)
         print(pAbusive)
    

开始 createVocabList()里return写进缩进里报了如下错误:
在这里插入图片描述
RuntimeWarning: invalid value encountered in true_divide 报错原因:因为在计算p0Vect和p1Vect时出现了分母为0的情况。
在这里插入图片描述
运行结果:vocabList = [‘him’, ‘my’, ‘please’, ‘mr’, ‘licks’, ‘quit’, ‘to’, ‘so’, ‘steak’, ‘dog’, ‘help’, ‘problems’, ‘cute’, ‘ate’, ‘stop’, ‘not’, ‘dalmation’, ‘stupid’, ‘love’, ‘has’, ‘maybe’, ‘how’, ‘I’, ‘buying’, ‘take’, ‘garbage’, ‘food’, ‘is’, ‘flea’, ‘posting’, ‘park’, ‘worthless’]
在这里插入图片描述 p0Vect存放的是每个单词属于类别0,也就是非侮辱类词汇的概率。p1Vect存放的就是各个单词属于侮辱类的条件概率。pAb就是先验概率。

(3)训练函数修正——避免p(wi|ci)=0的情况
 当某个类别下某个特征项划分没有出现过一次时,就会导致p(wi|ci)=0,0乘上其他的数结果为0。这就导致我们求得的条件概率结果不准确。为了解决这个问题,我们引入”拉普拉斯修正”,具体思路如下图所示:在这里插入图片描述其中|D|所有分类的样本总数,|DC|表示分类为C的样本总数。
 在本例中,为了避免上述影响,将所有词汇出现的次数初始化为1,将分母初始化为2。
在trainNB0中的第四行和第五行修改成:
p0Num = ones(numWords);p1Num = ones(numWords)
p0Denom = 2.0,p1Denom = 2.0
另一个问题是下溢出,这是由于太多很小的数想乘造成的。当计算乘积p(w0|ci)p(w1|ci))…p(wN|ci)时,由于大部分因子都很小,所以程序会下溢出或者得不到正确的答案。(读者可以用Python尝试相乘许多很小的数,最后四舍五入后会得到0),一种解决办法是对乘积取自然对数。在代数中有ln(a*b) = ln(a) + ln(b),于是通过求对数可以避免下溢出或者浮点数的四舍五入导致的错误。在计算概率的时候,对结果进行求对数,虽然结果会不相同,但是不影响最终结果。修改代码如下:
p1Vect = log(p1Num/p1Denom )
p0Vect =log( p0Num/p0Denom)
修改运行后报错:
TypeError: only length-1 arrays can be converted to Python scalars或者
TypeError: ‘module’ object is not callable
导入log模块时导入:from math import log时报错信息为TypeError: only length-1 arrays can be converted to Python scalars;导入模块log时导入:import math as log时报错信息为TypeError: ‘module’ object is not callable 出现错误的原因是math.log()不能对矩阵进行直接操作。

  • numpy.log和math.log
    求对数可以使用两个方法(即两个包):math和numpy
    1、使用math包在这里插入图片描述在这里插入图片描述在这里插入图片描述
    在这里插入图片描述1.1math以e,2,10为底的情况:
    在这里插入图片描述
    1.2 使用任意底数
    math.log(m,n)
    其中n为底数,m为真数(即幂),如以4为底8的对数
    在这里插入图片描述
    2、使用numpy包
    2.1以e,2,10为底的情况:
    在这里插入图片描述
    log下什么都不写默认是自然对数
    在这里插入图片描述
    2为底直接将2写在前面即可
    在这里插入图片描述
    2.2使用numpy任意数为底稍微麻烦一点点~,需要用到换底公式
    在这里插入图片描述
    最简单的方法就是换底之后以底数为自然数e,因为e不需要写出来,比如以3为底4的对数
    在这里插入图片描述
    (3)分类函数
    前面几段代码分别实现的文本的向量化,并计算出了P(wi|ci),P(ci),这个时候给定文章,将其向量化并计算P(c0|wi)和P(c1|wi),如果P(c0|wi)>P(c1|wi),则判断该文章为非侮辱性;如果P(c0|wi)<P(c1|wi),则判断该文章为侮辱性。前面已经给定了计算公式:
    在这里插入图片描述
    由于同意篇文章p(w)的结果相同,因此这里只要计算并比较分子的乘积P(w|ci)⋅P(ci)大小即可。代码如下所示:

     def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
     """
     函数说明:朴素贝叶斯分类器分类函数
     :param vec2Classify:待分类的词条数组
     :param p0Vec:侮辱类的条件概率数组
     :param p1Vec:非侮辱类的条件概率数组
     :param pClass1:文档属于侮辱类的概率
     :return:
     0 - 属于非侮辱类
     1 - 属于侮辱类
     """
     p1 = sum(vec2Classify * p1Vec) + log(pClass1)# 由于是取对数,所以乘积变为直接求和即可,注意这里list和list是无法相乘,vec2Classify需要为array格式
     p0 = sum(vec2Classify * p0Vec) + log(1-pClass1)
     if(p1>p0):
         return 1
     if(p0>p1):
         return 0
    

(4)文档词袋模型
目前为止,我们将每个词的出现与否作为一个特征,这可以被描述为词集模型(set-of-words-model)。如果一个词在文档中出现不止一次,这可能意味着包含该词是否出现在文档中所不能表达的某种信息,这种方法被称为词袋模型(bag-of-words model)。在词袋中,每个单词可以出现多次,而在词集中,每个词只能出现一次。为了适应词袋模型,需要对函数setOfWords2Vec()稍加修改,修改后的函数称为bagOfWords2Vec()
修改后的函数唯一不同的是每遇到一个单词,它会增加词向量中的对应值,而不只是将对应的数值设为1。

def bagOfWords2Vec(vocabList, inputSet):
    returnVec = [0]*len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1
    return returnVec
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值