2016-01-08 23 views
8

Il mio obiettivo è creare un classificatore multicals.Spark, ML, StringIndexer: gestione etichette non visualizzate

Ho costruito una pipeline per l'estrazione di funzionalità e include come primo passo un trasformatore StringIndexer per mappare ogni nome di classe in un'etichetta, questa etichetta verrà utilizzata nel passaggio di formazione del classificatore.

Il gasdotto è dotato del set di allenamento.

Il set di prova deve essere elaborato dalla tubazione installata per estrarre gli stessi vettori di caratteristiche.

Sapendo che i file del set di test hanno la stessa struttura del set di allenamento. Lo scenario possibile qui è di affrontare un nome di classe invisibile nel set di test, in tal caso StringIndexer non riuscirà a trovare l'etichetta e verrà sollevata un'eccezione.

Esiste una soluzione per questo caso? o come possiamo evitare che ciò accada?

risposta

7

Non è un bel modo per farlo, ho paura. In entrambi i filtri

  • gli esempi di test con le etichette sconosciute prima di applicare StringIndexer
  • o adattarsi StringIndexer all'unione di treno e prova dataframe, così si ha la certezza tutte le etichette sono là
  • o trasformare il caso ad esempio di test con etichetta sconosciuta a un'etichetta nota

Ecco alcuni esempi di codice per eseguire operazioni sopra:

// 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))) 
+0

Non esiste un modo per fornire dati di test senza alcuna etichetta in modo che l'algoritmo lo preveda da zero. Nel mio caso, non ho etichette per nessuno dei miei dati di prova. Vedi: https://stackoverflow.com/questions/44127634/providing-test-data-items-with-empty-labels-in-spark-random-forest-classifier Nel mio caso devo associare etichette casuali per gli oggetti? – suat

+0

la risposta da @queise usando spark 2.2 è ora la migliore risposta – mrjrdnthms

10

C'è un modo per aggirare questo in Spark 1.6.

Ecco il jira: https://issues.apache.org/jira/browse/SPARK-8764

Ecco un esempio:

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

ho iniziato ad usare questo, ma ha finito per tornare al 2 ° punto proiettile di Krisp su raccordo questa particolare Estimator all'intero dataset.

Avrete bisogno di questo più avanti nella pipeline quando convertite IndexToString.

Ecco l'esempio modificato:

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 
+3

Ma cosa succede quando provi ad applicare il modello ai nuovi dati? Potresti scoprire che ci sono nuovi valori in alcune colonne che non erano nel test originale o nei dati di allenamento. Temo che setHandleInvalid ("salta") farà scartare l'intera riga, quando si vuole davvero ignorare il valore mai visto prima, ma si usano ancora gli altri valori nella riga. – user1933178

+0

ciò significherebbe che i nuovi dati non visualizzati prima non appartengono alla stessa distribuzione statistica dei dati di allenamento. Va bene, e in effetti è auspicabile probabilmente nella maggior parte delle applicazioni NON dargli un'etichetta poiché non sembra "simile" ai dati che il modello ha visto prima. "Assomiglia" in senso statistico, ovviamente stavano cercando di fare Learning NOT Memorizing. – Kai

+1

C'è una nuova opzione .setHandleInvalid ("keep") fornita con Spark 2.2., Che aggiungerà nuovi indici quando si ha a che fare con nuovi dati. A mio parere questa funzione sarà molto utile, poiché si spera che un modello predittivo applicato successivamente emetterà una previsione valida facendo uso di tutte le altre variabili (ovviamente i nuovi indici hanno zero potere predittivo). – queise

1

Nel mio caso, stavo correndo scintilla SLA su un grande insieme di dati e il dato non era disponibile in tutte le partizioni così ho dovuto cache() i dati in modo appropriato e ha funzionato come un fascino

1

Per me, ignorare completamente le righe impostando un argomento (https://issues.apache.org/jira/browse/SPARK-8764) non è un modo veramente fattibile per risolvere il problema.

Ho finito per creare il mio trasformatore CustomStringIndexer che assegnerà un nuovo valore per tutte le nuove stringhe che non sono state rilevate durante l'allenamento. Puoi anche farlo modificando le parti rilevanti del codice della funzione spark (basta rimuovere la condizione if che verifica esplicitamente questo e far sì che restituisca la lunghezza dell'array) e ricompilare il jar.

Non proprio una soluzione facile, ma è certamente una soluzione.

Ricordo di aver visto un bug in JIRA per incorporare anche questo: https://issues.apache.org/jira/browse/SPARK-17498

è impostato per essere rilasciato con Spark 2.2 però. Devo solo indovinare: S

11

Con Spark 2.2 (rilasciato 7-2017) è possibile utilizzare l'opzione .setHandleInvalid("keep") durante la creazione dell'indicizzatore. Con questa opzione, l'indicizzatore aggiunge nuovi indici quando vede nuove etichette. Nota che con le versioni precedenti hai anche l'opzione "skip", che fa in modo che l'indicizzatore ignori (rimuovi) le righe con nuove etichette.

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