1. 广义线性模型
1. 算法介绍
与线性回归假设输出服从高斯分布不同, 广义线性模型(GLMs)指定先行模型的因变量 Y¡ 服从指数型分布。Spark的GeneralizedLinearRegression接口允许指定GLMs包括线性回归、泊松回归、逻辑回归等来处理多种预测问题。 目前spark.ml 仅支持指数型分布家族中的一部分如下:
Family | Response Type | Supported Links |
---|---|---|
Gaussian | Continuous | Identity*, Log, Inverse |
Binomial | Binary | Logit*, Probit, CLogLog |
Poisson | Count | Log*, Identity, Sqrt |
Gamma | Continuous | Inverse*, Idenity, Log |
Tweedie | Zero-inflated continuous | Power link function |
*注意目前Spark在 GeneralizedLinearRegression 仅支持最多4096个特征, 如果特征超过4096个将会发生异常。对于线性回归和逻辑回归,如果模型特征数量不断增长, 则可通过LinerRegression 和logisticRegression来训练。
GLMs 桥求指数型分布可以为正则或者自然数形式。 自然指数分布为如下形式:$f_{Y(y|\theta,\tau)}=h(y,\tau) exp (\frac{\theta*y-A(\theta)}{d(\tau)})$ 其中 $\theta$ 是强度参数,$\tau$ 是分散度参数。
在 GLM 中响应变量 $Y_i$ 服从自然指数族分布:$y_i \sim f(.|\theta_i,\tau)$
其中强度参数 $\theta_i$ 与响应变量 $\mu_i$的期望值联系如下:$\mu_i = A’(\theta_i)$
其中 $A’(\theta_i)$ 由所选择的分布形式决定。
GLMs 同样允许指定连接函数,连接函数决定了响应变量期望值与现行预测期之间的关系:$g(\mu_i) = \eta_i = x_i^T \beta$
通常,连接函数选择:如 $A’ = g^{-1}$ 在强度参数矛现行预测期之间产生一个简单的关系。
这种情况下,连接函数也称为正则连接函数 $\theta_i = A’^{-1}(\mu_i) = g(g^{-1}(\eta_i)) = \eta_i$
Spark的GeneralizedLinearRegression接口提供汇总统计来诊断GLM模型的你和程度, 包括残差, P值。Akaike信息准则以及其它。
2. 参数说明
参数名称 | 类型 | 说明 |
---|---|---|
featuresCol | 字符串 | 特征列名 |
labelCol | 字符串 | 标签列名 |
predictionCol | 字符串 | 预测结果列名 |
family | 字符串 | 模型中使用的误差分布类型 默认 :gaussian |
fitIntercept | 布尔 | 是否训练拦截对象 默认true |
link | 字符串 | 连接函数名,描述线性预测器和分布函数均值之间关系 在family 不是“tweedie” 时使用 |
linkPredictiongCol | 字符串 | 连接函数(线性预测器列名) |
maxIter | 整数 | 最多迭代次数(>=0) |
regParam | 双精度 | 正则化参数(>=0) |
solver | 字符串 | 优化的求解算法 |
tol | 双精度 | 迭代算法的收敛性 |
weightCol | 字符串 | 列权重 |
3. 代码示例
import hnbian.spark.utils.SparkUtils
import org.apache.spark.ml.regression.GeneralizedLinearRegression
import utils.FileUtils
/**
* @author hnbian 2019/1/17 15:17
*/
object GeneralizedLinearRegression extends App {
val spark = SparkUtils.getSparkSession("GeneralizedLinearRegression", 4)
val filePath = FileUtils.getFilePath("sample_linear_regression_data.txt")
//加载数据
val dataDF = spark.read.format("libsvm").load(filePath)
//查看数据
dataDF.show(false)
/**
* +-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
* |label |features |
* +-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
* |14.323146365332388 |(10,[0,1,2,3,4,5,6,7,8,9],[-0.2049276879687938,0.1470694373531216,-0.48366999792166787,0.643491115907358,0.3183669486383729,0.22821350958477082,-0.023605251086149304,-0.2770587742156372,0.47596326458377436,0.7107229819632654]) |
* |-20.057482615789212|(10,[0,1,2,3,4,5,6,7,8,9],[-0.3205057828114841,0.51605972926996,0.45215640988181516,0.01712446974606241,0.5508198371849293,-0.2478254241316491,0.7256483175955235,0.39418662792516,-0.6797384914236382,0.6001217520150142]) |
* |-3.2256352187273354|(10,[0,1,2,3,4,5,6,7,8,9],[0.35278245969741096,0.7022211035026023,0.5686638754605697,-0.4202155290448111,-0.26102723928249216,0.010688215941416779,-0.4311544807877927,0.9500151672991208,0.14380635780710693,-0.7549354840975826])|
* |1.5299675726687754 |(10,[0,1,2,3,4,5,6,7,8,9],[-0.13079299081883855,0.0983382230287082,0.15347083875928424,0.45507300685816965,0.1921083467305864,0.6361110540492223,0.7675261182370992,-0.2543488202081907,0.2927051050236915,0.680182444769418]) |
* |-0.250102447941961 |(10,[0,1,2,3,4,5,6,7,8,9],[-0.8062832278617296,0.8266289890474885,0.22684501241708888,0.1726291966578266,-0.6778773666126594,0.9993906921393696,0.1789490173139363,0.5584053824232391,0.03495894704368174,-0.8505720014852347]) |
* +-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
*/
val glr = new GeneralizedLinearRegression()
.setFamily("gaussian") //模型中使用的误差分布类型
.setLink("identity") //连接函数名,描述线性预测器和分布函数均值之间关系
.setMaxIter(10)
.setRegParam(0.3)
.setFitIntercept(false)
dataDF.persist()
val model = glr.fit(dataDF)
model.transform(dataDF).show()
/**
* +-------------------+--------------------+--------------------+
* | label| features| prediction|
* +-------------------+--------------------+--------------------+
* | -9.490009878824548|(10,[0,1,2,3,4,5,...| 1.4843492954223414|
* | 0.2577820163584905|(10,[0,1,2,3,4,5,...| -0.6294499974835653|
* | -4.438869807456516|(10,[0,1,2,3,4,5,...| 0.1576720300223913|
* |-19.782762789614537|(10,[0,1,2,3,4,5,...| 0.6289046454051012|
* | -7.966593841555266|(10,[0,1,2,3,4,5,...| 2.303825503787376|
* +-------------------+--------------------+--------------------+
*/
dataDF.unpersist()
// 打印广义线性回归模型的系数
println(s"Coefficients: ${model.coefficients}")
/**
* Coefficients: [0.010541828081257216,
* 0.8003253100560949,
* -0.7845165541420371,
* 2.3679887171421914,
* 0.5010002089857577,
* 1.1222351159753026,
* -0.2926824398623296,
* -0.49837174323213035,
* -0.6035797180675657,
* 0.6725550067187461]
*/
// 打印广义线性回归模型的截距
println(s"Intercept: ${model.intercept}")
//Intercept: 0.14592176145232041
// 打印一些训练模型的指标
val summary = model.summary
//获取拟合模型的默认残差(偏差残差)
summary.residuals().show(3)
/**
* +-------------------+
* | devianceResiduals|
* +-------------------+
* |-10.974359174246889|
* | 0.8872320138420559|
* | -4.596541837478908|
* +-------------------+
*/
/**
* 估计系数和截距的标准误差。
* 该值仅在使用“normal”求解器的基础`WeightedLeastSquares`时可用。
* 如果`GeneralizedLinearRegression.fitIntercept`设置为true,则返回的最后一个元素对应于截距。
*/
println(s"Coefficient Standard Errors: ${summary.coefficientStandardErrors.mkString(",")}")
//fitIntercept => true
/**
* Coefficient Standard Errors:
* 0.7950428434287478,
* 0.8049713176546897,
* 0.7975916824772489,
* 0.8312649247659919,
* 0.7945436200517938,
* 0.8118992572197593,
* 0.7919506385542777,
* 0.7973378214726764,
* 0.8300714999626418,
* 0.7771333489686802,
* 0.463930109648428
*/
//fitIntercept => false
/**
* Coefficient Standard Errors:
* 0.7941100255684304,
* 0.8031247068718659,
* 0.79662609636158,
* 0.8261662019965853,
* 0.7935816505866371,
* 0.8106214100004155,
* 0.7909712249109007,
* 0.7961347545897113,
* 0.82925509068747,
* 0.7758421050548494
*/
/**
* 估计系数和截距的T统计量。
* 此值仅在基础`WeightedLeastSquares`时可用
* 使用“normal”求解器。
*
* 如果`GeneralizedLinearRegression.fitIntercept`设置为true,
* 然后返回的最后一个元素对应于截距。
*/
println(s"T Values: ${summary.tValues.mkString(",")}")
/**
* T Values:
* 0.02032442997598987,
* 1.0130249701589424,
* -0.9772419958506912,
* 2.8985180905449903,
* 0.6237570818704646,
* 1.3730429273930078,
* -0.3621369326138608,
* -0.6152022734204684,
* -0.7313257944391011,
* 0.8789835279996452
*/
/**
* 估计系数和截距的双边p值。
* 此值仅在基础`WeightedLeastSquares`时可用
* 使用“normal”求解器。
*
* 如果`GeneralizedLinearRegression.fitIntercept`设置为true,
* 然后返回的最后一个元素对应于截距。
*/
println(s"P Values: ${summary.pValues.mkString(",")}")
/**
* P Values:
* 0.9837928240022737,
* 0.3115471660567466,
* 0.32893059312318096,
* 0.003917052014281275,
* 0.5330767956629119,
* 0.17036571617850105,
* 0.7174055133265125,
* 0.5387061676966605,
* 0.4649290456175268,
* 0.37984016058182113
*/
/**
* 拟合模型的分散。
* 对于 family 是 “binomial”和“poisson”,它被视为1.0,
* 并且通过剩余的 Pearson 的Chi-Squared统计量(其被定义为Pearson残差的平方和)除以剩余自由度来估计。
*/
println(s"Dispersion: ${summary.dispersion}")
//Dispersion: 105.4150330254249
//null 模型的偏差。
println(s"Null Deviance: ${summary.nullDeviance}")
//null Deviance: 53262.42735923456
//null 模型的剩余自由度
println(s"Residual Degree Of Freedom Null: ${summary.residualDegreeOfFreedomNull}")
//Residual Degree Of Freedom Null: 501
//打印训练模型的偏差
println(s"Deviance: ${summary.deviance}")
//Deviance: 51758.78121548363
//剩余的自由度
println(s"Residual Degree Of Freedom: ${summary.residualDegreeOfFreedom}")
//Residual Degree Of Freedom: 491
//拟合模型的Akaike信息准则(AIC)
println(s"AIC: ${summary.aic}")
//AIC: 3767.2857940376434
}
2. 决策树回归
2.1 算法介绍
算法介绍:
决策树以及其集成算法是机器学习分类和回归问题中非常流行的算法, 因其已解释性、可处理类别特征、易扩展到多分类、不需要特征缩放等性质被广泛使用。 树集成算法如随机森林以及boosting 算法几乎是解决分类和回归问题中表现最优的算法。
决策树是一个贪心算法递归地将特征空间划分为两部分, 在同一个叶子节点的数据最后会拥有同样的标签。 每次划分通过贪心的以获得最大信息增益为目的, 从可选择的分裂方式中选择最佳的分裂节点, 节点不纯度有节点所含类别的同质性来衡量。 同居提供为分类提供两种不纯度衡量(基尼不纯度和熵), 为回归提供一种不纯度衡量(方差)
spark.ml支持二分类、多分类以及回归的决策树算法,适用于连续特征以及类别特征。 另外, 对于分类问题, 工具可以返回属于每种类别的概率(类别条件概率), 对于回归问题工具可以返回预测在偏置样本上的方差。
2.2 参数介绍
参数名称 | 类型 | 说明 |
---|---|---|
featuresCol | 字符串 | 特征列名 |
labelCol | 字符串 | 标签列名 |
predictionCol | 字符串 | 预测结果列名 |
varianceCol | 字符串 | 预测的有偏样本偏差的列名 |
checkpointInterval | 整数 | 设置检查点间隔(>=1),或不设置检查点(-1) |
impurity | 字符串 | 计算信息增益的准则(不区分大小写) |
maxBins | 整数 | 连续特征离散化的最大数量,以及选择每个节点分裂特征的方式 |
maxDepth | 整数 | 树的最大深度(>=0) |
minInfoGain | 双精度 | 分裂节点时所需最小信息增益 |
minInstancesPerNode | 整数 | 分裂后自节点最少包含的实例数量 |
seed | 长整型 | 随机种子 |
2.3 调用示例
下面的例子导入LibSVM格式数据, 并将之划分为训练数据和测试数据。 使用第一部分数据进行训练, 剩下数据来测试。 训练之前我们使用了两种数据预处理芳芳来对特征进行转换, 并且添加了元数据到DataFrame
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.sql.SparkSession
/**
* Created by admin on 2018/4/25.
* 决策树回归
*/
object b_DecisionTreeRegressionModel extends App{
val conf = new SparkConf().setAppName("a_Tokenizer")
//设置master local[4] 指定本地模式开启模拟worker线程数
conf.setMaster("local[4]")
//创建sparkContext文件
val sc = new SparkContext(conf)
val spark = SparkSession.builder().getOrCreate()
sc.setLogLevel("Error")
val data = spark.read.format("libsvm").load("D:\\data\\sample_libsvm_data.txt")
import org.apache.spark.ml.Pipeline
import org.apache.spark.ml.evaluation.RegressionEvaluator
import org.apache.spark.ml.feature.VectorIndexer
import org.apache.spark.ml.regression.DecisionTreeRegressionModel
import org.apache.spark.ml.regression.DecisionTreeRegressor
//自动识别分类特征,并为它们编制索引。
//在这里,我们将具有> 4个不同值的要素视为连续的。
val featureIndexer = new VectorIndexer()
.setInputCol("features")
.setOutputCol("indexedFeatures")
.setMaxCategories(4)
.fit(data)
// 将数据分成训练和测试集(30%用于测试)。
val Array(trainingData, testData) = data.randomSplit(Array(0.7, 0.3))
// 训练 DecisionTree model.
val dt = new DecisionTreeRegressor()
.setLabelCol("label")
.setFeaturesCol("indexedFeatures")
// Chain indexer and tree in a Pipeline.
val pipeline = new Pipeline()
.setStages(Array(featureIndexer, dt))
// Train model. This also runs the indexer.
val model = pipeline.fit(trainingData)
// 进行预测
val predictions = model.transform(testData)
predictions.show(3)
/**
+-----+--------------------+--------------------+----------+
|label| features| indexedFeatures|prediction|
+-----+--------------------+--------------------+----------+
| 0.0|(692,[98,99,100,1...|(692,[98,99,100,1...| 0.0|
| 0.0|(692,[100,101,102...|(692,[100,101,102...| 0.0|
| 0.0|(692,[122,123,124...|(692,[122,123,124...| 0.0|
+-----+--------------------+--------------------+----------+
*/
// 选择要显示的示例行。
predictions.select("prediction", "label", "features").show(3)
/**
+----------+-----+--------------------+
|prediction|label| features|
+----------+-----+--------------------+
| 0.0| 0.0|(692,[98,99,100,1...|
| 0.0| 0.0|(692,[100,101,102...|
| 0.0| 0.0|(692,[122,123,124...|
+----------+-----+--------------------+
*/
// Select (prediction, true label) and compute test error.
val evaluator = new RegressionEvaluator()
.setLabelCol("label")
.setPredictionCol("prediction")
.setMetricName("rmse")
val rmse = evaluator.evaluate(predictions)
println("Root Mean Squared Error (RMSE) on test data = " + rmse)
//Root Mean Squared Error (RMSE) on test data = 0.23570226039551587
val treeModel = model.stages(1).asInstanceOf[DecisionTreeRegressionModel]
println("Learned regression tree model:\n" + treeModel.toDebugString)
/**
Learned regression tree model:
DecisionTreeRegressionModel (uid=dtr_4e64258e8cb8) of depth 1 with 3 nodes
If (feature 406 <= 0.0)
Predict: 0.0
Else (feature 406 > 0.0)
Predict: 1.0
*/
}
3. 随机森林回归
3.1 算法介绍
随机森林是决策树的继承算法。随机森林包含多个决策树来降低过拟合的风险。随机森林同样具有已解释性、可处理类别特征、易扩展到多分类问题、不需特征缩放等特性。
随机森林分别训练一系列决策树,所以训练过程是并行的。因算法中加入随机过程, 所以每个决策树又有少量区别。 通过合并每个树的预测结果来减少预测的方差, 提高在测试集上的性能表现。
随机性提现:
1,每次迭代时, 对原始数据进行二次抽样来获得不同的训练数据
2,对于每个树节点, 考虑不同的随机特征子集来进行分裂。
除此之外, 决策时的训练过程和单独决策树训练过程相同。
对新实例进行预测时,随机森林需要整合其各个决策树的预测结果。回归和分类问题的整合方式略有不同。
分类问题采取投票制, 每个决策树投票给一个类别, 获得最多投票的类别为最终结果。
回归问题每个树得到的预测结果为实数, 最终的预测结果为各个树预测结果的平均值。
spark.ml 支持二分类、多分类以及回归的随机森林算法, 适用于连续特征以及类别特征。
3.2 参数介绍
参数名称 | 类型 | 说明 |
---|---|---|
featuresCol | 字符串 | 特征列名 |
labelCol | 字符串 | 标签列名 |
predictionCol | 字符串 | 预测结果列名 |
probabilityCol | 字符串 | 类别条件概率预测结果列名 |
checkpointInterval | 整数 | 设置检查点间隔(>=1), 或不设置检查点(-1) |
featureSubsetStrategy | 字符串 | 每次分裂候选特征数量 |
impurity | 字符串 | 计算信息增益的准则(不区分大小写) |
maxBins | 整数 | 连续特征离散化的最大数量,以及选择每个节点分裂特征的方式 |
maxDepth | 整数 | 树的最大深度(>=0) |
minInfoGain | 双精度 | 分裂节点时所需最小信息增益 |
minInstancesPerNode | 整数 | 分裂后自节点最少包含的实例数量 |
numTrees | 整数 | 训练的树的数量 |
seed | 长整型 | 随机种子 |
subsamplingRate | 双精度 | 学习一棵决策树使用的训练数据比例,范围[0,1] |
thresholds | 双精度数组 | 多分类预测的阀值,以调整预测结果在各个类别的概率 |
3.3 调用示例
下面的例子导入LibSVM格式数据, 并将之划分为训练数据和测试数据, 使用第一部分数据进行训练, 剩下数据来测试。 训练之前我们使用了两种数据预测处理方法对特征进行转换,并且添加了元数据到DataFrame
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.sql.SparkSession
/**
* 随机森林回归
*/
object c_RandomForest extends App{
val conf = new SparkConf().setAppName("c_RandomForest")
//设置master local[4] 指定本地模式开启模拟worker线程数
conf.setMaster("local[4]")
//创建sparkContext文件
val sc = new SparkContext(conf)
val spark = SparkSession.builder().getOrCreate()
sc.setLogLevel("Error")
val data = spark.read.format("libsvm").load("F:\\data\\sample_libsvm_data.txt")
import org.apache.spark.ml.Pipeline
import org.apache.spark.ml.evaluation.RegressionEvaluator
import org.apache.spark.ml.feature.VectorIndexer
import org.apache.spark.ml.regression.{RandomForestRegressionModel, RandomForestRegressor}
//自动识别分类特征,并为它们编制索引。
//设置maxCategories,所以具有> 4个不同值的特征被视为连续的。
val featureIndexer = new VectorIndexer()
.setInputCol("features")
.setOutputCol("indexedFeatures")
.setMaxCategories(4)
.fit(data)
//将数据分成训练和测试集(30%用于测试)。
val Array(trainingData, testData) = data.randomSplit(Array(0.7, 0.3))
// Train a RandomForest model.
val rf = new RandomForestRegressor()
.setLabelCol("label")
.setFeaturesCol("indexedFeatures")
// 链式索引器和管道中的森林。
val pipeline = new Pipeline()
.setStages(Array(featureIndexer, rf))
// 训练模型。 也运行索引器。
val model = pipeline.fit(trainingData)
// 进行预测
val predictions = model.transform(testData)
predictions.show(3)
/**
+-----+--------------------+--------------------+----------+
|label| features| indexedFeatures|prediction|
+-----+--------------------+--------------------+----------+
| 0.0|(692,[98,99,100,1...|(692,[98,99,100,1...| 0.0|
| 0.0|(692,[123,124,125...|(692,[123,124,125...| 0.0|
| 0.0|(692,[125,126,127...|(692,[125,126,127...| 0.05|
+-----+--------------------+--------------------+----------+
*/
// 选择要显示的示例行。
predictions.select("prediction", "label", "features").show(3)
/**
+----------+-----+--------------------+
|prediction|label| features|
+----------+-----+--------------------+
| 0.0| 0.0|(692,[98,99,100,1...|
| 0.0| 0.0|(692,[123,124,125...|
| 0.05| 0.0|(692,[125,126,127...|
+----------+-----+--------------------+
*/
// 选择(预测,真实标签)并计算测试错误。
val evaluator = new RegressionEvaluator()
.setLabelCol("label")
.setPredictionCol("prediction")
.setMetricName("rmse")
val rmse = evaluator.evaluate(predictions)
println("Root Mean Squared Error (RMSE) on test data = " + rmse)
//Root Mean Squared Error (RMSE) on test data = 0.057983350761380115
val rfModel = model.stages(1).asInstanceOf[RandomForestRegressionModel]
println("Learned regression forest model:\n" + rfModel.toDebugString)
/**
Learned regression forest model:
RandomForestRegressionModel (uid=rfr_e0d106832036) with 20 trees
Tree 0 (weight 1.0):
If (feature 434 <= 0.0)
Predict: 0.0
Else (feature 434 > 0.0)
Predict: 1.0
Tree 1 (weight 1.0):
If (feature 490 <= 31.0)
If (feature 544 <= 156.0)
Predict: 0.0
Else (feature 544 > 156.0)
Predict: 1.0
Else (feature 490 > 31.0)
Predict: 1.0
Tree 2 (weight 1.0):
If (feature 462 <= 0.0)
If (feature 323 <= 251.0)
Predict: 0.0
Else (feature 323 > 251.0)
Predict: 1.0
Else (feature 462 > 0.0)
Predict: 1.0
Tree 3 (weight 1.0):
If (feature 461 <= 0.0)
Predict: 0.0
Else (feature 461 > 0.0)
Predict: 1.0
Tree 4 (weight 1.0):
If (feature 483 <= 0.0)
If (feature 514 <= 251.0)
Predict: 1.0
Else (feature 514 > 251.0)
Predict: 0.0
Else (feature 483 > 0.0)
Predict: 0.0
Tree 5 (weight 1.0):
If (feature 379 <= 0.0)
Predict: 0.0
Else (feature 379 > 0.0)
Predict: 1.0
Tree 6 (weight 1.0):
If (feature 490 <= 0.0)
If (feature 492 <= 160.0)
Predict: 0.0
Else (feature 492 > 160.0)
Predict: 1.0
Else (feature 490 > 0.0)
Predict: 1.0
Tree 7 (weight 1.0):
If (feature 379 <= 23.0)
Predict: 0.0
Else (feature 379 > 23.0)
If (feature 294 <= 254.0)
Predict: 1.0
Else (feature 294 > 254.0)
Predict: 0.0
Tree 8 (weight 1.0):
If (feature 434 <= 0.0)
If (feature 436 <= 0.0)
Predict: 0.0
Else (feature 436 > 0.0)
Predict: 1.0
Else (feature 434 > 0.0)
Predict: 1.0
Tree 9 (weight 1.0):
If (feature 490 <= 31.0)
If (feature 320 <= 253.0)
Predict: 0.0
Else (feature 320 > 253.0)
Predict: 1.0
Else (feature 490 > 31.0)
Predict: 1.0
Tree 10 (weight 1.0):
If (feature 406 <= 20.0)
If (feature 351 <= 204.0)
Predict: 0.0
Else (feature 351 > 204.0)
Predict: 1.0
Else (feature 406 > 20.0)
Predict: 1.0
Tree 11 (weight 1.0):
If (feature 406 <= 72.0)
Predict: 0.0
Else (feature 406 > 72.0)
Predict: 1.0
Tree 12 (weight 1.0):
If (feature 489 <= 0.0)
If (feature 407 <= 0.0)
Predict: 0.0
Else (feature 407 > 0.0)
Predict: 1.0
Else (feature 489 > 0.0)
Predict: 1.0
Tree 13 (weight 1.0):
If (feature 462 <= 0.0)
If (feature 627 <= 0.0)
Predict: 1.0
Else (feature 627 > 0.0)
Predict: 0.0
Else (feature 462 > 0.0)
Predict: 1.0
Tree 14 (weight 1.0):
If (feature 429 <= 0.0)
If (feature 273 <= 0.0)
Predict: 1.0
Else (feature 273 > 0.0)
Predict: 0.0
Else (feature 429 > 0.0)
Predict: 0.0
Tree 15 (weight 1.0):
If (feature 379 <= 23.0)
Predict: 0.0
Else (feature 379 > 23.0)
If (feature 497 <= 0.0)
Predict: 1.0
Else (feature 497 > 0.0)
Predict: 0.0
Tree 16 (weight 1.0):
If (feature 434 <= 0.0)
Predict: 0.0
Else (feature 434 > 0.0)
Predict: 1.0
Tree 17 (weight 1.0):
If (feature 379 <= 23.0)
Predict: 0.0
Else (feature 379 > 23.0)
Predict: 1.0
Tree 18 (weight 1.0):
If (feature 434 <= 0.0)
Predict: 0.0
Else (feature 434 > 0.0)
Predict: 1.0
Tree 19 (weight 1.0):
If (feature 490 <= 31.0)
Predict: 0.0
Else (feature 490 > 31.0)
Predict: 1.0
*/
}