分类
每个人每天都会进行很多次的分类操作。比如,当你看到一个陌生人,你的大脑中的分类器就会根据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,其中:
- 垃圾邮件占比60%,即 P(Spam) = 0.6
- 80%的垃圾邮件包含关键字“buy”,即 P(Buy|Spam) = 0.8
- 20%的垃圾邮件不包含关键字“buy”
- 正常邮件占比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 | 1 | 6 | 180 | 12 |
2 | 1 | 5.92 | 190 | 11 |
3 | 1 | 5.58 | 170 | 12 |
4 | 1 | 5.92 | 165 | 10 |
5 | 0 | 5 | 100 | 6 |
6 | 0 | 5.5 | 150 | 8 |
7 | 0 | 5.42 | 130 | 7 |
8 | 0 | 5.75 | 150 | 9 |
已知某人身高6英尺,体重130磅,脚掌8英寸,请问此人是男是女?
Spark的 MLlib 中有一种特殊的数据结构,LabeledPoint,它由 label 和 features 两部分组成,其中 label 是 Double 类型,features 是由 Double 类型的数据组成的集合 Vector。具体到我们要做的分类操作,label 是类别,features 是特征集合。由于数据结构的要求,我们把性别(男,女)转为(1,0)。
将上面表格中的数据存储在 input/human-body-features.txt 文件中,格式为“性别,身高 体重 脚掌”,即 label 和 features 用逗号分割,feature 和 feature 之间用空格分割:
1 | $ cat input/human-body-features.txt |
下面是使用Spark来对这组数据进行分类训练并对新数据进行预测的Scala代码:
1 | package nbayes |
我用的IDE是 Scala IDE(基于 eclipse),使用 maven 管理依赖,下面是 pom.xml 文件中的依赖部分:
1 | <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),执行下面的命令:
1 | $ spark-submit --class nbayes.bayes1 --master local spark-ml.jar |
参数 class 指定完整类名,参数 master 指定 Spark 的运行模式,由于本例比较简单,数据量较小,且存储在本地(没有存储在 HDFS 上),因此使用 local 模式运行 Spark。
运行的结果是:
1 | 预测此人性别是:女 |
与维基百科以及阮一峰的推演结果相同,对算法细节和推演过程感兴趣的朋友,请参阅文后的参考链接。
分类器的质量评价
可以根据一个分类器的准确率来评价它的质量好坏。分类器的准确率是指,分类器正确分类的项目占所有被分类项目的比率。
通常的做法是,在构造初期,将训练数据一分为二(trainDataSet 和 testDataSet),用 trainDataSet 来构造分类器,用 testDataSet 来检测分类器的准确率。
1 | // 把全部数据按照一定比例分成两份 |