web-dev-qa-db-fra.com

Spark, ML, StringIndexer: gestion des étiquettes invisibles

Mon objectif est de construire un classificateur multicalss.

J'ai construit un pipeline pour l'extraction de fonctionnalités et il comprend dans un premier temps un transformateur StringIndexer pour mapper chaque nom de classe à une étiquette, cette étiquette sera utilisée dans l'étape de formation du classificateur.

Le pipeline est équipé de l'ensemble de formation.

L'ensemble de test doit être traité par le pipeline ajusté afin d'extraire les mêmes vecteurs de caractéristiques.

Sachant que mes fichiers de jeu de test ont la même structure que le jeu de formation. Le scénario possible ici consiste à faire face à un nom de classe invisible dans l'ensemble de test, dans ce cas, StringIndexer ne trouvera pas l'étiquette et une exception sera déclenchée.

Y a-t-il une solution pour ce cas? ou comment éviter que cela se produise?

16
Rami

Avec Spark 2.2 (version 7-2017), vous pouvez utiliser l'option .setHandleInvalid("keep") lors de la création de l'indexeur. Avec cette option, l'indexeur ajoute de nouveaux index lorsqu'il voit de nouveaux Notez qu'avec les versions précédentes, vous disposez également du "skip" option, qui fait que l'indexeur ignore (supprime) les lignes avec de nouvelles étiquettes.

val categoryIndexerModel = new StringIndexer()
  .setInputCol("category")
  .setOutputCol("indexedCategory")
  .setHandleInvalid("keep") // options are "keep", "error" or "skip"
20
queise

Il y a un moyen de contourner cela dans Spark 1.6.

Voici la jira: https://issues.Apache.org/jira/browse/SPARK-8764

Voici un exemple:

val categoryIndexerModel = new StringIndexer()
  .setInputCol("category")
  .setOutputCol("indexedCategory")
  .setHandleInvalid("skip") // new method.  values are "error" or "skip"

J'ai commencé à l'utiliser, mais j'ai fini par revenir au deuxième point de KrisP sur l'ajustement de cet estimateur particulier à l'ensemble de données complet.

Vous en aurez besoin plus tard dans le pipeline lorsque vous convertirez IndexToString.

Voici l'exemple modifié:

val categoryIndexerModel = new StringIndexer()
  .setInputCol("category")
  .setOutputCol("indexedCategory")
  .fit(itemsDF) // Fit the Estimator and create a Model (Transformer)

... do some kind of classification ...

val categoryReverseIndexer = new IndexToString()
  .setInputCol(classifier.getPredictionCol)
  .setOutputCol("predictedCategory")
  .setLabels(categoryIndexerModel.labels) // Use the labels from the Model
15
Chris Fregly

Pas une belle façon de le faire, j'ai peur. Soit

  • filtrer les exemples de test avec des étiquettes inconnues avant d'appliquer StringIndexer
  • ou ajustez StringIndexer à l'union du train et de la trame de données de test, ainsi vous êtes assuré que toutes les étiquettes sont là
  • ou transformer l'exemple de test avec une étiquette inconnue en une étiquette connue

Voici quelques exemples de code pour effectuer les opérations ci-dessus:

// get training labels from original train dataframe
val trainlabels = traindf.select(colname).distinct.map(_.getString(0)).collect  //Array[String]
// or get labels from a trained StringIndexer model
val trainlabels = simodel.labels 

// define an UDF on your dataframe that will be used for filtering
val filterudf = udf { label:String => trainlabels.contains(label)}

// filter out the bad examples 
val filteredTestdf = testdf.filter( filterudf(testdf(colname)))

// transform unknown value to some value, say "a"
val mapudf = udf { label:String => if (trainlabels.contains(label)) label else "a"}

// add a new column to testdf: 
val transformedTestdf = testdf.withColumn( "newcol", mapudf(testdf(colname)))
11
KrisP

Dans mon cas, j'exécutais spark ALS sur un grand ensemble de données et les données n'étaient pas disponibles sur toutes les partitions, j'ai donc dû mettre en cache () les données de manière appropriée et cela a fonctionné comme un charme

2
Suresh Gorakala

Pour moi, ignorer complètement les lignes en définissant un argument ( https://issues.Apache.org/jira/browse/SPARK-8764 ) n'est pas vraiment un moyen vraiment possible de résoudre le problème.

J'ai fini par créer mon propre transformateur CustomStringIndexer qui attribuera une nouvelle valeur à toutes les nouvelles chaînes qui n'ont pas été rencontrées pendant l'entraînement. Vous pouvez également le faire en modifiant les parties pertinentes du code de fonction spark (supprimez simplement la condition if en vérifiant explicitement cela et faites-lui renvoyer la longueur du tableau à la place)) et recompilez le bocal.

Pas vraiment une solution facile, mais c'est certainement une solution.

Je me souviens avoir vu un bogue dans JIRA pour l'incorporer également: https://issues.Apache.org/jira/browse/SPARK-17498

Il devrait sortir avec Spark 2.2 cependant. Il suffit d'attendre, je suppose: S