web-dev-qa-db-fra.com

Comment créer un bloc de données correct pour la classification dans Spark ML

J'essaie d'exécuter une classification aléatoire des forêts en utilisant Spark ML api mais j'ai des problèmes avec la création de la bonne entrée de trame de données dans le pipeline.

Voici des exemples de données:

age,hours_per_week,education,sex,salaryRange
38,40,"hs-grad","male","A"
28,40,"bachelors","female","A"
52,45,"hs-grad","male","B"
31,50,"masters","female","B"
42,40,"bachelors","male","B"

age et hours_per_week sont des nombres entiers tandis que d'autres fonctionnalités, y compris label salaireRange sont catégoriques (chaîne)

Le chargement de ce fichier csv (appelons-le sample.csv) peut être fait par bibliothèque Spark csv comme ceci:

val data = sqlContext.csvFile("/home/dusan/sample.csv")

Par défaut, toutes les colonnes sont importées sous forme de chaîne, nous devons donc remplacer "age" et "hours_per_week" par Int:

val toInt    = udf[Int, String]( _.toInt)
val dataFixed = data.withColumn("age", toInt(data("age"))).withColumn("hours_per_week",toInt(data("hours_per_week")))

Juste pour vérifier à quoi ressemble le schéma maintenant:

scala> dataFixed.printSchema
root
 |-- age: integer (nullable = true)
 |-- hours_per_week: integer (nullable = true)
 |-- education: string (nullable = true)
 |-- sex: string (nullable = true)
 |-- salaryRange: string (nullable = true)

Permet ensuite de définir le validateur croisé et le pipeline:

val rf = new RandomForestClassifier()
val pipeline = new Pipeline().setStages(Array(rf)) 
val cv = new CrossValidator().setNumFolds(10).setEstimator(pipeline).setEvaluator(new BinaryClassificationEvaluator)

Une erreur apparaît lors de l'exécution de cette ligne:

val cmModel = cv.fit(dataFixed)

Java.lang.IllegalArgumentException: le champ "fonctionnalités" n'existe pas.

Il est possible de définir une colonne d'étiquette et une colonne d'entités dans RandomForestClassifier, mais j'ai 4 colonnes comme prédicteurs (fonctionnalités), pas une seule.

Comment dois-je organiser mon bloc de données pour qu'il ait les colonnes d'étiquette et de fonctionnalités organisées correctement?

Pour votre commodité, voici le code complet:

import org.Apache.spark.SparkConf
import org.Apache.spark.SparkContext
import org.Apache.spark.ml.classification.RandomForestClassifier
import org.Apache.spark.ml.evaluation.BinaryClassificationEvaluator
import org.Apache.spark.ml.tuning.CrossValidator
import org.Apache.spark.ml.Pipeline
import org.Apache.spark.sql.DataFrame

import org.Apache.spark.sql.functions._
import org.Apache.spark.mllib.linalg.{Vector, Vectors}


object SampleClassification {

  def main(args: Array[String]): Unit = {

    //set spark context
    val conf = new SparkConf().setAppName("Simple Application").setMaster("local");
    val sc = new SparkContext(conf)
    val sqlContext = new org.Apache.spark.sql.SQLContext(sc)

    import sqlContext.implicits._
    import com.databricks.spark.csv._

    //load data by using databricks "Spark CSV Library" 
    val data = sqlContext.csvFile("/home/dusan/sample.csv")

    //by default all columns are imported as string so we need to change "age" and  "hours_per_week" to Int
    val toInt    = udf[Int, String]( _.toInt)
    val dataFixed = data.withColumn("age", toInt(data("age"))).withColumn("hours_per_week",toInt(data("hours_per_week")))


    val rf = new RandomForestClassifier()

    val pipeline = new Pipeline().setStages(Array(rf))

    val cv = new CrossValidator().setNumFolds(10).setEstimator(pipeline).setEvaluator(new BinaryClassificationEvaluator)

    // this fails with error
    //Java.lang.IllegalArgumentException: Field "features" does not exist.
    val cmModel = cv.fit(dataFixed) 
  }

}

Merci pour l'aide!

34
Dusan Grubjesic

Vous devez simplement vous assurer que vous disposez d'un "features" colonne de votre trame de données de type VectorUDF comme indiqué ci-dessous:

scala> val df2 = dataFixed.withColumnRenamed("age", "features")
df2: org.Apache.spark.sql.DataFrame = [features: int, hours_per_week: int, education: string, sex: string, salaryRange: string]

scala> val cmModel = cv.fit(df2) 
Java.lang.IllegalArgumentException: requirement failed: Column features must be of type org.Apache.spark.mllib.linalg.VectorUDT@1eef but was actually IntegerType.
    at scala.Predef$.require(Predef.scala:233)
    at org.Apache.spark.ml.util.SchemaUtils$.checkColumnType(SchemaUtils.scala:37)
    at org.Apache.spark.ml.PredictorParams$class.validateAndTransformSchema(Predictor.scala:50)
    at org.Apache.spark.ml.Predictor.validateAndTransformSchema(Predictor.scala:71)
    at org.Apache.spark.ml.Predictor.transformSchema(Predictor.scala:118)
    at org.Apache.spark.ml.Pipeline$$anonfun$transformSchema$4.apply(Pipeline.scala:164)
    at org.Apache.spark.ml.Pipeline$$anonfun$transformSchema$4.apply(Pipeline.scala:164)
    at scala.collection.IndexedSeqOptimized$class.foldl(IndexedSeqOptimized.scala:51)
    at scala.collection.IndexedSeqOptimized$class.foldLeft(IndexedSeqOptimized.scala:60)
    at scala.collection.mutable.ArrayOps$ofRef.foldLeft(ArrayOps.scala:108)
    at org.Apache.spark.ml.Pipeline.transformSchema(Pipeline.scala:164)
    at org.Apache.spark.ml.tuning.CrossValidator.transformSchema(CrossValidator.scala:142)
    at org.Apache.spark.ml.PipelineStage.transformSchema(Pipeline.scala:59)
    at org.Apache.spark.ml.tuning.CrossValidator.fit(CrossValidator.scala:107)
    at $iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC.<init>(<console>:67)
    at $iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC.<init>(<console>:72)
    at $iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC.<init>(<console>:74)
    at $iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC$$iwC.<init>(<console>:76)

EDIT1

Essentiellement, il doit y avoir deux champs dans votre bloc de données "entités" pour le vecteur d'entités et "étiquette" pour les étiquettes d'instance. L'instance doit être de type Double.

Pour créer un champ "features" avec Vector tapez d'abord créez un udf comme indiqué ci-dessous:

val toVec4    = udf[Vector, Int, Int, String, String] { (a,b,c,d) => 
  val e3 = c match {
    case "hs-grad" => 0
    case "bachelors" => 1
    case "masters" => 2
  }
  val e4 = d match {case "male" => 0 case "female" => 1}
  Vectors.dense(a, b, e3, e4) 
}

Maintenant, pour encoder également le champ "label", créez un autre udf comme indiqué ci-dessous:

val encodeLabel    = udf[Double, String]( _ match { case "A" => 0.0 case "B" => 1.0} )

Maintenant, nous transformons la trame de données d'origine en utilisant ces deux udf:

val df = dataFixed.withColumn(
  "features",
  toVec4(
    dataFixed("age"),
    dataFixed("hours_per_week"),
    dataFixed("education"),
    dataFixed("sex")
  )
).withColumn("label", encodeLabel(dataFixed("salaryRange"))).select("features", "label")

Notez qu'il peut y avoir des colonnes/champs supplémentaires présents dans la trame de données, mais dans ce cas, j'ai sélectionné uniquement features et label:

scala> df.show()
+-------------------+-----+
|           features|label|
+-------------------+-----+
|[38.0,40.0,0.0,0.0]|  0.0|
|[28.0,40.0,1.0,1.0]|  0.0|
|[52.0,45.0,0.0,0.0]|  1.0|
|[31.0,50.0,2.0,1.0]|  1.0|
|[42.0,40.0,1.0,0.0]|  1.0|
+-------------------+-----+

Maintenant, c'est à vous de définir les paramètres corrects pour votre algorithme d'apprentissage pour le faire fonctionner.

33
tuxdna

À partir de Spark 1.4, vous pouvez utiliser Transformer org.Apache.spark.ml.feature.VectorAssembler . Indiquez simplement les noms de colonnes que vous souhaitez être des fonctionnalités.

val assembler = new VectorAssembler()
  .setInputCols(Array("col1", "col2", "col3"))
  .setOutputCol("features")

et ajoutez-le à votre pipeline.

45
WeiChing Lin

Selon spark documentation sur mllib - arbres aléatoires, il me semble que vous devez définir la carte des fonctionnalités que vous utilisez et les points doivent être un point étiqueté.

Cela indiquera à l'algorithme quelle colonne doit être utilisée comme prédiction et quelles sont les fonctionnalités.

https://spark.Apache.org/docs/latest/mllib-decision-tree.html

0
Adriano Almeida