Detect threats intelligently ย ยทย Learn from network flows ย ยทย Adapt through reward-driven decisions
| ๐ง Autoencoders | โ๏ธ RL Algorithm | ๐ Latent Dim | ๐ฆ Dataset | ๐ฏ Best F1 | ๐ Peak Reward |
|---|---|---|---|---|---|
| FF-AE ยท DAE ยท Conv-AE | PPO (MlpPolicy) | 32 (FF/Conv) ยท 64 (DAE) | CICIDS-2017 | ~0.92 | 4932 |
NetGuard AI is a hybrid Network Intrusion Detection System (NIDS) that fuses unsupervised deep representation learning with adaptive reinforcement learning to classify network traffic as benign or malicious. Rather than relying on hand-crafted features or static thresholds, the system autonomously discovers compact latent representations of raw network flows and trains a PPO policy-gradient agent to make detection decisions through reward-driven optimization.
| ย | What makes it different? | How it achieves it |
|---|---|---|
| ๐ง | Deep Representation Learning | Three autoencoder architectures compress raw features into a 32โ64 dimensional latent space |
| ๐ฎ | Adaptive Decision Boundary | PPO RL agent learns to detect anomalies through structured reward signals โ not static thresholds |
| โ๏ธ | Cost-Sensitive Reward Shaping | Missed attacks penalized at โ4, false alarms at โ1 โ explicitly prioritizing security recall |
| ๐ | Multi-Threshold Evaluation | Final detection evaluated at thresholds 0.25, 0.30, and 0.35 for fine-grained sensitivity control |
| ๐ฆ | Large-Scale Validation | Trained and tested on 2.8M+ real network flow records from the CICIDS-2017 benchmark |
The project implements a tightly integrated, end-to-end pipeline executed independently for each autoencoder. The notebook is organized in this exact order: Pipeline 1: FF-AE โ Pipeline 2: DAE โ Pipeline 3: Conv-AE.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ STAGE 1 โ Feature Extraction โ
โ โ
โ Raw Network Flow (76+ features after preprocessing) โ
โ โ โ
โ โโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โผ โผ โผ โ
โ [FF-AE] [DAE] [Conv-AE] โ
โ Dense GaussianNoise(0.05)โDense Conv1DโMaxPoolโ โ
โ 128โ64โ32 128โ64โ64(latent) FlattenโDense(32) โ
โ โ
โ All produce: Latent Vector (32 or 64 dims) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ STAGE 2 โ Custom Gymnasium RL Environment โ
โ โ
โ Observation : Latent vector (32 or 64 dims from frozen encoder) โ
โ Actions : Discrete(2) โ {0: Benign, 1: Malicious} โ
โ Rewards : +2 correct ยท โ4 missed attack ยท โ1 false alarm โ
โ Episode len : max_steps = 3000 samples per episode โ
โ โ
โ PPO Agent โ MlpPolicy [128, 128] hidden layers โ
โ Total training: 120,000 timesteps โ
โ EvalCallback every 4,000 steps โ saved to ./best_model/ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ STAGE 3 โ Evaluation โ
โ โ
โ Mode A: Argmax Policy (deterministic=True) โ
โ Mode B: Custom Threshold โ CategoricalDistribution probs[:,1] > thr โ
โ Thresholds: 0.25 / 0.30 / 0.35 (DAE and Conv-AE) ยท 0.30 only (FF-AE) โ
โ Metrics: Precision ยท Recall ยท F1 ยท Confusion Matrix ยท PR-AUC โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The Feedforward Autoencoder is the first and baseline pipeline. A dense encoderโdecoder stack learns to compress and reconstruct high-dimensional network traffic features into a compact 32-dimensional latent representation passed to the PPO agent.
input_dim = X_train.shape[1] # dynamic โ features after preprocessing
latent_dim = 32
# โโ ENCODER โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
input_layer = Input(shape=(input_dim,))
encoded = Dense(128, activation='relu', kernel_regularizer=l2(1e-5))(input_layer)
encoded = Dropout(0.3)(encoded)
encoded = Dense(64, activation='relu', kernel_regularizer=l2(1e-5))(encoded)
encoded = Dropout(0.3)(encoded)
encoded = Dense(32, activation='relu')(encoded) # Latent Space (32-dim)
# โโ DECODER โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
decoded = Dense(64, activation='relu')(encoded)
decoded = Dropout(0.3)(decoded)
decoded = Dense(128, activation='relu')(decoded)
decoded = Dense(input_dim, activation='linear')(decoded) # linear โ unbounded reconstruction
autoencoder = Model(inputs=input_layer, outputs=decoded)
autoencoder.compile(optimizer=Adam(learning_rate=1e-4, clipvalue=1.0), loss='mse')
# โโ ENCODER SUBMODEL (passed to RL environment) โโโโโโโโโโโโโโโโโโโโโโโ
encoder = Model(inputs=input_layer, outputs=encoded)| Hyperparameter | Value |
|---|---|
| Latent Dimensionality | 32 |
| Encoder Hidden Layers | 128 โ 64 โ 32 |
| Decoder Hidden Layers | 64 โ 128 โ input_dim |
| Decoder Final Activation | linear |
| Dropout Rate | 0.3 |
| L2 Regularization | 1e-5 (encoder Dense layers only) |
| Optimizer | Adam (lr=1e-4, clipvalue=1.0) |
| Loss | MSE |
| Epochs | 50 (EarlyStopping: patience=5, restore_best_weights=True) |
| Batch Size | 2048 |
| Validation | validation_split=0.1 |
| LR Scheduler | ReduceLROnPlateau (factor=0.5, patience=3, min_lr=1e-6) |
The FF-AE environment uses a symmetric / balanced reward โ any misclassification (whether missing an attack or triggering a false alarm) costs the same โ3:
# โโ Reward Logic (FF-AE) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
if action == true_label:
reward = 2 # Correct classification โ any class
else:
reward = -3 # Any misclassification โ symmetric penalty
# โโ PPO Hyperparameters (FF-AE) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
model = PPO(
"MlpPolicy", env,
policy_kwargs = {"net_arch": {"pi": [128, 128], "vf": [128, 128]}},
learning_rate = 1e-3, # Higher LR than DAE / Conv-AE
n_steps = 1024, # Larger rollout buffer
batch_size = 512,
gamma = 0.995,
clip_range = 0.2,
ent_coef = 0.04, # Higher entropy coefficient
# gae_lambda and vf_coef not set โ SB3 defaults used
verbose = 1,
tensorboard_log = "./ppo_logs/",
device = "auto"
)
model.learn(total_timesteps=120000, callback=eval_callback, progress_bar=True)
model.save("ppo_anomaly_detector")๐ Threshold Evaluation: The FF-AE pipeline evaluates at a single fixed threshold = 0.30 only โ it does not sweep multiple thresholds like DAE and Conv-AE.
The Denoising Autoencoder is the most robust of the three architectures. It deliberately injects Gaussian noise (stddev=0.05) into the input via a GaussianNoise layer during training, forcing the encoder to learn noise-invariant latent representations that capture the true statistical structure of network traffic. This pipeline also introduces a cost-sensitive reward environment, uses PyTorch tensors directly inside _get_encoded_state, and expands the latent space to 64 dimensions.
input_dim = X_train.shape[1]
latent_dim = 64 # Larger than FF-AE (32)
# โโ NOISE INJECTION (unique to DAE) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
input_layer = Input(shape=(input_dim,))
noisy_input = GaussianNoise(0.05)(input_layer) # stddev=0.05
# โโ ENCODER (learns to denoise and compress) โโโโโโโโโโโโโโโโโโโโโโโโโโ
encoded = Dense(128, activation='relu', kernel_regularizer=l2(1e-5))(noisy_input)
encoded = Dropout(0.4)(encoded) # Higher dropout than FF-AE
encoded = Dense(64, activation='relu', kernel_regularizer=l2(1e-5))(encoded)
encoded = Dropout(0.4)(encoded)
encoded = Dense(64, activation='relu')(encoded) # Latent Space (64-dim)
# โโ DECODER (reconstructs clean signal from noisy encoding) โโโโโโโโโโโ
decoded = Dense(64, activation='relu')(encoded)
decoded = Dropout(0.4)(decoded)
decoded = Dense(128, activation='relu')(decoded)
decoded = Dense(input_dim, activation='sigmoid')(decoded) # sigmoid โ bounded output
autoencoder = Model(inputs=input_layer, outputs=decoded)
autoencoder.compile(optimizer=Adam(learning_rate=1e-4, clipvalue=1.0), loss='mse')
# โโ ENCODER SUBMODEL โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
encoder = Model(inputs=input_layer, outputs=encoded)| Hyperparameter | Value |
|---|---|
| Noise Layer | GaussianNoise(stddev=0.05) |
| Latent Dimensionality | 64 (2ร FF-AE) |
| Encoder Hidden Layers | 128 โ 64 โ 64 |
| Decoder Final Activation | sigmoid (bounded โ key difference vs FF-AE's linear) |
| Dropout Rate | 0.4 (higher than FF-AE's 0.3) |
| L2 Regularization | 1e-5 (encoder Dense layers) |
| Optimizer | Adam (lr=1e-4, clipvalue=1.0) |
| Loss | MSE |
| Epochs | 80 (extended from FF-AE's 50) |
| Batch Size | 4096 (double FF-AE's 2048) |
| Validation | validation_split=0.1 |
| Val Loss Converged | ~0.52 โ Best reconstruction accuracy across all three |
The DAE pipeline's _get_encoded_state is the only one in the notebook that explicitly uses PyTorch tensors for encoder inference โ bridging TensorFlow-trained weights via SB3's PyTorch runtime:
def _get_encoded_state(self, idx):
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
x_tensor = torch.tensor(self.X[idx:idx+1], dtype=torch.float32).to(device)
with torch.no_grad():
enc_out = self.encoder(x_tensor).cpu().numpy()[0]
return enc_outThe DAE environment is explicitly labeled "Cost-Sensitive" in the notebook. Missed attacks are penalized 4ร harder than false alarms:
# โโ Cost-Sensitive Reward Logic (DAE) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
if action == true_label:
reward = 2 # Correct classification โ any class
else:
reward = -4 if true_label == 1 else -1
# -4: missed attack (false negative) โ security-critical
# -1: false alarm (false positive) โ minor operational cost
# โโ PPO Hyperparameters (DAE) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
model = PPO(
"MlpPolicy", env,
policy_kwargs = {"net_arch": {"pi": [128, 128], "vf": [128, 128]}},
learning_rate = 5e-4, # Lower than FF-AE's 1e-3
n_steps = 512, # Shorter rollout than FF-AE's 1024
batch_size = 256, # Smaller than FF-AE's 512
gamma = 0.995,
gae_lambda = 0.95, # Added โ not present in FF-AE
vf_coef = 1.0, # Added โ not present in FF-AE
clip_range = 0.2,
ent_coef = 0.02, # Lower than FF-AE's 0.04
verbose = 1,
tensorboard_log = "./ppo_logs/",
device = "auto"
)
model.learn(total_timesteps=120000, callback=eval_callback, progress_bar=True)
model.save("ppo_anomaly_detector")๐ Best Autoencoder: DAE achieves the lowest val_loss (~0.52) across all three architectures, producing the highest-quality latent features for the RL agent.
The Convolutional Autoencoder treats each network flow record as a 1D signal by reshaping inputs from (N, features) to (N, features, 1), enabling Conv1D filters to detect local spatial patterns across feature dimensions. This is also the only pipeline with a three-way train/val/test split โ using an explicit validation_data tuple instead of validation_split.
# Conv-AE uses a different data split strategy than FF-AE and DAE
X_train, X_temp, y_train, y_temp = train_test_split(
X_scaled, y, test_size=0.2, random_state=42, stratify=y
)
# Further split X_temp into validation and test
X_train_final, X_val, y_train_final, y_val = train_test_split(
X_temp, y_temp, test_size=0.1, random_state=42, stratify=y_temp
)
# Reshape for Conv1D: (N, features) โ (N, features, 1)
X_train_conv = X_train.reshape(-1, X_train.shape[1], 1)
X_val_conv = X_val.reshape(-1, X_val.shape[1], 1)
X_test_conv = X_temp.reshape(-1, X_train.shape[1], 1) # X_temp used as test setinput_shape = (X_train.shape[1], 1) # e.g., (76, 1) โ treated as 1D signal
latent_dim = 32
inputs = Input(shape=input_shape)
# โโ ENCODER (1D Convolutional) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
x = Conv1D(32, kernel_size=3, activation='relu', padding='same')(inputs)
x = MaxPooling1D(2, padding='same')(x)
x = Conv1D(16, kernel_size=3, activation='relu', padding='same')(x)
x = MaxPooling1D(2, padding='same')(x)
shape_before_flatten = tf.keras.backend.int_shape(x) # captured for decoder reshape
x = Flatten()(x)
latent = Dense(32, activation='relu')(x) # Latent Space (32-dim)
# โโ DECODER (Upsampling + Deconvolution) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
x = Dense(np.prod(shape_before_flatten[1:]), activation='relu')(latent)
x = Reshape(shape_before_flatten[1:])(x)
x = UpSampling1D(2)(x)
x = Conv1D(16, kernel_size=3, activation='relu', padding='same')(x)
x = UpSampling1D(2)(x)
decoded = Conv1D(1, kernel_size=3, activation='sigmoid', padding='same')(x)
conv_autoencoder = Model(inputs, decoded)
conv_autoencoder.compile(optimizer=Adam(learning_rate=1e-4, clipvalue=1.0), loss='mse')
# โโ ENCODER SUBMODEL (called encoder_output in notebook) โโโโโโโโโโโโโ
encoder_output = Model(inputs, latent)| Hyperparameter | Value |
|---|---|
| Input Shape | (features, 1) โ 1D signal |
| Conv Filters | 32 โ 16 (encoder) ยท 16 โ 1 (decoder) |
| Kernel Size | 3 with padding='same' |
| Pooling / Upsampling | MaxPooling1D(2) / UpSampling1D(2) |
| Latent Dimensionality | 32 |
| Decoder Final Activation | sigmoid |
| No Dropout | Conv-AE encoder has no dropout layers |
| Optimizer | Adam (lr=1e-4, clipvalue=1.0) |
| Loss | MSE |
| Epochs | 50 (EarlyStopping: patience=5) |
| Batch Size | 4096 |
| Validation | Explicit validation_data=(X_val_conv, X_val_conv) |
| Val Loss Converged | ~0.67 |
Conv-AE shares the same asymmetric cost-sensitive reward and refined PPO config as DAE. The key uniqueness in _get_encoded_state is the per-sample reshape before encoder inference:
def _get_encoded_state(self, idx):
# Conv-AE must reshape each sample to (features, 1) for Conv1D encoder
sample = self.X[idx:idx+1].reshape(-1, self.X.shape[1], 1)
return self.encoder(sample).numpy()[0] # encoder = encoder_output submodelmodel = PPO(
"MlpPolicy", env,
policy_kwargs = {"net_arch": {"pi": [128, 128], "vf": [128, 128]}},
learning_rate = 5e-4,
n_steps = 512,
batch_size = 256,
gamma = 0.995,
gae_lambda = 0.95,
vf_coef = 1.0,
clip_range = 0.2,
ent_coef = 0.02,
verbose = 1,
tensorboard_log = "./ppo_logs/",
device = "auto"
)
model.learn(total_timesteps=120000, callback=eval_callback, progress_bar=True)
model.save("ppo_anomaly_detector")| Feature | FF-AE | DAE | Conv-AE |
|---|---|---|---|
| Architecture Type | Feedforward Dense | Dense + Noise | 1D Convolutional |
| Latent Dim | 32 | 64 | 32 |
| Noise Injection | โ | โ
GaussianNoise(0.05) |
โ |
| Dropout (Encoder) | 0.3 |
0.4 |
โ None |
| Decoder Activation | linear |
sigmoid |
sigmoid |
| Batch Size | 2048 | 4096 | 4096 |
| Max Epochs | 50 | 80 | 50 |
| Validation Method | validation_split=0.1 |
validation_split=0.1 |
explicit validation_data |
| Data Split | 80/20 | 80/20 | 80/10/10 (3-way) |
| Val Loss | โ | ~0.52 โญ | ~0.67 |
| Reward Strategy | Symmetric (+2/โ3) |
Asymmetric (+2/โ4/โ1) |
Asymmetric (+2/โ4/โ1) |
PPO learning_rate |
1e-3 |
5e-4 |
5e-4 |
PPO n_steps |
1024 |
512 |
512 |
PPO batch_size |
512 |
256 |
256 |
PPO ent_coef |
0.04 |
0.02 |
0.02 |
gae_lambda |
SB3 default | โ
0.95 |
โ
0.95 |
vf_coef |
SB3 default | โ
1.0 |
โ
1.0 |
| Threshold Sweep | Fixed 0.30 only |
[0.25, 0.30, 0.35] |
[0.25, 0.30, 0.35] |
Each autoencoder pipeline feeds its frozen encoder into a custom Gymnasium environment where a Proximal Policy Optimization (PPO) agent learns to classify network flows through repeated interaction.
Latent Vector (32-dim or 64-dim)
โ
โผ
Dense(128, relu) โโโโ Shared net_arch trunk โโโโ
โ
Dense(128, relu)
โ
โโโโโโดโโโโโ
โผ โผ
Policy Value
Head Head
(ฯ: action (V: scalar
logits) estimate)
net_arch = {"pi": [128, 128], "vf": [128, 128]}
| Training Signal | Observed Trend |
|---|---|
| KL Divergence | Stabilizes after initial policy updates |
| Value Loss | Decreases steadily โ improving state-value estimation |
| Policy Entropy | Decreases over time โ agent becomes more confident |
| Explained Variance | Improves across training iterations |
| Training Speed | ~220 fps during training loops |
| TensorBoard Logging | ./ppo_logs/ โ metrics logged for all runs |
| Best Model Checkpointing | ./best_model/ via EvalCallback(eval_freq=4000) |
Evaluated via
evaluate_policy(model, test_env, n_eval_episodes=10, deterministic=True)
| Pipeline | Mean Reward | Reward Design |
|---|---|---|
| FF-AE + PPO | 4415 | Balanced symmetric +2/โ3 |
| DAE + PPO | 4839 | Cost-sensitive +2/โ4/โ1 |
| Conv-AE + PPO | 4932 โญ | Cost-sensitive +2/โ4/โ1 |
A custom gym.Env named AnomalyDetectionEnv wraps the frozen autoencoder encoder and the labeled dataset, presenting each network flow as an encoded observation and rewarding correct binary classification decisions.
class AnomalyDetectionEnv(gym.Env):
"""
Cost-Sensitive reward shaping:
+2 correct classification (benign or malicious)
-1 false positive (benign predicted as malicious)
-4 missed malicious (malicious predicted as benign) โ security-critical!
"""
def __init__(self, X, y, encoder, latent_dim, max_steps=3000):
super().__init__()
self.X = X.astype(np.float32)
self.y = y
self.encoder = encoder
self.latent_dim = latent_dim
self.max_steps = max_steps
self.current_idx = 0
self.step_count = 0
self.action_space = spaces.Discrete(2) # 0=benign, 1=malicious
self.observation_space = spaces.Box(
low=-np.inf, high=np.inf, shape=(latent_dim,), dtype=np.float32
)
def reset(self, seed=None, options=None):
self.current_idx = 0
self.step_count = 0
return self._get_encoded_state(0), {}
def step(self, action):
true_label = self.y[self.current_idx]
if action == true_label:
reward = 2
else:
reward = -4 if true_label == 1 else -1 # asymmetric cost-sensitive!
self.current_idx = (self.current_idx + 1) % len(self.X)
self.step_count += 1
done = self.step_count >= self.max_steps
next_obs = (
self._get_encoded_state(self.current_idx)
if not done else np.zeros(self.latent_dim)
)
return next_obs, reward, done, False, {}
def _get_encoded_state(self, idx):
# DAE uses explicit PyTorch tensors for inference
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
x_tensor = torch.tensor(self.X[idx:idx+1], dtype=torch.float32).to(device)
with torch.no_grad():
enc_out = self.encoder(x_tensor).cpu().numpy()[0]
return enc_outenv = AnomalyDetectionEnv(X_train, y_train, encoder, latent_dim, max_steps=3000)
env = Monitor(env) # Stable-Baselines3 Monitor wrapper
env = make_vec_env(lambda: env, n_envs=1) # Vectorized (single process)| Scenario | True Label | Action | FF-AE | DAE / Conv-AE |
|---|---|---|---|---|
| Correct benign | 0 | 0 | +2 | +2 |
| Correct attack | 1 | 1 | +2 | +2 |
| Missed attack (FN) | 1 | 0 | โ3 | โ4 |
| False alarm (FP) | 0 | 1 | โ3 | โ1 |
๐ The asymmetric
โ4 / โ1structure in DAE and Conv-AE encodes a security-first philosophy โ undetected attacks cause exponentially more damage than false alarms in operational network environments.
All pipelines are evaluated in two modes: deterministic Argmax Policy and probabilistic Custom Threshold.
# โโ MODE A: Argmax (Deterministic Policy) โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
y_pred_argmax = []
obs, _ = test_env.reset()
for _ in range(len(X_test)):
action, _ = model.predict(obs, deterministic=True)
y_pred_argmax.append(action)
obs, _, done, _, _ = test_env.step(action)
if done:
break
print(classification_report(y_test[:len(y_pred_argmax)], y_pred_argmax))
# Confusion Matrix
cm = confusion_matrix(y_test[:len(y_pred_argmax)], y_pred_argmax)
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
# Precision-Recall AUC
precision, recall, _ = precision_recall_curve(y_test[:len(y_pred_argmax)], y_pred_argmax)
pr_auc = auc(recall, precision)
# โโ MODE B: Custom Threshold (Probabilistic) โโโโโโโโโโโโโโโโโโโโโโโโโ
def predict_with_threshold(model, obs, threshold=0.3):
obs_tensor = torch.tensor(obs, dtype=torch.float32).to(model.device)
with torch.no_grad():
distribution = model.policy.get_distribution(obs_tensor)
probs = distribution.distribution.probs # CategoricalDistribution โ shape [batch, 2]
predicted_actions = (probs[:, 1] > threshold).long().cpu().numpy()
return predicted_actions
# DAE and Conv-AE sweep all three thresholds in a loop:
for threshold in [0.25, 0.30, 0.35]:
...FF-AE uses
threshold=0.30only. DAE and Conv-AE run the full[0.25, 0.30, 0.35]loop.
| Metric | Score |
|---|---|
| Precision | ~0.91 |
| Recall | ~0.92 |
| F1-Score | ~0.92 |
More liberal โ higher recall, marginally more false alarms.
| Metric | Score |
|---|---|
| Precision | ~0.92 |
| Recall | ~0.92 |
| F1-Score | ~0.92 |
Best overall balance โ recommended default for deployment.
| Metric | Score |
|---|---|
| Precision | ~0.92 |
| Recall | ~0.89 |
| F1-Score | ~0.90 |
More conservative โ fewer false alarms, slightly more missed attacks.
| Observation | Detail |
|---|---|
| ๐ฏ Optimal Threshold | 0.30 delivers the best Precision-Recall balance |
| ๐ด False Positives | Remain extremely low across all threshold values |
| ๐ RL vs. Reconstruction | PPO significantly outperforms raw autoencoder reconstruction-error baselines |
| ๐งฉ Latent Quality Matters | Lower val_loss (DAE: ~0.52) directly improves RL stability and final classification quality |
| ๐ PR-AUC | Computed via auc(recall, precision) for both Argmax and Threshold modes in all pipelines |
| ๐ Confusion Matrices | Plotted via sns.heatmap(cmap='Blues') for every evaluation run |
| ๐ Reward Progression | 4415 (FF-AE) โ 4839 (DAE) โ 4932 (Conv-AE) |
| Property | Detail |
|---|---|
| ๐ Total Records | 2.8M+ network flow records |
| ๐ File Format | Multiple CSV files loaded via glob.glob("Dataset/*.csv") then pd.concat |
| ๐ข Original Features | ~80 flow-level features |
| โ๏ธ Features After Preprocessing | ~76 numerical features |
| ๐ท๏ธ Label Encoding | 0 = Benign, 1 = Malicious (all other labels) |
| โ๏ธ Class Imbalance | Majority benign โ addressed via stratified splits and asymmetric reward shaping |
| ๐ Attack Categories | DDoS, DoS (Hulk, GoldenEye, Slowloris), Brute Force (FTP/SSH), Web Attacks (XSS, SQL Injection, Clickjacking), Botnet ARES, Port Scan, Infiltration |
# Step 1: Load all CSV files with glob
all_files = glob.glob("C:\\Users\\dell\\Downloads\\Dataset\\*.csv")
df = pd.concat([pd.read_csv(f) for f in all_files], axis=0, ignore_index=True)
# Step 2: Standardize column names
df.columns = df.columns.str.strip().str.lower()
# Step 3: Binary label encoding
df['label'] = df['label'].apply(lambda x: 0 if str(x).lower() == 'benign' else 1)
# Step 4: Print label distribution for inspection
print(df['label'].value_counts())
# Step 5: Drop non-useful columns
cols_to_drop = ['destination port', 'flow duration']
df = df.drop([c for c in cols_to_drop if c in df.columns], axis=1)
# Step 6: Handle infinities and NaNs
df = df.replace([np.inf, -np.inf], np.nan)
df = df.dropna(axis=1, thresh=int(0.7 * len(df))) # drop columns with >30% missing
df = df.fillna(df.median()) # fill remainder with column median
# Step 7: IQR-based outlier detection (diagnostic)
# Q1 / Q3 / IQR computed per numeric column
# Outlier bounds: [Q1 - 1.5*IQR, Q3 + 1.5*IQR]
# Total count printed; may include duplicates across columns
# Step 8: Feature matrix + StandardScaler
X = df.drop('label', axis=1).values
y = df['label'].values
X_scaled = StandardScaler().fit_transform(X)
# Step 9: Stratified train/test split (FF-AE and DAE)
X_train, X_test, y_train, y_test = train_test_split(
X_scaled, y, test_size=0.2, random_state=42, stratify=y
)
# Step 10: NaN/Inf safety clipping on training data
X_train = np.nan_to_num(X_train, nan=0.0, posinf=1e5, neginf=-1e5)
# Step 11 (Conv-AE only): Additional val split + 1D reshape
# X_train, X_val split from X_temp; then:
# X_train_conv = X_train.reshape(-1, X_train.shape[1], 1)# TensorFlow GPU detection and memory growth configuration
print("Num GPUs Available (TensorFlow):", len(tf.config.list_physical_devices('GPU')))
print("PyTorch CUDA available:", torch.cuda.is_available())
gpus = tf.config.list_physical_devices('GPU')
if gpus:
for gpu in gpus:
tf.config.experimental.set_memory_growth(gpu, True)Both TensorFlow (for autoencoder training via Keras) and PyTorch (for PPO via SB3 and DAE encoder inference) are active simultaneously.
device="auto"in PPO selects GPU when available.
| Requirement | Version / Detail |
|---|---|
| ๐ Python | 3.9+ |
| ๐ฅ๏ธ GPU | Optional โ CUDA-compatible for faster AE training and PPO |
| ๐พ RAM | 16 GB+ recommended (2.8M record dataset) |
| ๐ฆ Frameworks | TensorFlow + PyTorch (both required simultaneously) |
# 1. Clone the repository
git clone https://github.com/kumarpiyushraj/Network-Anomaly-Detection-using-RL-model-and-Autoencoders
cd Network-Anomaly-Detection-using-RL-model-and-Autoencoders
# 2. Install all dependencies
pip install tensorflow numpy pandas scikit-learn matplotlib seaborn \
shimmy>=2.0 "stable-baselines3[extra]" gymnasium torch# Create Dataset directory and place all CICIDS-2017 CSV files inside
mkdir Dataset
# Dataset/Monday-WorkingHours.pcap_ISCX.csv
# Dataset/Tuesday-WorkingHours.pcap_ISCX.csv
# Dataset/Wednesday-workingHours.pcap_ISCX.csv
# ... (remaining daily capture files)
# Update glob path in the notebook if needed:
# all_files = glob.glob("Dataset/*.csv")jupyter notebook Network_Anomaly_Detection.ipynb| Section | Notebook Header | Autoencoder | Reward | Thresholds |
|---|---|---|---|---|
| 1 | RL model PPO with Simple FeedForward AutoEncoder | FF-AE | Symmetric | 0.30 only |
| 2 | RL model PPO with Denoising AutoEncoder | DAE | Asymmetric | [0.25, 0.30, 0.35] |
| 3 | RL model PPO with Simple Convolutional Autoencoder | Conv-AE | Asymmetric | [0.25, 0.30, 0.35] |
tensorflow # Autoencoder training (Keras API)
torch # SB3 backend + DAE encoder inference
numpy
pandas
scikit-learn # StandardScaler, train_test_split, classification metrics
matplotlib
seaborn # Confusion matrix heatmaps (cmap='Blues')
gymnasium # RL environment base class
shimmy>=2.0 # Gymnasium compatibility shim for Stable-Baselines3
stable-baselines3[extra] # PPO, Monitor, make_vec_env, EvalCallback, evaluate_policy
glob # CSV loading (Python stdlib)
๐ง Advanced Autoencoder Architectures
- Variational Autoencoders (VAE) โ probabilistic latent space with uncertainty quantification
- Transformer-based encoders โ multi-head self-attention over flow feature sequences
- Graph Neural Networks (GNN) โ model inter-flow correlations as graph edges
- Sparse Autoencoders โ enforce sparsity for interpretable latent features
- LSTM / GRU encoders โ temporal sequence modelling across flow sessions
๐ค Reinforcement Learning Improvements
- Multi-agent RL โ cooperative agents handling different traffic slices simultaneously
- Hierarchical RL โ macro-agent selects strategy, micro-agent classifies individual flows
- Multi-class action space โ extend
Discrete(2)toDiscrete(N)for per-attack-type classification - SAC / TD3 โ continuous-action alternatives to PPO for finer probability control
- Prioritized Experience Replay โ oversample rare attack types (Infiltration, Botnet) during training
๐ Real-Time Deployment
- Suricata / Zeek integration โ consume live PCAP streams for real-time classification
- Online learning mode โ continuously update autoencoder and PPO weights on streaming traffic
- Model quantization / TFLite โ reduce inference latency for edge deployment
- REST API / gRPC endpoint โ serve the detection pipeline as a containerized microservice
- TensorBoard live dashboards โ expose
./ppo_logs/metrics for production monitoring
@dataset{cic_ids2017,
author = {Sharafaldin, Iman and Lashkari, Arash Habibi and Ghorbani, Ali A},
title = {Intrusion Detection Evaluation Dataset (CICIDS2017)},
year = {2017},
publisher = {Canadian Institute for Cybersecurity},
url = {https://www.unb.ca/cic/datasets/ids-2017.html}
}
@article{ppo_schulman2017,
author = {Schulman, John and Wolski, Filip and Dhariwal, Prafulla and Radford, Alec and Klimov, Oleg},
title = {Proximal Policy Optimization Algorithms},
year = {2017},
journal = {arXiv preprint arXiv:1707.06347},
url = {https://arxiv.org/abs/1707.06347}
}
@software{stable_baselines3,
author = {Raffin, Antonin and Hill, Ashley and Gleave, Adam and Kanervisto, Anssi and Ernestus, Maximilian and Dormann, Noah},
title = {Stable-Baselines3: Reliable Reinforcement Learning Implementations},
year = {2021},
journal = {Journal of Machine Learning Research},
volume = {22},
pages = {1--8},
url = {https://jmlr.org/papers/v22/20-1364.html}
}
@misc{gymnasium2022,
author = {Farama Foundation},
title = {Gymnasium: A Standard Interface for Reinforcement Learning Environments},
year = {2022},
url = {https://gymnasium.farama.org}
}| ย | Acknowledgment |
|---|---|
| ๐ | Canadian Institute for Cybersecurity โ For releasing the CICIDS-2017 benchmark dataset |
| ๐ค | Stable-Baselines3 Team โ For production-grade PPO, EvalCallback and evaluation utilities |
| ๐ง | TensorFlow / Keras Team โ For the deep learning framework powering all three autoencoders |
| ๐ฎ | Farama Foundation โ For the Gymnasium RL environment standard and shimmy compatibility layer |
| ๐ฅ | PyTorch Community โ For the tensor infrastructure underlying SB3 and DAE encoder inference |
| ๐ | Open Source ML Community โ For the ecosystem that makes this research reproducible |
Built with โค๏ธ for intelligent network security ย ยทย TensorFlow ย ยทย Stable-Baselines3 ย ยทย Gymnasium
ยฉ 2025 Kumar Piyush Raj ย ยทย GitHub @kumarpiyushraj