it-swarm.dev

Come gestire le caratteristiche categoriali con spark-ml?

Come gestisco i dati categoriali con spark-ml e non spark-mllib?

Ho pensato che la documentazione non sia molto chiara, sembra che i classificatori ad es. RandomForestClassifier, LogisticRegression, hanno un argomento featuresCol, che specifica il nome della colonna di caratteristiche nel DataFrame e un argomento labelCol, che specifica il nome della colonna delle classi etichettate in DataFrame.

Ovviamente voglio usare più di una funzione nella mia previsione, quindi ho provato ad usare VectorAssembler per mettere tutte le mie funzionalità in un singolo vettore sotto featuresCol

Tuttavia, VectorAssembler accetta solo tipi numerici, tipo booleano e tipo vettoriale (secondo il sito web di Spark), quindi non posso mettere le stringhe nel mio vettore di funzionalità.

Come dovrei procedere? 

30
Rainmaker

Volevo solo completare la risposta di Holden.

Poiché Spark 2.3.0, OneHotEncoder è stato deprecato e verrà rimosso in 3.0.0. Si prega di utilizzare OneHotEncoderEstimator invece.

In Scala:

import org.Apache.spark.ml.Pipeline
import org.Apache.spark.ml.feature.{OneHotEncoderEstimator, StringIndexer}

val df = Seq((0, "a", 1), (1, "b", 2), (2, "c", 3), (3, "a", 4), (4, "a", 4), (5, "c", 3)).toDF("id", "category1", "category2")

val indexer = new StringIndexer().setInputCol("category1").setOutputCol("category1Index")
val encoder = new OneHotEncoderEstimator()
  .setInputCols(Array(indexer.getOutputCol, "category2"))
  .setOutputCols(Array("category1Vec", "category2Vec"))

val pipeline = new Pipeline().setStages(Array(indexer, encoder))

pipeline.fit(df).transform(df).show
// +---+---------+---------+--------------+-------------+-------------+
// | id|category1|category2|category1Index| category1Vec| category2Vec|
// +---+---------+---------+--------------+-------------+-------------+
// |  0|        a|        1|           0.0|(2,[0],[1.0])|(4,[1],[1.0])|
// |  1|        b|        2|           2.0|    (2,[],[])|(4,[2],[1.0])|
// |  2|        c|        3|           1.0|(2,[1],[1.0])|(4,[3],[1.0])|
// |  3|        a|        4|           0.0|(2,[0],[1.0])|    (4,[],[])|
// |  4|        a|        4|           0.0|(2,[0],[1.0])|    (4,[],[])|
// |  5|        c|        3|           1.0|(2,[1],[1.0])|(4,[3],[1.0])|
// +---+---------+---------+--------------+-------------+-------------+

In Python:

from pyspark.ml import Pipeline
from pyspark.ml.feature import StringIndexer, OneHotEncoderEstimator

df = spark.createDataFrame([(0, "a", 1), (1, "b", 2), (2, "c", 3), (3, "a", 4), (4, "a", 4), (5, "c", 3)], ["id", "category1", "category2"])

indexer = StringIndexer(inputCol="category1", outputCol="category1Index")
inputs = [indexer.getOutputCol(), "category2"]
encoder = OneHotEncoderEstimator(inputCols=inputs, outputCols=["categoryVec1", "categoryVec2"])
pipeline = Pipeline(stages=[indexer, encoder])
pipeline.fit(df).transform(df).show()
# +---+---------+---------+--------------+-------------+-------------+
# | id|category1|category2|category1Index| categoryVec1| categoryVec2|
# +---+---------+---------+--------------+-------------+-------------+
# |  0|        a|        1|           0.0|(2,[0],[1.0])|(4,[1],[1.0])|
# |  1|        b|        2|           2.0|    (2,[],[])|(4,[2],[1.0])|
# |  2|        c|        3|           1.0|(2,[1],[1.0])|(4,[3],[1.0])|
# |  3|        a|        4|           0.0|(2,[0],[1.0])|    (4,[],[])|
# |  4|        a|        4|           0.0|(2,[0],[1.0])|    (4,[],[])|
# |  5|        c|        3|           1.0|(2,[1],[1.0])|(4,[3],[1.0])|
# +---+---------+---------+--------------+-------------+-------------+

Poiché Spark 1.4.0, MLLib fornisce anche OneHotEncoder feature, che mappa una colonna di indici di etichette in una colonna di vettori binari, con al massimo un singolo valore. 

Questa codifica consente algoritmi che si aspettano funzionalità continue, come la regressione logistica, per utilizzare funzionalità categoriali

Consideriamo il seguente DataFrame:

val df = Seq((0, "a"),(1, "b"),(2, "c"),(3, "a"),(4, "a"),(5, "c"))
            .toDF("id", "category")

Il primo passo sarebbe creare il DataFrame indicizzato con StringIndexer:

import org.Apache.spark.ml.feature.StringIndexer

val indexer = new StringIndexer()
                   .setInputCol("category")
                   .setOutputCol("categoryIndex")
                   .fit(df)

val indexed = indexer.transform(df)

indexed.show
// +---+--------+-------------+                                                    
// | id|category|categoryIndex|
// +---+--------+-------------+
// |  0|       a|          0.0|
// |  1|       b|          2.0|
// |  2|       c|          1.0|
// |  3|       a|          0.0|
// |  4|       a|          0.0|
// |  5|       c|          1.0|
// +---+--------+-------------+

Puoi quindi codificare categoryIndex con OneHotEncoder:

import org.Apache.spark.ml.feature.OneHotEncoder

val encoder = new OneHotEncoder()
                   .setInputCol("categoryIndex")
                   .setOutputCol("categoryVec")

val encoded = encoder.transform(indexed)

encoded.select("id", "categoryVec").show
// +---+-------------+
// | id|  categoryVec|
// +---+-------------+
// |  0|(2,[0],[1.0])|
// |  1|    (2,[],[])|
// |  2|(2,[1],[1.0])|
// |  3|(2,[0],[1.0])|
// |  4|(2,[0],[1.0])|
// |  5|(2,[1],[1.0])|
// +---+-------------+
36
eliasah

Ho intenzione di fornire una risposta da un'altra prospettiva, dal momento che mi chiedevo anche le caratteristiche categoriali per quanto riguarda i modelli basati su albero in Spark ML (non MLlib), e la documentazione non è chiara su come tutto funzioni. 

Quando trasformi una colonna nel tuo dataframe utilizzando pyspark.ml.feature.StringIndexer, i metadati extra vengono archiviati nel dataframe che contrassegna specificamente la funzione trasformata come caratteristica categoriale. 

Quando stampi il dataframe vedrai un valore numerico (che è un indice che corrisponde a uno dei tuoi valori categoriali) e se guardi lo schema vedrai che la tua nuova colonna trasformata è di tipo double. Tuttavia, questa nuova colonna che hai creato con pyspark.ml.feature.StringIndexer.transform non è solo una normale doppia colonna, ma ha dei metadati extra associati ad essa che sono molto importanti. Puoi esaminare questi metadati esaminando la proprietà metadata del campo appropriato nello schema del tuo dataframe (puoi accedere agli oggetti dello schema del tuo dataframe guardando il tuo.dataframe.schema)

Questi metadati aggiuntivi hanno due importanti implicazioni: 

  1. Quando chiami .fit() quando usi un modello basato su albero, eseguirà la scansione dei meta-dati del tuo dataframe e riconoscerà i campi codificati come categorici con trasformatori come pyspark.ml.feature.StringIndexer (come notato sopra ci sono altri trasformatori che avranno anche questo effetto come pyspark.ml.feature.VectorIndexer). Per questo motivo, NON è necessario codificare in modo univoco le funzionalità dopo averle trasformate con StringIndxer quando si utilizzano modelli basati su albero in spark ML (tuttavia, è comunque necessario eseguire una codifica one-hot quando si utilizzano altri modelli che non lo sono gestire naturalmente categorie come regressione lineare, ecc.). 

  2. Poiché questi metadati sono memorizzati nel frame di dati, è possibile utilizzare pyspark.ml.feature.IndexToString per invertire gli indici numerici ai valori categoriali originali (che sono spesso stringhe) in qualsiasi momento. 

21
hamel

C'è un componente della pipeline ML chiamato StringIndexer che puoi usare per convertire le tue stringhe in Double in modo ragionevole. http://spark.Apache.org/docs/latest/api/scala/index.html#org.Apache.spark.ml.feature.StringIndexer ha più documentazione e http: // spark. Apache.org/docs/latest/ml-guide.html mostra come costruire le pipeline.

5
Holden

È possibile eseguire il cast di un tipo string column in un frame di dati spark in un numerico tipo di dati utilizzando la funzione cast.

from pyspark.sql import SQLContext
from pyspark.sql.types import DoubleType, IntegerType

sqlContext = SQLContext(sc)
dataset = sqlContext.read.format('com.databricks.spark.csv').options(header='true').load('./data/titanic.csv')   

dataset = dataset.withColumn("Age", dataset["Age"].cast(DoubleType()))
dataset = dataset.withColumn("Survived", dataset["Survived"].cast(IntegerType()))

Nell'esempio precedente, leggiamo in un file csv come frame di dati, convertiamo i tipi di dati stringa predefiniti in numeri interi e doppi e sovrascriviamo il frame dati originale. Possiamo quindi utilizzare VectorAssembler per unire le funzionalità in un singolo vettore e applicare il tuo algoritmo Spark ML preferito.

0
Vadim Smolyakov