Evalml: One Hot Encoder: elimine una función redundante de forma predeterminada para funciones con dos categorías

Creado en 5 mar. 2021  ·  14Comentarios  ·  Fuente: alteryx/evalml

Nuestro único codificador activo crea una característica para cada nivel de la característica categórica original:

from evalml.pipelines import OneHotEncoder
import pandas as pd
df = pd.DataFrame({"category": ["a", "b"], "number": [4,5 ]})
OneHotEncoder().fit_transform(df).to_dataframe()

image

Las columnas category_a y category_b son completamente colineales, lo que hace que una sea redundante. Esto podría tener efectos adversos en el ajuste del estimador. Creo que deberíamos eliminar uno por defecto.

Para tu información @rpeck

enhancement

Comentario más útil

Tercera Ley del Código: No Harás == Comparaciones con Flotadores

Todos 14 comentarios

💯 % deberíamos eliminar la columna de casos negativos.

Si primero hacemos el OHE nosotros mismos, es de esperar que sklearn no los amplíe. Como dijo Freddy, puedes pensar en esto como generar dos columnas que tienen una colinealidad perfecta.

Hay dos problemas que veo al expandir un binario en dos columnas en lugar de una:

  1. Al igual que otras formas de colinealidad de características, estropea muchas cosas en la interpretabilidad, porque el efecto de la columna fuente original se divide entre las dos columnas OHE. Los nuevos paquetes acumulativos de SHAP de Freddy abordan esto, obviamente, pero cosas como las gráficas de Importancia de características y Dependencia parcial seguirán teniendo problemas.
  2. Los modelos de árbol como Random Forest y GBM muestrean aleatoriamente sus características de entrada. En este caso, la columna de origen se muestreará aleatoriamente con el doble de frecuencia de lo que realmente debería ser, por lo que puede tener un impacto enorme en el modelo.

@freddyaboulton P: El marco de datos anterior para las columnas OHE las muestra como flotantes. ¿Es esto realmente cierto?

@rpeck ¡Sí!

@freddyaboulton ¿

Tercera Ley del Código: No Harás == Comparaciones con Flotadores

(ok, a menos que sea con Math.NaN )

Hmm, ¡pensé que estábamos haciendo esto!

Estoy de acuerdo en que deberíamos. Pensé que era solo una bandera que teníamos que establecer en el impl subyacente.

@dsherry @freddyaboulton Parece que tenemos soporte para él a través de nuestro parámetro drop , pero solo tiene en cuenta la entrada del usuario y no lo usa nuestro impl, por lo que este problema solo rastrea la configuración predeterminada para drop a algo que no sea Ninguno?

https://github.com/alteryx/evalml/blob/91775ffc26c47205adc0fb255832d828ead6e7c9/evalml/pipelines/components/transformers/encoders/onehot_encoder.py#L28

https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.OneHotEncoder.html

Podríamos ir con first o if_binary , no estamos seguros de cuál es la decisión correcta.

@ angela97lin ¡ Tienes razón en que cambiar el valor predeterminado sería suficiente! Creo que first es el camino a seguir, ya que debemos evitar características perfectamente colineales, incluso cuando el número de categorías es > 2. ¿Qué opinas, @rpeck ?

Estaba leyendo esto un poco y encontré este enlace: https://inmachineswetrust.com/posts/drop-first-columns/

Conclusiones clave:

  • La eliminación de columnas solo es necesaria cuando se crea un modelo OLS sin regularización (creo que el regresor lineal cae en esta categoría: https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html, https:// scikit-learn.org/stable/modules/linear_model.html#ordinary-least-squares)
  • La eliminación de columnas codificadas en caliente cambia los parámetros y las predicciones de un modelo de regresión lineal, lo que afecta el modelo devuelto. Sin embargo, es difícil para mí determinar si esto es para bien o no.

Primer comentario de RE @rpeck : "Al igual que otras formas de colinealidad de características, estropea muchas cosas en la interpretabilidad, porque el efecto de la columna de fuente original se divide entre las dos columnas OHE. Los nuevos paquetes acumulativos SHAP de Freddy abordan esto, obviamente, pero cosas como las gráficas de Importancia de características y Dependencia parcial seguirán teniendo problemas".

Esto tiene sentido para los casos binarios, pero en el caso de que tengamos varias categorías, eliminar una columna seguirá teniendo este problema.

¿Quizás no deberíamos hacer esto por defecto, pero deberíamos actualizar make_pipeline para crear un OHE con first como parámetro si el estimador es un regresor lineal?

Por desgracia, no tengo un buen conocimiento de las matemáticas subyacentes para hacer el juicio, así que me encantaría escuchar sus pensamientos, @freddyaboulton @rpeck @dsherry

Discusión posterior con @rpeck @dsherry @chukarsten @jeremyliweishih

  • Solo haremos esto para casos binarios.
  • Un "bueno de tener" es usar, en el caso binario, es la clase minoritaria, pero de lo contrario, solo elegir una de las dos categorías debería ser suficiente.

@angela97lin suena bien como comportamiento predeterminado de RE. Otro buen tener: la capacidad de anular ese comportamiento predeterminado a través de los parámetros del componente

@dsherry Si lo entiendo correctamente, dado que estamos actualizando el valor predeterminado de drop (un parámetro), los usuarios tendrán la capacidad de anular esto configurando el parámetro del componente manualmente.

Cavó alrededor para ver lo que era necesario para implementar esto. En particular, tenía curiosidad por lo difícil que sería eliminar siempre la clase minoritaria en el caso binario.

El resultado de esa excavación es:

  • Con scikit-learn, es bastante difícil seleccionar qué categoría eliminar. Según la documentación, esto parece factible a través de la opción de matriz para el parámetro drop (https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.OneHotEncoder.html). Sin embargo, después de probarlo, requiere que se especifique un valor de índice para cada columna. Por lo tanto, lo siguiente, que intenta eliminar la categoría especificada en el índice 0 para la columna 0 y ningún otro valor para los errores de las columnas 1 y 2:
import pandas as pd
import numpy as np
from sklearn.preprocessing import OneHotEncoder

X = pd.DataFrame({'col_1': ["a", "b", "b", "a", "b"],
                      'col_2': ["a", "b", "a", "c", "b"],
                      'col_3': ["a", "a", "a", "a", "a"]})

indices_to_drop = np.array([0, None, None])

ohe = OneHotEncoder(drop=indices_to_drop)
ohe.fit(X)
ValueError                                Traceback (most recent call last)
<ipython-input-4-a099fa2fc4a7> in <module>
----> 1 ohe.fit(X)

~/Desktop/venv/lib/python3.7/site-packages/sklearn/preprocessing/_encoders.py in fit(self, X, y)
    417         self._fit(X, handle_unknown=self.handle_unknown,
    418                   force_all_finite='allow-nan')
--> 419         self.drop_idx_ = self._compute_drop_idx()
    420         return self
    421 

~/Desktop/venv/lib/python3.7/site-packages/sklearn/preprocessing/_encoders.py in _compute_drop_idx(self)
    394                                 ["Category: {}, Feature: {}".format(c, v)
    395                                     for c, v in missing_drops])))
--> 396                 raise ValueError(msg)
    397             return np.array(drop_indices, dtype=object)
    398 

ValueError: The following categories were supposed to be dropped, but were not found in the training data.
Category: 0, Feature: 0
Category: 1, Feature: None
Category: 2, Feature: None

Creo que esto también es la mitad de lo que señala este problema: https://github.com/scikit-learn/scikit-learn/issues/16511

Una alternativa que podemos hacer para respaldar esto es hacer un seguimiento manual de qué columnas y qué valores queremos eliminar durante el ajuste. Pase los datos a scikit-learn. Luego, elimine las columnas que almacenamos y especificamos que queríamos eliminar. Sin embargo, esto requiere un manejo lógico para determinar el original (característica, valor) del nombre de la columna transformada. (Tenemos esta lógica en get_feature_names pero eso nos ayuda a conectar los nombres de las columnas asumiendo que no se debe descartar nada...)

Todo esto es para decir que tal vez solo usar el scikit-learn predeterminado if_binary será suficiente por ahora, y podemos presentar un problema por separado para usar siempre la clase minoritaria. Sinceramente, también estoy a favor de alejarme de la implementación de OHE de scikit-learn dado lo mucho que hemos tenido que solucionarlo.

Recursos útiles:
Documento de OHE: https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.OneHotEncoder.html
Código en scikit-learn que causa inflexibilidad: https://github.com/scikit-learn/scikit-learn/blob/95119c13af77c76e150b753485c662b7c52a41a2/sklearn/preprocessing/_encoders.py#L338
Problema relacionado: https://github.com/scikit-learn/scikit-learn/issues/16511


Para usar if_binary : scikit-learn requiere que handle_unknown sea error . Esto no funciona bien con nuestros parámetros top_n , que eliminan todo menos las N categorías principales porque los datos que se van a transformar no sabrán qué hacer con las nuevas categorías. Como señaló Becca en https://github.com/alteryx/evalml/pull/830 , tendríamos que establecer top_n en Ninguno para que estos parámetros funcionen.

Con esto en mente, tal vez sea mejor rodar nuestro propio impl 🤔

¿Fue útil esta página
0 / 5 - 0 calificaciones