Artigo Mestrado
This commit is contained in:
100
Dados.txt
Normal file
100
Dados.txt
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
🔍 4.1 Visualização dos Dados Brutos
|
||||||
|
Imagens:
|
||||||
|
|
||||||
|
raw_data_feature_distribution.png
|
||||||
|
|
||||||
|
raw_data_label_distribution.png
|
||||||
|
|
||||||
|
📊 raw_data_feature_distribution.png
|
||||||
|
Analisando a distribuição da feature Destination_Port:
|
||||||
|
|
||||||
|
A densidade concentra-se em portas conhecidas (ex: 80, 443, 22, 21), o que é esperado em tráfego legítimo.
|
||||||
|
|
||||||
|
Observa-se picos isolados em portas não convencionais, indicativo de atividades anômalas como port scans ou ataques que testam portas alternativas.
|
||||||
|
|
||||||
|
A cauda longa mostra que há uma variedade grande de portas utilizadas, com uso esporádico – característica típica de datasets com ataques variados (como DDoS ou Botnet).
|
||||||
|
|
||||||
|
📊 raw_data_label_distribution.png
|
||||||
|
Forte desbalanceamento: o tráfego BENIGNO domina amplamente o conjunto de dados.
|
||||||
|
|
||||||
|
Isso valida a preocupação expressa no artigo sobre a necessidade de estratégias de pré-processamento (como normalização, e eventualmente balanceamento de classes).
|
||||||
|
|
||||||
|
Os ataques são diversos, mas individualmente minoritários. Isso ressalta a importância de interpretar o modelo com cuidado, pois ele pode tender a aprender o padrão benigno como dominante.
|
||||||
|
|
||||||
|
🧼 4.2 Visualização Após Tratamento dos Dados
|
||||||
|
Imagens:
|
||||||
|
|
||||||
|
processed_data_feature_distribution.png
|
||||||
|
|
||||||
|
processed_data_label_distribution.png
|
||||||
|
|
||||||
|
📊 processed_data_feature_distribution.png
|
||||||
|
Após normalização (Min-Max), os valores de Destination_Port foram re-escalados para o intervalo [0, 1].
|
||||||
|
|
||||||
|
O histograma mantém a forma geral da distribuição, mas permite comparar valores com outras features normalizadas no mesmo intervalo.
|
||||||
|
|
||||||
|
Os picos mantêm-se nos mesmos pontos (ex: 0.03, 0.08...), representando as portas mais comuns.
|
||||||
|
|
||||||
|
A cauda longa permanece presente, mas com visualização mais limpa (sem outliers extremos que distorcem a escala).
|
||||||
|
|
||||||
|
Interpretação visual:
|
||||||
|
→ A normalização não alterou a estrutura semântica da feature, o que é positivo: ela preserva os padrões enquanto permite que o modelo compare múltiplas features em igualdade de escala.
|
||||||
|
|
||||||
|
📊 processed_data_label_distribution.png
|
||||||
|
A distribuição de classes foi mantida após o tratamento – ou seja, não houve balanceamento artificial das classes.
|
||||||
|
|
||||||
|
Isso é coerente com o objetivo do estudo, que focou na visualização e interpretabilidade, e não na maximização da performance via técnicas como SMOTE.
|
||||||
|
|
||||||
|
O fato de o modelo ter atingido alta performance mesmo com classes desbalanceadas reforça a robustez do Random Forest e da engenharia de features realizada.
|
||||||
|
|
||||||
|
🧠 4.3 Visualizações de Interpretabilidade com SHAP
|
||||||
|
Imagens:
|
||||||
|
|
||||||
|
shap_bar_plot.png
|
||||||
|
|
||||||
|
shap_summary_plot.png
|
||||||
|
|
||||||
|
shap_dependence_plot.png
|
||||||
|
|
||||||
|
📊 shap_bar_plot.png
|
||||||
|
Exibe a importância média de cada feature no modelo Random Forest.
|
||||||
|
|
||||||
|
A feature mais importante é Destination_Port, seguida por variáveis relacionadas a fluxo (Flow_Duration, Flow_IAT_Max, Fwd_Packet_Length_Std etc).
|
||||||
|
|
||||||
|
Essas features indicam características temporais e estruturais do tráfego, que são cruciais para distinguir entre comportamento normal e malicioso.
|
||||||
|
|
||||||
|
Insights:
|
||||||
|
|
||||||
|
Destination_Port domina por ser fortemente correlacionada a certos tipos de ataque (e.g., ataques a SSH, FTP, RDP).
|
||||||
|
|
||||||
|
A combinação de múltiplas features temporais mostra que o modelo aprendeu padrões comportamentais de tráfego.
|
||||||
|
|
||||||
|
📊 shap_summary_plot.png
|
||||||
|
Vai além da média: mostra a distribuição dos valores SHAP por feature.
|
||||||
|
|
||||||
|
Cada ponto representa uma amostra e sua contribuição para a classificação.
|
||||||
|
|
||||||
|
A coloração representa o valor da feature (vermelho = alto, azul = baixo).
|
||||||
|
|
||||||
|
Exemplo de interpretação:
|
||||||
|
|
||||||
|
Para Destination_Port, valores altos (vermelho) tendem a contribuir para classificação como ataque, enquanto valores baixos (azul) muitas vezes indicam tráfego benigno.
|
||||||
|
|
||||||
|
Isso confirma que portas incomuns estão associadas a comportamentos maliciosos no dataset.
|
||||||
|
|
||||||
|
📊 shap_dependence_plot.png
|
||||||
|
Exibe a relação entre o valor da feature mais importante (Destination_Port) e seu impacto SHAP.
|
||||||
|
|
||||||
|
Mostra que para certas faixas de portas (principalmente as mais altas ou não padrão), há um aumento expressivo no valor SHAP → maior probabilidade de o modelo classificar como ataque.
|
||||||
|
|
||||||
|
Pode-se observar clusters ou transições bruscas, indicando possíveis thresholds que o modelo implicitamente aprendeu.
|
||||||
|
|
||||||
|
📌 Conclusão da Análise Visual
|
||||||
|
Etapa Gráfico Insight Principal
|
||||||
|
Dados Brutos raw_data_feature_distribution Presença de portas suspeitas; picos em portas padrão e anomalias em portas altas.
|
||||||
|
raw_data_label_distribution Desbalanceamento de classes.
|
||||||
|
Dados Tratados processed_data_feature_distribution Distribuição preservada; normalização eficaz.
|
||||||
|
processed_data_label_distribution Desbalanceamento mantido para estudo realista.
|
||||||
|
Interpretação com SHAP shap_bar_plot Destination_Port domina; features temporais também são muito relevantes.
|
||||||
|
shap_summary_plot Correlação clara entre valores altos da porta e ataques.
|
||||||
|
shap_dependence_plot Portas incomuns aumentam a probabilidade predita de ataque.
|
||||||
89
commands.txt
Normal file
89
commands.txt
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
(artigo_final) root@tiago:~/artigo_final# python3 preprocess_data_full.py
|
||||||
|
Carregando o dataset completo de: cicids2017.csv
|
||||||
|
Dataset completo carregado com 3056496 linhas e 79 colunas
|
||||||
|
Distribuição das classes antes da limpeza:
|
||||||
|
Label
|
||||||
|
BENIGN 2370815
|
||||||
|
DDoS 256054
|
||||||
|
DoS Hulk 231073
|
||||||
|
PortScan 158930
|
||||||
|
DoS GoldenEye 10293
|
||||||
|
FTP-Patator 7938
|
||||||
|
SSH-Patator 5897
|
||||||
|
DoS slowloris 5796
|
||||||
|
DoS Slowhttptest 5499
|
||||||
|
Bot 1966
|
||||||
|
Web Attack Brute Force 1507
|
||||||
|
Web Attack XSS 652
|
||||||
|
Infiltration 36
|
||||||
|
Web Attack Sql Injection 21
|
||||||
|
Heartbleed 11
|
||||||
|
Label 8
|
||||||
|
Name: count, dtype: int64
|
||||||
|
Convertendo 78 colunas para numérico...
|
||||||
|
Removidas 2909 linhas com valores NaN ou infinitos.
|
||||||
|
Removidas colunas com baixa variância: ['Bwd_PSH_Flags', 'Bwd_URG_Flags', 'Fwd_Avg_Bytes_Bulk', 'Fwd_Avg_Packets_Bulk', 'Fwd_Avg_Bulk_Rate', 'Bwd_Avg_Bytes_Bulk', 'Bw'Bwd_Avg_Bulk_Rate']
|
||||||
|
Características finais: 70 colunas
|
||||||
|
Amostras finais: 3053587 linhas
|
||||||
|
Distribuição das classes após limpeza:
|
||||||
|
Label
|
||||||
|
BENIGN 2369006
|
||||||
|
DDoS 256050
|
||||||
|
DoS Hulk 230124
|
||||||
|
PortScan 158804
|
||||||
|
DoS GoldenEye 10293
|
||||||
|
FTP-Patator 7935
|
||||||
|
SSH-Patator 5897
|
||||||
|
DoS slowloris 5796
|
||||||
|
DoS Slowhttptest 5499
|
||||||
|
Bot 1956
|
||||||
|
Web Attack Brute Force 1507
|
||||||
|
Web Attack XSS 652
|
||||||
|
Infiltration 36
|
||||||
|
Web Attack Sql Injection 21
|
||||||
|
Heartbleed 11
|
||||||
|
Name: count, dtype: int64
|
||||||
|
Aplicando normalização Min-Max...
|
||||||
|
Dataset pré-processado salvo em: cicids2017_preprocessed.csv
|
||||||
|
Formato final: (3053587, 71)
|
||||||
|
|
||||||
|
(artigo_final) root@tiago:~/artigo_final# python3 train_model.py
|
||||||
|
Carregando o dataset pré-processado de: cicids2017_preprocessed.csv
|
||||||
|
Treinando o modelo Random Forest...
|
||||||
|
Modelo salvo em: random_forest_model.joblib
|
||||||
|
Avaliando o modelo...
|
||||||
|
Acurácia: 0.9990
|
||||||
|
Precisão: 0.9979
|
||||||
|
Recall: 0.9978
|
||||||
|
F1-score: 0.9978
|
||||||
|
Matriz de confusão:
|
||||||
|
[[710271 431]
|
||||||
|
[ 455 204920]]
|
||||||
|
|
||||||
|
(artigo_final) root@tiago:~/artigo_final# python3 visualize_raw_data_optimized.py
|
||||||
|
Carregando o dataset completo de: cicids2017.csv
|
||||||
|
Dataset carregado com 3056496 linhas e 79 colunas.
|
||||||
|
✅ Coluna de rótulo detectada: 'Label'
|
||||||
|
📊 Coluna numérica selecionada para distribuição: 'Destination_Port'
|
||||||
|
✅ Gráfico de distribuição salvo: visualizations/raw_data_feature_distribution.png
|
||||||
|
✅ Gráfico de distribuição das classes salvo: visualizations/raw_data_label_distribution.png
|
||||||
|
|
||||||
|
(artigo_final) root@tiago:~/artigo_final# python3 visualize_processed_data.py
|
||||||
|
Carregando o dataset pré-processado de: cicids2017_preprocessed.csv
|
||||||
|
✅ Dataset carregado com 3053587 linhas e 71 colunas.
|
||||||
|
✅ Colunas no dataset: Destination_Port, Flow_Duration, Total_Fwd_Packets, Total_Backward_Packets, Total_Length_of_Fwd_Packets, Total_Length_of_Bwd_Packets, Fwd_Packet_Length_Max, Fwd_Packet_Length_Min, Fwd_Packet_Length_Mean, Fwd_Packet_Length_Std, Bwd_Packet_Length_Max, Bwd_Packet_Length_Min, Bwd_Packet_Length_Mean, Bwd_Packet_Length_Std, Flow_Bytes_s, Flow_Packets_s, Flow_IAT_Mean, Flow_IAT_Std, Flow_IAT_Max, Flow_IAT_Min, Fwd_IAT_Total, Fwd_IAT_Mean, Fwd_IAT_Std, Fwd_IAT_Max, Fwd_IAT_Min, Bwd_IAT_Total, Bwd_IAT_Mean, Bwd_IAT_Std, Bwd_IAT_Max, Bwd_IAT_Min, Fwd_PSH_Flags, Fwd_URG_Flags, Fwd_Header_Length, Bwd_Header_Length, Fwd_Packets_s, Bwd_Packets_s, Min_Packet_Length, Max_Packet_Length, Packet_Length_Mean, Packet_Length_Std, Packet_Length_Variance, FIN_Flag_Count, SYN_Flag_Count, RST_Flag_Count, PSH_Flag_Count, ACK_Flag_Count, URG_Flag_Count, CWE_Flag_Count, ECE_Flag_Count, Down_Up_Ratio, Average_Packet_Size, Avg_Fwd_Segment_Size, Avg_Bwd_Segment_Size, Fwd_Header_Length.1, Subflow_Fwd_Packets, Subflow_Fwd_Bytes, Subflow_Bwd_Packets, Subflow_Bwd_Bytes, Init_Win_bytes_forward, Init_Win_bytes_backward, act_data_pkt_fwd, min_seg_size_forward, Active_Mean, Active_Std, Active_Max, Active_Min, Idle_Mean, Idle_Std, Idle_Max, Idle_Min, Label
|
||||||
|
✅ Gráfico de distribuição das classes salvo como visualizations/processed_data_label_distribution.png
|
||||||
|
✅ Gráfico de distribuição da feature 'Destination_Port' salvo como visualizations/processed_data_feature_distribution.png
|
||||||
|
✅ Processamento completo!
|
||||||
|
✅ Gráficos salvos em: 'visualizations/'
|
||||||
|
✅ Total de linhas processadas: 3053587
|
||||||
|
✅ Total de colunas processadas: 71
|
||||||
|
|
||||||
|
(artigo_final) root@tiago:~/artigo_final# python3 interpret_model_with_shap.py
|
||||||
|
Carregando modelo de: random_forest_model.joblib
|
||||||
|
Carregando dados de: cicids2017_preprocessed.csv
|
||||||
|
Gerando explicações SHAP com 10000 amostras...
|
||||||
|
✅ Gráfico de importância (bar) salvo como shap_bar_plot.png
|
||||||
|
✅ Gráfico de importância (summary) salvo como shap_summary_plot.png
|
||||||
|
📌 Feature mais importante: Destination_Port
|
||||||
|
✅ Gráfico de dependência salvo como shap_dependence_plot.png
|
||||||
87
interpret_model_with_shap.py
Normal file
87
interpret_model_with_shap.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import pandas as pd
|
||||||
|
import joblib
|
||||||
|
import shap
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import os
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
def interpret_model_with_shap(model_path, data_path, output_dir="shap_outputs", sample_size=10000):
|
||||||
|
"""
|
||||||
|
Gera explicações SHAP para o modelo treinado e os dados, com paralelização para acelerar o processo.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
model_path (str): Caminho para o modelo salvo.
|
||||||
|
data_path (str): Caminho para o dataset pré-processado.
|
||||||
|
output_dir (str): Diretório onde os gráficos serão salvos.
|
||||||
|
sample_size (int): Número de amostras a ser utilizado para gerar as explicações SHAP.
|
||||||
|
"""
|
||||||
|
print(f"Carregando modelo de: {model_path}")
|
||||||
|
model = joblib.load(model_path)
|
||||||
|
|
||||||
|
print(f"Carregando dados de: {data_path}")
|
||||||
|
df = pd.read_csv(data_path)
|
||||||
|
|
||||||
|
if "Label" not in df.columns:
|
||||||
|
raise ValueError("A coluna 'Label' não foi encontrada no dataset.")
|
||||||
|
|
||||||
|
X = df.drop("Label", axis=1)
|
||||||
|
y = df["Label"].apply(lambda x: 0 if str(x).strip().upper() == "BENIGN" else 1)
|
||||||
|
|
||||||
|
# Se o dataset tiver mais de 10.000 amostras, fazemos uma amostragem aleatória
|
||||||
|
if len(X) > sample_size:
|
||||||
|
X_sample = X.sample(sample_size, random_state=42)
|
||||||
|
else:
|
||||||
|
X_sample = X
|
||||||
|
|
||||||
|
print(f"Gerando explicações SHAP com {len(X_sample)} amostras...")
|
||||||
|
|
||||||
|
# Usando o TreeExplainer corretamente sem n_jobs
|
||||||
|
explainer = shap.TreeExplainer(model) # Remove n_jobs=-1
|
||||||
|
shap_values_list = explainer.shap_values(X_sample)
|
||||||
|
|
||||||
|
# Se for uma lista (para modelos multiclasse), escolhemos os valores de SHAP para a classe 1 (ataque)
|
||||||
|
if isinstance(shap_values_list, list):
|
||||||
|
shap_values = shap_values_list[1] # Pegamos os valores SHAP da classe 1 (ataque)
|
||||||
|
else:
|
||||||
|
shap_values = shap_values_list
|
||||||
|
|
||||||
|
# Garante que os shap_values estão no formato correto
|
||||||
|
shap_values = np.array(shap_values)
|
||||||
|
if shap_values.ndim == 3:
|
||||||
|
shap_values = shap_values[:, :, 1] # Se for 3D: (n_samples, n_features, n_classes)
|
||||||
|
|
||||||
|
# Cria o diretório para salvar os gráficos, se não existir
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Gráfico de importância das features (tipo barra)
|
||||||
|
plt.figure()
|
||||||
|
shap.summary_plot(shap_values, X_sample, plot_type="bar", show=False)
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(f"{output_dir}/shap_bar_plot.png", dpi=300)
|
||||||
|
plt.close()
|
||||||
|
print("✅ Gráfico de importância (bar) salvo como shap_bar_plot.png")
|
||||||
|
|
||||||
|
# Gráfico de importância das features (sumário)
|
||||||
|
plt.figure()
|
||||||
|
shap.summary_plot(shap_values, X_sample, show=False)
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(f"{output_dir}/shap_summary_plot.png", dpi=300)
|
||||||
|
plt.close()
|
||||||
|
print("✅ Gráfico de importância (summary) salvo como shap_summary_plot.png")
|
||||||
|
|
||||||
|
# Gráfico de dependência para a feature mais importante
|
||||||
|
most_important_feature = X_sample.columns[abs(shap_values).mean(0).argmax()]
|
||||||
|
print(f"📌 Feature mais importante: {most_important_feature}")
|
||||||
|
|
||||||
|
plt.figure()
|
||||||
|
shap.dependence_plot(most_important_feature, shap_values, X_sample, show=False)
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(f"{output_dir}/shap_dependence_plot.png", dpi=300)
|
||||||
|
plt.close()
|
||||||
|
print(f"✅ Gráfico de dependência salvo como shap_dependence_plot.png")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
model_file = "random_forest_model.joblib" # Caminho para o modelo salvo
|
||||||
|
data_file = "cicids2017_preprocessed.csv" # Caminho para o dataset pré-processado
|
||||||
|
interpret_model_with_shap(model_file, data_file)
|
||||||
|
|
||||||
88
preprocess_data_full.py
Normal file
88
preprocess_data_full.py
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
|
from sklearn.preprocessing import MinMaxScaler
|
||||||
|
import gc
|
||||||
|
|
||||||
|
def preprocess_cicids2017_full(input_path, output_path):
|
||||||
|
"""
|
||||||
|
Realiza o pré-processamento e limpeza do dataset CICIDS2017 completo.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_path (str): Caminho para o arquivo CSV de entrada do dataset.
|
||||||
|
output_path (str): Caminho para salvar o arquivo CSV pré-processado.
|
||||||
|
"""
|
||||||
|
print(f"Carregando o dataset completo de: {input_path}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
df = pd.read_csv(input_path, low_memory=False)
|
||||||
|
print(f"Dataset completo carregado com {df.shape[0]} linhas e {df.shape[1]} colunas")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao carregar o dataset completo: {e}")
|
||||||
|
print("Por favor, verifique se há memória suficiente ou se o arquivo está corrompido.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Renomear colunas para remover espaços e caracteres problemáticos
|
||||||
|
df.columns = df.columns.str.strip().str.replace(' ', '_').str.replace('/', '_').str.replace('(', '', regex=False).str.replace(')', '', regex=False)
|
||||||
|
|
||||||
|
# Verifica se a coluna 'Label' existe
|
||||||
|
if 'Label' not in df.columns:
|
||||||
|
raise ValueError("A coluna 'Label' não foi encontrada no dataset. Verifique se o arquivo contém os rótulos.")
|
||||||
|
|
||||||
|
print(f"Distribuição das classes antes da limpeza:")
|
||||||
|
print(df['Label'].value_counts())
|
||||||
|
|
||||||
|
# Converte todas as colunas (exceto 'Label') para numérico
|
||||||
|
feature_cols = df.columns[df.columns != 'Label']
|
||||||
|
print(f"Convertendo {len(feature_cols)} colunas para numérico...")
|
||||||
|
|
||||||
|
for col in feature_cols:
|
||||||
|
df[col] = pd.to_numeric(df[col], errors='coerce')
|
||||||
|
|
||||||
|
# Substitui valores infinitos por NaN e remove linhas inválidas
|
||||||
|
df.replace([np.inf, -np.inf], np.nan, inplace=True)
|
||||||
|
initial_rows = df.shape[0]
|
||||||
|
df.dropna(inplace=True)
|
||||||
|
print(f"Removidas {initial_rows - df.shape[0]} linhas com valores NaN ou infinitos.")
|
||||||
|
|
||||||
|
# Remover colunas com baixa variância (constantes ou quase constantes)
|
||||||
|
numeric_cols = df.select_dtypes(include=[np.number]).columns
|
||||||
|
low_variance_cols = [col for col in numeric_cols if df[col].nunique() <= 1]
|
||||||
|
if low_variance_cols:
|
||||||
|
df.drop(columns=low_variance_cols, inplace=True)
|
||||||
|
print(f"Removidas colunas com baixa variância: {low_variance_cols}")
|
||||||
|
else:
|
||||||
|
print("Nenhuma coluna com baixa variância encontrada.")
|
||||||
|
|
||||||
|
# Separar X e y
|
||||||
|
X = df.drop('Label', axis=1)
|
||||||
|
y = df['Label']
|
||||||
|
|
||||||
|
print(f"Características finais: {X.shape[1]} colunas")
|
||||||
|
print(f"Amostras finais: {X.shape[0]} linhas")
|
||||||
|
print(f"Distribuição das classes após limpeza:")
|
||||||
|
print(y.value_counts())
|
||||||
|
|
||||||
|
# Normalização Min-Max
|
||||||
|
print("Aplicando normalização Min-Max...")
|
||||||
|
scaler = MinMaxScaler()
|
||||||
|
X_scaled = scaler.fit_transform(X)
|
||||||
|
X_processed = pd.DataFrame(X_scaled, columns=X.columns)
|
||||||
|
|
||||||
|
# Combina X normalizado e y
|
||||||
|
df_processed = pd.concat([X_processed, y.reset_index(drop=True)], axis=1)
|
||||||
|
|
||||||
|
# Salva o resultado
|
||||||
|
df_processed.to_csv(output_path, index=False)
|
||||||
|
print(f"Dataset pré-processado salvo em: {output_path}")
|
||||||
|
print(f"Formato final: {df_processed.shape}")
|
||||||
|
|
||||||
|
# Limpeza de memória
|
||||||
|
del df, X, y, X_scaled, X_processed
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
input_file = 'cicids2017.csv' # Caminho real do dataset bruto
|
||||||
|
output_file = 'cicids2017_preprocessed.csv'
|
||||||
|
|
||||||
|
preprocess_cicids2017_full(input_file, output_file)
|
||||||
|
|
||||||
BIN
shap_outputs/shap_bar_plot.png
(Stored with Git LFS)
Normal file
BIN
shap_outputs/shap_bar_plot.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
shap_outputs/shap_dependence_plot.png
(Stored with Git LFS)
Normal file
BIN
shap_outputs/shap_dependence_plot.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
shap_outputs/shap_summary_plot.png
(Stored with Git LFS)
Normal file
BIN
shap_outputs/shap_summary_plot.png
(Stored with Git LFS)
Normal file
Binary file not shown.
72
train_model.py
Normal file
72
train_model.py
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import pandas as pd
|
||||||
|
from sklearn.model_selection import train_test_split
|
||||||
|
from sklearn.ensemble import RandomForestClassifier
|
||||||
|
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, confusion_matrix
|
||||||
|
import joblib
|
||||||
|
import os
|
||||||
|
|
||||||
|
def train_and_evaluate_random_forest(input_path, model_output_path):
|
||||||
|
"""
|
||||||
|
Treina e avalia um modelo Random Forest no dataset pré-processado CICIDS2017.
|
||||||
|
"""
|
||||||
|
print(f"Carregando o dataset pré-processado de: {input_path}")
|
||||||
|
df = pd.read_csv(input_path)
|
||||||
|
|
||||||
|
if 'Label' not in df.columns:
|
||||||
|
raise ValueError("A coluna 'Label' não foi encontrada no dataset.")
|
||||||
|
|
||||||
|
X = df.drop('Label', axis=1)
|
||||||
|
y = df['Label']
|
||||||
|
|
||||||
|
# Codifica rótulos: BENIGN = 0, qualquer outro = 1
|
||||||
|
y = y.apply(lambda x: 0 if str(x).strip().upper() == 'BENIGN' else 1)
|
||||||
|
|
||||||
|
# Divide em treino e teste
|
||||||
|
X_train, X_test, y_train, y_test = train_test_split(
|
||||||
|
X, y, test_size=0.3, random_state=42, stratify=y
|
||||||
|
)
|
||||||
|
|
||||||
|
print("Treinando o modelo Random Forest...")
|
||||||
|
model = RandomForestClassifier(
|
||||||
|
n_estimators=100,
|
||||||
|
random_state=42,
|
||||||
|
n_jobs=-1,
|
||||||
|
class_weight='balanced'
|
||||||
|
)
|
||||||
|
model.fit(X_train, y_train)
|
||||||
|
|
||||||
|
joblib.dump(model, model_output_path)
|
||||||
|
print(f"Modelo salvo em: {model_output_path}")
|
||||||
|
|
||||||
|
print("Avaliando o modelo...")
|
||||||
|
y_pred = model.predict(X_test)
|
||||||
|
acc = accuracy_score(y_test, y_pred)
|
||||||
|
prec = precision_score(y_test, y_pred)
|
||||||
|
rec = recall_score(y_test, y_pred)
|
||||||
|
f1 = f1_score(y_test, y_pred)
|
||||||
|
cm = confusion_matrix(y_test, y_pred)
|
||||||
|
|
||||||
|
print(f"Acurácia: {acc:.4f}")
|
||||||
|
print(f"Precisão: {prec:.4f}")
|
||||||
|
print(f"Recall: {rec:.4f}")
|
||||||
|
print(f"F1-score: {f1:.4f}")
|
||||||
|
print("Matriz de confusão:")
|
||||||
|
print(cm)
|
||||||
|
|
||||||
|
# Salvar métricas
|
||||||
|
with open("model_metrics.txt", "w") as f:
|
||||||
|
f.write(f"Acurácia: {acc:.4f}\n")
|
||||||
|
f.write(f"Precisão: {prec:.4f}\n")
|
||||||
|
f.write(f"Recall: {rec:.4f}\n")
|
||||||
|
f.write(f"F1-score: {f1:.4f}\n")
|
||||||
|
f.write(f"Matriz de Confusão:\n{cm}\n")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
input_file = 'cicids2017_preprocessed.csv'
|
||||||
|
model_file = 'random_forest_model.joblib'
|
||||||
|
|
||||||
|
if not os.path.exists(input_file):
|
||||||
|
print(f"❌ Arquivo '{input_file}' não encontrado. Execute o preprocess_data.py antes.")
|
||||||
|
else:
|
||||||
|
train_and_evaluate_random_forest(input_file, model_file)
|
||||||
|
|
||||||
BIN
visualizations/processed_data_feature_distribution.png
(Stored with Git LFS)
Normal file
BIN
visualizations/processed_data_feature_distribution.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
visualizations/processed_data_label_distribution.png
(Stored with Git LFS)
Normal file
BIN
visualizations/processed_data_label_distribution.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
visualizations/raw_data_feature_distribution.png
(Stored with Git LFS)
Normal file
BIN
visualizations/raw_data_feature_distribution.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
visualizations/raw_data_label_distribution.png
(Stored with Git LFS)
Normal file
BIN
visualizations/raw_data_label_distribution.png
(Stored with Git LFS)
Normal file
Binary file not shown.
70
visualize_processed_data.py
Normal file
70
visualize_processed_data.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import pandas as pd
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import seaborn as sns
|
||||||
|
import os
|
||||||
|
|
||||||
|
def visualize_processed_data(input_path):
|
||||||
|
"""
|
||||||
|
Gera visualizações para o dataset pré-processado.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_path (str): Caminho para o arquivo CSV do dataset pré-processado.
|
||||||
|
"""
|
||||||
|
print(f"Carregando o dataset pré-processado de: {input_path}")
|
||||||
|
df = pd.read_csv(input_path)
|
||||||
|
|
||||||
|
# Verifica se a coluna "Label" está presente
|
||||||
|
if "Label" not in df.columns:
|
||||||
|
raise ValueError("A coluna 'Label' não foi encontrada no dataset.")
|
||||||
|
|
||||||
|
# Exibe informações iniciais sobre o dataset
|
||||||
|
print(f"✅ Dataset carregado com {df.shape[0]} linhas e {df.shape[1]} colunas.")
|
||||||
|
print(f"✅ Colunas no dataset: {', '.join(df.columns)}")
|
||||||
|
|
||||||
|
# Conversão para rótulos legíveis
|
||||||
|
label_counts = df["Label"].apply(lambda x: "Ataque" if x == 1 else "Benigno")
|
||||||
|
|
||||||
|
# Gráfico de barras da distribuição das classes
|
||||||
|
plt.figure(figsize=(8, 5))
|
||||||
|
sns.countplot(x=label_counts)
|
||||||
|
plt.title("Distribuição das Classes (Dados Tratados)")
|
||||||
|
plt.xlabel("Classe")
|
||||||
|
plt.ylabel("Contagem")
|
||||||
|
plt.grid(True)
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig("visualizations/processed_data_label_distribution.png", dpi=300)
|
||||||
|
plt.close()
|
||||||
|
print("✅ Gráfico de distribuição das classes salvo como visualizations/processed_data_label_distribution.png")
|
||||||
|
|
||||||
|
# Gráfico da mesma feature do 4.1 (exemplo: "Destination_Port")
|
||||||
|
feature = "Destination_Port"
|
||||||
|
if feature in df.columns:
|
||||||
|
plt.figure(figsize=(10, 6))
|
||||||
|
sns.histplot(df[feature], kde=True)
|
||||||
|
plt.title(f"Distribuição da Feature '{feature}' (Dados Tratados)")
|
||||||
|
plt.xlabel(feature)
|
||||||
|
plt.ylabel("Frequência")
|
||||||
|
plt.grid(True)
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig("visualizations/processed_data_feature_distribution.png", dpi=300)
|
||||||
|
plt.close()
|
||||||
|
print(f"✅ Gráfico de distribuição da feature '{feature}' salvo como visualizations/processed_data_feature_distribution.png")
|
||||||
|
else:
|
||||||
|
print(f"⚠️ A feature '{feature}' não foi encontrada no dataset.")
|
||||||
|
|
||||||
|
# Informações finais
|
||||||
|
print("✅ Processamento completo!")
|
||||||
|
print("✅ Gráficos salvos em: 'visualizations/'")
|
||||||
|
print(f"✅ Total de linhas processadas: {df.shape[0]}")
|
||||||
|
print(f"✅ Total de colunas processadas: {df.shape[1]}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
input_file = "cicids2017_preprocessed.csv" # Caminho para o dataset pré-processado
|
||||||
|
if not os.path.exists(input_file):
|
||||||
|
print(f"Erro: Arquivo {input_file} não encontrado.")
|
||||||
|
else:
|
||||||
|
# Cria o diretório para salvar as visualizações, se não existir
|
||||||
|
os.makedirs("visualizations", exist_ok=True)
|
||||||
|
# Chama a função para gerar as visualizações
|
||||||
|
visualize_processed_data(input_file)
|
||||||
|
|
||||||
91
visualize_raw_data_optimized.py
Normal file
91
visualize_raw_data_optimized.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import pandas as pd
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import seaborn as sns
|
||||||
|
import os
|
||||||
|
import gc
|
||||||
|
|
||||||
|
def visualize_raw_data(input_path):
|
||||||
|
"""
|
||||||
|
Gera visualizações para o dataset completo (distribuição de uma feature e das classes),
|
||||||
|
processando todas as linhas do arquivo.
|
||||||
|
"""
|
||||||
|
|
||||||
|
print(f"Carregando o dataset completo de: {input_path}")
|
||||||
|
|
||||||
|
# Carregar o dataset inteiro
|
||||||
|
df_raw = pd.read_csv(input_path, low_memory=False) # Carrega o dataset inteiro
|
||||||
|
print(f"Dataset carregado com {df_raw.shape[0]} linhas e {df_raw.shape[1]} colunas.")
|
||||||
|
|
||||||
|
# Renomear colunas para remover espaços e caracteres problemáticos
|
||||||
|
df_raw.columns = df_raw.columns.str.strip().str.replace(' ', '_').str.replace('/', '_').str.replace('(', '', regex=False).str.replace(')', '', regex=False)
|
||||||
|
|
||||||
|
# Criar diretório de saída
|
||||||
|
os.makedirs("visualizations", exist_ok=True)
|
||||||
|
|
||||||
|
# Detectar coluna de rótulo
|
||||||
|
label_col = None
|
||||||
|
for col in df_raw.columns:
|
||||||
|
if col.strip().lower() in ["label", "attack", "class"]:
|
||||||
|
label_col = col
|
||||||
|
break
|
||||||
|
|
||||||
|
if label_col is None:
|
||||||
|
print("❌ Nenhuma coluna de rótulo encontrada.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"✅ Coluna de rótulo detectada: \'{label_col}\'")
|
||||||
|
|
||||||
|
# Tentativa de conversão forçada para float (ignora erros e deixa NaN)
|
||||||
|
df_converted = df_raw.copy()
|
||||||
|
for col in df_converted.columns:
|
||||||
|
if col != label_col: # Não converter a coluna de label
|
||||||
|
df_converted[col] = pd.to_numeric(df_converted[col], errors='coerce')
|
||||||
|
|
||||||
|
# Seleciona a primeira coluna numérica válida (com poucos NaNs)
|
||||||
|
numeric_cols = df_converted.select_dtypes(include=["float", "int"]).columns
|
||||||
|
if len(numeric_cols) == 0:
|
||||||
|
print("❌ Ainda nenhuma coluna numérica detectada após conversão.")
|
||||||
|
return
|
||||||
|
|
||||||
|
selected_feature = None
|
||||||
|
for feature in numeric_cols:
|
||||||
|
if df_converted[feature].notna().sum() > 1000: # Pelo menos 1000 valores não-NaN
|
||||||
|
selected_feature = feature
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("❌ Nenhuma feature numérica com dados suficientes encontrada.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"📊 Coluna numérica selecionada para distribuição: \'{selected_feature}\'")
|
||||||
|
|
||||||
|
# Histograma da feature numérica
|
||||||
|
plt.figure(figsize=(10, 6))
|
||||||
|
sns.histplot(df_converted[selected_feature].dropna(), kde=True, bins=50)
|
||||||
|
plt.title(f"Distribuição da Feature \'{selected_feature}\' (Dados Brutos Completo)")
|
||||||
|
plt.xlabel(selected_feature)
|
||||||
|
plt.ylabel("Frequência")
|
||||||
|
plt.grid(True)
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig("visualizations/raw_data_feature_distribution.png", dpi=300)
|
||||||
|
plt.close()
|
||||||
|
print("✅ Gráfico de distribuição salvo: visualizations/raw_data_feature_distribution.png")
|
||||||
|
|
||||||
|
# Gráfico de distribuição da classe
|
||||||
|
plt.figure(figsize=(10, 5))
|
||||||
|
sns.countplot(x=df_raw[label_col].astype(str).str.strip())
|
||||||
|
plt.title("Distribuição das Classes (Dados Brutos Completo)")
|
||||||
|
plt.xlabel("Classe")
|
||||||
|
plt.ylabel("Contagem")
|
||||||
|
plt.xticks(rotation=45, ha='right')
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig("visualizations/raw_data_label_distribution.png", dpi=300)
|
||||||
|
plt.close()
|
||||||
|
print("✅ Gráfico de distribuição das classes salvo: visualizations/raw_data_label_distribution.png")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
input_file = 'cicids2017.csv'
|
||||||
|
if not os.path.exists(input_file):
|
||||||
|
print(f"❌ Arquivo '{input_file}' não encontrado.")
|
||||||
|
else:
|
||||||
|
visualize_raw_data(input_file)
|
||||||
|
|
||||||
Reference in New Issue
Block a user