如何在决策树中处理多个分类特征?

时间:2016-07-28 09:31:36

标签: scala apache-spark apache-spark-mllib

我从一本书中读到了一段关于二元决策树的代码。它在原始数据中只有一个分类特征,即字段(3),并且被转换为一个k(一个热编码)。

def PrepareData(sc: SparkContext): (RDD[LabeledPoint], RDD[LabeledPoint], RDD[LabeledPoint], Map[String, Int]) = {

  val rawDataWithHeader = sc.textFile("data/train.tsv")
  val rawData = rawDataWithHeader.mapPartitionsWithIndex { (idx, iter) => if (idx == 0) iter.drop(1) else iter }
  val lines = rawData.map(_.split("\t"))


  val categoriesMap = lines.map(fields => fields(3)).distinct.collect.zipWithIndex.toMap
  val labelpointRDD = lines.map { fields =>
     val trFields = fields.map(_.replaceAll("\"", ""))
     val categoryFeaturesArray = Array.ofDim[Double](categoriesMap.size)
     val categoryIdx = categoriesMap(fields(3))
     categoryFeaturesArray(categoryIdx) = 1
     val numericalFeatures = trFields.slice(4, fields.size - 1).map(d => if (d == "?") 0.0 else d.toDouble)
     val label = trFields(fields.size - 1).toInt
     LabeledPoint(label, Vectors.dense(categoryFeaturesArray ++ numericalFeatures))
  }

  val Array(trainData, validationData, testData) = labelpointRDD.randomSplit(Array(8, 1, 1))
  return (trainData, validationData, testData, categoriesMap)
}

我想知道如果原始数据中有多个分类特征,如何修改代码,让我们说字段(3),字段(5),字段(7)都是分类特征。

我修改了第一行:

def PrepareData(sc: SparkContext): (RDD[LabeledPoint], RDD[LabeledPoint], RDD[LabeledPoint], Map[String, Int], Map[String, Int], Map[String, Int], Map[String, Int]) =......

然后,我将另外两个字段转换为1-of-k编码,如下所示:

val categoriesMap5 = lines.map(fields => fields(5)).distinct.collect.zipWithIndex.toMap
val categoriesMap7 = lines.map(fields => fields(7)).distinct.collect.zipWithIndex.toMap
val categoryFeaturesArray5 = Array.ofDim[Double](categoriesMap5.size)
val categoryFeaturesArray7 = Array.ofDim[Double](categoriesMap7.size)
val categoryIdx3 = categoriesMap5(fields(5))
val categoryIdx5 = categoriesMap7(fields(7))
categoryFeaturesArray5(categoryIdx5) = 1
categoryFeaturesArray7(categoryIdx7) = 1

最后,我修改了LabeledPoint并返回如下:

LabeledPoint(label, Vectors.dense(categoryFeaturesArray ++ categoryFeaturesArray5 ++ categoryFeaturesArray7 ++ numericalFeatures))
return (trainData, validationData, testData, categoriesMap, categoriesMap5, categoriesMap7)

这是对的吗?

=============================================== ===

我遇到的第二个问题是:来自该书的以下代码,在trainModel中,它使用

  DecisionTree.trainRegressor(trainingData, categoricalFeaturesInfo, impurity, maxDepth, maxBins)

以下是代码:

def trainModel(trainData: RDD[LabeledPoint], impurity: String, maxDepth: Int, maxBins: Int): (DecisionTreeModel, Double) = {
   val startTime = new DateTime()
   val model = DecisionTree.trainClassifier(trainData, 2, Map[Int, Int](), impurity, maxDepth, maxBins)
   val endTime = new DateTime()
   val duration = new Duration(startTime, endTime)
   (model, duration.getMillis())
}

问题是:如果它具有前面提到的三个分类特征,我如何将categoricalFeaturesInfo传递给此方法?

我只想按照本书中的步骤,使用决策树自行构建预测系统。更具体地说,我选择的数据集有几个分类特征,如: 性别:男,女

教育:HS-grad,Bachelors,Master,PH.D,......

国家:美国,加拿大,英国,澳大利亚......

但我不知道如何将它们合并为一个categoryFeatures ++ numericalFeatures以放入Vector.dense(),将一个categoricalFeaturesInfo合并到DecisionTree.trainRegressor()

1 个答案:

答案 0 :(得分:2)

我不清楚你到底在做什么,但从一开始看起来就错了。

忽略通过从头开始实施单热编码重新发明轮子的事实,编码的全部要点是将分类变量转换为数字变量。这对于线性模型是必需的,但可以说在使用决策树时没有意义。

记住这一点,你有两个选择:

  • 索引没有编码的分类字段,并将索引功能传递给categoricalFeaturesInfo
  • 对热门编码分类功能并将其视为数值变量。

我认为前一种方法是正确的方法。后者应该在实践中起作用,但它只是人为地增加了维度而没有提供任何好处。它也可能与Spark实现使用的一些启发式方法相冲突。

您应该考虑使用ML Pipelines来提供所有必需的索引,编码和合并工具。