Artigo Mestrado

This commit is contained in:
2025-07-28 22:40:31 -03:00
commit 1e3adbbc4a
15 changed files with 618 additions and 0 deletions

100
Dados.txt Normal file
View 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.

0
README.md Normal file
View File

89
commands.txt Normal file
View 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

View 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
View 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

Binary file not shown.

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

Binary file not shown.

72
train_model.py Normal file
View 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

Binary file not shown.

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

Binary file not shown.

BIN
visualizations/raw_data_label_distribution.png (Stored with Git LFS) Normal file

Binary file not shown.

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

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