本章重点介绍如何使用PySpark
构建随机森林(RF)进行分类。 我们将了解它们的各个方面以及预测是如何发生的; 但在了解有关随机森林的更多信息之前,我们必须学习作为决策树(DT)的RF构建模块。决策树也用于分类/回归。但就准确性而言,由于各种原因,随机森林击败了DT分类器,我们将在本章后面介绍。让我们更多地了解决策树。
决策树属于监督的机器学习类别,并使用频率表进行预测。 决策树的一个优点是它可以处理分类和数字变量。 顾名思义,它以一种树形结构运作,并根据各种分割形成这些规则,最终进行预测。 决策树中使用的算法是由J. R. Quinlan开发的ID3。
树分支出的最顶层分割节点称为根节点; 在上面的例子中,Age
是根节点。圆圈中表示的值称为叶节点或预测。让我们采用一个样本数据集来理解决策树的实际工作方式。
表6-1中显示的数据包含来自不同年龄组和属性的人的一些样本数据。基于这些属性做出的最终决定是保险费是否应该更高。这是典型的分类案例,我们将使用决策树对其进行分类。 我们有四个输入栏(年龄组,吸烟者,医疗条件,薪资水平)。
Age Group | Smoker | Medical Condition | Salary Level | Insurance Premium |
---|---|---|---|---|
Old | Yes | Yes | High | High |
Teenager | Yes | Yes | Medium | High |
Young | Yes | Yes | Medium | Low |
Old | No | Yes | High | High |
Young | Yes | Yes | High | Low |
Teenager | No | Yes | Low | High |
Old | No | No | Low | High |
Teenager | No | No | Low | Low |
Teenager | No | Yes | Medium | High |
Young | No | Yes | Low | High |
Young | Yes | No | High | Low |
Teenager | Yes | No | Medium | Low |
Young | No | No | Medium | High |
Old | Yes | No | Medium | High |
决策树以这样的方式产生这些数据的子集,即每个子集包含相同的类值(同质的); 为了计算同质性,我们使用称为熵的东西。 这也可以使用其他指标(如基尼指数和分类错误)进行计算,但我们将采用熵来了解决策树的工作原理。 计算熵的公式是
−p log2p –q log2q
如果我们想要计算目标变量(Insurance Premium)的熵,我们必须首先计算每个类的概率,然后使用上面的公式来计算熵。
Insurance Premium | |
---|---|
High(9) Low(5) |
High分类的概率是 9/14 = 0.64
Low分类的概率是 5/14 = 0.36
熵 = -p(High)log2(p(High)) - p(Low)log2(p(Low))
= -(0.64 * log2(0.64)) - (0.36 * log2(0.36))
= 0.94
为了构建决策树,我们需要计算两种熵:
- 目标熵(保险费)
- 具有属性的目标熵(例如保险费 - 吸烟者)
我们已经看到了目标熵,所以让我们用输入特征计算目标的第二个熵。 例如,让我们考虑一下吸烟者的特征。
Entropy(Target, Feature) =
Sum(Probability(Feature) * Entropy(Categories))
Target-Insurance Premium/ Feature-Smoker | High(9) Low(5) |
---|---|
Smoker(Feature) Yes(7) | 3 4 |
Smoker(Feature) No(7) | 6 1 |
Entropy(Target, Feature) = P(yes) * Entropy(3,4) + P(no) * Entropy(6,1)
P(yes) = 7/14
P(no) = 7/14
Entropy(3,4) = -3/7*log2(3/7) - 4/7*log2(4/7) = 0.99
Entropy(6,1) = -6/7*log2(6/7) - 1/7*log2(1/7) = 0.59
Entropy(Target, Smoker) = 0.5 * 0.99 + 0.5 * 0.59 = 0.79
信息增益(Information Gain)用于在决策树中进行分割。 提供最大信息增益的属性用于分割子集。 信息增益告诉我们在进行预测方面哪个是最重要的特征。 就熵而言,IG是在分裂之前和分割特征之后目标的熵的变化。
Information Gain = Entropy(Target) - Entropy(Target, Feature)
Information Gain(Smoker) = Entropy(target) - Entropy(target, Smoker)
Information Gain(Age) = Entropy(target) - Entropy(target, Age)
Information Gain(Medical) = Entropy(target) - Entropy(target, Medical)
Information Gain(Salary) = Entropy(target) - Entropy(target, Salary)
我们可以观察到,Age
组属性给出了最大的信息增益; 因此决策树的根节点将是Age组
,第一次拆分发生在该属性上,如图6-3所示。
寻找提供最大信息增益的下一个属性的过程以递归方式继续,并且在决策树中进行进一步的分割。 最后,决策树可能看起来如图6-4所示。
决策树提供可以通过跟随根节点到任何叶节点而容易地转换成规则集是它的优点,因此可以容易地用于分类。有一组与决策树相关联的超参数,它们提供了更多以不同方式构建树的选项。 其中之一是Max Depth
,它允许我们决定决策树的深度; 树越深,树的裂缝越多,就有机会过度拟合
。
现在我们知道决策树是如何工作的,我们可以继续进入随机森林。顾名思义,随机森林由许多树组成:很多决策树。它们非常受欢迎,有时候是监督机器学习的首选方法。随机森林也可用于分类和回归
。他们结合了许多个人决策树的投票,然后用多数票预测班级,或者在回归的情况下取平均值。这非常有效,因为弱学习者最终聚在一起做出强有力的预测。重要性在于这些决策树的形成方式。 “随机”这个名称在RF中是有原因的,因为树是由随机的一组特征和一组随机的训练样例组成的。现在,每个决策树都使用一组不同的数据点进行训练,尝试学习输入和输出之间的关系,最终将其与其他决策树的预测相结合,这些决策树使用其他数据点集进行训练,从而得到随机森林。如果我们采用上面的类似示例并创建一个包含五个决策树的随机林。
现在,这些决策树中的每一个都使用了数据子集来进行训练以及一部分特征。 这也称为“Bagging”技术 - Bootstrap聚合。 关于预测的每种树类投票和具有最大投票的类是由随机森林分类器进行的最终预测。
随机森林的一些优点如下:
- 特征重要性:随机森林可以根据预测能力给出用于训练的每个特征的重要性。 这提供了一个选择相关功能并放弃较弱功能的绝佳机会。 所有要素的重要性总和总是等于1。
- 提高准确度:由于它从单个决策树中收集投票,因此与单个决策树相比,随机森林的预测能力相对较高。
- 较少过度拟合:单个分类器的结果得到平均或最大投票,因此降低了过度拟合的可能性。
随机森林的一个缺点是,与决策树相比,难以可视化,并且在构建多个单独的分类器时,在计算方面需要更多一些。
创建SparkSession
[In]: from pyspark.sql import SparkSession
[In]: spark=SparkSession.builder.appName('random_forest').getOrCreate()
读取数据,输入的是相对路径则要求在跟项目同一路径下。
[In]: df=spark.read.csv('affairs.csv',inferSchema=True,header=True)
探索性数据分析。
数据大小
[In]: print((df.count(), len(df.columns)))
[Out]: (6366, 6)
列名
[In]: df.printSchema()
[Out]: root
|-- rate_marriage: integer (nullable = true)
|-- age: double (nullable = true)
|-- yrs_married: double (nullable = true)
|-- children: double (nullable = true)
|-- religious: integer (nullable = true)
|-- affairs: integer (nullable = true)
查看前几行数据
[In]: df.show(5)
数理统计
[In]: df.describe().select('summary',
'rate_marriage',
'age',
'yrs_married',
'children',
'religious').show()
[In]: df.groupBy('affairs').count().show()
[In]: df.groupBy('rate_marriage').count().show()
[In]: df.groupBy('rate_marriage',
'affairs').count().orderBy('rate_marriage',
'affairs',
'count',
ascending=True).show()
[In]: df.groupBy('religious',
'affairs').count().orderBy('religious',
'affairs',
'count',
ascending=True).show()
[In]: df.groupBy('children',
'affairs').count().orderBy('children',
'affairs',
'count',
ascending=True).show()
[In]: df.groupBy('affairs').mean().show()
使用Spark 的 VectorAssembler
,将所有输入特征整合为一个向量。
[In]: from pyspark.ml.feature import VectorAssembler
我们需要将所有输入列组合成一个向量,该向量将作为模型的输入要素。 因此,我们选择需要用于创建单个特征向量的输入列,并将输出向量命名为要素。
[In]: df_assembler = VectorAssembler(inputCols=['rate_marriage',
'age',
'yrs_married',
'children',
'religious'],
outputCol="features")
[In}:df = df_assembler.transform(df)
[In]: df.printSchema()
[Out]:
root
|-- rate_marriage: integer (nullable = true)
|-- age: double (nullable = true)
|-- yrs_married: double (nullable = true)
|-- children: double (nullable = true)
|-- religious: integer (nullable = true)
|-- affairs: integer (nullable = true)
|-- features: vector (nullable = true)
正如我们所看到的,现在我们有一个名为features的额外列,它只是表示为单个密集向量的所有输入要素的组合。
[In]: df.select(['features','affairs']).show(10,False)
让我们只选择features列作为输入,并选择事务列作为训练随机森林模型的输出。
[In]: model_df=df.select(['features','affairs'])
我们必须将数据集拆分为训练和测试数据集,以便训练和评估随机森林模型的性能。 我们将其分成75/25比率,并在75%的数据集上训练我们的模型。 我们可以打印火车的形状并测试数据以验证尺寸。
[In]: train_df,test_df=model_df.randomSplit([0.75,0.25])
[In]: print(train_df.count())
[Out]: 4775
[In]: train_df.groupBy('affairs').count().show()
[Out]:
+-------+-----+
|affairs|count|
+-------+-----+
| 1 | 1560|
| 0 | 3215|
+-------+-----+
这确保了我们将目标类('affairs’) 在训练和测试集中处于平衡分布。
在这一部分中,我们使用诸如输入和状态之类的特征作为输出列来构建和训练随机森林模型。
[In]: from pyspark.ml.classification import RandomForestClassifier
[In]: rf_classifier=RandomForestClassifier(labelCol='affairs',
numTrees=50).fit(train_df)
有许多超参数可以设置来调整模型的性能,但是除了我们想要构建的决策树的数量之外,我们在这里选择了一些超级参数。
一旦我们在训练数据集上训练了我们的模型,我们就可以在测试集上评估其性能。
[In]: rf_predictions=rf_classifier.transform(test_df)
[In]: rf_predictions.show()
[Out]:
+-----------+-------+-----------------+-----------------+---------------+
|features |affairs|rawPrediction |probability |prediction |
+-----------+-------+-----------------+-----------------+---------------+
| | 1| |[0.2920 | 1.0|
预测表中的第一列是测试数据的输入特征。 第二列是测试数据的实际标签或输出。 第三列(rawPrediction
)表示两种可能输出的置信度。 第四列是每个类标签的条件概率,最后一列是随机森林分类器的预测。我们可以在预测列上应用groupBy
函数来找出对正负类的预测数量。
[In]: rf_predictions.groupBy('prediction').count().show()
[Out]:
+----------+-----+
|prediction|count|
+----------+-----+
| 0.0 | 1257|
| 1.0 | 334 |
+----------+-----+
为了评估这些预测,我们将导入classificationEvaluators
。
[In]: from pyspark.ml.evaluation import MulticlassClassificationEvaluator
[In]: from pyspark.ml.evaluation import BinaryClassificationEvaluator
[In]: rf_accuracy=MulticlassClassificationEvaluator(labelCol='affairs',
metricName='accuracy').evaluate(rf_predictions)
[In]: print('The accuracy of RF on test data is {0:.0%}'.format(rf_accuracy))
[Out]: The accuracy of RF on test data is 73%
[In]: rf_precision=MulticlassClassificationEvaluator(labelCol='affairs',
metricName='weightedPrecision').evaluate(rf_predictions)
[In]: print('The precision rate on test data is {0:.0%}'.format(rf_precision))
[Out]: The precision rate on test data is 71%
[In]: rf_auc=BinaryClassificationEvaluator(labelCol='affairs').evaluate(rf_predictions)
[In]: print( rf_auc)
[Out]: 0.738
正如前面部分所述,RF在预测能力方面给出了每个特征的重要性,并且找出对预测贡献最大的关键变量非常有用。
[In]: rf_classifier.featureImportances
[Out]:
(5,[0,1,2,3,4],[0.563965247822,0.0367408623003,0.243756511958,0.0657893200779,0.0897480578415])
我们使用了五个特征,并且可以使用特征重要性功能找到重要性。 要知道哪个输入要素映射到哪个索引值,我们可以使用元数据信息。
[In]: df.schema["features"].metadata["ml_attr"]["attrs"]
[Out]:
{{'idx': 0, 'name': 'rate_marriage'},
{'idx': 1, 'name': 'age'},
{'idx': 2, 'name': 'yrs_married'},
{'idx': 3, 'name': 'children'},
{'idx': 4, 'name': 'religious'}}
因此,从 yrs_married
后面的预测角度来看,rate_marriage
是最重要的特征。 最不重要的变量似乎是Age
。
有时,在训练模型之后,我们只需要调用模型进行预测,因此持久化模型对象并将其重用于预测是很有意义的。 这有两个部分。
- 保存模型
- 加载模型
[In]: from pyspark.ml.classification import RandomForestClassificationModel
[In]: rf_classifier.save("/home/jovyan/work/RF_model")
This way we saved the model as object locally.The next
step is to load the model again for predictions
[In]: rf=RandomForestClassificationModel.load("/home/jovyan/work/RF_model")
[In]: new_preditions=rf.transform(new_df)
新的预测表将包含具有模型预测的列。
在本章中,我们讨论了随机森林构建模块的过程,并在PySpark
中创建了一个ML模型,用于分类以及精度,精度和auc
等评估指标。 我们还介绍了如何在本地保存ML模型对象并将其重用于预测。