MLLib实践Naive Bayes

引言

本文基于Spark (1.5.0) ml库提供的pipeline完整地实践一次文本分类。pipeline将串联单词分割(tokenize)、单词频数统计(TF),特征向量计算(TF-IDF),朴素贝叶斯(Naive Bayes)模型训练等。
本文将基于“20 NewsGroups” 数据集训练并测试Naive Bayes模型。这二十个新闻组数据集合是收集大约20,000新闻组文档,均匀的分布在20个不同的集合。我将使用'20news-bydate.tar.gz'文件,因为该数据集中已经将数据划分为两类:train和test,非常方便我们对模型进行训练和评价。

20news-bydate.tar.gz - 20 Newsgroups sorted by date; duplicates and some headers removed (18846 documents)

Naive Bayes算法介绍

NB算法属于有监督分类算法,对输入数据:

M表示输入样本容量,我们的目标是将其对号入座到某一个分类结果:

我们将选择可能性最大的那个分类结果,或者说概率最大的那个分类:

根据贝叶斯公式:![][bayes]
我们在分类时只需要考虑分子上的两项乘积,并由此可以得出结论:后验概率∝似然概率✖️先验概率(最大后验概率问题转化为最大似然问题)。
进一步地,Naive Bayes模型假设了似然函数的计算时简单地假设X的各维度之间独立,这样可以简化似然概率计算公式为:![][likeli]
即给定分类下某个输入X出现的概率等于该分类下输入X各个维度分别出现的概率乘积。
综上,在naive bayes算法框架下,对于某个输入X

  • 如果X属于某个分类y的概率的概率大于属于其它分类的概率,则判定该输入属于分类y;
  • X属于某个分类y的概率正比于分类y自身出现的概率✖️该分类y条件下X各个维度出现的概率的乘积。

那么,模型训练的目标就很明朗了,我们需要基于给定的训练样本计算出:

  • 各个分类的先验概率:
  • 训练样本中,每个分类条件下,输入各个维度出现的似然概率:![][likeli_i]

模型用于分类新数据的计算:
![][predict]


[bayes]: http://latex.codecogs.com/svg.latex?\begin{align}P(y|(x_i,...,x_N))\=\frac{P(x_1,...,x_N|y)P(y)}{P(x_1,...,x_N)}\end{align}
[likeli]:http://latex.codecogs.com/svg.latex?P(x_1,...,x_N|y)=\prod_i^NP(x_i|y)
[likeli_i]:http://latex.codecogs.com/svg.latex?\theta(i,k)=P(x_i|y_k),i\in{1,...,N}
[predict]:http://latex.codecogs.com/svg.latex?y{new}=\text{arg}max_y{P(y)\cdot\prod_iNP(x^{new}_i|y)}

spark mllib中算法流程

spark中对NaiveBayes算法的实现非常清晰明了,算法通过combineByKey计算每个分类下:
![][calc_pi]和![][calc_th_i_k]
[calc_pi]:http://latex.codecogs.com/svg.latex?p_k=\frac{\sum_{j=1}M\mathbb{I}(yj=y_k)}{M}
[calc_th_i_k]: http://latex.codecogs.com/svg.latex?\theta(,k)=\frac{\sum_{j=1}M\mathbb{I}(yj=y_k)\cdot{Xj}+\alpha}{\sum_{j=1}M{X^j}+\alpha\cdot{M}}

20 newsgroups实践

数据集分为train和test两组,分别用于训练和测试。每组数据都分为20类,每类数据存放在各自子文件下:

.
├── 20news-bydate-test
│   ├── alt.atheism
│   ├── comp.graphics
│   ├── comp.os.ms-windows.misc
│   ├── comp.sys.ibm.pc.hardware
│   ├── comp.sys.mac.hardware
│   ├── comp.windows.x
│   ├── misc.forsale
│   ├── rec.autos
│   ├── rec.motorcycles
│   ├── rec.sport.baseball
│   ├── rec.sport.hockey
│   ├── sci.crypt
│   ├── sci.electronics
│   ├── sci.med
│   ├── sci.space
│   ├── soc.religion.christian
│   ├── talk.politics.guns
│   ├── talk.politics.mideast
│   ├── talk.politics.misc
│   └── talk.religion.misc
└── 20news-bydate-train
    ├── alt.atheism
    ├── comp.graphics
    ├── comp.os.ms-windows.misc
    ├── comp.sys.ibm.pc.hardware
    ├── comp.sys.mac.hardware
    ├── comp.windows.x
    ├── misc.forsale
    ├── rec.autos
    ├── rec.motorcycles
    ├── rec.sport.baseball
    ├── rec.sport.hockey
    ├── sci.crypt
    ├── sci.electronics
    ├── sci.med
    ├── sci.space
    ├── soc.religion.christian
    ├── talk.politics.guns
    ├── talk.politics.mideast
    ├── talk.politics.misc
    └── talk.religion.misc

原始文档将经过如下流程训练得到NaiveBayes模型:



代码中的几点注解:

  • 各类数据根据所在的子文件夹来分类,我们在写代码时需要利用子文件夹名称,这时可以通过调用sc.wholeTextFiles(...)函数得到RDD(String,String)类型的原始数据,_1表示文件的绝对路径,_2表示该文件的内容。我们进一步从_1中截取出子文件夹的名称f.split("/").takeRight(2).head.
  • pipeline框架基于DataFrame,所有我们需要将RDD转为DataFrame:
import sqlContext.implicits._
labelNameAndData.toDF("id", "sentence").cache()```
- 所有的转换都使用ml提供的类,未做任何定制或改动,当前模型在测试集上的准确度为82%。

代码:
```scala
import org.apache.spark.ml.Pipeline
import org.apache.spark.ml.classification.NaiveBayes
import org.apache.spark.ml.evaluation.MulticlassClassificationEvaluator
import org.apache.spark.ml.feature.{HashingTF, IDF, Tokenizer}
import org.apache.spark.{Logging, SparkConf, SparkContext}


object NBTest extends App with Logging {
  def createRawDf(s: String) = {
    //sc.setLogLevel("INFO")
    val fileNameData = sc.wholeTextFiles(s)

    val uniqueLabels = Array("alt.atheism", "comp.graphics", "comp.os.ms-windows.misc", "comp.sys.ibm.pc.hardware", "comp.sys.mac.hardware", "comp.windows.x", "misc.forsale", "rec.autos", "rec.motorcycles", "rec.sport.baseball", "rec.sport.hockey", "sci.crypt", "sci.electronics", "sci.med", "sci.space", "soc.religion.christian", "talk.politics.guns", "talk.politics.mideast", "talk.politics.misc", "talk.religion.misc")
    val uniqueLabelsBc = sc.broadcast(uniqueLabels)

    val labelNameAndData = fileNameData
      .map { case (f, data) => (f.split("/").takeRight(2).head, data) }
      .mapPartitions {
        itrs =>
          val labelIdMap = uniqueLabelsBc.value.zipWithIndex.toMap
          itrs.map {
            case (labelName, data) => (labelIdMap(labelName), data)
          }
      }

    import sqlContext.implicits._
    labelNameAndData.toDF("id", "sentence").cache()

  }

  def createTrainPpline() = {
    val tokenizer = new Tokenizer().setInputCol("sentence").setOutputCol("words")

    val hashingTF = new HashingTF().setInputCol("words").setOutputCol("rawFeatures")

    val idf = new IDF().setInputCol("rawFeatures").setOutputCol("features")

    //val vecAssembler = new VectorAssembler().setInputCols(Array("features")).setOutputCol("id")

    val nb = new NaiveBayes().setFeaturesCol("features").setLabelCol("id")

    new Pipeline().setStages(Array(tokenizer, hashingTF, idf, nb))
  }

  val conf = new SparkConf().setMaster("local[2]").setAppName("nb")
    .set("spark.ui.enabled", "false")
  val sc = new SparkContext(conf)
  val sqlContext = new org.apache.spark.sql.SQLContext(sc)

  val training = createRawDf("file:////root/work/test/20news-bydate-train/*")

  val ppline = createTrainPpline()
  val nbModel = ppline.fit(training)

  val test = createRawDf("file:////root/work/test/20news-bydate-test/*")
  val testRes = nbModel.transform(test)

  val evaluator = new MulticlassClassificationEvaluator().setLabelCol("id")
  val accuracy = evaluator.evaluate(testRes)
  println("Test Error = " + (1.0 - accuracy))

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容

  • 首页 资讯 文章 资源 小组 相亲 登录 注册 首页 最新文章 IT 职场 前端 后端 移动端 数据库 运维 其他...
    Helen_Cat阅读 3,869评论 1 10
  • 一、实验目的 学习使用 weka 中的常用分类器,完成数据分类任务。 二、实验内容 了解 weka 中 explo...
    yigoh阅读 8,524评论 5 4
  • 注:题中所指的『机器学习』不包括『深度学习』。本篇文章以理论推导为主,不涉及代码实现。 前些日子定下了未来三年左右...
    我偏笑_NSNirvana阅读 39,968评论 12 145
  • 不计过往,勇敢前行……不要在小事上纠缠。快乐会传染
    简言恩阅读 132评论 0 0
  • “徒弟,以后行走江湖,千万小心那些背后背着刀的人。” “师傅,是因为他们都是习武之人吗?” “放屁!你不是习武之人...
    江大桥阅读 797评论 0 1