决策树算法就是通过对已有明确结果的历史数据进行分析,寻找数据中的特征,并以此为依据对新产生的数据结果进行预测。决策树主要由下面三部分组成:
决策节点
:每个决策节点表示一个待分类的数据类别或属性,其中最顶部的是根决策节点。分支
:每一个分支都有一个新的决策节点。叶子节点
:每个叶子节点表示一种结果。本节课主要介绍 k-means 算法,并介绍一个简单案例。下面介绍贴近生活的一个决策树例子,方便大家理解。
小明最近由于用钱紧张,又想购买 iPhone 7 Plus,通过某种渠道发现某公司可以办理分期购,于是联系上这家公司。这家公司员工需要对小明的情况进行进一步审核。下面这张图大致可以表示该员工的审核过程。
这张图看似是一颗二叉树,只不过没有具体的量化值,就像 Java 基本语法的选择判断语句 if else ,如果 A,那么 B,否则 C。
说它“基本可以算”是因为图中的判定条件没有量化,如收入高中低等等,还不能算是严格意义上的决策树,如果将所有条件量化,则就变成真正的决策树了。
下面给出决策树的定义:
决策树是一个树结构(可以是二叉树或非二叉树)。其每个非叶节点表示一个特征属性上的测试,每个分支代表这个特征属性在某个值域上的输出,而每个叶节点存放一个类别。使用决策树进行决策的过程就是从根节点开始,测试待分类项中相应的特征属性,并按照其值选择输出分支,直到到达叶子节点,将叶子节点存放的类别作为决策结果。
更多的理解参见百度百科。
该项目难度一般,属于中等偏下难度,该项目适合有一定的 Spark 基础,对 MLib 有一定的了解,并热爱机器学习。
Covtype 数据集介绍页面:
在介绍页面点击 Data Set Description 可以查看对于数据集的字段解释,如下图所示。
对其简要解释如下:
数据集中共有 12 个特征,由 54 列数据组成。
字段名称 | 数据类型 | 量度单位 | 描述 |
---|---|---|---|
Elevation | 定量数据 | 米 | 海拔高度 |
Aspect | 定量数据 | 度 | 方位角 |
Slope | 定量数据 | 度 | 坡度 |
Horizontal_Distance_To_Hydrology | 定量数据 | 米 | 与最近水文特征的水平距离 |
Vertical_Distance_To_Hydrology | 定量数据 | 米 | 与最近水文特征的垂直距离 |
Horizontal_Distance_To_Roadways | 定量数据 | 米 | 与最近道路的水平距离 |
Hillshade_9am | 定量数据 | 0至255的索引 | 早上9:00光的投射度(夏至) |
Hillshade_Noon | 定量数据 | 0至255的索引 | 正午光的投射度(夏至) |
Hillshade_3pm | 定量数据 | 0至255的索引 | 下午3:00光的投射度(夏至) |
Horizontal_Distance_To_Fire_Points | 定量数据 | 米 | 与最近野火燃起点的距离 |
Wilderness_Area (4个二元列) | 定性数据 | 0或1(缺失/存在) | 荒野地区等级 |
Soil_Type (40个二元列) | 定性数据 | 0或1(缺失/存在) | 土壤类型等级 |
Cover_Type (7种) | 整数 | 0至7 | 森林覆盖类型等级 |
数据集的原始下载地址为:
实验楼已通过课程附件的形式,为您提供了该数据集的副本。请根据后续实验步骤进行下载。
covtype.data.gz 为压缩数据文件,大小约11M
。它记录了美国科罗拉多州不同地块森林植被特征:海拔、坡度、到水源的距离、遮阳情况和土壤类型,并且随同给出了地块的已知森林植被类型,有 581012 个样本。
Spark MLib 将特征向量抽象为 LabeledPoint ,它由一个包含对个特征值的 Spark MLib Vector 和一个称为标号( label )的目标值组成。该目标为 Double 类型,而 Vector 本质上是对多个 Double 类型值的抽象。这说明 LabeledPoint 只适用于数值型特征。但只要经过适当编码,LabeledPoint 也可用于类别型特征。
其中一种编码是 one-hot 或 1-of-n 编码。在这种编码中,一个有 N 个不同取值的类别型特征可以变成 N 个数值型特征,变换后的每个数值型特征的取值为 0 或 1 。在这 N 个特征中,有且只有一个取值为 1,其他特征取值都为 0 。
本实验的整体流程如下图所示:
决策树最重要的是决策树的构造。
所谓决策树的构造就是进行属性选择度量确定各个特征属性之间的拓扑结构。构造决策树的关键步骤是分裂属性。所谓分裂属性就是在某个节点处按照某一特征属性的不同划分构造不同的分支,其目标是让各个分裂子集尽可能地“纯”。决策树帮助我们把复杂的数据表示转换成相对简单的直观的结构。
决策树学习算法主要由三部分构成:
这里主要讲解决策树生成
。
决策树的生成算法有很多变形,这里介绍几种经典的实现算法:ID3 算法、C4.5 算法和 CART 算法。这些算法的主要区别在于分类结点上特征选择的选取标准不同。下面详细了解一下算法的具体实现过程。
ID3 算法(Iterative Dichotomiser 3 迭代二叉树3代)是一个由 Ross Quinlan 发明的用于决策树的算法。
这个算法是建立在奥卡姆剃刀的基础上:越是小型的决策树越优于大的决策树(简单理论)。尽管如此,该算法也不是总是生成最小的树形结构。而是一个启发式算法。奥卡姆剃刀阐述了一个信息熵的概念:
这个 ID3 算法可以归纳为以下几点:
C4.5 算法是由 Ross Quinlan 开发的用于产生决策树的算法。该算法是对 Ross Quinlan 之前开发的 ID3 算法的一个扩展。C4.5 算法产生的决策树可以被用作分类目的,因此该算法也可以用于统计分类。
相对于其他数据挖掘算法,决策树拥有的优势:
进入到实验环境,双击桌面上的 Xfce ,效果如下图:
下载 covtype.data.gz 数据集,请输入以下代码获取数据集:
wget http://labfile.oss.aliyuncs.com/courses/845/covtype.data.gz
gunzip covtype.data.gz
使用 head
命令显示前 10 行内容。
head covtype.data
使用 wc -l
命令显示行数,可以看到有 581012 行。
wc -l covtype.data
请在终端中输入如下代码:
spark-shell
Spark Shell 启动耗时较长,请耐心等候。
进入到 Spark 的 REPL 环境,相当于就是 Spark 的 Console。
下面我们就进入到实验的关键位置。
拓展:Spark textFile 进行数据的导入,将外部数据导入到 Spark 中来,并将其转换成 RDD。
键入如下命令:
val dataset = sc.textFile("/home/shiyanlou/covtype.data")
从图中我们可以看到我们引入了 /home/shiyanlou 下面的 covtype.data 文件。
在最后,我们也了解到了当前的数据格式为 RDD[String] 类型的数据。
现在我们可以看看当前 dataset 的数据样式。我们查看 dataset 的前十行数据。
拓展:这里使用到 RDD Action 操作,take(num)
函数,take 的作用就是获取 RDD 中下标 0 到 num-1 的元素。foreach 遍历,并打印。
键入命令:
dataset.take(10).foreach(println)
可以看到和我们之前用的 head
命令结果一致。
使用 import
命令导入依赖:
import org.apache.spark.mllib.linalg._
import org.apache.spark.mllib.regression._
然后我们将这些数据以逗号分割开,拆分数据,将其转换 Vector 格式。
val data = dataset.map { line =>
val values = line.split(',').map(_.toDouble)
val featureVector = Vectors.dense(values.init)
val label = values.last - 1
LabeledPoint(label, featureVector)
}
将数据分割为训练集(占 80% )、交叉检验集(CV,占 10% )和测试集(占 10% ),用 cache()
缓存常用的数据。
val Array(trainData, cvData, testData) =data.randomSplit(Array(0.8, 0.1, 0.1))
trainData.cache()
cvData.cache()
testData.cache()
结果如下图:
DecisionTree 实现也有几个超参数,我们需要为它们选择值。
和之前一样,训练集和 CV 集用于给这些超参数选择一个合适值。这里第三个数据集,也就是测试集,用于对基于选定超参数的模型期望准确度做无偏估计。模型在交叉检验集上的准确度往往有点过于乐观,不是无偏差的。
接下来我们开始建立模型:
键入命令:
import org.apache.spark.mllib.evaluation._
import org.apache.spark.mllib.tree._
import org.apache.spark.mllib.tree.model._
import org.apache.spark.rdd._
我们先来试试在训练集上构造一个 DecisionTreeModel 模型,参数采用默认值,并用 CV 集来计算结果模型的指标:
输入如下命令:
def getMetrics(model: DecisionTreeModel, data: RDD[LabeledPoint]):
MulticlassMetrics = {
val predictionsAndLabels = data.map(example =>
(model.predict(example.features), example.label)
)
new MulticlassMetrics(predictionsAndLabels)
}
结果如下图:
输入如下命令:
val model = DecisionTree.trainClassifier( trainData, 7, Map[Int,Int](), "gini", 4, 100)
在调用 trainClassifier 方法时,除训练集以外,填入的参数分别是:
7
:设定分类数目。Map[Int, Int]()
:设定输入数据的格式。"gini"
:设定信息增益计算方式,此处使用了 gini 不纯度。4
:设定树的高度(Depth)。100
:设定分裂数据集(Bin)数量。创建模型后最后会有如下输出:
注意
:这里我们使用 trainClassifier,而不是 trainRegressor,trainClassifier指示每个 LabeledPoint 里的目标应该当做不同的类别指标,而不是数值型特征。
接下来用 CV 集来计算结果模型的指标。
输入如下命令:
val metrics = getMetrics(model, cvData)
结果如下:
查看混淆矩阵:
计算过程需要一点时间,请耐心等待。
metrics.confusionMatrix
结果如下:
注意
:你得到的值可能稍有不同,构造决策树过程中的一些随机选项会导致分类结果稍有不同。
矩阵每一行对应一个实际的正确类别值,矩阵每一列按序对应预测值。
第 i 行第 j 列的元素代表一个正确类别为 i 的样本被预测为类别为 j 的次数。
因此,对角线上的元素代表预测正确的次数,而其他元素则代表预测错误的次数。对角线上的次数大多是好的,但也出现了一些分类错误的情况,比如上面结果中没将任何一个样本类别预测为 7。
查看预测准确度:
metrics.precision
每个类别相对其他类别的精确度:
(0 until 7).map(
p => (metrics.precision(p), metrics.recall(p))
).foreach(println)
可以看到每个类型准确度各有不同。
def classPro(data: RDD[LabeledPoint]): Array[Double] = {
val cb = data.map(_.label).countByValue()
//排序
val random = cb.toArray.sortBy(_._1).map(_._2)
//取样本数
random.map(_.toDouble / random.sum)
}
把训练集和 CV 集中的某个类别的概率结成对,相乘然后相加:
val trainPro = classPro(trainData)
val cvPro = classPro(cvData)
//把训练集和CV集中的某个类别的概率结成对,相乘然后相加
trainPro.zip(cvPro).map {
case (trainProb, cvProb) => trainProb * cvProb
}.sum
3.5.2节
的预测准确度远高于3.5.3节
预测准确度,因为3.5.2节
将数据分割为训练集(占 80%)、交叉检验集(CV,占 10%)和测试集(占 10%),而3.5.3节
是随机的。如果感兴趣的话,您可以在3.5.2节
的基础上取不同的参数进行调优,或者调整数据切分比例,进行再次测试,准确度应该还会提高。
检验分类模型的方式便是通过该模型对测试集进行预测,因为测试集中的样本并没有参与训练过程。
此处我们直接使用由 3.5.2 小节部分调整得到的模型参数,但在输入训练集时,我们将交叉验证集也加入进来。你也可以重复 3.5.3 小节的过程,通过迭代地更改信息增益计算方式、树的最大高度和分裂数据集数量等参数来获得更优的模型。
val model = DecisionTree.trainClassifier(trainData.union(cvData), 7, Map[Int,Int](), "entropy", 4, 100)
最后利用测试集对该模型进行验证。
val metrics = getMetrics(model, testData)
metrics.precision
得到的预测准确率如下图所示(仅供参考):
注意:由于内存限制,此处的参数并不是最优值,故得到的预测准确率不是最高值,仅供参考过程。通过持续调优和足够的计算条件,该测试集的预测准确率可以达到 90% 。
决策树的生成过程主要分为特征选择,决策树生成,剪枝。本节课主要讲解了决策树算法,在此基础上运用于数据集。决策树算法既可以作为分类算法,也可以作为回归算法,同时也特别适合集成学习比如随机森林。