Spark机器学习1:朴素贝叶斯分类

本文原始地址

分类

每个人每天都会进行很多次的分类操作。比如,当你看到一个陌生人,你的大脑中的分类器就会根据TA的体貌特征、衣着举止,判断出TA是男是女,是穷是富等等。这就是分类操作。

其中,男人、女人、穷人、富人,这些是类别;那个陌生人,是个待分类项;把一个待分类项映射到一个类别的映射规则,就是一个分类器。

分类算法的任务就是构造出分类器。

贝叶斯定理

贝叶斯定理解决的是这样一个问题:已知在事件B发生的条件下,事件A的发生概率P(A|B),怎样得到事件A发生的条件下,事件B的发生概率 P(B|A)?贝叶斯定理为我们打通了从 P(A|B) 到 P(B|A) 的道路。

P(B|A) = P(A|B) × P(B) / P(A)

举例说明,假设已经有了100个 email,其中:

  1. 垃圾邮件占比60%,即 P(Spam) = 0.6
  • 80%的垃圾邮件包含关键字“buy”,即 P(Buy|Spam) = 0.8
  • 20%的垃圾邮件不包含关键字“buy”
  1. 正常邮件占比40%,即 P(NotSpam) = 0.4
  • 10%的正常邮件包含关键字“buy”,即 P(Buy|NotSpam) = 0.1
  • 90%的正常邮件不包含关键字“buy”

现在,第101个 email 进来了,它包含关键字“buy”,那么它是垃圾邮件的概率 P(Spam|Buy) 是多少?

P(Spam|Buy) = P(Buy|Spam) × P(Spam) / P(Buy)

P(Buy) = P(Buy|Spam) × P(Spam) + P(Buy|NotSpam) × P(NotSpam)

P(Spam|Buy) = (0.8 × 0.6) / (0.8 × 0.6 + 0.1 × 0.4) = 0.48 / 0.52 = 0.923

由此得出,这个 email 有92.3%的可能是一个垃圾邮件。

朴素贝叶斯分类

朴素贝叶斯分类之所以朴素,是因为它背后的分类思想真的很朴素:对于某个待分类项,它属于哪个类别的概率较高,就把它分到哪个类别。

上面的例子中,包含关键字“buy”的 email 也可能是正常邮件,但是概率只有7.7%,因此就把它分到垃圾邮件类别了。

Spark分类示例

首先明确任务,下面是一组人类身体特征的统计资料,数据来自维基百科。

性别(0女1男) 身高(英尺) 体重(磅) 脚掌(英寸)
1 6 180 12
1 5.92 190 11
1 5.58 170 12
1 5.92 165 10
0 5 100 6
0 5.5 150 8
0 5.42 130 7
0 5.75 150 9

已知某人身高6英尺,体重130磅,脚掌8英寸,请问此人是男是女?

Spark的 MLlib 中有一种特殊的数据结构,LabeledPoint,它由 label 和 features 两部分组成,其中 label 是 Double 类型,features 是由 Double 类型的数据组成的集合 Vector。具体到我们要做的分类操作,label 是类别,features 是特征集合。由于数据结构的要求,我们把性别(男,女)转为(1,0)。

将上面表格中的数据存储在 human-body-features.txt 文件中,格式为“性别,身高 体重 脚掌”,即 label 和 features 用逗号分割,feature 和 feature 之间用空格分割。

package nbayes

import org.apache.spark.SparkConf
import org.apache.spark.SparkContext
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.mllib.classification.NaiveBayes

object bayes1 extends App {

    val conf = new SparkConf()
    conf.setAppName("nbayes 1")

    val sc = new SparkContext(conf)

    // Spark 读取文本文件
    val rawtxt = sc.textFile("human-body-features.txt")

    // 将文本文件的内容转化为我们需要的数据结构 LabeledPoint
    val allData = rawtxt.map {
        line =>
            val colData = line.split(',')
            LabeledPoint(colData(0).toDouble,
                    Vectors.dense(colData(1).split(' ').map(_.toDouble)))
    }

    // 训练
    val nbTrained = NaiveBayes.train(allData)

    // 待分类的特征集合
    val txt = "6 130 8";
    val vec = Vectors.dense(txt.split(' ').map(_.toDouble))

    // 预测(分类)
    val nbPredict = nbTrained.predict(vec)

    println("预测此人性别是:" + (if(nbPredict == 0) "女" else "男"))
}

我用的IDE是 Scala IDE(基于 eclipse),使用 maven 管理依赖,下面是 pom.xml 文件中的依赖部分:

<dependencies>
    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-core_2.10</artifactId>
        <version>1.6.2</version>
    </dependency>

    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-mllib_2.10</artifactId>
        <version>1.6.2</version>
    </dependency>
</dependencies>

这里依赖了 spark-core 和 spark-mllib 两个组件,版本都是1.6.2,与我 安装的 Spark 版本相同。另外还要注意一个细节,这两个组件,以及它们所依赖的很多其他组件,使用的 Scala 版本都是 2.10,目前版本的 Scale IDE(4.4.1)默认的 Scala 版本是 2.11,编译时需要调整 Build Path。

接下来把工程导出为jar文件(例如 spark-ml.jar),执行下面的命令:

$ spark-submit --class nbayes.bayes1 --master local spark-ml.jar

参数 class 指定完整类名,参数 master 指定 Spark 的运行模式,由于本例比较简单,数据量较小,且存储在本地(没有存储在 HDFS 上),因此使用 local 模式运行 Spark。

运行的结果是:

预测此人性别是:女

与维基百科以及阮一峰的推演结果相同,对算法细节和推演过程感兴趣的朋友,请参阅文后的参考链接。

分类器的质量评价

可以根据一个分类器的准确率来评价它的质量好坏。分类器的准确率是指,分类器正确分类的项目占所有被分类项目的比率。

通常的做法是,在构造初期,将训练数据一分为二(trainDataSet 和 testDataSet),用 trainDataSet 来构造分类器,用 testDataSet 来检测分类器的准确率。

// 把全部数据按照一定比例分成两份
val divData = allData.randomSplit(Array(0.7, 0.3), seed = 13L)
// 一份用来构造分类器
val trainDataSet = divData(0)
// 一份用来检测分类器质量
val testDataSet = divData(1)

val nbTrained = NaiveBayes.train(trainDataSet)
// 根据 features 预测 label
val nbPredict = nbTrained.predict(testDataSet.map(_.features))

// 把预测得到的 label 和实际的 label 做对比
val predictionAndLabel = nbPredict.zip(testDataSet.map(_.label))
// 预测正确的项目,占所有项目的比率
val accuracy = 100.0 * predictionAndLabel.filter(x => x._1 == x._2).count() / testDataSet.count()

println("准确率:" + accuracy)

参考链接

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

推荐阅读更多精彩内容