Dguipla / TFM-SemiSup

Semisupervised classification methods (SSC) with Spark-ML, study and implementation
Apache License 2.0
0 stars 0 forks source link

Crear la clase del "desetiquetador" #33

Closed alvarag closed 3 years ago

alvarag commented 3 years ago

Para crear la clase que "desetiquete" un porcentaje de las instancias/ejemplos que tenga un conjunto de datos deberás coger de modelo algún transformador de los que trae Spark.

Creo que el más sencillo puede ser https://github.com/apache/spark/blob/master/mllib/src/main/scala/org/apache/spark/ml/feature/Normalizer.scala

Es importante que vayas ya creándote un diagrama de carpetas/paquetes que deje cada clase en su sitio. En este caso los trasnformadores en la carpeta src/main/scala/org/apache/spark/ml/feature

Puedes copiar y pegar el código en tu IDE (el que vayas a utilizar) y empezar a entender esa clase. Verás que hay muchos @since (todo eso lo quitas). Algo importante que verás es que hereda de la clase Transformer, esta clase abstracta define la interfaz que deben cumplir los Transformadores de Spark ML: básicamente que tengan un método transform. Hereda de Transformer y no de UnaryTransformer, que en tu caso no es unario (creo).

Luego podrás ver en Normalizer cómo crea el parámetro "p" y su get y set. Tu deberás crear un parámetro que sea el porcentaje de instancias a desetiquetar.

En tu caso el esquema del DataFrame de salida será igual que el DataFrame de entrada, puesto que no añades ni quitas columnas, solo modificas instancias.

En tu caso el código que realiza el desetiquetado irá en la función

def transform(dataset: Dataset[_]): DataFrame

Cuando te pongas con ello me preguntas, que así de primeras parece difícil pero seguro que no te cuesta :)

Dguipla commented 3 years ago

@alvarag Gracias por la info. No me queda muy claro lo de Transform o UnaryTransform ¿Cual seria la diferencia? Para Normalizer https://github.com/apache/spark/blob/master/mllib/src/main/scala/org/apache/spark/ml/feature/Normalizer.scala trabaja con UnaryTransfom pero se ejecuta con el transform:

val normalizer = new Normalizer() .setInputCol("features") .setOutputCol("normFeatures") .setP(1.0) val l1NormData = normalizer.transform(dataFrame)

fuente: https://spark.apache.org/docs/1.4.1/ml-features.html

alvarag commented 3 years ago

Todo transformador debe (o debería) heredar de Transformer, solo que hay otra clase abstracta UnaryTransformer que hereda a su vez de Transformer y que según su documentación:

Abstract class for transformers that take one input column, apply transformation, and output the result as a new column.

En resumen:

imagen

Pero como como tu Transformador no crea una nueva columna, creo que directamente deberías heredar de Transformer

Dguipla commented 3 years ago

@alvarag @jjrodriguez ahora me queda mas claro ! gracias!!

he implementado la clase "Desetiquetar" el problema que tengo es que si lo introduzco en un pipeline, con una estructura

featurization-->desetiquetado-->Modelo

Al ser un transform y al estar dentro del pipeline se me reducen los datos de entrenamiento y los de test, no se si veis bien hacer el desetiquetado antes del pipline con un transform y despues aplicar el pipeline, lo que quizá no tendria mucho sentido hacer una clase para no implementarla en el pipeline, ya que se podria hacer lo mismo con el randomSplit.

Es que he estado buscando información a ver si puedo solo aplicarlo para los datos de entrenamiento/training pero si se aplica en el pipeline tambien lo hace para los de entrenamiento segun he leido.

Un saludo,

David.

alvarag commented 3 years ago

Creo que vamos a tener algo así como dos pipelines y no uno único, me temo. Pasos a seguir:

Pipeline 1 con todos los datos:

  1. Aplicar featurization o todas aquellas transformaciones que nos interesen: one-hot-encoding...

Una vez tienes el conjunto de dato con el "aspecto" que te interese, ya hacemos validación cruzada:

Pipeline 2 con los datos de entrenamiento de un fold:

  1. Aplicar el desetiquetado.
  2. Entrenar el modelo

Una vez tienes tu modelo, ya puedes evaluarlo con la partición que tenías de test.

No sé si me he explicado bien.

Por cierto, sube a Github la clase del desetiquetador para echarla un ojo cuando puedas

Dguipla commented 3 years ago

Actualmente lo que tengo seria: Pipeline 1: val featurizationPipeline = new Pipeline().setStages(Array( datosFeaturesLabelPipeline, indiceClasePipeline) )

Pipeline 2:

val pipelineFinal = new Pipeline().setStages(Array( featurizationPipeline , modelo ))

Reduccion de datos para training --> dataTrainingReducida

Ejecucion Pipeline 2:

pipelineFinal.fit(dataTrainingReducida).transform(dataTest)

Lo que se va a modificar:

Pipeline 1 --> Igual

Ejecucion pipeline 1:

featData=featurizationPipeline(data).transfrom(data)

Pipeline 2

val pipelineFinal = new Pipeline().setStages(Array( Desetiquetado, modelo ))

for para el CV donde calculamos cada iteracion para featData(en funcion del número de folds) y posteriormente hacemos avg:

pipelineFinal.fit(dataFoldTraining).transmorm(dataFoldTest)

El problema es que si hago esto me reduce también el conjunto de dataFoldTest ya que el Desetiquetado lo pongo en el pipeline, si lo hago fuera sobre los datos de dataFoldTraining entonces si que me funciona.

Quiza lo podemos hablar seria más fácil (algo se me escapa).

alvarag commented 3 years ago

Yo estaré conectado por Teams toda la mañana, podríamos hablarlo cuando te venga bien.

No obstante, tienes que plantear que una vez partes en train y set (para cada fold), tu ya tienes un conjunto de datos de train al que aplicar el pipeline (que puede ser tan complejo como sea necesario... pero en nuestro caso es solo desetiquetar y entrenar el clasificador). Y al conjunto de test tu nunca lo tocas, se tiene que quedar como ha salido de hacer la validación cruzada, con todas sus etiquetas intactas.

Lo dicho, lo hablamos sin problemas cuando te vaya bien

Dguipla commented 3 years ago

Perfecto a las 12:00 me conecto, creo que sera mas facil hablarlo por Teams :)

Dguipla commented 3 years ago

@alvarag @jjrodriguez, creo que se podria integrar todo en un único pipeline:

featuritzartion (StringIndexer....) --> UnlabeledProcess -->Model

En vez de cambiar la columna de label (con los valores etiquetados y NaN) creo otra columna "labelSelection" donde desetiqueto (NaN) entonces una vez en el modelo SeltTraining lo divido en 2 (Labeled y Unlabeled como hemos hablado) pero siempre manteniendo la columna label igual, para el conjunto de test también me generara esa columana de labelSeleccion pero no hacemos nada con ella y para el clasificador tengo configurada la colmuna label como : setLabelCol("label") con lo cual solo hará caso a su columana de labels no a la labelSelection que sera exactamente igual (sin NaN)

No se si me he explicado muy bien, en todo caso podemos hablarlo.

Gracias!

alvarag commented 3 years ago

No me parece mala opción, pero ten en cuenta que eso cambiaría la estructura del Dataset con el que esta pensado que trabaje Spark ML.

Si te fijas en su documentación siempre hablan de tener dos campos: features y labels:

val data = Seq(
  (0.0, Vectors.dense(0.5, 10.0)),
  (0.0, Vectors.dense(1.5, 20.0)),
  (1.0, Vectors.dense(1.5, 30.0)),
  (0.0, Vectors.dense(3.5, 30.0)),
  (0.0, Vectors.dense(3.5, 40.0)),
  (1.0, Vectors.dense(3.5, 40.0))
)

val df = data.toDF("label", "features")

Lo bueno de cambiar en features algunos valores a NaN mientras otros se queden con datos es que el DF resultante seguirá teniendo solo dos campos: features y labels. Mientras que si creamos una columna nueva estaríamos teniendo algo como: features, labels, nolabels (o similar).

Por este motivo, veo más sencillo y conveniente utilizar NaN para el desetiquetado.

Dguipla commented 3 years ago

@alvarag @jjrodriguez, Sí sí la idea es utilizar NaN para el desetiquetado en una columna nueva pero cuando tengo que utilizar el clasificadorBase trabajo solo con labels y features, la columna nueva la utilizo para la separación de NaN (No etiquetados) y no NaN (Etiquetados) una ves ya estan separados trabajo con las columnas de labels y features.

Lo hablamos en la proxima reunión

Gracias

jjrodriguez commented 3 years ago

Aunque en los experimentos estemos quitando etiquetas, no hay que olvidar que en situaciones reales de aprendizaje semisupervisado vamos a tener ya datos sin etiquetas.

También me parece preferible que el método de construcción de clasificadores no reciba las etiquetas supuestamente desconocidas, para evitar que nos hagamos (inconscientemente) trampas al solitario.

Dguipla commented 3 years ago

@jjrodriguez @alvarag De todas formas para la nueva clase que he hecho (UnlabeledTransformer) para desetiquetar (para nuestro caso de experimento) se podrá seleccionar (con un set) si se quiere utilizar la ya existente columna "label" o bien crear otra "labeledUnlabeled" de esta manera se podrá utilizar el concepto de pipeline de Spark y tenerlo todo en un único pipeline (sin afectar a los datos de test) o bien si se utiliza la columna "label" hacer el proceso de desetiquetado fuera del pipeline (ya que sino también desetiquetaria los datos de test)

Lo hablamos en la proxima reunión, así será también más fácil.

Dguipla commented 3 years ago

@alvarag @jjrodriguez PD: Tengo que actualizar el github con la clase (UnlabeledTransformer) y con el CrossValidator para la evaluacion de resultados utilizando MLUtils con Kfolds os aviso en cuanto lo tenga

Dguipla commented 3 years ago

@alvarag @jjrodriguez He actualizado el codigo en Github (TFM-SemiSup/notebooks/SemiSupervisado/), donde hay la clase UnlabeledTransformer y CrossValidator, tambien he reestructurado el notebook, crearé tambien el Milestone como hablamos.

¿Si teneis un hueco esta semana podemos hablarlo? Y aprovecho a preguntaros unas dudas :)

alvarag commented 3 years ago

¿Mañana o el viernes? Sobre qué hora?

Dguipla commented 3 years ago

mañana me iria bien por la tarde a partir de las 17:00, ¿Os iria bien?