hacktricks/a.i.-exploiting/bra.i.nsmasher-presentation/ml-basics/feature-engineering.md

19 KiB

Apprenez le piratage AWS de zéro à héros avec htARTE (Expert en équipe rouge AWS de HackTricks)!

Autres façons de soutenir HackTricks :

Types de données possibles de base

Les données peuvent être continues (valeurs infinies) ou catégorielles (nominales) où la quantité de valeurs possibles est limitée.

Types catégoriels

Binaire

Juste 2 valeurs possibles : 1 ou 0. Dans le cas où dans un ensemble de données les valeurs sont au format chaîne de caractères (par exemple "Vrai" et "Faux"), vous attribuez des nombres à ces valeurs avec :

dataset["column2"] = dataset.column2.map({"T": 1, "F": 0})

Ordinal

Les valeurs suivent un ordre, comme dans : 1ère place, 2ème place... Si les catégories sont des chaînes de caractères (comme : "débutant", "amateur", "professionnel", "expert"), vous pouvez les mapper à des nombres comme nous l'avons vu dans le cas binaire.

column2_mapping = {'starter':0,'amateur':1,'professional':2,'expert':3}
dataset['column2'] = dataset.column2.map(column2_mapping)
  • Pour les colonnes alphabétiques, vous pouvez les ordonner plus facilement :
# First get all the uniq values alphabetically sorted
possible_values_sorted = dataset.column2.sort_values().unique().tolist()
# Assign each one a value
possible_values_mapping = {value:idx for idx,value in enumerate(possible_values_sorted)}
dataset['column2'] = dataset.column2.map(possible_values_mapping)

Cyclique

Ressemble à une valeur ordinale car il y a un ordre, mais cela ne signifie pas qu'une valeur est plus grande que l'autre. De plus, la distance entre elles dépend de la direction dans laquelle vous comptez. Exemple : les jours de la semaine, dimanche n'est pas "plus grand" que lundi.

  • Il existe différentes façons d'encoder les caractéristiques cycliques, certaines peuvent fonctionner avec seulement quelques algorithmes. En général, l'encodage factice peut être utilisé.
column2_dummies = pd.get_dummies(dataset.column2, drop_first=True)
dataset_joined = pd.concat([dataset[['column2']], column2_dummies], axis=1)

Dates

Les dates sont des variables continues. Elles peuvent être considérées comme cycliques (car elles se répètent) ou comme des variables ordinales (car un moment dans le temps est plus grand qu'un précédent).

  • Habituellement, les dates sont utilisées comme index.
# Transform dates to datetime
dataset["column_date"] = pd.to_datetime(dataset.column_date)
# Make the date feature the index
dataset.set_index('column_date', inplace=True)
print(dataset.head())

# Sum usage column per day
daily_sum = dataset.groupby(df_daily_usage.index.date).agg({'usage':['sum']})
# Flatten and rename usage column
daily_sum.columns = daily_sum.columns.get_level_values(0)
daily_sum.columns = ['daily_usage']
print(daily_sum.head())

# Fill days with 0 usage
idx = pd.date_range('2020-01-01', '2020-12-31')
daily_sum.index = pd.DatetimeIndex(daily_sum.index)
df_filled = daily_sum.reindex(idx, fill_value=0) # Fill missing values


# Get day of the week, Monday=0, Sunday=6, and week days names
dataset['DoW'] = dataset.transaction_date.dt.dayofweek
# do the same in a different way
dataset['weekday'] = dataset.transaction_date.dt.weekday
# get day names
dataset['day_name'] = dataset.transaction_date.apply(lambda x: x.day_name())

Multi-catégorie/nominale

Plus de 2 catégories sans ordre spécifique. Utilisez dataset.describe(include='all') pour obtenir des informations sur les catégories de chaque caractéristique.

  • Une chaîne de référence est une colonne qui identifie un exemple (comme le nom d'une personne). Cela peut être dupliqué (car 2 personnes peuvent avoir le même nom) mais la plupart seront uniques. Ces données sont inutiles et doivent être supprimées.
  • Une colonne clé est utilisée pour lier des données entre les tables. Dans ce cas, les éléments sont uniques. Ces données sont inutiles et doivent être supprimées.

Pour encoder des colonnes multi-catégories en nombres (afin que l'algorithme ML les comprenne), on utilise l'encodage factice (et non l'encodage one-hot car cela n'évite pas la multicollinéarité parfaite).

Vous pouvez obtenir une colonne multi-catégorie encodée en one-hot avec pd.get_dummies(dataset.column1). Cela transformera toutes les classes en caractéristiques binaires, créant ainsi une nouvelle colonne par classe possible et attribuera 1 valeur Vraie à une colonne, les autres étant fausses.

Vous pouvez obtenir une colonne multi-catégorie encodée en factice avec pd.get_dummies(dataset.column1, drop_first=True). Cela transformera toutes les classes en caractéristiques binaires, créant ainsi une nouvelle colonne par classe possible moins une car les deux dernières colonnes refléteront "1" ou "0" dans la dernière colonne binaire créée. Cela évitera la multicollinéarité parfaite, réduisant les relations entre les colonnes.

Colinéaire/Multicollinéarité

La colinéarité se produit lorsque 2 caractéristiques sont liées entre elles. La multicollinéarité se produit lorsqu'il y en a plus de 2.

En ML, vous voulez que vos caractéristiques soient liées aux résultats possibles mais pas entre elles. C'est pourquoi l'encodage factice mélange les deux dernières colonnes de celle-ci et est meilleur que l'encodage one-hot qui ne le fait pas, créant une relation claire entre toutes les nouvelles caractéristiques de la colonne multi-catégorie.

Le VIF est le Facteur d'Inflation de la Variance qui mesure la multicollinéarité des caractéristiques. Une valeur supérieure à 5 signifie qu'une des deux ou plusieurs caractéristiques colinéaires doit être supprimée.

from statsmodels.stats.outliers_influence import variance_inflation_factor
from statsmodels.tools.tools import add_constant

#dummies_encoded = pd.get_dummies(dataset.column1, drop_first=True)
onehot_encoded = pd.get_dummies(dataset.column1)
X = add_constant(onehot_encoded) # Add previously one-hot encoded data
print(pd.Series([variance_inflation_factor(X.values,i) for i in range(X.shape[1])], index=X.columns))

Déséquilibre catégoriel

Cela se produit lorsqu'il n'y a pas le même nombre de chaque catégorie dans les données d'entraînement.

# Get statistic of the features
print(dataset.describe(include='all'))
# Get an overview of the features
print(dataset.info())
# Get imbalance information of the target column
print(dataset.target_column.value_counts())

Dans un déséquilibre, il y a toujours une classe ou des classes majoritaires et une classe ou des classes minoritaires.

Il existe 2 principales façons de résoudre ce problème :

  • Sous-échantillonnage : Supprimer des données sélectionnées de manière aléatoire de la classe majoritaire afin qu'elle ait le même nombre d'échantillons que la classe minoritaire.
from imblearn.under_sampling import RandomUnderSampler
rus = RandomUserSampler(random_state=1337)

X = dataset[['column1', 'column2', 'column3']].copy()
y = dataset.target_column

X_under, y_under = rus.fit_resample(X,y)
print(y_under.value_counts()) #Confirm data isn't imbalanced anymore
  • Suréchantillonnage: Générer plus de données pour la classe minoritaire jusqu'à ce qu'elle ait autant d'échantillons que la classe majoritaire.
from imblearn.under_sampling import RandomOverSampler
ros = RandomOverSampler(random_state=1337)

X = dataset[['column1', 'column2', 'column3']].copy()
y = dataset.target_column

X_over, y_over = ros.fit_resample(X,y)
print(y_over.value_counts()) #Confirm data isn't imbalanced anymore

Vous pouvez utiliser l'argument sampling_strategy pour indiquer le pourcentage que vous souhaitez sous-échantillonner ou sur-échantillonner (par défaut, c'est 1 (100%) ce qui signifie égaliser le nombre de classes minoritaires avec les classes majoritaires)

{% hint style="info" %} Le sous-échantillonnage ou le sur-échantillonnage ne sont pas parfaits. Si vous obtenez des statistiques (avec .describe()) des données sur-échantillonnées/sous-échantillonnées et que vous les comparez à l'original, vous verrez qu'elles ont changé. Par conséquent, le sur-échantillonnage et le sous-échantillonnage modifient les données d'entraînement. {% endhint %}

Sur-échantillonnage SMOTE

SMOTE est généralement une méthode plus fiable pour sur-échantillonner les données.

from imblearn.over_sampling import SMOTE

# Form SMOTE the target_column need to be numeric, map it if necessary
smote = SMOTE(random_state=1337)
X_smote, y_smote = smote.fit_resample(dataset[['column1', 'column2', 'column3']], dataset.target_column)
dataset_smote = pd.DataFrame(X_smote, columns=['column1', 'column2', 'column3'])
dataset['target_column'] = y_smote
print(y_smote.value_counts()) #Confirm data isn't imbalanced anymore

Catégories Rarement Occurrentes

Imaginez un ensemble de données où l'une des classes cibles se produit très rarement.

C'est comme le déséquilibre des catégories de la section précédente, mais la catégorie rarement présente se produit encore moins que la "classe minoritaire" dans ce cas. Les méthodes de suréchantillonnage et de sous-échantillonnage brutes pourraient également être utilisées ici, mais en général ces techniques ne donneront pas vraiment de bons résultats.

Poids

Dans certains algorithmes, il est possible de modifier les poids des données ciblées afin que certaines d'entre elles aient par défaut plus d'importance lors de la génération du modèle.

weights = {0: 10 1:1} #Assign weight 10 to False and 1 to True
model = LogisticRegression(class_weight=weights)

Vous pouvez mélanger les poids avec des techniques de sur/ sous-échantillonnage pour essayer d'améliorer les résultats.

ACP - Analyse en Composantes Principales

Est une méthode qui aide à réduire la dimensionnalité des données. Elle va combiner différentes caractéristiques pour réduire la quantité d'entre elles en générant des caractéristiques plus utiles (moins de calculs sont nécessaires).

Les caractéristiques résultantes ne sont pas compréhensibles par les humains, donc cela anonymise également les données.

Catégories d'étiquettes incohérentes

Les données peuvent comporter des erreurs dues à des transformations infructueuses ou simplement à des erreurs humaines lors de la saisie des données.

Par conséquent, vous pourriez trouver la même étiquette avec des fautes d'orthographe, des différences de casse, des abréviations telles que : BLEU, Bleu, b, bule. Vous devez corriger ces erreurs d'étiquetage dans les données avant d'entraîner le modèle.

Vous pouvez résoudre ces problèmes en mettant tout en minuscules et en faisant correspondre les étiquettes mal orthographiées aux bonnes.

Il est très important de vérifier que toutes les données que vous avez sont correctement étiquetées, car par exemple, une erreur de faute d'orthographe dans les données, lors de l'encodage des classes factices, générera une nouvelle colonne dans les caractéristiques finales avec de mauvaises conséquences pour le modèle final. Cet exemple peut être détecté très facilement en encodant à chaud une colonne et en vérifiant les noms des colonnes créées.

Données manquantes

Certaines données de l'étude peuvent être manquantes.

Il se peut que certaines données aléatoires complètes manquent pour une erreur quelconque. Ce type de données est Manquant Complètement au Hasard (MCAR).

Il se pourrait que certaines données aléatoires manquent mais qu'il y ait quelque chose qui rend plus probable que certains détails spécifiques manquent, par exemple, les hommes diront plus fréquemment leur âge que les femmes. Cela s'appelle Manquant au Hasard (MAR).

Enfin, il pourrait y avoir des données Manquantes Non au Hasard (MNAR). La valeur des données est directement liée à la probabilité d'avoir les données. Par exemple, si vous voulez mesurer quelque chose de gênant, plus quelqu'un est gêné, moins il est probable qu'il le partage.

Les deux premières catégories de données manquantes peuvent être ignorées. Mais la troisième nécessite de considérer seulement des portions des données qui ne sont pas impactées ou d'essayer de modéliser d'une certaine manière les données manquantes.

Une façon de détecter les données manquantes est d'utiliser la fonction .info() car elle indiquera le nombre de lignes mais aussi le nombre de valeurs par catégorie. Si une catégorie a moins de valeurs que le nombre de lignes, alors il manque des données :

# Get info of the dataset
dataset.info()

# Drop all rows where some value is missing
dataset.dropna(how='any', axis=0).info()

Il est généralement recommandé que si une fonctionnalité est manquante dans plus de 20 % de l'ensemble de données, la colonne devrait être supprimée :

# Remove column
dataset.drop('Column_name', axis='columns', inplace=True)
dataset.info()

{% hint style="info" %} Notez que toutes les valeurs manquantes ne sont pas absentes dans l'ensemble de données. Il est possible que les valeurs manquantes aient été données avec la valeur "Inconnu", "n/a", "", -1, 0... Vous devez vérifier l'ensemble de données (en utilisant ensemble.de.données.nom.colonne.valeur.comptes(dropna=False) pour vérifier les valeurs possibles). {% endhint %}

Si des données manquent dans l'ensemble de données (et que ce n'est pas trop), vous devez trouver la catégorie des données manquantes. Pour cela, vous devez essentiellement savoir si les données manquantes sont aléatoires ou non, et pour cela, vous devez déterminer si les données manquantes étaient corrélées avec d'autres données de l'ensemble de données.

Pour déterminer si une valeur manquante est corrélée avec une autre colonne, vous pouvez créer une nouvelle colonne qui met des 1 et des 0 si les données sont manquantes ou non, puis calculer la corrélation entre elles :

# The closer it's to 1 or -1 the more correlated the data is
# Note that columns are always perfectly correlated with themselves.
dataset[['column_name', 'cloumn_missing_data']].corr()

Si vous décidez d'ignorer les données manquantes, vous devez quand même décider quoi en faire : vous pouvez supprimer les lignes avec des données manquantes (les données d'entraînement du modèle seront plus petites), vous pouvez supprimer complètement la caractéristique, ou vous pouvez la modéliser.

Vous devriez vérifier la corrélation entre la caractéristique manquante et la colonne cible pour voir à quel point cette caractéristique est importante pour la cible, si elle est vraiment faible, vous pouvez la supprimer ou la remplir.

Pour remplir les données continues manquantes, vous pourriez utiliser : la moyenne, la médiane ou utiliser un algorithme d'imputation. L'algorithme d'imputation peut essayer d'utiliser d'autres caractéristiques pour trouver une valeur pour la caractéristique manquante :

from sklearn.impute import KNNImputer

X = dataset[['column1', 'column2', 'column3']]
y = dataset.column_target

# Create the imputer that will fill the data
imputer = KNNImputer(n_neightbors=2, weights='uniform')
X_imp = imputer.fit_transform(X)

# Check new data
dataset_imp = pd.DataFrame(X_imp)
dataset.columns = ['column1', 'column2', 'column3']
dataset.iloc[10:20] # Get some indexes that contained empty data before

Pour remplir les données catégorielles, tout d'abord, vous devez réfléchir s'il y a une raison pour laquelle les valeurs sont manquantes. Si c'est par choix des utilisateurs (ils ne voulaient pas fournir les données), vous pouvez peut-être créer une nouvelle catégorie l'indiquant. S'il s'agit d'une erreur humaine, vous pouvez supprimer les lignes ou la caractéristique (vérifiez les étapes mentionnées précédemment) ou la remplir avec le mode, la catégorie la plus utilisée (non recommandé).

Combinaison de caractéristiques

Si vous trouvez deux caractéristiques qui sont corrélées entre elles, vous devriez généralement en abandonner une (celle qui est moins corrélée avec la cible), mais vous pourriez également essayer de les combiner et créer une nouvelle caractéristique.

# Create a new feautr combining feature1 and feature2
dataset['new_feature'] = dataset.column1/dataset.column2

# Check correlation with target column
dataset[['new_feature', 'column1', 'column2', 'target']].corr()['target'][:]

# Check for collinearity of the 2 features and the new one
X = add_constant(dataset[['column1', 'column2', 'target']])
# Calculate VIF
pd.Series([variance_inflation_factor(X.values, i) for i in range(X.shape[1])], index=X.columns)
Apprenez le piratage AWS de zéro à héros avec htARTE (Expert en équipe rouge AWS de HackTricks)!

Autres façons de soutenir HackTricks: