贝叶斯学习,spark mlib 朴素贝叶斯使用

贝叶斯算法属于监督类机器学习算法,他的原理很简单,但是他的用处很广泛。

先简单介绍一下算法原理:

如果预测两个球员比赛,谁胜谁负,再没有其他信息的情况下,我们可能会说 五五开吧。

但是,如果我们知道两个球员之前的比赛信息,就像解说员刚开始解说比赛的时候往往会说以往的对阵战绩是几胜几平几负。那么,我们会根据这个信息估计,球员A获胜的概率80%,比较看好他。

那么,我们估计这个结果的时候,恰巧与贝叶斯的算法原理吻合。

贝叶斯公式表示(摘自《概率论与数理统计》浙大版):

贝叶斯学习,spark mlib 朴素贝叶斯使用_第1张图片

ok,具体的推理证明就不讲了,估计我也讲不太明白:)

这里说样本空间有 n 个划分,那么这里的划分其实就是样本空间的分类,那么这个公式的意思就是说 事件 A 属于分类 Bi 的概率是怎么计算的。

分类就是计算属于哪个类别的概率最大。

公式原理是这样的,但是在分类的时候,对于每一个分类来讲,这个值是一定的,所以,具体计算的时候,我们只计算分子的值就可以。


朴素贝叶斯分类:


如果有一个待分类的 A:

A =(a1, a2, a3……an) ai 为 A 的一个特征属性,A 为一个向量

如果 ai 之间 相互独立 ,那么

P(A | Bi) = P(a1|Bi) * P(a2|Bi) * P(a3|Bi)……P(an|Bi)

那么贝叶斯公式的分子可以这么计算:

P(a1|Bi) * P(a2|Bi) * P(a3|Bi)……P(an|Bi) * P(Bi)

我们知道,n 个这样的概率相乘,可能会得到一个非常小的小数,如果用计算机表示,容易造成数字下溢。

代数中

ln(a * b) = ln(a) + ln(b)

而且 a * b 正比于 ln(a * b), 分类是选概率最大的类别,所以我们求

ln(P(a1|Bi) * P(a2|Bi) * P(a3|Bi)……P(an|Bi) * P(Bi)) = ln(P(a1|Bi)) + ln(P(a2|Bi)) + ln(P(a3|Bi)) +…… + ln(P(an|Bi))

这个的最大值即可


朴素贝叶斯分类的大概计算流程:

贝叶斯学习,spark mlib 朴素贝叶斯使用_第2张图片


我们以《机器学习实战》中的例子——垃圾邮件分类,介绍一下计算过程:

1、确定特征属性:

         先将所有的邮件中的单词提取出来,存到set里,以每个单词出现频率作为特征属性。假设该单词集的单词数为 m

2、清洗训练样本

         对于垃圾邮件和正常邮件两个类别的邮件分别计算出来每个邮件的特征属性,即:特征属性的单词出现在该邮件中,则特性向量置为1,否则置0。假设共有 n 个邮件。

         生成一个 n *m 的矩阵 A, A[i]为第i封邮件的特性向量

        每个邮件的所属类别则有一个 list B 存储,B[ i ] 为第 i 封邮件所属的类别,0:为正常邮件,1: 为垃圾邮件 

3、对每个训练样本计算对应不同类别的条件概率

        这个过程称为训练过程,使用训练样本训练出先验概率。

        p0num = zeros(numWords)         正常邮件结果 向量           
        p1Num = zeros(numWords)        垃圾邮件结果 向量
        p0Denom = 0.0                               正常邮件中的词的个数
        p1Denom = 0.0                              垃圾邮件中的词数

        for 训练样本 i :训练样本集

             如果 i 属于 正常邮件:

                     p0num += 样本 i 的特征向量

                     p0Denom +=  样本 i 的词的个数

            else:

                    p1num += 样本 i 的特征向量

                    p1Denom +=  样本 i 的词的个数


         p1 =   垃圾邮件个数/总的文档个数     垃圾邮件类别的概率 , 那么 正常邮件类别概率 p0 = 1 - p1

         p1Vect = p1Num/p1Denom               垃圾邮件每个词的先验概率向量
         p0Vect = p0num/p0Denom               正常邮件每个词的先验概率向量


有了上面的每个类别的概率,和每个词属于不同类别的先验概率,就可以进行分类了


4、计算分类样本 A 属于每个类别的后验概率

     之前我们分析,我们将概率乘法转换成 去 ln 之后的加法也是等效的。

     所以我们可以通过下面的方法进行计算不同类别预测的概率:

     p1 :A 属于垃圾邮件的预测概率

     p0 :A 属于正常邮件的预测概率

     p1 = sum(vecA* p1Vec) + log(pClass1)
     p0 = sum(vecA * p0Vec) + log(1.0 - pClass1)


5、确定分类:

     最后一步就非常简单了,比较 p0 p1 的大小即可,哪个概率大,邮件A 属于哪个类别。

具体的代码可以参考《机器学习实战》书中例子


下面我们通过 Spark mlib 的贝叶斯分类算法实现一下上面的例子,主要是学习一下mlib 的api如何使用。

object NaiveBayesEmailClassifier extends  App{
  //2 workers
  val sc = new SparkContext("local[2]","nb-email-spam")
  val dataDir = if (args.length == 1){
    args.head
  } else {
    "not specify input data dir"
  }

  createNaiveBayesModel(dataDir)

  //将文章的词转换成向量
  def setOfWord2Vec(allWordSet: Set[String] , fileWordList:Set[String])={
    val wSetSize = allWordSet.size
    var vocabArray = Array.fill(wSetSize)(0)
    val allWordArray = allWordSet.toArray
    fileWordList.foreach(x=>{
      if(allWordSet.contains(x)){
        val index = allWordArray.indexOf(x)
        vocabArray(index) = 1
      }
      else{
        println("the word: %s is not in my vocabulary!",x)
      }
    })
    vocabArray.map(x=>x.toDouble)
  }

  //文本分词
  def textParse(inputFile: File) = {
    var wordSet: Set[String] = Set()
    for(line <- Source.fromFile(inputFile,"latin1").getLines()){
      wordSet = wordSet | line.toLowerCase.split("\\W+").filter(x => x.length > 2).toSet
    }
    wordSet
  }

  //
  def createNaiveBayesModel(directory: String) = {
    val hamFiles = new File(directory + "/ham").listFiles()
    val spamFiles = new File(directory + "/spam").listFiles()
    var wordSet: Set[String] = Set()
    var classList: List[Int] = List()
    var fileWordList: List[Set[String]] = List()

    hamFiles.foreach(f => {
      val fs = textParse(f)
      wordSet = wordSet | fs
      fileWordList = fileWordList.::(fs)
      classList = classList.::(0)
    })

    spamFiles.foreach(f=>{
      val fs = textParse(f)
      wordSet = wordSet | fs
      fileWordList = fileWordList.::(fs)
      classList = classList.::(1)
    })

    val labelPoints = fileWordList.map{
      x=>
        val index = fileWordList.indexOf(x)
        val wordVec = setOfWord2Vec(wordSet,x)
        LabeledPoint(classList(index).toDouble,Vectors.dense(wordVec))
    }

    val lpRdd = sc.makeRDD(labelPoints)
    val splits = lpRdd.randomSplit(Array(0.7,0.3),seed = 11L)
    val training = splits(0)
    val test = splits(1)
    
    //训练模型
    val model = NaiveBayes.train(training)
   
    //测试模型
    val predictionAndLabel = test.map(p => (model.predict(p.features), p.label))
    val accuracy = 1.0 * predictionAndLabel.filter(x => x._1 == x._2).count() / test.count()
    println("sparm email classifier accuracy: "+accuracy)
  }

}

这里主要的难点在于如何构建向量的属性,这个需要根据具体的情况进行分析。

希望这里的两个例子能有所帮助。





你可能感兴趣的:(机器学习,spark)