Codifica delle variabili categoriali: un’analisi approfondita della codifica target |  di Juan José Munoz |  Febbraio 2024

 | Intelligenza-Artificiale

I dati sono disponibili in diverse forme e forme. Una di queste forme e forme è nota come dati categorici.

Ciò pone un problema perché la maggior parte degli algoritmi di Machine Learning utilizzano solo dati numerici come input. Tuttavia, i dati categorici di solito non sono una sfida da gestire, grazie a funzioni semplici e ben definite che li trasformano in valori numerici. Se hai seguito un corso di scienza dei dati, avrai familiarità con l’unica strategia di codifica a caldo per le funzionalità categoriali. Questa strategia è ottima quando le tue funzionalità hanno categorie limitate. Tuttavia, potresti riscontrare alcuni problemi quando hai a che fare con funzionalità cardinali elevate (funzionalità con molte categorie)

Ecco come utilizzare la codifica target per trasformare le caratteristiche categoriche in valori numerici.

fotografato da Sonika Agarwal SU Unsplash

All’inizio di qualsiasi corso di scienza dei dati, ti viene presentata una codifica a caldo come strategia chiave per gestire i valori categoricie giustamente, poiché questa strategia funziona molto bene su caratteristiche cardinali basse (caratteristiche con categorie limitate).

In poche parole, One hot encoding trasforma ogni categoria in un vettore binario, dove la categoria corrispondente è contrassegnata come “Vero” o “1” e tutte le altre categorie sono contrassegnate 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 output di codifica a caldo: potremmo migliorarlo eliminando una colonna perché se conosciamo il blu e il verde, possiamo calcolare il valore del rosso. Immagine dell’autore

Anche se funziona benissimo per funzionalità con categorie limitate (Meno di 10-20 categorie)all’aumentare del numero di categorie, i vettori codificati one-hot diventano più lunghi e più radi, portando potenzialmente a un maggiore utilizzo della memoria e alla complessità computazionale, diamo un’occhiata a un esempio.

Il codice seguente utilizza i dati di accesso dei dipendenti Amazon, resi disponibili in Kaggle: https://www.kaggle.com/datasets/lucamassaron/amazon-employee-access-challenge

I dati contengono otto colonne di caratteristiche categoriche che indicano le caratteristiche della risorsa, del ruolo e del gruppo di lavoro richiesti del dipendente in Amazon.

data.info()
Informazioni sulla colonna. Immagine dell’autore
# 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)

Le otto caratteristiche hanno un’elevata cardinalità. Immagine dell’autore

L’utilizzo di una codifica a caldo potrebbe essere complicato in un set di dati come questo a causa dell’elevato numero di categorie distinte per ciascuna funzionalità.

#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")
Il set di dati iniziale è 11,24 MB. Immagine dell’autore
#one-hot encoding categorical features
data_encoded = pd.get_dummies(data,
columns=data.select_dtypes(include='object').columns,
drop_first=True)

data_encoded.shape

Dopo la codifica a caldo, il set di dati ha 15 618 colonne. Immagine dell’autore
Il set di dati risultante è molto scarno, ovvero contiene molti 0 e 1. Immagine dell’autore
# 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")
L’utilizzo della memoria del set di dati è aumentato a 488,08 MB a causa del maggiore numero di colonne. Immagine dell’autore

Come puoi vedere, la codifica one-hot non è una soluzione praticabile per gestire caratteristiche categoriche cardinali elevate, poiché aumenta significativamente la dimensione del set di dati.

Nei casi con caratteristiche cardinali elevate, la codifica target è un’opzione migliore.

La codifica target trasforma una caratteristica categoriale in una caratteristica numerica senza aggiungere colonne aggiuntive, evitando di trasformare il set di dati in un set di dati più grande e più sparso.

La codifica target funziona convertendo ciascuna categoria di una caratteristica categoriale nel corrispondente valore previsto. L’approccio al calcolo del valore atteso dipenderà dal valore che stai cercando di prevedere.

Per i problemi di regressione, il valore atteso è semplicemente il valore medio per quella categoria.

Per i problemi di classificazione, il valore atteso è la probabilità condizionata data quella categoria.

In entrambi i casi, possiamo ottenere i risultati semplicemente utilizzando la funzione ‘group_by’ in 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 tabella risultante indica la probabilità di ciascun risultato “AZIONE” in base all’ID univoco “Role_title”. Immagine dell’autore

La tabella risultante indica la probabilità di ciascun “AZIONE” risultato unico “ROLE_TITOLO” id. Non resta che sostituire il “ROLE_TITOLO” id con i valori della probabilità che “AZIONE” sia 1 nel set di dati originale. (cioè invece della categoria 117879 il set di dati mostrerà 0.889331)

Sebbene questo possa darci un’intuizione di come funziona la codifica target, l’utilizzo di questo semplice metodo corre il rischio di un adattamento eccessivo. Soprattutto per le categorie rare, come in questi casi, la codifica target fornirà essenzialmente il valore target al modello. Inoltre, il metodo sopra riportato può gestire solo le categorie visualizzate, quindi se i dati del test hanno una nuova categoria, non sarà in grado di gestirla.

Per evitare questi errori, è necessario rendere più robusto il trasformatore di codifica di destinazione.

Per rendere la codifica target più robusta, puoi creare una classe trasformatore personalizzata e integrarla con scikit-learn in modo che possa essere utilizzata in qualsiasi pipeline di modello.

NOTA: il codice seguente è tratto dal libro “The Kaggle Book” e può essere trovato in 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)

All’inizio potrebbe sembrare scoraggiante, ma analizziamo ogni parte del codice per capire come creare un robusto codificatore Target.

Definizione di classe

class TargetEncode(BaseEstimator, TransformerMixin):

Questo primo passaggio garantisce la possibilità di utilizzare questa classe di trasformazione nelle pipeline di scikit-learn per i flussi di lavoro di preelaborazione dei dati, ingegneria delle funzionalità e apprendimento automatico. Raggiunge questo obiettivo ereditando le classi scikit-learn BaseEstimator E TransformerMixin.

L’ereditarietà consente il TargetEncode classe per riutilizzare o sovrascrivere metodi e attributi definiti nelle classi base, in questo caso, BaseEstimator E TransformerMixin

BaseEstimator è una classe base per tutti gli stimatori di scikit-learn. Gli stimatori sono oggetti in scikit-learn con un metodo “fit” per l’addestramento sui dati e un metodo “predict” per fare previsioni.

TransformerMixin è una classe mixin per trasformatori in scikit-learn, fornisce metodi aggiuntivi come “fit_transform”, che combina l’adattamento e la trasformazione in un unico passaggio.

Ereditando da BaseEstimator & TransformerMixin, consente a TargetEncode di implementare questi metodi, rendendolo compatibile con l’API scikit-learn.

Definizione del costruttore

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

Questo secondo passaggio definisce il costruttore per il “Codifica destinazione” e inizializza le variabili di istanza con valori predefiniti o specificati dall’utente.

IL “categorie” Il parametro determina quali colonne nei dati di input devono essere considerate come variabili categoriali per la codifica di destinazione. Per impostazione predefinita è impostato su “auto” per identificare automaticamente le colonne categoriali durante il processo di adattamento.

I parametri k, f e noise_level controllano l’effetto di livellamento durante la codifica del target e il livello di rumore aggiunto durante la trasformazione.

Aggiunta di rumore

Il passaggio successivo è molto importante per evitare un adattamento eccessivo.

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

IL “Aggiungi del rumoreIl metodo aggiunge rumore casuale per introdurre variabilità e prevenire l’adattamento eccessivo durante la fase di trasformazione.

“np.random.randn(len(serie))” genera una matrice di numeri casuali da una distribuzione normale standard (media = 0, deviazione standard = 1).

Moltiplicando questo array per “livello_rumore” scalcola il rumore casuale in base al livello di rumore specificato.”

Questo passaggio contribuisce alla robustezza e alle capacità di generalizzazione del processo di codifica di destinazione.

Montaggio dell’encoder target

Questa parte del codice addestra il codificatore di destinazione sui dati forniti calcolando le codifiche di destinazione per le colonne categoriali e archiviandole per un utilizzo successivo durante la trasformazione.

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)

Il termine livellamento aiuta a prevenire l’adattamento eccessivo, soprattutto quando si ha a che fare con categorie con campioni piccoli.

Il metodo segue la convenzione scikit-learn per i metodi di adattamento nei trasformatori.

Inizia controllando e identificando le colonne categoriali e creando un DataFrame temporaneo, contenente solo le colonne categoriali selezionate dall’input X e la variabile di destinazione y.

La media a priori della variabile target viene calcolata e memorizzata nell’attributo precedente. Questo rappresenta la media complessiva della variabile target nell’intero set di dati.

Quindi, calcola la media e il conteggio della variabile target per ciascuna categoria utilizzando il metodo group-by, come visto in precedenza.

È disponibile un ulteriore passaggio di livellamento per evitare l’adattamento eccessivo su categorie con un numero ridotto di campioni. Lo smoothing viene calcolato in base al numero di campioni in ciascuna categoria. Maggiore è il conteggio, minore è l’effetto levigante.

Le codifiche calcolate per ciascuna categoria nella variabile corrente vengono archiviate nel dizionario delle codifiche. Questo dizionario verrà utilizzato successivamente durante la fase di trasformazione.

Trasformare i dati

Questa parte del codice sostituisce i valori categorici originali con i corrispondenti valori codificati di destinazione archiviati autocodifiche.

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

Questo passaggio prevede un ulteriore controllo di robustezza per garantire che il codificatore di destinazione possa gestire categorie nuove o invisibili. Per quelle categorie nuove o sconosciute, le sostituisce con la media della variabile target memorizzato nella variabile prior_mean.

Se hai bisogno di maggiore robustezza contro il sovradattamento, puoi impostare a livello di rumore maggiore di 0 per aggiungere rumore casuale ai valori codificati.

IL fit_transform Il metodo combina la funzionalità di adattamento e trasformazione dei dati adattando prima il trasformatore ai dati di addestramento e quindi trasformandolo in base alle codifiche calcolate.

Ora che hai capito come funziona il codice, vediamolo in azione.

#Instantiate TargetEncode class
te = TargetEncode(categories='ROLE_TITLE')
te.fit(data, data('ACTION'))
te.transform(data(('ROLE_TITLE')))
Output con titolo del ruolo codificato di destinazione. Immagine dell’autore

Il codificatore Target ha sostituito ogni “ROLE_TITOLO” id con la probabilità di ciascuna categoria. Ora facciamo lo stesso per tutte le funzionalità e controlliamo l’utilizzo della memoria dopo aver utilizzato la codifica target.

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()

Output, funzionalità codificate di destinazione. Immagine dell’autore
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")
Il set di dati risultante utilizza solo 2,25 MB, rispetto ai 488,08 MB del codificatore one-hot. Immagine dell’autore

La codifica target ha trasformato con successo i dati categorici in numerici senza creare colonne aggiuntive o aumentare l’utilizzo della memoria.

Finora abbiamo creato la nostra classe di codifica di destinazione, tuttavia non è più necessario farlo.

Nella versione 1.3 di scikit-learn, intorno a giugno 2023, hanno introdotto la classe Target Encoder nella loro API. Ecco come puoi utilizzare la codifica target 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)

Output dalla trasformazione di sklearn Target Encoder. Immagine dell’autore

Tieni presente che stiamo ottenendo risultati leggermente diversi dalla classe del codificatore Target manuale a causa del parametro uniforme e della casualità del livello di rumore.

Come vedi, sklearn semplifica l’esecuzione delle trasformazioni della codifica di destinazione. Tuttavia, è importante capire innanzitutto come funziona la trasformazione dietro le quinte per comprendere e spiegare l’output.

Sebbene la codifica Target sia un metodo di codifica potente, è importante considerare i requisiti e le caratteristiche specifici del set di dati e scegliere il metodo di codifica più adatto alle tue esigenze e ai requisiti dell’algoritmo di machine learning che prevedi di utilizzare.

(1) Banachewicz, K. & Massaron, L. (2022). Il libro Kaggle: analisi dei dati e apprendimento automatico per la scienza dei dati competitiva. pacchetto>

(2) Massaron, L. (2022, gennaio). Sfida per l’accesso dei dipendenti Amazon. Estratto il 1 febbraio 2024 da https://www.kaggle.com/datasets/lucamassaron/amazon-employee-access-challenge

(3) Massaron, L. Meta-caratteristiche e codifica target. Estratto il 1 febbraio 2024 da https://www.kaggle.com/luca-massaron/meta-features-and-target-encoding

(4) Scikit-impara.sklearn.preprocessing.TargetEncoder. In scikit-learn: apprendimento automatico in Python (versione 1.3). Estratto il 1 febbraio 2024 da https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.TargetEncoder.html

Fonte: towardsdatascience.com

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *