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()
No description has been provided for this image
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()
No description has been provided for this image
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()
No description has been provided for this image
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()
No description has been provided for this image
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()
No description has been provided for this image
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()
No description has been provided for this image
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()
No description has been provided for this image
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()
No description has been provided for this image
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()
No description has been provided for this image
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()
No description has been provided for this image
In [ ]: