当前位置 博文首页 > 文章内容

    通过源码分析朴素贝叶斯

    作者: 栏目:未分类 时间:2020-07-25 9:01:06

    本站于2023年9月4日。收到“大连君*****咨询有限公司”通知
    说我们IIS7站长博客,有一篇博文用了他们的图片。
    要求我们给他们一张图片6000元。要不然法院告我们

    为避免不必要的麻烦,IIS7站长博客,全站内容图片下架、并积极应诉
    博文内容全部不再显示,请需要相关资讯的站长朋友到必应搜索。谢谢!

    另祝:版权碰瓷诈骗团伙,早日弃暗投明。

    相关新闻:借版权之名、行诈骗之实,周某因犯诈骗罪被判处有期徒刑十一年六个月

    叹!百花齐放的时代,渐行渐远!



    通过源码分析朴素贝叶斯

    通过源码分析朴素贝叶斯

    手写朴素贝叶斯源码

      1 from numpy import *
      2 from functools import reduce
      3 
      4 # 广告、垃圾标识
      5 adClass = 1
      6 
      7 
      8 def loadDataSet():
      9     """加载数据集合及其对应的分类"""
     10     wordsList = [['周六', '公司', '一起', '聚餐', '时间'],
     11                  ['优惠', '返利', '打折', '优惠', '金融', '理财'],
     12                  ['喜欢', '机器学习', '一起', '研究', '欢迎', '贝叶斯', '算法', '公式'],
     13                  ['公司', '发票', '税点', '优惠', '增值税', '打折'],
     14                  ['北京', '今天', '雾霾', '不宜', '外出', '时间', '在家', '讨论', '学习'],
     15                  ['招聘', '兼职', '日薪', '保险', '返利']]
     16     # 1 是, 0 否
     17     classVec = [0, 1, 0, 1, 0, 1]
     18     return wordsList, classVec
     19 
     20 
     21 # python中的& | 是位运算符   and or是逻辑运算符 当and的运算结果为true时候返回的并不是true而是运算结果最后一位变量的值
     22 # 当and返回的结果是false时候,如果A AND B 返回的是第一个false的值,如果a为false 则返回a,如果a不是false,那么返回b
     23 # 如果a or b 为true时候,返回的是第一个真的变量的值,如果a,b都为真时候那么返回a 如果a为假b为真那么返回b
     24 # a & b a和b为两个set,返回结果取a和b的交集  a|b a和b为两个set,返回结果为两个集合的不重复并集
     25 def doc2VecList(docList):
     26     # 从第一个和第二个集合开始进行并集操作,最后返回一个不重复的并集
     27     a = list(reduce(lambda x, y: set(x) | set(y), docList))
     28     return a
     29 
     30 
     31 def words2Vec(vecList, inputWords):
     32     """把单子转化为词向量"""
     33     # 转化成以一维数组
     34     resultVec = [0] * len(vecList)
     35     for word in inputWords:
     36         if word in vecList:
     37             # 在单词出现的位置上的计数加1
     38             resultVec[vecList.index(word)] += 1
     39         else:
     40             print('没有发现此单词')
     41 
     42     return array(resultVec)
     43 
     44 
     45 def trainNB(trainMatrix, trainClass):
     46     """计算,生成每个词对于类别上的概率"""
     47     # 类别行数
     48     numTrainClass = len(trainClass)
     49     # 列数
     50     numWords = len(trainMatrix[0])
     51 
     52     # 全部都初始化为1, 防止出现概率为0的情况出现
     53     # 见于韩家炜的数据挖掘概念与技术上的讲解,避免出现概率为0的状况,影响计算,因为在数量很大的情况下,在分子和分母同时+1的情况不会
     54     # 影响主要的数据
     55     p0Num = ones(numWords)
     56     p1Num = ones(numWords)
     57     # 相应的单词初始化为2
     58     # 为了分子分母同时都加上某个数λ
     59     p0Words = 2.0
     60     p1Words = 2.0
     61     # 统计每个分类的词的总数
     62     # 训练数据集的行数作为遍历的条件,从1开始
     63     # 如果当前类别为1,那么p1Num会加上当前单词矩阵行数据,依次遍历
     64     # 如果当前类别为0,那么p0Num会加上当前单词矩阵行数据,依次遍历
     65     # 同时统计当前类别下单词的个数和p1Words和p0Words
     66     for i in range(numTrainClass):
     67         if trainClass[i] == 1:
     68             # 数组在对应的位置上相加
     69             p1Num += trainMatrix[i]
     70             p1Words += sum(trainMatrix[i])
     71         else:
     72             p0Num += trainMatrix[i]
     73             p0Words += sum(trainMatrix[i])
     74     # 计算每种类型里面, 每个单词出现的概率
     75     # 朴素贝叶斯分类中,y=x是单调递增函数,y=ln(x)也是单调的递增的
     76     # 如果x1>x2 那么ln(x1)>ln(x2)
     77     # 在计算过程中,由于概率的值较小,所以我们就取对数进行比较,根据对数的特性
     78     # ln(MN) = ln(M)+ln(N)
     79     # ln(M/N) = ln(M)-ln(N)
     80     # ln(M**n)= nln(M)
     81     # 注:其中ln可替换为log的任意对数底
     82     p0Vec = log(p0Num / p0Words)
     83     p1Vec = log(p1Num / p1Words)
     84     # 计算在类别中1出现的概率,0出现的概率可通过1-p得到
     85     pClass1 = sum(trainClass) / float(numTrainClass)
     86     return p0Vec, p1Vec, pClass1
     87 
     88 
     89 def classifyNB(testVec, p0Vec, p1Vec, pClass1):
     90     # 朴素贝叶斯分类, max(p0, p1)作为推断的分类
     91     # y=x 是单调递增的, y=ln(x)也是单调递增的。 , 如果x1 > x2, 那么ln(x1) > ln(x2)
     92     # 因为概率的值太小了,所以我们可以取ln, 根据对数特性ln(ab) = lna + lnb, 可以简化计算
     93     # sum是numpy的函数,testVec是一个数组向量,p1Vec是一个1的概率向量,通过矩阵之间的乘机
     94     # 获得p(X1|Yj)*p(X2|Yj)*...*p(Xn|Yj)*p(Yj)
     95     # 其中pClass1即为p(Yj)
     96     # 此处计算出的p1是用对数表示,按照上面所说的,对数也是单调的,而贝叶斯分类主要是通过比较概率
     97     # 出现的大小,不需要确切的概率数据,因此下述表述完全正确
     98     p1 = sum(testVec * p1Vec) + log(pClass1)
     99     p0 = sum(testVec * p0Vec) + log(1 - pClass1)
    100     if p0 > p1:
    101         return 0
    102     return 1
    103 
    104 
    105 def printClass(words, testClass):
    106     if testClass == adClass:
    107         print(words, '推测为:广告邮件')
    108     else:
    109         print(words, '推测为:正常邮件')
    110 
    111 
    112 def tNB():
    113     # 从训练数据集中提取出属性矩阵和分类数据
    114     docList, classVec = loadDataSet()
    115     # 生成包含所有单词的list
    116     # 此处生成的单词向量是不重复的
    117     allWordsVec = doc2VecList(docList)
    118     # 构建词向量矩阵
    119     # 计算docList数据集中每一行每个单词出现的次数,其中返回的trainMat是一个数组的数组
    120     trainMat = list(map(lambda x: words2Vec(allWordsVec, x), docList))
    121     # 训练计算每个词在分类上的概率, p0V:每个单词在非分类出现的概率, p1V:每个单词在是分类出现的概率
    122     # 其中概率是以ln进行计算的
    123     # pClass1为类别中是1的概率
    124     p0V, p1V, pClass1 = trainNB(trainMat, classVec)
    125     # 测试数据集
    126     testWords = ['公司', '聚餐', '讨论', '贝叶斯']
    127     # 转换成单词向量,32个单词构成的数组,如果此单词在数组中,数组的项值置1
    128     testVec = words2Vec(allWordsVec, testWords)
    129     # 通过将单词向量testVec代入,根据贝叶斯公式,比较各个类别的后验概率,判断当前数据的分类情况
    130     testClass = classifyNB(testVec, p0V, p1V, pClass1)
    131     # 打印出测试结果
    132     printClass(testWords, testClass)
    133 
    134     testWords = ['公司', '保险', '金融']
    135     # 转换成单词向量,32个单词构成的数组,如果此单词在数组中,数组的项值置1
    136     testVec = words2Vec(allWordsVec, testWords)
    137     # 通过将单词向量testVec代入,根据贝叶斯公式,比较各个类别的后验概率,判断当前数据的分类情况
    138     testClass = classifyNB(testVec, p0V, p1V, pClass1)
    139     # 打印出测试结果
    140     printClass(testWords, testClass)
    141 
    142 
    143 if __name__ == '__main__':
    144     tNB()

    源码拆解分析

    1、训练数据集的加载

    数据包括特征列标签

     1 def loadDataSet():
     2     """加载数据集合及其对应的分类"""
     3     wordsList = [['周六', '公司', '一起', '聚餐', '时间'],
     4                  ['优惠', '返利', '打折', '优惠', '金融', '理财'],
     5                  ['喜欢', '机器学习', '一起', '研究', '欢迎', '贝叶斯', '算法', '公式'],
     6                  ['公司', '发票', '税点', '优惠', '增值税', '打折'],
     7                  ['北京', '今天', '雾霾', '不宜', '外出', '时间', '在家', '讨论', '学习'],
     8                  ['招聘', '兼职', '日薪', '保险', '返利']]
     9     # 1 是, 0 否
    10     classVec = [0, 1, 0, 1, 0, 1]
    11     return wordsList, classVec

    2、制作所有词的一个列表

    注意:这里有一个小技巧:合并俩个list中的所有不重复元素的方法可以使用位运算符--|

    list(reduce(lambda x, y: set(x) | set(y), docList))
    1 def doc2VecList(docList):
    2     # 从第一个和第二个集合开始进行并集操作,最后返回一个不重复的并集
    3     a = list(reduce(lambda x, y: set(x) | set(y), docList))
    4     return a

    3、制作类似one-hot的矩阵,在单词出现位置的是出现的次数

     1 def words2Vec(vecList, inputWords):
     2     """把单子转化为词向量"""
     3     # 转化成以一维数组
     4     resultVec = [0] * len(vecList)
     5     for word in inputWords:
     6         if word in vecList:
     7             # 在单词出现的位置上的计数加1
     8             resultVec[vecList.index(word)] += 1
     9         else:
    10             print('没有发现此单词')
    11 
    12     return array(resultVec)

    4、核心代码:训练朴素贝叶斯

     1 def trainNB(trainMatrix, trainClass):
     2     """计算,生成每个词对于类别上的概率"""
     3     # 类别行数
     4     numTrainClass = len(trainClass)
     5     # 列数
     6     numWords = len(trainMatrix[0])
     7 
     8     # 全部都初始化为1, 防止出现概率为0的情况出现
     9     # 见于韩家炜的数据挖掘概念与技术上的讲解,避免出现概率为0的状况,影响计算,因为在数量很大的情况下,在分子和分母同时+1的情况不会
    10     # 影响主要的数据
    11     p0Num = ones(numWords)
    12     p1Num = ones(numWords)
    13     # 相应的单词初始化为2
    14     # 为了分子分母同时都加上某个数λ
    15     p0Words = 2.0
    16     p1Words = 2.0
    17     # 统计每个分类的词的总数
    18     # 训练数据集的行数作为遍历的条件,从1开始
    19     # 如果当前类别为1,那么p1Num会加上当前单词矩阵行数据,依次遍历
    20     # 如果当前类别为0,那么p0Num会加上当前单词矩阵行数据,依次遍历
    21     # 同时统计当前类别下单词的个数和p1Words和p0Words
    22     for i in range(numTrainClass):
    23         if trainClass[i] == 1:
    24             # 数组在对应的位置上相加
    25             p1Num += trainMatrix[i]
    26             p1Words += sum(trainMatrix[i])
    27         else:
    28             p0Num += trainMatrix[i]
    29             p0Words += sum(trainMatrix[i])
    30     # 计算每种类型里面, 每个单词出现的概率
    31     # 朴素贝叶斯分类中,y=x是单调递增函数,y=ln(x)也是单调的递增的
    32     # 如果x1>x2 那么ln(x1)>ln(x2)
    33     # 在计算过程中,由于概率的值较小,所以我们就取对数进行比较,根据对数的特性
    34     # ln(MN) = ln(M)+ln(N)
    35     # ln(M/N) = ln(M)-ln(N)
    36     # ln(M**n)= nln(M)
    37     # 注:其中ln可替换为log的任意对数底
    38     p0Vec = log(p0Num / p0Words)
    39     p1Vec = log(p1Num / p1Words)
    40     # 计算在类别中1出现的概率,0出现的概率可通过1-p得到
    41     pClass1 = sum(trainClass) / float(numTrainClass)
    42     return p0Vec, p1Vec, pClass1

    其实上面做的就是统计计数工作,最终计算我们需要的概率

    • 统计类别数--计算p(嫁) or p(不嫁)
      • numTrainClass = len(trainClass)
    • 统计所有的特征的总数
      • numWords = len(trainMatrix[0])  
    • 避免出现概率为0的状况,影响计算,因为在数量很大的情况下,在分子和分母同时+1的情况不会影响主要的数据
      • p0Num = ones(numWords)
      • p1Num = ones(numWords)
    • 单词初始化为2,为了分子分母同时都加上某个数λ
      • p0Words = 2.0
      • p1Words = 2.0
    • 同时统计当前类别下单词的个数和p1Words和p0Words (这个部分实际上就是在统计每个类别在的单词的数量,用于计算)
      • 1  for i in range(numTrainClass):
        2     if trainClass[i] == 1:
        3         # 数组在对应的位置上相加
        4         p1Num += trainMatrix[i]
        5         p1Words += sum(trainMatrix[i])
        6     else:
        7         p0Num += trainMatrix[i]
        8         p0Words += sum(trainMatrix[i])
    • 计算每一个词在该类别下的概率,实际上这个位置是矩阵的运算(对应元素之间的计算),p0Words是类别零中的所有单词的数量,p0Num 是一个计数矩阵,例子:[开心,哈哈,嘻嘻,蹦蹦,跳跳],p0Num [1,2,1,3,2] ,p0Words = 1+2+1+3+2=9,p0Vec  = log([1/9,2/9,1/9,3/9,2/9]) log运算是为了下面的计算
      • p0Vec = log(p0Num / p0Words)
      • p1Vec = log(p1Num / p1Words)
    •  计算一个类别的概率:P(嫁)P(不嫁)=1-P(嫁)
      • pClass1 = sum(trainClass) / float(numTrainClass)
    • 最终的返回值实际上就是上一篇blog中可求的三个量
      • p0Vec, p1Vec, pClass1

    5.预测过程:分类

     1 def classifyNB(testVec, p0Vec, p1Vec, pClass1):
     2     # 朴素贝叶斯分类, max(p0, p1)作为推断的分类
     3     # y=x 是单调递增的, y=ln(x)也是单调递增的。 , 如果x1 > x2, 那么ln(x1) > ln(x2)
     4     # 因为概率的值太小了,所以我们可以取ln, 根据对数特性ln(ab) = lna + lnb, 可以简化计算
     5     # sum是numpy的函数,testVec是一个数组向量,p1Vec是一个1的概率向量,通过矩阵之间的乘机
     6     # 获得p(X1|Yj)*p(X2|Yj)*...*p(Xn|Yj)*p(Yj)
     7     # 其中pClass1即为p(Yj)
     8     # 此处计算出的p1是用对数表示,按照上面所说的,对数也是单调的,而贝叶斯分类主要是通过比较概率
     9     # 出现的大小,不需要确切的概率数据,因此下述表述完全正确
    10     p1 = sum(testVec * p1Vec) + log(pClass1)
    11     p0 = sum(testVec * p0Vec) + log(1 - pClass1)
    12     if p0 > p1:
    13         return 0
    14     return 1

    核心的代码就是计算概率,最终根据概率的大小决定分类:

    • 对于原始公式的一个说明:
        • 在这里对于类别1和类别0的P(A)是相同的,所以不计算也不影响结果
        • P(B|A) 等价于P(A|B)*P(B) 取log运算 log(P(B|A))~ log(P(A|B)) + log(B) ~ i 从1到allwords sum(log(P(Ai|B))) + log(B)
        • 代码  p1 = sum(testVec * p1Vec) + log(pClass1)    前面的sum(testVec * p1Vec) 向量计算就是在计算P(A|B) ,后面的log(pClass1)就是计算log(B)
        • 最终计算得到p0和p1的概率,比较就可以做出分类
    • 对上面的预测的计算过程进行简单理解
      • 我们在训练过程中,就会得到一个关于allwords中各个词对于不同类别之间的一个贡献向量,表明各个词对类别的贡献程度
      • 测试过程中我们统计了一个样本的allwords的词语个数的一个向量,和我们之前准备好的贡献向量相乘,再加上一个类别的概率,就会将测试文本属于各个类别的概率计算出来
      • 比较各个类别概率的大小,就完成了分类的过程

    6.最终的结果展示:

    1 ['公司', '聚餐', '讨论', '贝叶斯'] 推测为:正常邮件
    2 ['公司', '保险', '金融'] 推测为:广告邮件