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?
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"
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
Pas une belle façon de le faire, j'ai peur. Soit
StringIndexer
StringIndexer
à l'union du train et de la trame de données de test, ainsi vous êtes assuré que toutes les étiquettes sont là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)))
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
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