commit 1e3adbbc4a5dcdfeca975b1826aa9c51785353d4 Author: tiago.ferreira Date: Mon Jul 28 22:40:31 2025 -0300 Artigo Mestrado diff --git a/Dados.txt b/Dados.txt new file mode 100644 index 0000000..32f63dd --- /dev/null +++ b/Dados.txt @@ -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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/commands.txt b/commands.txt new file mode 100644 index 0000000..67ed074 --- /dev/null +++ b/commands.txt @@ -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 \ No newline at end of file diff --git a/interpret_model_with_shap.py b/interpret_model_with_shap.py new file mode 100644 index 0000000..56cae56 --- /dev/null +++ b/interpret_model_with_shap.py @@ -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) + diff --git a/preprocess_data_full.py b/preprocess_data_full.py new file mode 100644 index 0000000..8c45144 --- /dev/null +++ b/preprocess_data_full.py @@ -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) + diff --git a/shap_outputs/shap_bar_plot.png b/shap_outputs/shap_bar_plot.png new file mode 100644 index 0000000..c4376b4 --- /dev/null +++ b/shap_outputs/shap_bar_plot.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca6e4f785f14006f34fbbeb03c5d8aba92c04580abbe961811ab8b12ce965a3c +size 322921 diff --git a/shap_outputs/shap_dependence_plot.png b/shap_outputs/shap_dependence_plot.png new file mode 100644 index 0000000..312ccc5 --- /dev/null +++ b/shap_outputs/shap_dependence_plot.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:647f34a846ce5771d4bca3881d2b451f7390a820668d9b40051910547fedb298 +size 284136 diff --git a/shap_outputs/shap_summary_plot.png b/shap_outputs/shap_summary_plot.png new file mode 100644 index 0000000..6601d8c --- /dev/null +++ b/shap_outputs/shap_summary_plot.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ef1cd70a7f028368bc232bd3c07bc2a22fa2d9f11dc20e2e044830057563816 +size 520583 diff --git a/train_model.py b/train_model.py new file mode 100644 index 0000000..dea960f --- /dev/null +++ b/train_model.py @@ -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) + diff --git a/visualizations/processed_data_feature_distribution.png b/visualizations/processed_data_feature_distribution.png new file mode 100644 index 0000000..261729b --- /dev/null +++ b/visualizations/processed_data_feature_distribution.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9640ff6549ed01608b5cbe708e2c254da565fbf0603b7e1db2d342c0eee74979 +size 91250 diff --git a/visualizations/processed_data_label_distribution.png b/visualizations/processed_data_label_distribution.png new file mode 100644 index 0000000..5973b6d --- /dev/null +++ b/visualizations/processed_data_label_distribution.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1eb1e25a97d9edca8b5686e118dbeb3b4b29f30021bc465518eba1fd5ce28b68 +size 72732 diff --git a/visualizations/raw_data_feature_distribution.png b/visualizations/raw_data_feature_distribution.png new file mode 100644 index 0000000..f541ecf --- /dev/null +++ b/visualizations/raw_data_feature_distribution.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ef287c07d96f63180616b5ce93a5f9478dbdd1abef1f36ae9483feeef7f704b7 +size 110459 diff --git a/visualizations/raw_data_label_distribution.png b/visualizations/raw_data_label_distribution.png new file mode 100644 index 0000000..7ee0a26 --- /dev/null +++ b/visualizations/raw_data_label_distribution.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0379a3ec936a9894a5899d86ac0829cc95d3796ee92d1e0b17b3642124a02876 +size 237250 diff --git a/visualize_processed_data.py b/visualize_processed_data.py new file mode 100644 index 0000000..5bb032a --- /dev/null +++ b/visualize_processed_data.py @@ -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) + diff --git a/visualize_raw_data_optimized.py b/visualize_raw_data_optimized.py new file mode 100644 index 0000000..3b432c4 --- /dev/null +++ b/visualize_raw_data_optimized.py @@ -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) +