In [1]:
# NSL-KDD ensemble experiment
# Save as nsl_kdd_ensembles.py or run cell-by-cell in Jupyter.

import time
import warnings
warnings.filterwarnings("ignore")

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

from sklearn.model_selection import StratifiedKFold, RandomizedSearchCV
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score
In [2]:
# Loading the dataset
train = pd.read_excel('kdd_train.xlsx')
test = pd.read_excel('kdd_test.xlsx')
print("Train shape:", train.shape, "Test shape:", test.shape)
Train shape: (125973, 42) Test shape: (22544, 42)
In [3]:
train.head()
Out[3]:
duration protocol_type service flag src_bytes dst_bytes land wrong_fragment urgent hot ... 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 labels
0 0 tcp ftp_data SF 491 0 0 0 0 0 ... 25 0.17 0.03 0.17 0.00 0.00 0.00 0.05 0.00 normal
1 0 udp other SF 146 0 0 0 0 0 ... 1 0.00 0.60 0.88 0.00 0.00 0.00 0.00 0.00 normal
2 0 tcp private S0 0 0 0 0 0 0 ... 26 0.10 0.05 0.00 0.00 1.00 1.00 0.00 0.00 neptune
3 0 tcp http SF 232 8153 0 0 0 0 ... 255 1.00 0.00 0.03 0.04 0.03 0.01 0.00 0.01 normal
4 0 tcp http SF 199 420 0 0 0 0 ... 255 1.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 normal

5 rows × 42 columns

In [4]:
test.head()
Out[4]:
duration protocol_type service flag src_bytes dst_bytes land wrong_fragment urgent hot ... 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 labels
0 5 tcp smtp SF 2429 475 0 0 0 0 ... 57 0.11 0.02 0.01 0.04 0.0 0.0 0.87 0.0 normal
1 0 udp domain_u SF 45 134 0 0 0 0 ... 155 0.80 0.02 0.01 0.00 0.0 0.0 0.00 0.0 normal
2 0 udp domain_u SF 45 80 0 0 0 0 ... 255 1.00 0.00 0.01 0.00 0.0 0.0 0.00 0.0 normal
3 1979 udp other SF 145 105 0 0 0 0 ... 2 0.01 0.84 1.00 0.00 0.0 0.0 0.00 0.0 normal
4 14462 tcp other RSTR 1 0 0 0 0 0 ... 2 0.01 0.68 1.00 0.00 0.0 0.0 1.00 1.0 portsweep

5 rows × 42 columns

In [5]:
# Let us see the info of the training data
train.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 125973 entries, 0 to 125972
Data columns (total 42 columns):
 #   Column                       Non-Null Count   Dtype  
---  ------                       --------------   -----  
 0   duration                     125973 non-null  int64  
 1   protocol_type                125973 non-null  object 
 2   service                      125973 non-null  object 
 3   flag                         125973 non-null  object 
 4   src_bytes                    125973 non-null  int64  
 5   dst_bytes                    125973 non-null  int64  
 6   land                         125973 non-null  int64  
 7   wrong_fragment               125973 non-null  int64  
 8   urgent                       125973 non-null  int64  
 9   hot                          125973 non-null  int64  
 10  num_failed_logins            125973 non-null  int64  
 11  logged_in                    125973 non-null  int64  
 12  num_compromised              125973 non-null  int64  
 13  root_shell                   125973 non-null  int64  
 14  su_attempted                 125973 non-null  int64  
 15  num_root                     125973 non-null  int64  
 16  num_file_creations           125973 non-null  int64  
 17  num_shells                   125973 non-null  int64  
 18  num_access_files             125973 non-null  int64  
 19  num_outbound_cmds            125973 non-null  int64  
 20  is_host_login                125973 non-null  int64  
 21  is_guest_login               125973 non-null  int64  
 22  count                        125973 non-null  int64  
 23  srv_count                    125973 non-null  int64  
 24  serror_rate                  125973 non-null  float64
 25  srv_serror_rate              125973 non-null  float64
 26  rerror_rate                  125973 non-null  float64
 27  srv_rerror_rate              125973 non-null  float64
 28  same_srv_rate                125973 non-null  float64
 29  diff_srv_rate                125973 non-null  float64
 30  srv_diff_host_rate           125973 non-null  float64
 31  dst_host_count               125973 non-null  int64  
 32  dst_host_srv_count           125973 non-null  int64  
 33  dst_host_same_srv_rate       125973 non-null  float64
 34  dst_host_diff_srv_rate       125973 non-null  float64
 35  dst_host_same_src_port_rate  125973 non-null  float64
 36  dst_host_srv_diff_host_rate  125973 non-null  float64
 37  dst_host_serror_rate         125973 non-null  float64
 38  dst_host_srv_serror_rate     125973 non-null  float64
 39  dst_host_rerror_rate         125973 non-null  float64
 40  dst_host_srv_rerror_rate     125973 non-null  float64
 41  labels                       125973 non-null  object 
dtypes: float64(15), int64(23), object(4)
memory usage: 40.4+ MB
In [6]:
# Let us see the info of the testing data
test.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 22544 entries, 0 to 22543
Data columns (total 42 columns):
 #   Column                       Non-Null Count  Dtype  
---  ------                       --------------  -----  
 0   duration                     22544 non-null  int64  
 1   protocol_type                22544 non-null  object 
 2   service                      22544 non-null  object 
 3   flag                         22544 non-null  object 
 4   src_bytes                    22544 non-null  int64  
 5   dst_bytes                    22544 non-null  int64  
 6   land                         22544 non-null  int64  
 7   wrong_fragment               22544 non-null  int64  
 8   urgent                       22544 non-null  int64  
 9   hot                          22544 non-null  int64  
 10  num_failed_logins            22544 non-null  int64  
 11  logged_in                    22544 non-null  int64  
 12  num_compromised              22544 non-null  int64  
 13  root_shell                   22544 non-null  int64  
 14  su_attempted                 22544 non-null  int64  
 15  num_root                     22544 non-null  int64  
 16  num_file_creations           22544 non-null  int64  
 17  num_shells                   22544 non-null  int64  
 18  num_access_files             22544 non-null  int64  
 19  num_outbound_cmds            22544 non-null  int64  
 20  is_host_login                22544 non-null  int64  
 21  is_guest_login               22544 non-null  int64  
 22  count                        22544 non-null  int64  
 23  srv_count                    22544 non-null  int64  
 24  serror_rate                  22544 non-null  float64
 25  srv_serror_rate              22544 non-null  float64
 26  rerror_rate                  22544 non-null  float64
 27  srv_rerror_rate              22544 non-null  float64
 28  same_srv_rate                22544 non-null  float64
 29  diff_srv_rate                22544 non-null  float64
 30  srv_diff_host_rate           22544 non-null  float64
 31  dst_host_count               22544 non-null  int64  
 32  dst_host_srv_count           22544 non-null  int64  
 33  dst_host_same_srv_rate       22544 non-null  float64
 34  dst_host_diff_srv_rate       22544 non-null  float64
 35  dst_host_same_src_port_rate  22544 non-null  float64
 36  dst_host_srv_diff_host_rate  22544 non-null  float64
 37  dst_host_serror_rate         22544 non-null  float64
 38  dst_host_srv_serror_rate     22544 non-null  float64
 39  dst_host_rerror_rate         22544 non-null  float64
 40  dst_host_srv_rerror_rate     22544 non-null  float64
 41  labels                       22544 non-null  object 
dtypes: float64(15), int64(23), object(4)
memory usage: 7.2+ MB
In [7]:
# Checking unique values in each column
train.nunique()
Out[7]:
duration                       2981
protocol_type                     3
service                          70
flag                             11
src_bytes                      3341
dst_bytes                      9326
land                              2
wrong_fragment                    3
urgent                            4
hot                              28
num_failed_logins                 6
logged_in                         2
num_compromised                  88
root_shell                        2
su_attempted                      3
num_root                         82
num_file_creations               35
num_shells                        3
num_access_files                 10
num_outbound_cmds                 1
is_host_login                     2
is_guest_login                    2
count                           512
srv_count                       509
serror_rate                      89
srv_serror_rate                  86
rerror_rate                      82
srv_rerror_rate                  62
same_srv_rate                   101
diff_srv_rate                    95
srv_diff_host_rate               60
dst_host_count                  256
dst_host_srv_count              256
dst_host_same_srv_rate          101
dst_host_diff_srv_rate          101
dst_host_same_src_port_rate     101
dst_host_srv_diff_host_rate      75
dst_host_serror_rate            101
dst_host_srv_serror_rate        100
dst_host_rerror_rate            101
dst_host_srv_rerror_rate        101
labels                           23
dtype: int64
In [8]:
# Checking unique values in each column
test.nunique()
Out[8]:
duration                        704
protocol_type                     3
service                          66
flag                             11
src_bytes                      1517
dst_bytes                      3745
land                              2
wrong_fragment                    3
urgent                            3
hot                              18
num_failed_logins                 6
logged_in                         2
num_compromised                  29
root_shell                        2
su_attempted                      3
num_root                         27
num_file_creations               14
num_shells                        3
num_access_files                  7
num_outbound_cmds                 1
is_host_login                     2
is_guest_login                    2
count                           476
srv_count                       428
serror_rate                      85
srv_serror_rate                  66
rerror_rate                      85
srv_rerror_rate                  80
same_srv_rate                    97
diff_srv_rate                    92
srv_diff_host_rate               73
dst_host_count                  255
dst_host_srv_count              255
dst_host_same_srv_rate          101
dst_host_diff_srv_rate          101
dst_host_same_src_port_rate     101
dst_host_srv_diff_host_rate      60
dst_host_serror_rate            100
dst_host_srv_serror_rate        100
dst_host_rerror_rate            101
dst_host_srv_rerror_rate        101
labels                           36
dtype: int64
In [9]:
# Numerical summary of the training data
train.describe()
Out[9]:
duration src_bytes dst_bytes land wrong_fragment urgent hot num_failed_logins logged_in num_compromised ... 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
count 125973.00000 1.259730e+05 1.259730e+05 125973.000000 125973.000000 125973.000000 125973.000000 125973.000000 125973.000000 125973.000000 ... 125973.000000 125973.000000 125973.000000 125973.000000 125973.000000 125973.000000 125973.000000 125973.000000 125973.000000 125973.000000
mean 287.14465 4.556674e+04 1.977911e+04 0.000198 0.022687 0.000111 0.204409 0.001222 0.395736 0.279250 ... 182.148945 115.653005 0.521242 0.082951 0.148379 0.032542 0.284452 0.278485 0.118832 0.120240
std 2604.51531 5.870331e+06 4.021269e+06 0.014086 0.253530 0.014366 2.149968 0.045239 0.489010 23.942042 ... 99.206213 110.702741 0.448949 0.188922 0.308997 0.112564 0.444784 0.445669 0.306557 0.319459
min 0.00000 0.000000e+00 0.000000e+00 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 ... 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
25% 0.00000 0.000000e+00 0.000000e+00 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 ... 82.000000 10.000000 0.050000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
50% 0.00000 4.400000e+01 0.000000e+00 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 ... 255.000000 63.000000 0.510000 0.020000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
75% 0.00000 2.760000e+02 5.160000e+02 0.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 ... 255.000000 255.000000 1.000000 0.070000 0.060000 0.020000 1.000000 1.000000 0.000000 0.000000
max 42908.00000 1.379964e+09 1.309937e+09 1.000000 3.000000 3.000000 77.000000 5.000000 1.000000 7479.000000 ... 255.000000 255.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000

8 rows × 38 columns

In [10]:
# Numerical summary of the testing data
test.describe()
Out[10]:
duration src_bytes dst_bytes land wrong_fragment urgent hot num_failed_logins logged_in num_compromised ... 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
count 22544.000000 2.254400e+04 2.254400e+04 22544.000000 22544.000000 22544.000000 22544.000000 22544.000000 22544.000000 22544.000000 ... 22544.000000 22544.000000 22544.000000 22544.000000 22544.000000 22544.000000 22544.000000 22544.000000 22544.000000 22544.000000
mean 258.858499 1.226873e+04 2.966787e+03 0.000089 0.016989 0.000177 0.155562 0.008783 0.406095 0.259359 ... 186.878549 123.129613 0.545571 0.084905 0.141117 0.028275 0.229375 0.225729 0.155008 0.153560
std 2184.355090 4.665783e+05 6.293205e+04 0.009419 0.217507 0.016313 1.699324 0.104942 0.491114 11.370701 ... 97.508604 111.577695 0.447079 0.199813 0.305538 0.105966 0.410665 0.412500 0.339255 0.350511
min 0.000000 0.000000e+00 0.000000e+00 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 ... 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
25% 0.000000 0.000000e+00 0.000000e+00 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 ... 95.000000 12.000000 0.050000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
50% 0.000000 4.500000e+01 0.000000e+00 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 ... 255.000000 85.000000 0.670000 0.020000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
75% 0.000000 2.750000e+02 5.950000e+02 0.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 ... 255.000000 255.000000 1.000000 0.070000 0.050000 0.010000 0.100000 0.010000 0.010000 0.000000
max 54451.000000 6.282565e+07 5.153771e+06 1.000000 3.000000 2.000000 30.000000 5.000000 1.000000 796.000000 ... 255.000000 255.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000

8 rows × 38 columns

In [11]:
# Row index info of the train data
train.index
Out[11]:
RangeIndex(start=0, stop=125973, step=1)
In [12]:
# Row index info of the test data
test.index
Out[12]:
RangeIndex(start=0, stop=22544, step=1)
In [13]:
# 2) Identify label column (common names: 'label','labels','attack','class')
possible_label_cols = [c for c in train.columns if c.lower() in ("label","labels","class","attack","difficulty_level")]
label_col = possible_label_cols[0] if possible_label_cols else train.columns[-1]
print("Using label column:", label_col)
Using label column: labels
In [14]:
# 3) Map attack names to 5 classes
dos = {'back','land','neptune','pod','smurf','teardrop','apache2','udpstorm','processtable','mailbomb','worm'}
probe = {'satan','ipsweep','nmap','portsweep','mscan','saint'}
r2l = {'ftp_write','guess_passwd','imap','multihop','phf','spy','warezclient','warezmaster','sendmail'}
u2r = {'buffer_overflow','loadmodule','perl','rootkit','sqlattack','xterm'}

def map_attack(a):
    a = str(a).lower().strip()
    if a in ('normal','normal.'): return 'normal'
    if a in dos: return 'dos'
    if a in probe: return 'probe'
    if a in r2l: return 'r2l'
    if a in u2r: return 'u2r'
    if any(x in a for x in ['scan','sweep','nmap','ips']): return 'probe'
    if any(x in a for x in ['back','nept','smurf','teardrop','pod']): return 'dos'
    if any(x in a for x in ['overflow','root','loadmod','perl','sql']): return 'u2r'
    if any(x in a for x in ['warez','guess','ftp','imap','phf','spy']): return 'r2l'
    if 'dos' in a: return 'dos'
    return 'other'

train['attack_category'] = train[label_col].apply(map_attack)
test['attack_category']  = test[label_col].apply(map_attack)

print("Mapped class counts (train):")
print(train['attack_category'].value_counts())

print("Mapped class counts (test):")
print(test['attack_category'].value_counts())
Mapped class counts (train):
attack_category
normal    67343
dos       45927
probe     11656
r2l         995
u2r          52
Name: count, dtype: int64
Mapped class counts (test):
attack_category
normal    11245
dos        8095
probe      2157
r2l         918
other       100
u2r          29
Name: count, dtype: int64
In [15]:
# 4) Select features
# Common categorical fields in NSL-KDD: protocol_type, service, flag
cat_cols = [c for c in train.columns if c.lower() in ('protocol_type','service','flag')]
num_cols = [c for c in train.columns if c not in cat_cols + [label_col,'attack_category']]

# Ensure numeric columns are numeric
for c in num_cols:
    if train[c].dtype == object:
        train[c] = pd.to_numeric(train[c], errors='coerce')
        test[c]  = pd.to_numeric(test[c], errors='coerce')
train[num_cols] = train[num_cols].fillna(0)
test[num_cols]  = test[num_cols].fillna(0)
In [16]:
# 5) Preprocessor: scale numeric, one-hot encode categorical
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer

preprocessor = ColumnTransformer(transformers=[
    ('num', StandardScaler(), num_cols),
    ('cat', OneHotEncoder(handle_unknown='ignore', sparse_output=False), cat_cols)
])
In [19]:
# 6) For computational reasons: optionally subsample the training set for tuning/training
#    (set sample_n=None to use the full training set)
sample_n = 20000    # set to None or a larger number if you have more compute
if sample_n is not None and train.shape[0] > sample_n:
    # stratified sample by attack_category
    frac = sample_n / train.shape[0]
    sample = train.groupby('attack_category', group_keys=False).apply(
        lambda df: df.sample(frac=min(1, frac), random_state=42)
    ).sample(n=min(sample_n, train.shape[0]), random_state=42)
else:
    sample = train.copy()
print("Using sample size:", sample.shape)

X_sample = sample.drop(columns=[label_col,'attack_category'])
y_sample = sample['attack_category']
X_test   = test.drop(columns=[label_col,'attack_category'])
y_test   = test['attack_category']
Using sample size: (20000, 43)
In [20]:
# 7) Define models and pipelines
rf = RandomForestClassifier(random_state=42, n_jobs=1)
gb = GradientBoostingClassifier(random_state=42, n_estimators=100, learning_rate=0.1, max_depth=3)

pipe_rf = Pipeline([('pre', preprocessor), ('clf', rf)])
pipe_gb = Pipeline([('pre', preprocessor), ('clf', gb)])
In [21]:
# 8) Hyperparameter tuning for RF (small randomized search)
rf_param_dist = {
    'clf__n_estimators': [50, 100, 150],
    'clf__max_depth': [None, 10, 20],
    'clf__min_samples_split': [2, 5, 10]
}

cv = StratifiedKFold(n_splits=3, shuffle=True, random_state=42)
rs_rf = RandomizedSearchCV(pipe_rf, rf_param_dist, n_iter=6, cv=cv, scoring='f1_macro', n_jobs=1, random_state=42, verbose=1)

t0 = time.time()
rs_rf.fit(X_sample, y_sample)
rf_search_time = time.time() - t0
print("RF best params:", rs_rf.best_params_, "search time:", rf_search_time)
Fitting 3 folds for each of 6 candidates, totalling 18 fits
RF best params: {'clf__n_estimators': 50, 'clf__min_samples_split': 2, 'clf__max_depth': None} search time: 8.22621750831604
In [22]:
# 9) For GB we will use the default/sensible param values (or you can run a small search)
# (If you have the compute, create a similar RandomizedSearchCV for GB.)
# Fit the chosen/found models on the sampled training set
best_rf = rs_rf.best_estimator_
print("Fitting best RF on the sample...")
t0 = time.time()
best_rf.fit(X_sample, y_sample)
rf_fit_time = time.time() - t0

print("Fitting default GB on the sample...")
t0 = time.time()
pipe_gb.fit(X_sample, y_sample)
gb_fit_time = time.time() - t0
Fitting best RF on the sample...
Fitting default GB on the sample...
In [23]:
# 10) Evaluate on test set
y_pred_rf = best_rf.predict(X_test)
y_pred_gb = pipe_gb.predict(X_test)

print("\nRandom Forest classification report:\n")
print(classification_report(y_test, y_pred_rf, digits=4))
print("\nGradient Boosting classification report:\n")
print(classification_report(y_test, y_pred_gb, digits=4))

print("\nAccuracy - RF: {:.4f}, GB: {:.4f}".format(accuracy_score(y_test, y_pred_rf), accuracy_score(y_test, y_pred_gb)))
Random Forest classification report:

              precision    recall  f1-score   support

         dos     0.9895    0.9354    0.9617      8095
      normal     0.8702    0.9927    0.9274     11245
       other     0.0000    0.0000    0.0000       100
       probe     0.9623    0.8642    0.9106      2157
         r2l     0.9918    0.1318    0.2327       918
         u2r     0.6000    0.1034    0.1765        29

    accuracy                         0.9192     22544
   macro avg     0.7356    0.5046    0.5348     22544
weighted avg     0.9226    0.9192    0.9048     22544


Gradient Boosting classification report:

              precision    recall  f1-score   support

         dos     0.9903    0.9576    0.9737      8095
      normal     0.8944    0.9900    0.9398     11245
       other     0.0000    0.0000    0.0000       100
       probe     0.9387    0.8739    0.9052      2157
         r2l     0.9447    0.2233    0.3612       918
         u2r     0.2273    0.3448    0.2740        29

    accuracy                         0.9308     22544
   macro avg     0.6659    0.5650    0.5756     22544
weighted avg     0.9303    0.9308    0.9201     22544


Accuracy - RF: 0.9192, GB: 0.9308
In [24]:
# 11) Confusion matrices
labels = ['normal','dos','probe','r2l','u2r','other']
cm_rf = confusion_matrix(y_test, y_pred_rf, labels=labels)
cm_gb = confusion_matrix(y_test, y_pred_gb, labels=labels)

fig, axs = plt.subplots(1,2, figsize=(14,5))
axs[0].imshow(cm_rf, interpolation='nearest')
axs[0].set_title('RF Confusion Matrix')
axs[0].set_xticks(range(len(labels))); axs[0].set_yticks(range(len(labels)))
axs[0].set_xticklabels(labels, rotation=45); axs[0].set_yticklabels(labels)
for i in range(cm_rf.shape[0]):
    for j in range(cm_rf.shape[1]):
        axs[0].text(j, i, cm_rf[i,j], ha='center', va='center', color='white' if cm_rf[i,j] > cm_rf.max()/2 else 'black')

axs[1].imshow(cm_gb, interpolation='nearest')
axs[1].set_title('GB Confusion Matrix')
axs[1].set_xticks(range(len(labels))); axs[1].set_yticks(range(len(labels)))
axs[1].set_xticklabels(labels, rotation=45); axs[1].set_yticklabels(labels)
for i in range(cm_gb.shape[0]):
    for j in range(cm_gb.shape[1]):
        axs[1].text(j, i, cm_gb[i,j], ha='center', va='center', color='white' if cm_gb[i,j] > cm_gb.max()/2 else 'black')

plt.tight_layout(); plt.show()
No description has been provided for this image
In [25]:
# 12) Comparison of Random Forest vs Gradient Boosting 
import time

# Measure training time for Random Forest
start = time.time()
best_rf.fit(X_sample, y_sample)
rf_time = time.time() - start

# Measure training time for Gradient Boosting
start = time.time()
pipe_gb.fit(X_sample, y_sample)
gb_time = time.time() - start

print("Training Time (seconds):")
print(f"Random Forest: {rf_time:.2f} seconds")
print(f"Gradient Boosting: {gb_time:.2f} seconds")
Training Time (seconds):
Random Forest: 0.40 seconds
Gradient Boosting: 15.58 seconds
In [26]:
model_times = {"Random Forest": rf_time, "Gradient Boosting": gb_time}
model_times
Out[26]:
{'Random Forest': 0.4006197452545166, 'Gradient Boosting': 15.579497575759888}
In [27]:
# 13) Feature importances (RF and GB)
pre = best_rf.named_steps['pre']
num_features = num_cols
cat_encoder = pre.named_transformers_['cat']

try:
    cat_features = list(cat_encoder.get_feature_names_out(cat_cols))
except Exception:
    # fallback if older sklearn
    cat_features = []
    for col in cat_cols:
        values = sorted(sample[col].astype(str).unique())
        cat_features.extend([f"{col}__{v}" for v in values])

all_features = num_features + cat_features

rf_imps = best_rf.named_steps['clf'].feature_importances_
gb_imps = pipe_gb.named_steps['clf'].feature_importances_

imp_df = pd.DataFrame({"feature": all_features, "rf_importance": rf_imps, "gb_importance": gb_imps})
imp_df = imp_df.sort_values("rf_importance", ascending=False).reset_index(drop=True)
print("Top features by RF importance:")
print(imp_df.head(20))
Top features by RF importance:
                        feature  rf_importance  gb_importance
0                     src_bytes       0.100335       0.387996
1                 diff_srv_rate       0.066176       0.031542
2                       flag_S0       0.059355       0.002485
3          dst_host_serror_rate       0.057830       0.093042
4                       flag_SF       0.057699       0.000022
5        dst_host_diff_srv_rate       0.057490       0.051138
6                     dst_bytes       0.049812       0.025881
7                 same_srv_rate       0.047991       0.165326
8        dst_host_same_srv_rate       0.035741       0.012401
9                   serror_rate       0.034991       0.022129
10                        count       0.033569       0.020397
11  dst_host_srv_diff_host_rate       0.032294       0.000577
12           dst_host_srv_count       0.029690       0.001365
13  dst_host_same_src_port_rate       0.029662       0.007624
14              srv_serror_rate       0.028490       0.000102
15                    logged_in       0.022835       0.001407
16                    srv_count       0.021627       0.000044
17     dst_host_srv_rerror_rate       0.020713       0.000154
18               dst_host_count       0.020206       0.000454
19     dst_host_srv_serror_rate       0.019995       0.000144
In [28]:
# Plot top 15 features
top_n = 15
top_rf = imp_df.head(top_n).iloc[::-1]  # reverse for plotting
top_gb = imp_df.sort_values("gb_importance", ascending=False).head(top_n).iloc[::-1]

fig, ax = plt.subplots(1,2, figsize=(16,7))
ax[0].barh(range(len(top_rf)), top_rf['rf_importance'])
ax[0].set_yticks(range(len(top_rf))); ax[0].set_yticklabels(top_rf['feature'])
ax[0].set_title("Top RF importances")
ax[1].barh(range(len(top_gb)), top_gb['gb_importance'])
ax[1].set_yticks(range(len(top_gb))); ax[1].set_yticklabels(top_gb['feature'])
ax[1].set_title("Top GB importances")
plt.tight_layout(); plt.show()
No description has been provided for this image
In [ ]: