07zcja5lg048ejdiz.jpeg

Los datos vienen en diferentes formas y formas. Una de esas formas y formas se conoce como datos categóricos.

Esto plantea un problema porque la mayoría de los algoritmos de aprendizaje automático utilizan únicamente datos numéricos como entrada.. Sin embargo, los datos categóricos no suelen ser un desafío, gracias a funciones simples y bien definidas que los transforman en valores numéricos. Si ha realizado algún curso de ciencia de datos, estará familiarizado con la única estrategia de codificación activa para características categóricas. Esta estrategia es excelente cuando sus funciones tienen categorías limitadas. Sin embargo, se encontrará con algunos problemas al tratar con características cardinales altas (características con muchas categorías)

A continuación se explica cómo puede utilizar la codificación de destino para transformar características categóricas en valores numéricos.

Foto por sonika agarwal en desempaquetar

Al principio de cualquier curso de ciencia de datos, se le presenta una codificación activa como estrategia clave para manejar valores categóricos.y con razón, ya que esta estrategia funciona muy bien en características cardinales bajas (características con categorías limitadas).

En pocas palabras, una codificación en caliente transforma cada categoría en un vector binario. donde la categoría correspondiente está marcada como «Verdadero» o «1», y todas las demás categorías están marcadas con «Falso» o «0».

import pandas as pd

# Sample categorical data
data = {'Category': ['Red', 'Green', 'Blue', 'Red', 'Green']}

# Create a DataFrame
df = pd.DataFrame(data)

# Perform one-hot encoding
one_hot_encoded = pd.get_dummies(df['Category'])

# Display the result
print(one_hot_encoded)

Un resultado de codificación activa: podríamos mejorar esto eliminando una columna porque si conocemos Azul y Verde, podemos calcular el valor de Rojo. Imagen del autor

Si bien esto funciona muy bien para funciones con categorías limitadas (Menos de 10 a 20 categorías)a medida que aumenta el número de categorías, los vectores codificados en caliente se vuelven más largos y escasos, lo que potencialmente conduce a un mayor uso de memoria y complejidad computacional. Veamos un ejemplo.

El siguiente código utiliza datos de acceso de empleados de Amazon, cuya publicidad está disponible en kaggle: https://www.kaggle.com/datasets/lucamassaron/amazon-employee-access-challenge

Los datos contienen ocho columnas de características categóricas que indican las características del recurso, rol y grupo de trabajo requeridos del empleado en Amazon.

data.info()
Información de la columna. Imagen del autor
# Display the number of unique values in each column
unique_values_per_column = data.nunique()

print("Number of unique values in each column:")
print(unique_values_per_column)

Las ocho características tienen una cardinalidad alta. Imagen del autor

Usar una codificación activa podría ser un desafío en un conjunto de datos como este debido a la gran cantidad de categorías distintas para cada característica.

#Initial data memory usage
memory_usage = data.memory_usage(deep=True)
total_memory_usage = memory_usage.sum()
print(f"\nTotal memory usage of the DataFrame: {total_memory_usage / (1024 ** 2):.2f} MB")
El conjunto de datos inicial es de 11,24 MB. Imagen del autor
#one-hot encoding categorical features
data_encoded = pd.get_dummies(data,
columns=data.select_dtypes(include='object').columns,
drop_first=True)

data_encoded.shape

Después de la codificación en caliente, el conjunto de datos tiene 15 618 columnas. Imagen del autor
El conjunto de datos resultante es muy escaso, lo que significa que contiene muchos ceros y uno. Imagen del autor
# Memory usage for the one-hot encoded dataset
memory_usage = data_encoded.memory_usage(deep=True)
total_memory_usage = memory_usage.sum()
print(f"\nTotal memory usage of the DataFrame: {total_memory_usage / (1024 ** 2):.2f} MB")
El uso de memoria del conjunto de datos aumentó a 488,08 MB debido al mayor número de columnas. Imagen del autor

Como puede ver, la codificación one-hot no es una solución viable para lidiar con características categóricas cardinales altas, ya que aumenta significativamente el tamaño del conjunto de datos.

En casos con características cardinales altas, la codificación de destino es una mejor opción.

La codificación de destino transforma una característica categórica en una característica numérica sin agregar columnas adicionales, evitando convertir el conjunto de datos en un conjunto de datos más grande y escaso.

La codificación de destino funciona convirtiendo cada categoría de una característica categórica en su valor esperado correspondiente. El enfoque para calcular el valor esperado dependerá del valor que intente predecir.

Para problemas de regresión, el valor esperado es simplemente el valor promedio para esa categoría.

Para problemas de clasificación, el valor esperado es la probabilidad condicional dada esa categoría.

En ambos casos, podemos obtener los resultados simplemente usando la función ‘group_by’ en pandas.

#Example of how to calculate the expected value for Target encoding of a Binary outcome
expected_values = data.groupby('ROLE_TITLE')['ACTION'].value_counts(normalize=True).unstack()
expected_values
La tabla resultante indica la probabilidad de cada resultado de «ACCIÓN» mediante un ID único de «Role_title». Imagen del autor

La tabla resultante indica la probabilidad de que cada “ACCIÓN» resultado por único “ROLE_TITLE» identificación. Todo lo que queda por hacer es reemplazar el «ROLE_TITLE”id con los valores de la probabilidad de que “ACCIÓN” sea 1 en el conjunto de datos original. (es decir, en lugar de la categoría 117879, el conjunto de datos mostrará 0,889331)

Si bien esto puede darnos una intuición de cómo funciona la codificación de destino, utilizar este método simple corre el riesgo de sobreajustar. Especialmente para categorías poco comunes, como en esos casos, la codificación de destino esencialmente proporcionará el valor de destino al modelo. Además, el método anterior solo puede manejar categorías vistas, por lo que si sus datos de prueba tienen una nueva categoría, no podrá manejarla.

Para evitar esos errores, es necesario hacer que el transformador de codificación de destino sea más robusto.

Para hacer que la codificación de destino sea más sólida, puede crear una clase de transformador personalizada e integrarla con scikit-learn para que pueda usarse en cualquier canalización de modelo.

NOTA: El siguiente código está tomado del libro «The Kaggle Book» y se puede encontrar en Kaggle: https://www.kaggle.com/code/lucamassaron/meta-features-and-target-encoding

import numpy as np
import pandas as pd

from sklearn.base import BaseEstimator, TransformerMixin

class TargetEncode(BaseEstimator, TransformerMixin):

def __init__(self, categories='auto', k=1, f=1,
noise_level=0, random_state=None):
if type(categories)==str and categories!='auto':
self.categories = [categories]
else:
self.categories = categories
self.k = k
self.f = f
self.noise_level = noise_level
self.encodings = dict()
self.prior = None
self.random_state = random_state

def add_noise(self, series, noise_level):
return series * (1 + noise_level *
np.random.randn(len(series)))

def fit(self, X, y=None):
if type(self.categories)=='auto':
self.categories = np.where(X.dtypes == type(object()))[0]

temp = X.loc[:, self.categories].copy()
temp['target'] = y
self.prior = np.mean(y)
for variable in self.categories:
avg = (temp.groupby(by=variable)['target']
.agg(['mean', 'count']))
# Compute smoothing
smoothing = (1 / (1 + np.exp(-(avg['count'] - self.k) /
self.f)))
# The bigger the count the less full_avg is accounted
self.encodings[variable] = dict(self.prior * (1 -
smoothing) + avg['mean'] * smoothing)

return self

def transform(self, X):
Xt = X.copy()
for variable in self.categories:
Xt[variable].replace(self.encodings[variable],
inplace=True)
unknown_value = {value:self.prior for value in
X[variable].unique()
if value not in
self.encodings[variable].keys()}
if len(unknown_value) > 0:
Xt[variable].replace(unknown_value, inplace=True)
Xt[variable] = Xt[variable].astype(float)
if self.noise_level > 0:
if self.random_state is not None:
np.random.seed(self.random_state)
Xt[variable] = self.add_noise(Xt[variable],
self.noise_level)
return Xt

def fit_transform(self, X, y=None):
self.fit(X, y)
return self.transform(X)

Puede parecer desalentador al principio, pero analicemos cada parte del código para comprender cómo crear un codificador Target sólido.

Definición de clase

class TargetEncode(BaseEstimator, TransformerMixin):

Este primer paso garantiza que pueda utilizar esta clase de transformador en canalizaciones de scikit-learn para preprocesamiento de datos, ingeniería de funciones y flujos de trabajo de aprendizaje automático. Lo logra heredando las clases de scikit-learn. Estimador base y transformadormixin.

La herencia permite Código de destino clase para reutilizar o anular métodos y atributos definidos en las clases base, en este caso, Estimador base y transformadormixin

Estimador base es una clase base para todos los estimadores de scikit-learn. Los estimadores son objetos en scikit-learn con un método de «ajuste» para entrenar con datos y un método de «predicción» para hacer predicciones.

transformadormixin es una clase mixta para transformadores en scikit-learn, proporciona métodos adicionales como «fit_transform», que combina ajuste y transformación en un solo paso.

Heredando de Estimador base & transformador de mezcla, permite que TargetEncode implemente estos métodos, haciéndolo compatible con la API scikit-learn.

Definiendo el constructor

def __init__(self, categories='auto', k=1, f=1, 
noise_level=0, random_state=None):
if type(categories)==str and categories!='auto':
self.categories = [categories]
else:
self.categories = categories
self.k = k
self.f = f
self.noise_level = noise_level
self.encodings = dict()
self.prior = None
self.random_state = random_state

Este segundo paso define el constructor del “Codificación de destino”e inicializa las variables de instancia con valores predeterminados o especificados por el usuario.

El «categoriasEl parámetro «determina qué columnas de los datos de entrada deben considerarse variables categóricas para la codificación de destino. Está configurado de forma predeterminada en «automático» para identificar automáticamente las columnas categóricas durante el proceso de ajuste.

Los parámetros k, f y noise_level controlan el efecto de suavizado durante la codificación del objetivo y el nivel de ruido agregado durante la transformación.

Agregando ruido

Este siguiente paso es muy importante para evitar el sobreajuste..

def add_noise(self, series, noise_level):
return series * (1 + noise_level *
np.random.randn(len(series)))

El «añadir ruidoEl método agrega ruido aleatorio para introducir variabilidad y evitar el sobreajuste durante la fase de transformación.

“np.random.randn(len(serie))” genera una matriz de números aleatorios a partir de una distribución normal estándar (media = 0, desviación estándar = 1).

Multiplicando esta matriz por «niveles de ruidoCalcula el ruido aleatorio en función del nivel de ruido especificado”.

Este paso contribuye a la solidez y las capacidades de generalización del proceso de codificación de destino.

Montaje del codificador de destino

Esta parte del código entrena al codificador de destino con los datos proporcionados calculando las codificaciones de destino para columnas categóricas y almacenándolas para su uso posterior durante la transformación.

def fit(self, X, y=None):
if type(self.categories)=='auto':
self.categories = np.where(X.dtypes == type(object()))[0]

temp = X.loc[:, self.categories].copy()
temp['target'] = y
self.prior = np.mean(y)
for variable in self.categories:
avg = (temp.groupby(by=variable)['target']
.agg(['mean', 'count']))
# Compute smoothing
smoothing = (1 / (1 + np.exp(-(avg['count'] - self.k) /
self.f)))
# The bigger the count the less full_avg is accounted
self.encodings[variable] = dict(self.prior * (1 -
smoothing) + avg['mean'] * smoothing)

El término de suavizado ayuda a evitar el sobreajuste, especialmente cuando se trata de categorías con muestras pequeñas.

El método sigue la convención scikit-learn para métodos de ajuste en transformadores.

Comienza verificando e identificando las columnas categóricas y creando un DataFrame temporal, que contiene solo las columnas categóricas seleccionadas de la entrada X y la variable de destino y.

La media anterior de la variable objetivo se calcula y almacena en el atributo anterior. Esto representa la media general de la variable objetivo en todo el conjunto de datos.

Luego, calcula la media y el recuento de la variable objetivo para cada categoría utilizando el método agrupar por, como se vio anteriormente.

Hay un paso de suavizado adicional para evitar el sobreajuste en categorías con un número pequeño de muestras. El suavizado se calcula en función del número de muestras en cada categoría. Cuanto mayor sea el recuento, menor será el efecto suavizante.

Las codificaciones calculadas para cada categoría en la variable actual se almacenan en el diccionario de codificaciones. Este diccionario se utilizará más adelante durante la fase de transformación.

Transformando los datos

Esta parte del código reemplaza los valores categóricos originales con sus correspondientes valores codificados en destino almacenados en autocodificaciones.

def transform(self, X):
Xt = X.copy()
for variable in self.categories:
Xt[variable].replace(self.encodings[variable],
inplace=True)
unknown_value = {value:self.prior for value in
X[variable].unique()
if value not in
self.encodings[variable].keys()}
if len(unknown_value) > 0:
Xt[variable].replace(unknown_value, inplace=True)
Xt[variable] = Xt[variable].astype(float)
if self.noise_level > 0:
if self.random_state is not None:
np.random.seed(self.random_state)
Xt[variable] = self.add_noise(Xt[variable],
self.noise_level)
return Xt

Este paso tiene una verificación de solidez adicional para garantizar que el codificador de destino pueda manejar categorías nuevas o invisibles. Para aquellas categorías nuevas o desconocidas, las reemplaza con la media de la variable objetivo. almacenado en la variable prior_mean.

Si necesita más solidez contra el sobreajuste, puede configurar un nivel de ruido mayor que 0 para agregar ruido aleatorio a los valores codificados.

El ajuste_transformación El método combina la funcionalidad de ajustar y transformar los datos ajustando primero el transformador a los datos de entrenamiento y luego transformándolos en función de las codificaciones calculadas.

Ahora que comprende cómo funciona el código, veámoslo en acción.

#Instantiate TargetEncode class
te = TargetEncode(categories='ROLE_TITLE')
te.fit(data, data['ACTION'])
te.transform(data[['ROLE_TITLE']])
Salida con título de rol codificado en destino. Imagen del autor

El codificador Target reemplazó cada «ROLE_TITLE”id con la probabilidad de cada categoría. Ahora, hagamos lo mismo con todas las funciones y verifiquemos el uso de la memoria después de usar Target Encoding.

y = data['ACTION']
features = data.drop('ACTION',axis=1)

te = TargetEncode(categories=features.columns)
te.fit(features,y)
te_data = te.transform(features)

te_data.head()

Salida, características codificadas de destino. Imagen del autor
memory_usage = te_data.memory_usage(deep=True)
total_memory_usage = memory_usage.sum()
print(f"\nTotal memory usage of the DataFrame: {total_memory_usage / (1024 ** 2):.2f} MB")
El conjunto de datos resultante solo utiliza 2,25 MB, en comparación con los 488,08 MB del codificador one-hot. Imagen del autor

La codificación de destino transformó con éxito los datos categóricos en numéricos sin crear columnas adicionales ni aumentar el uso de memoria.

Hasta ahora hemos creado nuestra propia clase de codificador de destino, sin embargo, ya no es necesario que hagas esto.

En la versión 1.3 de scikit-learn, alrededor de junio de 2023, introdujeron la clase Target Encoder en su API. Así es como puede utilizar la codificación de destino con Scikit Learn

from sklearn.preprocessing import TargetEncoder

#Splitting the data
y = data['ACTION']
features = data.drop('ACTION',axis=1)

#Specify the target type
te = TargetEncoder(smooth="auto",target_type='binary')
X_trans = te.fit_transform(features, y)

#Creating a Dataframe
features_encoded = pd.DataFrame(X_trans, columns = features.columns)

Salida de la transformación del codificador de destino de sklearn. Imagen del autor

Tenga en cuenta que estamos obteniendo resultados ligeramente diferentes de los de la clase de codificador Target manual debido al parámetro suave y la aleatoriedad en el nivel de ruido.

Como puede ver, sklearn facilita la ejecución de transformaciones de codificación de destino. Sin embargo, es importante comprender primero cómo funciona la transformación internamente para comprender y explicar el resultado.

Si bien la codificación Target es un método de codificación potente, es importante considerar los requisitos y características específicos de su conjunto de datos y elegir el método de codificación que mejor se adapte a sus necesidades y a los requisitos del algoritmo de aprendizaje automático que planea utilizar.

[1] Banachewicz, K. y Massaron, L. (2022). El libro de Kaggle: análisis de datos y aprendizaje automático para una ciencia de datos competitiva. Paquete>

[2] Massaron, L. (2022, enero). Desafío de acceso de empleados de Amazon. Recuperado el 1 de febrero de 2024 de https://www.kaggle.com/datasets/lucamassaron/amazon-employee-access-challenge

[3] Massaron, L. Metacaracterísticas y codificación de objetivos. Recuperado el 1 de febrero de 2024 de https://www.kaggle.com/luca-massaron/meta-features-and-target-encoding

[4] Scikit-aprende.sklearn.preprocessing.TargetEncoder. En scikit-learn: Aprendizaje automático en Python (Versión 1.3). Recuperado el 1 de febrero de 2024 de https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.TargetEncoder.html