In [51]:
print("playing with NSL_KDD Dataset")
playing with NSL_KDD Dataset
In [38]:
# For handling datasets and dataframes
import pandas as pd
# For advanced data visualization
import seaborn as sns
# For plotting graphs and charts
import matplotlib.pyplot as plt
# For numerical operations
import numpy as np
# Check current working directory contents (useful for locating dataset files)
import os
os.listdir()
# Import preprocessing tools from scikit-learn
from sklearn.preprocessing import MinMaxScaler
from sklearn.preprocessing import LabelEncoder
# Import model selection and tuning tools
from sklearn.model_selection import GridSearchCV
# Import model selection and tuning tools
# Import machine learning models
from sklearn.ensemble import RandomForestClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import GradientBoostingClassifier
# Import evaluation metrics
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score
# These are used to assess model performance (precision, recall, F1-score, accuracy, etc.)
In [3]:
# Load the NSL-KDD training dataset from CSV file
train = pd.read_csv("kdd_train (1).csv")
test = pd.read_csv("kdd_test (2).csv")
In [4]:
# Display the dimensions (rows, columns) of the training dataset
print(train.shape)
print(test.shape)
(125973, 42) (22544, 42)
In [5]:
# Define the list of column names for the NSL-KDD dataset
columns = [
'duration','protocol_type','service','flag','src_bytes','dst_bytes','land',
'wrong_fragment','urgent','hot','num_failed_logins','logged_in',
'num_compromised','root_shell','su_attempted','num_root','num_file_creations',
'num_shells','num_access_files','num_outbound_cmds','is_host_login',
'is_guest_login','count','srv_count','serror_rate','srv_serror_rate',
'rerror_rate','srv_rerror_rate','same_srv_rate','diff_srv_rate',
'srv_diff_host_rate','dst_host_count','dst_host_srv_count',
'dst_host_same_srv_rate','dst_host_diff_srv_rate','dst_host_same_src_port_rate',
'dst_host_srv_diff_host_rate','dst_host_serror_rate',
'dst_host_srv_serror_rate','dst_host_rerror_rate','dst_host_srv_rerror_rate',
'label'
]
# Assign the defined column names to the training dataset
train.columns = columns
# Assign the defined column names to the testing dataset
test.columns = columns
In [6]:
# Display the first five entries from the 'label' column of the training dataset
train['label'].head()
Out[6]:
0 normal 1 normal 2 neptune 3 normal 4 normal Name: label, dtype: object
In [7]:
# Display the top 10 most frequent class labels in the training dataset
train['label'].value_counts().head(10)
Out[7]:
label normal 67343 neptune 41214 satan 3633 ipsweep 3599 portsweep 2931 smurf 2646 nmap 1493 back 956 teardrop 892 warezclient 890 Name: count, dtype: int64
In [8]:
# Print the list of all column names in the training dataset
print(train.columns)
Index(['duration', 'protocol_type', 'service', 'flag', 'src_bytes',
'dst_bytes', 'land', 'wrong_fragment', 'urgent', 'hot',
'num_failed_logins', 'logged_in', 'num_compromised', 'root_shell',
'su_attempted', 'num_root', 'num_file_creations', 'num_shells',
'num_access_files', 'num_outbound_cmds', 'is_host_login',
'is_guest_login', 'count', 'srv_count', 'serror_rate',
'srv_serror_rate', 'rerror_rate', 'srv_rerror_rate', 'same_srv_rate',
'diff_srv_rate', 'srv_diff_host_rate', 'dst_host_count',
'dst_host_srv_count', 'dst_host_same_srv_rate',
'dst_host_diff_srv_rate', 'dst_host_same_src_port_rate',
'dst_host_srv_diff_host_rate', 'dst_host_serror_rate',
'dst_host_srv_serror_rate', 'dst_host_rerror_rate',
'dst_host_srv_rerror_rate', 'label'],
dtype='object')
In [9]:
# Define list of DoS (Denial of Service) attack types
dos_attacks = ['back', 'land', 'neptune', 'pod', 'smurf', 'teardrop', 'mailbomb', 'apache2', 'processtable', 'udpstorm']
# Define list of Probe attack types (information gathering/scanning attacks)
probe_attacks = ['satan', 'ipsweep', 'nmap', 'portsweep', 'mscan', 'saint']
# Define list of R2L (Remote to Local) attack types
# These represent unauthorized access attempts from a remote machine to a local system
r2l_attacks = ['ftp_write', 'guess_passwd', 'imap', 'multihop', 'phf', 'spy', 'warezclient', 'warezmaster', 'sendmail', 'named']
# Define list of U2R (User to Root) attack types
# These represent privilege escalation attacks where a normal user gains root access
u2r_attacks = ['buffer_overflow', 'loadmodule', 'rootkit', 'perl', 'sqlattack', 'xterm', 'ps']
In [10]:
# Define a function to map detailed attack labels into broader categories
def mapping_function(label):
# If the record is normal traffic, return 'normal'
if label == 'normal':
return 'normal'
# If the label belongs to the list of DoS attacks, return 'dos'
elif label in dos_attacks:
return 'dos'
# If the label belongs to the list of Probe attacks, return 'probe'
elif label in probe_attacks:
return 'probe'
# If the label belongs to the list of R2L attacks, return 'r2l'
elif label in r2l_attacks:
return 'r2l'
# If the label belongs to the list of U2R attacks, return 'u2r'
elif label in u2r_attacks:
return 'u2r'
In [11]:
# Apply the mapping_function to convert detailed attack labels into broader categories
# Create a new column 'label_mc' (multi-class labels) in the training dataset
train['label_mc'] = train['label'].apply(mapping_function)
# Do the same for the testing dataset
test['label_mc'] = test['label'].apply(mapping_function)
# Print the frequency distribution of each attack category in the training dataset
print(train['label_mc'].value_counts())
label_mc normal 67343 dos 45927 probe 11656 r2l 995 u2r 52 Name: count, dtype: int64
In [12]:
# Display all unique values present in the 'protocol_type' column
# This shows the different network protocols used (e.g., tcp, udp, icmp)
print(train['protocol_type'].unique())
# Display the first 10 unique values from the 'service' column
# This shows the types of network services accessed (e.g., http, telnet, ftp)
print(train['service'].unique()[:10]) # show first 10
# Display all unique values present in the 'flag' column
# This indicates the status of the connection (e.g., SF, REJ, S0)
print(train['flag'].unique())
['tcp' 'udp' 'icmp'] ['ftp_data' 'other' 'private' 'http' 'remote_job' 'name' 'netbios_ns' 'eco_i' 'mtp' 'telnet'] ['SF' 'S0' 'REJ' 'RSTR' 'SH' 'RSTO' 'S1' 'RSTOS0' 'S3' 'S2' 'OTH']
In [13]:
# Remove any rows from the testing dataset where the 'label_mc' column has missing values
test = test.dropna(subset=['label_mc'])
In [14]:
# Prepare training features by dropping the original 'label' and the mapped 'label_mc' columns
# These columns are targets, not predictors, so they should not be included in X_train
X_train = train.drop(['label', 'label_mc'], axis=1)
# Define the training target variable (multi-class labels)
y_train = train['label_mc']
# Prepare testing features by dropping the original 'label' and the mapped 'label_mc' columns
X_test = test.drop(['label', 'label_mc'], axis=1)
# Define the testing target variable (multi-class labels)
y_test = test['label_mc']
In [15]:
# Initialize a LabelEncoder instance
le = LabelEncoder()
# Define the list of categorical columns that need encoding
# These are non-numeric features in the dataset
cat_cols = ['protocol_type', 'service', 'flag']
# Create a dictionary to store the fitted encoders for each categorical column
le_dict = {}
# Loop through each categorical column
for col in cat_cols:
le = LabelEncoder() # Create a new LabelEncoder for the current column
X_train[col] = le.fit_transform(X_train[col]) # Fit encoder on training data and transform values
X_test[col] = le.transform(X_test[col]) # Apply the same transformation to testing data
le_dict[col] = le # Save the encoder for future reference (e.g., inverse transform)
In [16]:
# Initialize the MinMaxScaler for feature normalization
# This scales all feature values to a fixed range (default: 0 to 1)
scaler = MinMaxScaler()
# Fit the scaler on the training data and transform it
# Ensures that scaling parameters are learned only from training data
X_train_scaled = scaler.fit_transform(X_train)
# Apply the same scaling transformation to the testing data
# Important to use the same scaler to maintain consistency between train and test sets
X_test_scaled = scaler.transform(X_test)
In [17]:
# Print the dimensions of the training feature set (X_train) and training labels (y_train)
# This helps verify that the number of samples (rows) matches between features and labels
print(X_train.shape, y_train.shape)
# Print the dimensions of the testing feature set (X_test) and testing labels (y_test)
# Again, this ensures consistency between features and labels in the test dataset
print(X_test.shape, y_test.shape)
(125973, 41) (125973,) (22356, 41) (22356,)
In [18]:
# Print the first 5 rows of the scaled training feature set
# This allows inspection of normalized values (range 0–1) after applying MinMaxScaler
print(X_train_scaled[:5])
# Print the first 5 rows of the scaled testing feature set
# Ensures that the same scaling transformation has been correctly applied to the test data
print(X_test_scaled[:5])
[[0.00000000e+00 5.00000000e-01 2.89855072e-01 9.00000000e-01 3.55806412e-07 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 3.91389432e-03 3.91389432e-03 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 1.00000000e+00 0.00000000e+00 0.00000000e+00 5.88235294e-01 9.80392157e-02 1.70000000e-01 3.00000000e-02 1.70000000e-01 0.00000000e+00 0.00000000e+00 0.00000000e+00 5.00000000e-02 0.00000000e+00] [0.00000000e+00 1.00000000e+00 6.37681159e-01 9.00000000e-01 1.05799870e-07 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 2.54403131e-02 1.95694716e-03 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 8.00000000e-02 1.50000000e-01 0.00000000e+00 1.00000000e+00 3.92156863e-03 0.00000000e+00 6.00000000e-01 8.80000000e-01 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 5.00000000e-01 7.10144928e-01 5.00000000e-01 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 2.40704501e-01 1.17416830e-02 1.00000000e+00 1.00000000e+00 0.00000000e+00 0.00000000e+00 5.00000000e-02 7.00000000e-02 0.00000000e+00 1.00000000e+00 1.01960784e-01 1.00000000e-01 5.00000000e-02 0.00000000e+00 0.00000000e+00 1.00000000e+00 1.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 5.00000000e-01 3.47826087e-01 9.00000000e-01 1.68120341e-07 6.22396154e-06 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 1.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 9.78473581e-03 9.78473581e-03 2.00000000e-01 2.00000000e-01 0.00000000e+00 0.00000000e+00 1.00000000e+00 0.00000000e+00 0.00000000e+00 1.17647059e-01 1.00000000e+00 1.00000000e+00 0.00000000e+00 3.00000000e-02 4.00000000e-02 3.00000000e-02 1.00000000e-02 0.00000000e+00 1.00000000e-02] [0.00000000e+00 5.00000000e-01 3.47826087e-01 9.00000000e-01 1.44206672e-07 3.20626008e-07 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 1.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 5.87084149e-02 6.26223092e-02 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 1.00000000e+00 0.00000000e+00 9.00000000e-02 1.00000000e+00 1.00000000e+00 1.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00]] [[1.16528386e-04 5.00000000e-01 7.82608696e-01 9.00000000e-01 1.76019099e-06 3.62612747e-07 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 1.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 1.95694716e-03 1.95694716e-03 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 1.00000000e+00 0.00000000e+00 0.00000000e+00 7.05882353e-01 2.23529412e-01 1.10000000e-01 2.00000000e-02 1.00000000e-02 4.00000000e-02 0.00000000e+00 0.00000000e+00 8.70000000e-01 0.00000000e+00] [0.00000000e+00 1.00000000e+00 1.73913043e-01 9.00000000e-01 3.26095490e-08 1.02294965e-07 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 3.75733855e-01 5.26418787e-01 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 9.90000000e-01 1.00000000e-02 1.00000000e-02 7.60784314e-01 6.07843137e-01 8.00000000e-01 2.00000000e-02 1.00000000e-02 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [0.00000000e+00 1.00000000e+00 1.73913043e-01 9.00000000e-01 3.26095490e-08 6.10716206e-08 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 2.36790607e-01 2.36790607e-01 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 1.00000000e+00 0.00000000e+00 0.00000000e+00 1.00000000e+00 1.00000000e+00 1.00000000e+00 0.00000000e+00 1.00000000e-02 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [4.61219353e-02 1.00000000e+00 6.37681159e-01 9.00000000e-01 1.05075213e-07 8.01565021e-08 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 3.91389432e-03 3.91389432e-03 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 1.00000000e+00 0.00000000e+00 0.00000000e+00 1.00000000e+00 7.84313725e-03 1.00000000e-02 8.40000000e-01 1.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [3.37046705e-01 5.00000000e-01 6.37681159e-01 4.00000000e-01 7.24656644e-10 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 3.91389432e-03 3.91389432e-03 0.00000000e+00 0.00000000e+00 1.00000000e+00 1.00000000e+00 1.00000000e+00 0.00000000e+00 0.00000000e+00 1.00000000e+00 7.84313725e-03 1.00000000e-02 6.80000000e-01 1.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 1.00000000e+00 1.00000000e+00]]
In [20]:
# Initialize an empty list to store results for different parameter settings
results = []
# Define a list of parameter combinations (n_estimators and max_depth) to test
param_list = [
{"n_estimators": 50, "max_depth": 10},
{"n_estimators": 100, "max_depth": 10},
{"n_estimators": 100, "max_depth": 20},
{"n_estimators": 200, "max_depth": 20},
{"n_estimators": 300, "max_depth": None}
]
# Loop through each parameter setting in the list
for params in param_list:
print(f"\nTraining with: {params}") # Print the current parameter combination being tested
# Initialize the Random Forest model with the given parameters
rf = RandomForestClassifier(
n_estimators=params["n_estimators"], # Number of trees in the forest
max_depth=params["max_depth"], # Maximum depth of each tree
random_state=42, # Ensures reproducibility
n_jobs=1 # Number of parallel jobs (set to 1 for controlled execution)
)
# Train the Random Forest model on the scaled training data
rf.fit(X_train_scaled, y_train)
# Predict labels for the scaled testing data
y_pred = rf.predict(X_test_scaled)
# Calculate accuracy score for the current parameter setting
acc = accuracy_score(y_test, y_pred)
# Print the classification report (precision, recall, F1-score, support)
print(classification_report(y_test, y_pred))
# Save the results (parameters and accuracy) into the results list
results.append({
"n_estimators": params["n_estimators"],
"max_depth": params["max_depth"],
"accuracy": acc
})
Training with: {'n_estimators': 50, 'max_depth': 10}
precision recall f1-score support
dos 0.99 0.93 0.96 8095
normal 0.88 0.99 0.93 11245
probe 0.96 0.86 0.91 2157
r2l 0.99 0.17 0.28 821
u2r 1.00 0.16 0.27 38
accuracy 0.93 22356
macro avg 0.97 0.62 0.67 22356
weighted avg 0.93 0.93 0.92 22356
Training with: {'n_estimators': 100, 'max_depth': 10}
precision recall f1-score support
dos 0.99 0.93 0.96 8095
normal 0.88 0.99 0.93 11245
probe 0.97 0.88 0.92 2157
r2l 0.99 0.16 0.28 821
u2r 1.00 0.16 0.27 38
accuracy 0.93 22356
macro avg 0.97 0.62 0.67 22356
weighted avg 0.93 0.93 0.92 22356
Training with: {'n_estimators': 100, 'max_depth': 20}
precision recall f1-score support
dos 0.99 0.93 0.96 8095
normal 0.89 0.99 0.94 11245
probe 0.96 0.89 0.93 2157
r2l 0.99 0.18 0.30 821
u2r 1.00 0.32 0.48 38
accuracy 0.93 22356
macro avg 0.97 0.66 0.72 22356
weighted avg 0.94 0.93 0.92 22356
Training with: {'n_estimators': 200, 'max_depth': 20}
precision recall f1-score support
dos 0.99 0.94 0.96 8095
normal 0.89 0.99 0.94 11245
probe 0.96 0.88 0.92 2157
r2l 0.99 0.16 0.28 821
u2r 1.00 0.32 0.48 38
accuracy 0.93 22356
macro avg 0.97 0.66 0.72 22356
weighted avg 0.94 0.93 0.92 22356
Training with: {'n_estimators': 300, 'max_depth': None}
precision recall f1-score support
dos 0.99 0.93 0.96 8095
normal 0.89 0.99 0.94 11245
probe 0.96 0.88 0.92 2157
r2l 0.99 0.17 0.28 821
u2r 1.00 0.32 0.48 38
accuracy 0.93 22356
macro avg 0.97 0.66 0.72 22356
weighted avg 0.94 0.93 0.92 22356
In [21]:
# Convert the list of results (with parameters and accuracy) into a pandas DataFrame
results_df = pd.DataFrame(results)
# Sort the DataFrame by accuracy in descending order
# This helps identify which parameter combination achieved the highest accuracy
results_df.sort_values(by="accuracy", ascending=False)
Out[21]:
| n_estimators | max_depth | accuracy | |
|---|---|---|---|
| 3 | 200 | 20.0 | 0.930086 |
| 2 | 100 | 20.0 | 0.930041 |
| 4 | 300 | NaN | 0.928833 |
| 1 | 100 | 10.0 | 0.927626 |
| 0 | 50 | 10.0 | 0.926686 |
In [22]:
# Initialize the best Random Forest model based on prior hyperparameter tuning
# - n_estimators=200: number of trees in the forest
# - max_depth=20: maximum depth of each tree
# - random_state=42: ensures reproducibility
# - n_jobs=1: limits parallel jobs for controlled execution
best_rf = RandomForestClassifier(
n_estimators=200,
max_depth=20,
random_state=42,
n_jobs=1
)
# Train (fit) the Random Forest model on the scaled training data
best_rf.fit(X_train_scaled, y_train)
# Predict the labels for the scaled testing data
y_pred = best_rf.predict(X_test_scaled)
# Print a detailed classification report
# Includes precision, recall, F1-score, and support for each attack category
print(classification_report(y_test, y_pred))
precision recall f1-score support
dos 0.99 0.94 0.96 8095
normal 0.89 0.99 0.94 11245
probe 0.96 0.88 0.92 2157
r2l 0.99 0.16 0.28 821
u2r 1.00 0.32 0.48 38
accuracy 0.93 22356
macro avg 0.97 0.66 0.72 22356
weighted avg 0.94 0.93 0.92 22356
In [23]:
# Generate the confusion matrix for the Random Forest predictions
cm = confusion_matrix(y_test, y_pred)
# Create a figure with specified size for better readability
plt.figure(figsize=(8,6))
# Plot the confusion matrix as a heatmap using seaborn
# - annot=True: display the counts inside each cell
# - fmt='d': format the annotations as integers
# - cmap='Blues': use a blue color map for visualization
# - xticklabels/yticklabels: label axes with class names from the trained model
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
xticklabels=best_rf.classes_,
yticklabels=best_rf.classes_)
# Add title and axis labels for clarity
plt.title("Random Forest Confusion Matrix")
plt.xlabel("Predicted Label")
plt.ylabel("True Label")
# Display the plot
plt.show()
In [24]:
# Create a figure with specified size for readability
plt.figure(figsize=(8,6))
# Normalize the confusion matrix by dividing each cell by the total samples of its row
# This converts raw counts into proportions (0–1), making results comparable across classes
cm_norm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]
# Plot the normalized confusion matrix as a heatmap
# - annot=True: display the normalized values inside each cell
# - fmt=".2f": format the annotations with 2 decimal places
# - cmap="Blues": use a blue color map for visualization
# - xticklabels/yticklabels: label axes with class names from the trained model
sns.heatmap(cm_norm, annot=True, fmt=".2f", cmap="Blues",
xticklabels=best_rf.classes_,
yticklabels=best_rf.classes_)
# Add title and axis labels for clarity
plt.title("Normalized Confusion Matrix")
plt.ylabel("True label")
plt.xlabel("Predicted label")
# Display the plot
plt.show()
In [25]:
# Extract feature importance scores from the trained Random Forest model
importances = best_rf.feature_importances_
# Get the list of feature names from the training dataset
features = X_train.columns
# Create a DataFrame to store features and their corresponding importance scores
fi = pd.DataFrame({
'Feature': features,
'Importance': importances
})
# Sort features by importance (highest first) and select the top 15
fi = fi.sort_values(by='Importance', ascending=False).head(15)
# Plot the top 15 most important features as a horizontal bar chart
plt.figure(figsize=(10,6))
plt.barh(fi['Feature'], fi['Importance']) # Horizontal bars for readability
plt.gca().invert_yaxis() # Invert y-axis so the most important feature is at the top
plt.xlabel("Importance Score")
plt.title("Top 15 Important Features (Random Forest)")
plt.tight_layout() # Adjust layout to prevent label overlap
plt.show()
In [26]:
# Convert the list of results (parameters and accuracy) into a pandas DataFrame
rf_results = pd.DataFrame(results)
# Create a figure with specified size for readability
plt.figure(figsize=(7,5))
# Plot accuracy scores against the number of trees (n_estimators)
# - marker='o': use circle markers to highlight each data point
plt.plot(rf_results['n_estimators'], rf_results['accuracy'], marker='o')
# Label the axes to indicate what is being compared
plt.xlabel("Number of Trees")
plt.ylabel("Accuracy")
# Add a descriptive title to the plot
plt.title("Random Forest Accuracy vs Trees")
# Add grid lines for easier interpretation of values
plt.grid(True)
# Display the plot
plt.show()
In [27]:
# Plot the distribution of traffic classes in the training dataset as a pie chart
train['label_mc'].value_counts().plot.pie(
autopct='%1.1f%%', # Display percentages with one decimal place
figsize=(6,6), # Set the figure size
title="Traffic Class Distribution" # Add a descriptive title
)
# Remove the default y-axis label for a cleaner visualization
plt.ylabel("")
# Display the plot
plt.show()
In [34]:
# Define a list of Gradient Boosting configurations to test
# Each tuple contains: (n_estimators, learning_rate, max_depth)
configs = [
(100, 0.1, 2),
(150, 0.1, 3),
]
# Loop through each configuration
for n, lr, md in configs:
print(f"\nTraining -> n={n}, lr={lr}, depth={md}") # Display current parameters
# Initialize the Gradient Boosting Classifier with the given parameters
gb = GradientBoostingClassifier(
n_estimators=n, # Number of boosting stages (trees)
learning_rate=lr, # Contribution of each tree (controls learning speed)
max_depth=md, # Maximum depth of individual trees (weak learners)
random_state=42 # Ensures reproducibility
)
# Train the Gradient Boosting model on the scaled training data
gb.fit(X_train_scaled, y_train)
# Predict labels for the scaled testing data
y_pred = gb.predict(X_test_scaled)
# Print the accuracy score for the current configuration
print("Accuracy:", accuracy_score(y_test, y_pred))
Training -> n=100, lr=0.1, depth=2 Accuracy: 0.9282519234210056 Training -> n=150, lr=0.1, depth=3 Accuracy: 0.9352746466273036
In [28]:
# Initialize the Gradient Boosting Classifier
# - n_estimators=150: number of boosting stages (trees) in the ensemble
# - learning_rate=0.1: controls contribution of each tree (trade-off between speed and accuracy)
# - max_depth=3: maximum depth of individual trees (keeps them weak learners)
# - random_state=42: ensures reproducibility
gb = GradientBoostingClassifier(
n_estimators=150,
learning_rate=0.1,
max_depth=3,
random_state=42
)
# Train (fit) the Gradient Boosting model on the scaled training data
gb.fit(X_train_scaled, y_train)
# Predict the labels for the scaled testing data
y_pred_gb = gb.predict(X_test_scaled)
# Print the overall accuracy score
print("Accuracy:", accuracy_score(y_test, y_pred_gb))
# Print a detailed classification report
# Includes precision, recall, F1-score, and support for each attack category
print(classification_report(y_test, y_pred_gb))
Accuracy: 0.9352746466273036
precision recall f1-score support
dos 0.99 0.95 0.97 8095
normal 0.90 0.99 0.94 11245
probe 0.95 0.88 0.92 2157
r2l 0.98 0.23 0.37 821
u2r 0.78 0.37 0.50 38
accuracy 0.94 22356
macro avg 0.92 0.68 0.74 22356
weighted avg 0.94 0.94 0.93 22356
In [29]:
# Generate the confusion matrix for Gradient Boosting predictions
cm_gb = confusion_matrix(y_test, y_pred_gb)
# Create a figure with specified size for readability
plt.figure(figsize=(7,6))
# Plot the confusion matrix as a heatmap using seaborn
# annot=True: display the counts inside each cell
# fmt="d": format the annotations as integers
# cmap="Greens": use a green color map for visualization
# xticklabels/yticklabels: label axes with class names from the trained model
sns.heatmap(cm_gb, annot=True, fmt="d", cmap="Greens",
xticklabels=gb.classes_,
yticklabels=gb.classes_)
# Add axis labels and title for clarity
plt.xlabel("Predicted")
plt.ylabel("Actual")
plt.title("Confusion Matrix - Gradient Boosting")
# Display the plot
plt.show()
In [30]:
# Normalize the confusion matrix by dividing each cell by the total samples of its row
# This converts raw counts into proportions (0–1), making results comparable across classes
cm_norm_gb = cm_gb.astype('float') / cm_gb.sum(axis=1)[:, np.newaxis]
# Create a figure with specified size for readability
plt.figure(figsize=(7,6))
# Plot the normalized confusion matrix as a heatmap
# - annot=True: display the normalized values inside each cell
# - fmt=".2f": format the annotations with 2 decimal places
# - cmap="Greens": use a green color map for visualization
# - xticklabels/yticklabels: label axes with class names from the trained Gradient Boosting model
sns.heatmap(cm_norm_gb, annot=True, fmt=".2f", cmap="Greens",
xticklabels=gb.classes_,
yticklabels=gb.classes_)
# Add axis labels and title for clarity
plt.xlabel("Predicted")
plt.ylabel("Actual")
plt.title("Normalized Confusion Matrix - Gradient Boosting")
# Display the plot
plt.show()
In [31]:
# Create a DataFrame of feature importances from the trained Gradient Boosting model
fi_gb = pd.DataFrame({
"Feature": X_train.columns, # Feature names from the training dataset
"Importance": gb.feature_importances_ # Corresponding importance scores
})
# Sort features by importance (highest first) and select the top 15
fi_gb = fi_gb.sort_values(by="Importance", ascending=False).head(15)
# Plot the top 15 most important features as a horizontal bar chart
plt.figure(figsize=(9,5))
plt.barh(fi_gb["Feature"], fi_gb["Importance"]) # Horizontal bars for readability
plt.gca().invert_yaxis() # Invert y-axis so most important feature appears at the top
plt.title("Top 15 Features - Gradient Boosting") # Add descriptive title
plt.tight_layout() # Adjust layout to prevent label overlap
plt.show()
In [50]:
# Define Gradient Boosting results with different numbers of estimators (trees)
results_gb = [
{"n_estimators": 100, "accuracy": 0.928},
{"n_estimators": 150, "accuracy": 0.935},
{"n_estimators": 200, "accuracy": 0.932}
]
# Convert results into a pandas DataFrame for structured analysis
gb_results = pd.DataFrame(results_gb)
# Create a figure with specified size for readability
plt.figure(figsize=(7,5))
# Plot accuracy scores against number of estimators
# - marker='o': circle markers for each data point
# - color='green': consistent color theme for Gradient Boosting
plt.plot(gb_results['n_estimators'], gb_results['accuracy'], marker='o', color='green')
# Label axes to indicate what is being compared
plt.xlabel("Number of Estimators (Trees)")
plt.ylabel("Accuracy")
# Add a descriptive title to the plot
plt.title("Gradient Boosting Accuracy vs Number of Trees")
# Add grid lines for easier interpretation of values
plt.grid(True)
# Annotate each point with its accuracy value (formatted to 3 decimal places)
for i, row in gb_results.iterrows():
plt.text(row['n_estimators'], row['accuracy'] + 0.002, f"{row['accuracy']:.3f}", ha='center')
# Display the plot
plt.show()
In [35]:
# Define the models being compared
models = ["Random Forest", "Gradient Boosting"]
# Define their corresponding accuracy scores
accuracy = [0.93, 0.9353]
# Create a bar chart to compare model accuracies
plt.bar(models, accuracy)
# Label the y-axis to indicate metric being plotted
plt.ylabel("Accuracy")
# Add a descriptive title to the chart
plt.title("Model Accuracy Comparison")
# Set y-axis limits to focus on the accuracy range (0.85–1.0)
plt.ylim(0.85, 1.0)
# Display the plot
plt.show()
In [ ]: