hacktricks/a.i.-exploiting/bra.i.nsmasher-presentation/ml-basics/feature-engineering.md
2023-06-06 18:56:34 +00:00

291 lines
19 KiB
Markdown

<details>
<summary><a href="https://cloud.hacktricks.xyz/pentesting-cloud/pentesting-cloud-methodology"><strong>☁️ HackTricks Cloud ☁️</strong></a> -<a href="https://twitter.com/hacktricks_live"><strong>🐦 Twitter 🐦</strong></a> - <a href="https://www.twitch.tv/hacktricks_live/schedule"><strong>🎙️ Twitch 🎙️</strong></a> - <a href="https://www.youtube.com/@hacktricks_LIVE"><strong>🎥 Youtube 🎥</strong></a></summary>
- Você trabalha em uma **empresa de segurança cibernética**? Você quer ver sua **empresa anunciada no HackTricks**? ou você quer ter acesso à **última versão do PEASS ou baixar o HackTricks em PDF**? Confira os [**PLANOS DE ASSINATURA**](https://github.com/sponsors/carlospolop)!
- Descubra [**A Família PEASS**](https://opensea.io/collection/the-peass-family), nossa coleção exclusiva de [**NFTs**](https://opensea.io/collection/the-peass-family)
- Adquira [**produtos oficiais PEASS & HackTricks**](https://peass.creator-spring.com)
- **Junte-se ao** [**💬**](https://emojipedia.org/speech-balloon/) [**grupo do Discord**](https://discord.gg/hRep4RUj7f) ou ao [**grupo do telegram**](https://t.me/peass) ou **siga-me** no **Twitter** [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks_live)**.**
- **Compartilhe seus truques de hacking enviando PRs para o [repositório hacktricks](https://github.com/carlospolop/hacktricks) e [hacktricks-cloud repo](https://github.com/carlospolop/hacktricks-cloud)**.
</details>
# Tipos básicos de dados possíveis
Os dados podem ser **contínuos** (com **infinitos** valores) ou **categóricos** (nominais), onde a quantidade de valores possíveis é **limitada**.
## Tipos categóricos
### Binário
Apenas **2 valores possíveis**: 1 ou 0. No caso de um conjunto de dados em que os valores estão em formato de string (por exemplo, "Verdadeiro" e "Falso"), você atribui números a esses valores com:
```python
dataset["column2"] = dataset.column2.map({"T": 1, "F": 0})
```
### **Ordinal**
Os **valores seguem uma ordem**, como em: 1º lugar, 2º lugar... Se as categorias são strings (como: "iniciante", "amador", "profissional", "especialista") você pode mapeá-las para números como vimos no caso binário.
```python
column2_mapping = {'starter':0,'amateur':1,'professional':2,'expert':3}
dataset['column2'] = dataset.column2.map(column2_mapping)
```
* Para colunas **alfabéticas** você pode ordená-las mais facilmente:
```python
# 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)
```
### **Cíclico**
Parece um **valor ordinal** porque há uma ordem, mas isso não significa que um seja maior que o outro. Além disso, a **distância entre eles depende da direção** em que você está contando. Exemplo: os dias da semana, domingo não é "maior" que segunda-feira.
* Existem **diferentes maneiras** de codificar recursos cíclicos, algumas podem funcionar apenas com alguns algoritmos. **Em geral, a codificação dummy pode ser usada**.
```python
column2_dummies = pd.get_dummies(dataset.column2, drop_first=True)
dataset_joined = pd.concat([dataset[['column2']], column2_dummies], axis=1)
```
### **Datas**
Datas são **variáveis contínuas**. Podem ser vistas como **cíclicas** (porque se repetem) **ou** como variáveis **ordinais** (porque um tempo é maior que um anterior).
* Geralmente, as datas são usadas como **índice**.
```python
# 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-categoria/nominal
**Mais de 2 categorias** sem ordem relacionada. Use `dataset.describe(include='all')` para obter informações sobre as categorias de cada recurso.
* Uma **string de referência** é uma **coluna que identifica um exemplo** (como o nome de uma pessoa). Isso pode ser duplicado (porque 2 pessoas podem ter o mesmo nome), mas a maioria será única. Esses dados são **inúteis e devem ser removidos**.
* Uma **coluna de chave** é usada para **vincular dados entre tabelas**. Nesse caso, os elementos são únicos. Esses dados são **inúteis e devem ser removidos**.
Para **codificar colunas de várias categorias em números** (para que o algoritmo de ML as entenda), é usada a **codificação de dummy** (e **não a codificação one-hot** porque ela **não evita a multicolinearidade perfeita**).
Você pode obter uma **coluna de várias categorias codificada one-hot** com `pd.get_dummies(dataset.column1)`. Isso transformará todas as classes em recursos binários, criando **uma nova coluna por classe possível** e atribuirá 1 **valor verdadeiro a uma coluna**, e o restante será falso.
Você pode obter uma **coluna de várias categorias codificada em dummy** com `pd.get_dummies(dataset.column1, drop_first=True)`. Isso transformará todas as classes em recursos binários, criando **uma nova coluna por classe possível menos uma** como **as últimas 2 colunas serão refletidas como "1" ou "0" na última coluna binária criada**. Isso evitará a multicolinearidade perfeita, reduzindo as relações entre as colunas.
# Colinear/Multicolinearidade
Colinear aparece quando **2 recursos estão relacionados entre si**. Multicolinearidade aparece quando há mais de 2.
No ML, **você deseja que seus recursos estejam relacionados aos possíveis resultados, mas não deseja que estejam relacionados entre si**. É por isso que a **codificação de dummy mistura as duas últimas colunas** disso e **é melhor do que a codificação one-hot** que não faz isso, criando uma relação clara entre todos os novos recursos da coluna de várias categorias.
VIF é o **Fator de Inflação da Variância** que **mede a multicolinearidade dos recursos**. Um valor **acima de 5 significa que um dos dois ou mais recursos colineares deve ser removido**.
```python
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))
```
# Desequilíbrio Categórico
Isso ocorre quando **não há a mesma quantidade de cada categoria** nos dados de treinamento.
```python
# 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())
```
Em um desequilíbrio, sempre há uma **classe ou classes majoritárias** e uma **classe ou classes minoritárias**.
Existem duas maneiras principais de resolver esse problema:
* **Undersampling**: Removendo dados selecionados aleatoriamente da classe majoritária para que ela tenha o mesmo número de amostras que a classe minoritária.
```python
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
```
* **Oversampling**: Gerando mais dados para a classe minoritária até que ela tenha tantas amostras quanto a classe majoritária.
```python
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
```
Você pode usar o argumento **`sampling_strategy`** para indicar a **porcentagem** que você deseja **subamostrar ou sobreamostrar** (**por padrão é 1 (100%)**, o que significa igualar o número de classes minoritárias com as classes majoritárias).
{% hint style="info" %}
A subamostragem ou sobreamostragem não são perfeitas, se você obter estatísticas (com `.describe()`) dos dados subamostrados ou sobreamostrados e compará-los com o original, verá **que eles mudaram**. Portanto, a sobreamostragem e subamostragem estão modificando os dados de treinamento.
{% endhint %}
## Sobreamostragem SMOTE
**SMOTE** é geralmente uma **maneira mais confiável de sobreamostrar os dados**.
```python
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
```
# Categorias Raramente Ocorrentes
Imagine um conjunto de dados em que uma das classes de destino ocorre muito pouco.
Isso é semelhante ao desequilíbrio de categorias da seção anterior, mas a categoria raramente ocorrente ocorre ainda menos do que a "classe minoritária" nesse caso. Os métodos de **oversampling** e **undersampling** **brutos** podem ser usados aqui, mas geralmente essas técnicas **não fornecem resultados realmente bons**.
## Pesos
Em alguns algoritmos, é possível **modificar os pesos dos dados direcionados** para que alguns deles tenham por padrão mais importância ao gerar o modelo.
```python
weights = {0: 10 1:1} #Assign weight 10 to False and 1 to True
model = LogisticRegression(class_weight=weights)
```
Você pode **misturar os pesos com técnicas de oversampling/undersampling** para tentar melhorar os resultados.
## PCA - Análise de Componentes Principais
É um método que ajuda a reduzir a dimensionalidade dos dados. Ele vai **combinar diferentes características** para **reduzir a quantidade** delas gerando **características mais úteis** (_menos cálculos são necessários_).
As características resultantes não são compreensíveis pelos humanos, então ele também **anonimiza os dados**.
# Categorias de Rótulos Incongruentes
Os dados podem ter erros devido a transformações mal sucedidas ou apenas por erro humano ao escrever os dados.
Portanto, você pode encontrar o **mesmo rótulo com erros de ortografia**, diferentes **maiúsculas**, **abreviações** como: _BLUE, Blue, b, bule_. Você precisa corrigir esses erros de rótulo dentro dos dados antes de treinar o modelo.
Você pode limpar esses problemas colocando tudo em minúsculas e mapeando rótulos com erros de ortografia para os corretos.
É muito importante verificar se **todos os dados que você tem estão rotulados corretamente**, porque, por exemplo, um erro de ortografia nos dados, ao codificar as classes, gerará uma nova coluna nas características finais com **consequências ruins para o modelo final**. Esse exemplo pode ser detectado facilmente codificando uma coluna e verificando os nomes das colunas criadas.
# Dados Ausentes
Alguns dados do estudo podem estar ausentes.
Pode acontecer que alguns dados aleatórios estejam faltando por algum erro. Esse tipo de dado é **Completamente Ausente Aleatoriamente** (**MCAR**).
Pode ser que alguns dados aleatórios estejam faltando, mas há algo que torna alguns detalhes específicos mais prováveis de estar faltando, por exemplo, os homens frequentemente informam sua idade, mas as mulheres não. Isso é chamado de **Ausente Aleatoriamente** (**MAR**).
Finalmente, pode haver dados **Ausentes Não Aleatoriamente** (**MNAR**). O valor dos dados está diretamente relacionado com a probabilidade de ter os dados. Por exemplo, se você quiser medir algo embaraçoso, quanto mais embaraçosa for a pessoa, menos provável ela é de compartilhar.
As **duas primeiras categorias** de dados ausentes podem ser **ignoradas**. Mas a **terceira** requer considerar **apenas porções dos dados** que não são impactados ou tentar **modelar os dados ausentes de alguma forma**.
Uma maneira de descobrir sobre dados ausentes é usar a função `.info()`, pois ela indicará o **número de linhas, mas também o número de valores por categoria**. Se alguma categoria tiver menos valores do que o número de linhas, então há dados ausentes:
```bash
# Get info of the dataset
dataset.info()
# Drop all rows where some value is missing
dataset.dropna(how='any', axis=0).info()
```
Geralmente é recomendado que, se uma característica estiver **faltando em mais de 20%** do conjunto de dados, a **coluna deve ser removida:**
```bash
# Remove column
dataset.drop('Column_name', axis='columns', inplace=True)
dataset.info()
```
{% hint style="info" %}
Observe que **nem todos os valores ausentes estão faltando no conjunto de dados**. É possível que valores ausentes tenham sido atribuídos o valor "Desconhecido", "n/a", "", -1, 0... Você precisa verificar o conjunto de dados (usando `conjunto_de_dados.nome_da_coluna.valor_contagens(dropna=False)` para verificar os possíveis valores).
{% endhint %}
Se alguns dados estiverem faltando no conjunto de dados (e não forem muitos), você precisa encontrar a **categoria dos dados ausentes**. Para isso, basicamente, você precisa saber se os **dados ausentes estão aleatórios ou não**, e para isso, você precisa descobrir se os **dados ausentes estavam correlacionados com outros dados** do conjunto de dados.
Para descobrir se um valor ausente está correlacionado com outra coluna, você pode criar uma nova coluna que coloca 1s e 0s se os dados estão faltando ou não e, em seguida, calcular a correlação entre eles:
```bash
# 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()
```
Se você decidir ignorar os dados faltantes, ainda precisará decidir o que fazer com eles: você pode **remover as linhas** com dados faltantes (os dados de treinamento para o modelo serão menores), pode **remover completamente a característica** ou pode **modelá-la**.
Você deve **verificar a correlação entre a característica faltante com a coluna alvo** para ver o quão importante essa característica é para o alvo, se for realmente **pequena**, você pode **descartá-la ou preenchê-la**.
Para preencher dados contínuos faltantes, você pode usar: a **média**, a **mediana** ou usar um algoritmo de **imputação**. O algoritmo de imputação pode tentar usar outras características para encontrar um valor para a característica faltante:
```python
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
```
Para preencher dados categóricos, primeiro você precisa pensar se há alguma razão pela qual os valores estão faltando. Se for por **escolha dos usuários** (eles não quiseram fornecer os dados), talvez você possa **criar uma nova categoria** indicando isso. Se for por erro humano, você pode **remover as linhas** ou a **característica** (verifique as etapas mencionadas anteriormente) ou **preencher com a moda, a categoria mais usada** (não recomendado).
# Combinando Características
Se você encontrar **duas características** que estão **correlacionadas** entre si, geralmente deve **descartar** uma delas (a que está menos correlacionada com o alvo), mas também pode tentar **combiná-las e criar uma nova característica**.
```python
# 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)
```
<details>
<summary><a href="https://cloud.hacktricks.xyz/pentesting-cloud/pentesting-cloud-methodology"><strong>☁️ HackTricks Cloud ☁️</strong></a> -<a href="https://twitter.com/hacktricks_live"><strong>🐦 Twitter 🐦</strong></a> - <a href="https://www.twitch.tv/hacktricks_live/schedule"><strong>🎙️ Twitch 🎙️</strong></a> - <a href="https://www.youtube.com/@hacktricks_LIVE"><strong>🎥 Youtube 🎥</strong></a></summary>
- Você trabalha em uma **empresa de segurança cibernética**? Você quer ver sua **empresa anunciada no HackTricks**? ou quer ter acesso à **última versão do PEASS ou baixar o HackTricks em PDF**? Confira os [**PLANOS DE ASSINATURA**](https://github.com/sponsors/carlospolop)!
- Descubra [**A Família PEASS**](https://opensea.io/collection/the-peass-family), nossa coleção exclusiva de [**NFTs**](https://opensea.io/collection/the-peass-family)
- Adquira o [**swag oficial do PEASS & HackTricks**](https://peass.creator-spring.com)
- **Junte-se ao** [**💬**](https://emojipedia.org/speech-balloon/) [**grupo do Discord**](https://discord.gg/hRep4RUj7f) ou ao [**grupo do telegram**](https://t.me/peass) ou **siga-me** no **Twitter** [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks_live)**.**
- **Compartilhe seus truques de hacking enviando PRs para o [repositório hacktricks](https://github.com/carlospolop/hacktricks) e [hacktricks-cloud repo](https://github.com/carlospolop/hacktricks-cloud)**.
</details>