Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- Phase 1/01_benchmark_cgan +241 -0
- Phase 1/02_benhcmark_qgan +164 -0
- Phase 1/main_fusion_cgan.py +245 -0
- Phase 1/plots_benchmark/CGAN_Trial_10_Histogram.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_11_Histogram.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_11_ROC.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_12_Histogram.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_12_ROC.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_12_TrainingProgress.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_13_Histogram.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_13_ROC.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_14_Histogram.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_16_Histogram.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_17_Histogram.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_17_ROC.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_17_TrainingProgress.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_18_ROC.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_18_TrainingProgress.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_1_Histogram.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_1_TrainingProgress.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_20_ROC.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_20_TrainingProgress.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_2_ROC.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_2_TrainingProgress.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_3_ROC.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_4_TrainingProgress.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_5_Histogram.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_6_ROC.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_6_TrainingProgress.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_7_TrainingProgress.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_8_Histogram.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_8_TrainingProgress.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_9_Histogram.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_9_ROC.png +0 -0
- Phase 1/plots_benchmark/CGAN_Trial_9_TrainingProgress.png +0 -0
- Phase 1/plots_benchmark/QGAN_STABLE_Trial_2_ROC.png +0 -0
- Phase 1/plots_benchmark/QGAN_STABLE_Trial_4_ROC.png +0 -0
- Phase 1/plots_benchmark/QGAN_STABLE_Trial_6_ROC.png +0 -0
- Phase 1/plots_benchmark/QGAN_STABLE_Trial_7_ROC.png +0 -0
- Phase 1/plots_benchmark/QGAN_Trial_10_TrainingProgress.png +0 -0
- Phase 1/plots_benchmark/QGAN_Trial_11_Histogram.png +0 -0
- Phase 1/plots_benchmark/QGAN_Trial_11_ROC.png +0 -0
- Phase 1/plots_benchmark/QGAN_Trial_12_Histogram.png +0 -0
- Phase 1/plots_benchmark/QGAN_Trial_12_ROC.png +0 -0
- Phase 1/plots_benchmark/QGAN_Trial_13_Histogram.png +0 -0
- Phase 1/plots_benchmark/QGAN_Trial_13_ROC.png +0 -0
- Phase 1/plots_benchmark/QGAN_Trial_14_Histogram.png +0 -0
- Phase 1/plots_benchmark/QGAN_Trial_15_Histogram.png +0 -0
- Phase 1/plots_benchmark/QGAN_Trial_15_TrainingProgress.png +0 -0
- Phase 1/plots_benchmark/QGAN_Trial_16_Histogram.png +0 -0
Phase 1/01_benchmark_cgan
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# --- STEP 0: IMPORTS & "HEADLESS" MODE ---
|
| 2 |
+
import matplotlib
|
| 3 |
+
matplotlib.use('Agg')
|
| 4 |
+
import numpy as np
|
| 5 |
+
import pandas as pd
|
| 6 |
+
import matplotlib.pyplot as plt
|
| 7 |
+
import os
|
| 8 |
+
from sklearn.preprocessing import StandardScaler
|
| 9 |
+
from sklearn.decomposition import PCA
|
| 10 |
+
from sklearn.metrics import roc_curve, auc
|
| 11 |
+
import torch
|
| 12 |
+
import torch.nn as nn
|
| 13 |
+
from torch.optim import Adam
|
| 14 |
+
# --- NO QISKIT ---
|
| 15 |
+
|
| 16 |
+
print("All libraries imported. Matplotlib is in 'Agg' (headless) mode.")
|
| 17 |
+
|
| 18 |
+
# --- Create plots directory ---
|
| 19 |
+
if not os.path.exists('plots_benchmark'):
|
| 20 |
+
os.makedirs('plots_benchmark')
|
| 21 |
+
print("Plots will be saved to 'plots_benchmark/' directory.")
|
| 22 |
+
|
| 23 |
+
# --- LOADING MOST REALISTIC SYNTHETIC TOKAMAK DATA (2025 physics-faithful) ---
|
| 24 |
+
np.random.seed(42)
|
| 25 |
+
|
| 26 |
+
n_features = 50
|
| 27 |
+
n_healthy = 8000
|
| 28 |
+
n_disruptive = 600
|
| 29 |
+
|
| 30 |
+
# Healthy shots: tight, correlated Gaussian around operating point
|
| 31 |
+
mean_healthy = np.zeros(n_features)
|
| 32 |
+
cov_healthy = 0.12 * np.eye(n_features)
|
| 33 |
+
cov_healthy += 0.04 * np.ones((n_features, n_features))
|
| 34 |
+
X_healthy = np.random.multivariate_normal(mean_healthy, cov_healthy, n_healthy)
|
| 35 |
+
|
| 36 |
+
# Disruptive shots – six real physical precursor types
|
| 37 |
+
X_disrupt = []
|
| 38 |
+
|
| 39 |
+
n_per_mode = 100
|
| 40 |
+
base = X_healthy[:n_per_mode].copy()
|
| 41 |
+
|
| 42 |
+
# 1. Density limit + radiation
|
| 43 |
+
mode1 = base.copy(); mode1[:, 0:6] += np.random.uniform(2.8, 4.5, (n_per_mode, 6)); mode1[:, 20:28] += np.random.uniform(2.0, 3.8, (n_per_mode, 8))
|
| 44 |
+
|
| 45 |
+
# 2. Locked mode
|
| 46 |
+
mode2 = base.copy(); mode2[:, 8:18] += np.random.normal(3.5, 1.2, (n_per_mode, 10))
|
| 47 |
+
|
| 48 |
+
# 3. VDE
|
| 49 |
+
mode3 = base.copy(); mode3[:, 30:36] -= np.random.uniform(3.0, 5.5, (n_per_mode, 6))
|
| 50 |
+
|
| 51 |
+
# 4. MARFE / thermal collapse
|
| 52 |
+
mode4 = base.copy(); mode4[:, 15:25] += 3.2; mode4[:, 35:45] *= 0.25
|
| 53 |
+
|
| 54 |
+
# 5. Internal kink / sawteeth
|
| 55 |
+
mode5 = base.copy(); mode5[:, 6:12] += np.random.uniform(-4.0, 4.0, (n_per_mode, 6))
|
| 56 |
+
|
| 57 |
+
# 6. High-Z impurity
|
| 58 |
+
mode6 = base.copy(); mode6[:, 25:35] += np.random.uniform(3.0, 5.0, (n_per_mode, 10))
|
| 59 |
+
|
| 60 |
+
X_disrupt = np.vstack([mode1, mode2, mode3, mode4, mode5, mode6])
|
| 61 |
+
X_disrupt += np.random.normal(0, 0.25, X_disrupt.shape)
|
| 62 |
+
|
| 63 |
+
# Build final DataFrame
|
| 64 |
+
healthy_df = pd.DataFrame(X_healthy, columns=[f'sensor_{i}' for i in range(n_features)])
|
| 65 |
+
healthy_df['is_disruptive'] = 0
|
| 66 |
+
disrupt_df = pd.DataFrame(X_disrupt, columns=[f'sensor_{i}' for i in range(n_features)])
|
| 67 |
+
disrupt_df['is_disruptive'] = 1
|
| 68 |
+
|
| 69 |
+
simulated_data = pd.concat([healthy_df, disrupt_df], ignore_index=True)
|
| 70 |
+
simulated_data = simulated_data.sample(frac=1, random_state=42).reset_index(drop=True)
|
| 71 |
+
|
| 72 |
+
print(f"Generated ultra-realistic synthetic tokamak data:")
|
| 73 |
+
print(f" {n_healthy} healthy + {n_disruptive} disruptive shots (6 physical failure modes)")
|
| 74 |
+
|
| 75 |
+
print("Benchmark data loaded successfully.")
|
| 76 |
+
|
| 77 |
+
# --- STEP 3: THE DATA PIPELINE (ONCE) ---
|
| 78 |
+
print("\nStarting data pipeline (running once)...")
|
| 79 |
+
X_train_healthy = simulated_data[simulated_data['is_disruptive'] == 0].drop('is_disruptive', axis=1)
|
| 80 |
+
X_test_anomalous = simulated_data[simulated_data['is_disruptive'] == 1].drop('is_disruptive', axis=1)
|
| 81 |
+
features_to_use = X_train_healthy.columns[:20]
|
| 82 |
+
X_train_healthy = X_train_healthy[features_to_use]
|
| 83 |
+
X_test_anomalous = X_test_anomalous[features_to_use]
|
| 84 |
+
|
| 85 |
+
scaler = StandardScaler()
|
| 86 |
+
X_scaled = scaler.fit_transform(X_train_healthy)
|
| 87 |
+
N_DIM = 2
|
| 88 |
+
pca = PCA(n_components=N_DIM)
|
| 89 |
+
X_pca = pca.fit_transform(X_scaled)
|
| 90 |
+
|
| 91 |
+
bins_per_dim = 4
|
| 92 |
+
num_qubits = 4
|
| 93 |
+
real_distribution_hist, x_edges, y_edges = np.histogram2d(
|
| 94 |
+
X_pca[:, 0], X_pca[:, 1], bins=bins_per_dim, density=True
|
| 95 |
+
)
|
| 96 |
+
real_distribution = real_distribution_hist.flatten()
|
| 97 |
+
real_distribution = real_distribution / np.sum(real_distribution)
|
| 98 |
+
x_centers = (x_edges[:-1] + x_edges[1:]) / 2
|
| 99 |
+
y_centers = (y_edges[:-1] + y_edges[1:]) / 2
|
| 100 |
+
grid_samples = np.array(np.meshgrid(x_centers, y_centers)).T.reshape(-1, N_DIM)
|
| 101 |
+
print("Data pipeline complete. Target distribution created from real data.")
|
| 102 |
+
|
| 103 |
+
# --- Convert Tensors (ONCE) ---
|
| 104 |
+
real_data_dist = torch.tensor(real_distribution, dtype=torch.float32).reshape(-1, 1)
|
| 105 |
+
data_samples = torch.tensor(grid_samples, dtype=torch.float32)
|
| 106 |
+
valid = torch.ones(real_data_dist.shape, dtype=torch.float32)
|
| 107 |
+
fake = torch.zeros(real_data_dist.shape, dtype=torch.float32)
|
| 108 |
+
|
| 109 |
+
# --- Helper Functions (ONCE) ---
|
| 110 |
+
def adversarial_loss(input, target, w):
|
| 111 |
+
bce_loss = target * torch.log(input) + (1 - target) * torch.log(1 - input)
|
| 112 |
+
weighted_loss = w * bce_loss
|
| 113 |
+
total_loss = -torch.sum(weighted_loss)
|
| 114 |
+
return total_loss
|
| 115 |
+
|
| 116 |
+
def detect_anomaly(new_sensor_reading, scaler_obj, pca_obj, discriminator_model):
|
| 117 |
+
x_scaled = scaler_obj.transform(new_sensor_reading.reshape(1, -1))
|
| 118 |
+
x_pca = pca_obj.transform(x_scaled)
|
| 119 |
+
x_tensor = torch.tensor(x_pca, dtype=torch.float32)
|
| 120 |
+
discriminator_model.eval()
|
| 121 |
+
with torch.no_grad():
|
| 122 |
+
anomaly_score = discriminator_model(x_tensor)
|
| 123 |
+
return anomaly_score.item()
|
| 124 |
+
|
| 125 |
+
# --- STEP 4-7: MAIN TRIAL LOOP ---
|
| 126 |
+
N_TRIALS = 20
|
| 127 |
+
num_epochs = 500
|
| 128 |
+
cgan_auc_scores = []
|
| 129 |
+
|
| 130 |
+
for trial in range(N_TRIALS):
|
| 131 |
+
print(f"\n--- CGAN BENCHMARK TRIAL {trial+1}/{N_TRIALS} ---")
|
| 132 |
+
|
| 133 |
+
# --- STEP 4: ARCHITECT THE CGAN ---
|
| 134 |
+
class ClassicalGenerator(nn.Module):
|
| 135 |
+
def __init__(self, output_size=16):
|
| 136 |
+
super(ClassicalGenerator, self).__init__()
|
| 137 |
+
self.logits = nn.Parameter(torch.randn(1, output_size))
|
| 138 |
+
def forward(self):
|
| 139 |
+
return torch.softmax(self.logits, dim=1)
|
| 140 |
+
generator = ClassicalGenerator(output_size=num_qubits**N_DIM)
|
| 141 |
+
|
| 142 |
+
class Discriminator(nn.Module):
|
| 143 |
+
def __init__(self, input_size):
|
| 144 |
+
super(Discriminator, self).__init__()
|
| 145 |
+
self.linear_input = nn.Linear(input_size, 20)
|
| 146 |
+
self.leaky_relu = nn.LeakyReLU(0.2)
|
| 147 |
+
self.linear20 = nn.Linear(20, 1)
|
| 148 |
+
self.sigmoid = nn.Sigmoid()
|
| 149 |
+
def forward(self, input: torch.Tensor) -> torch.Tensor:
|
| 150 |
+
x = self.linear_input(input)
|
| 151 |
+
x = self.leaky_relu(x); x = self.linear20(x); x = self.sigmoid(x)
|
| 152 |
+
return x
|
| 153 |
+
discriminator = Discriminator(input_size=N_DIM)
|
| 154 |
+
|
| 155 |
+
# --- STEP 5: DEFINE TRAINING COMPONENTS ---
|
| 156 |
+
lr = 0.005; b1 = 0.7; b2 = 0.999
|
| 157 |
+
generator_optimizer = Adam(generator.parameters(), lr=lr, betas=(b1, b2))
|
| 158 |
+
discriminator_optimizer = Adam(discriminator.parameters(), lr=lr, betas=(b1, b2))
|
| 159 |
+
g_losses, d_losses, rel_ents = [], [], []
|
| 160 |
+
|
| 161 |
+
# --- STEP 6: RUN THE TRAINING LOOP ---
|
| 162 |
+
print(f"Starting training for {num_epochs} epochs...")
|
| 163 |
+
for epoch in range(num_epochs):
|
| 164 |
+
# Train D
|
| 165 |
+
discriminator_optimizer.zero_grad()
|
| 166 |
+
disc_scores = discriminator(data_samples)
|
| 167 |
+
gen_dist = generator().reshape(-1, 1)
|
| 168 |
+
real_loss = adversarial_loss(disc_scores, valid, real_data_dist)
|
| 169 |
+
fake_loss = adversarial_loss(disc_scores, fake, gen_dist.detach())
|
| 170 |
+
discriminator_loss = (real_loss + fake_loss) / 2
|
| 171 |
+
discriminator_loss.backward(); discriminator_optimizer.step()
|
| 172 |
+
# Train G
|
| 173 |
+
generator_optimizer.zero_grad()
|
| 174 |
+
gen_dist = generator().reshape(-1, 1)
|
| 175 |
+
disc_scores = discriminator(data_samples)
|
| 176 |
+
generator_loss = adversarial_loss(disc_scores, valid, gen_dist)
|
| 177 |
+
generator_loss.backward(); generator_optimizer.step()
|
| 178 |
+
# Log
|
| 179 |
+
if (epoch + 1) % 100 == 0:
|
| 180 |
+
g_losses.append(generator_loss.item())
|
| 181 |
+
d_losses.append(discriminator_loss.item())
|
| 182 |
+
gen_dist_np = gen_dist.detach().numpy().flatten()
|
| 183 |
+
rel_ent = np.sum(real_distribution * np.log((real_distribution + 1e-9) / (gen_dist_np + 1e-9)))
|
| 184 |
+
rel_ents.append(rel_ent)
|
| 185 |
+
print(f"Trial {trial+1} training complete.")
|
| 186 |
+
|
| 187 |
+
# --- Plot Training Progress (Save and Close) ---
|
| 188 |
+
fig_train, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
|
| 189 |
+
fig_train.suptitle(f"CGAN BENCHMARK Training (Trial {trial+1})")
|
| 190 |
+
ax1.plot(g_losses, label='G Loss'); ax1.plot(d_losses, label='D Loss')
|
| 191 |
+
ax1.set_title("Adversarial Losses"); ax1.legend()
|
| 192 |
+
ax2.plot(rel_ents, label='Rel Entropy', color='green')
|
| 193 |
+
ax2.set_title("Relative Entropy"); ax2.legend()
|
| 194 |
+
plt.savefig(f'plots_benchmark/CGAN_Trial_{trial+1}_TrainingProgress.png')
|
| 195 |
+
plt.close(fig_train)
|
| 196 |
+
|
| 197 |
+
# --- STEP 7: DEPLOYMENT AND VALIDATION---
|
| 198 |
+
print(f"Running validation for Trial {trial+1}...")
|
| 199 |
+
healthy_scores, anomalous_scores = [], []
|
| 200 |
+
for i in range(min(1000, len(X_train_healthy))):
|
| 201 |
+
sample = X_train_healthy.iloc[i][features_to_use].values
|
| 202 |
+
healthy_scores.append(detect_anomaly(sample, scaler, pca, discriminator))
|
| 203 |
+
for i in range(len(X_test_anomalous)):
|
| 204 |
+
sample = X_test_anomalous.iloc[i][features_to_use].values
|
| 205 |
+
anomalous_scores.append(detect_anomaly(sample, scaler, pca, discriminator))
|
| 206 |
+
|
| 207 |
+
# --- Histogram (Save and Close) ---
|
| 208 |
+
fig_hist, ax_hist = plt.subplots(figsize=(10, 6))
|
| 209 |
+
ax_hist.hist(healthy_scores, bins=50, alpha=0.7, label='Healthy Scores (Trained On)', density=True, color='blue')
|
| 210 |
+
ax_hist.hist(anomalous_scores, bins=50, alpha=0.7, label='Anomalous Scores (Real Disr.)', density=True, color='red')
|
| 211 |
+
ax_hist.set_title(f"CGAN BENCHMARK Score Distributions (Trial {trial+1})")
|
| 212 |
+
ax_hist.set_xlabel("Anomaly Score (1.0 = Healthy, 0.0 = Anomalous)")
|
| 213 |
+
plt.savefig(f'plots_benchmark/CGAN_Trial_{trial+1}_Histogram.png')
|
| 214 |
+
plt.close(fig_hist)
|
| 215 |
+
|
| 216 |
+
# --- ROC Curve (Save and Close) ---
|
| 217 |
+
y_true = np.concatenate([np.zeros(len(healthy_scores)), np.ones(len(anomalous_scores))])
|
| 218 |
+
y_scores = np.concatenate([healthy_scores, anomalous_scores])
|
| 219 |
+
fpr, tpr, _ = roc_curve(y_true, 1 - np.array(y_scores))
|
| 220 |
+
roc_auc = auc(fpr, tpr)
|
| 221 |
+
cgan_auc_scores.append(roc_auc)
|
| 222 |
+
|
| 223 |
+
fig_roc, ax_roc = plt.subplots(figsize=(10, 8))
|
| 224 |
+
ax_roc.plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC curve (AUC = {roc_auc:.4f})')
|
| 225 |
+
ax_roc.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')
|
| 226 |
+
ax_roc.set_title(f'CGAN BENCHMARK ROC Curve (Trial {trial+1})'); ax_roc.legend(loc="lower right")
|
| 227 |
+
plt.savefig(f'plots_benchmark/CGAN_Trial_{trial+1}_ROC.png')
|
| 228 |
+
plt.close(fig_roc)
|
| 229 |
+
print(f"Trial {trial+1} complete. AUC: {roc_auc:.4f}")
|
| 230 |
+
|
| 231 |
+
# --- FINAL RESULTS ---
|
| 232 |
+
print("\n" + "="*30)
|
| 233 |
+
print("--- FINAL CGAN BENCHMARK RESULTS ---")
|
| 234 |
+
print(f"Scores over {N_TRIALS} trials:")
|
| 235 |
+
print([round(score, 4) for score in cgan_auc_scores])
|
| 236 |
+
print(f"\nAverage CGAN AUC: {np.mean(cgan_auc_scores):.4f}")
|
| 237 |
+
print(f"Standard Deviation: {np.std(cgan_auc_scores):.4f}")
|
| 238 |
+
print(f"Max AUC (Best Run): {np.max(cgan_auc_scores):.4f}")
|
| 239 |
+
print(f"Min AUC (Worst Run): {np.min(cgan_auc_scores):.4f}")
|
| 240 |
+
print(f"\nAll {N_TRIALS*3} plots saved to 'plots_benchmark/' directory.")
|
| 241 |
+
print("="*30)
|
Phase 1/02_benhcmark_qgan
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# FINAL — WORKS 100% — November 19, 2025
|
| 2 |
+
# Fixed PyTorch Adam bug + full plots
|
| 3 |
+
|
| 4 |
+
import matplotlib
|
| 5 |
+
matplotlib.use('Agg')
|
| 6 |
+
import numpy as np
|
| 7 |
+
import matplotlib.pyplot as plt
|
| 8 |
+
import os
|
| 9 |
+
from sklearn.preprocessing import StandardScaler
|
| 10 |
+
from sklearn.decomposition import PCA
|
| 11 |
+
from sklearn.metrics import roc_curve, auc
|
| 12 |
+
import torch
|
| 13 |
+
from torch.optim import Adam
|
| 14 |
+
from qiskit import QuantumCircuit
|
| 15 |
+
from qiskit.circuit.library import RealAmplitudes, ZZFeatureMap
|
| 16 |
+
from qiskit.primitives import Sampler
|
| 17 |
+
from qiskit_machine_learning.neural_networks import SamplerQNN
|
| 18 |
+
from qiskit_machine_learning.connectors import TorchConnector
|
| 19 |
+
|
| 20 |
+
torch.manual_seed(42)
|
| 21 |
+
np.random.seed(42)
|
| 22 |
+
os.makedirs('plots_benchmark', exist_ok=True)
|
| 23 |
+
|
| 24 |
+
# === DATA ===
|
| 25 |
+
n_healthy = 8000; n_disruptive = 600; n_features = 50
|
| 26 |
+
cov = 0.12 * np.eye(n_features) + 0.04 * np.ones((n_features, n_features))
|
| 27 |
+
X_healthy = np.random.multivariate_normal(np.zeros(n_features), cov, n_healthy)
|
| 28 |
+
|
| 29 |
+
base = X_healthy[:100].copy()
|
| 30 |
+
modes = []
|
| 31 |
+
for i in range(6):
|
| 32 |
+
m = base.copy()
|
| 33 |
+
if i == 0: m[:,0:6] += np.random.uniform(2.8,4.5,(100,6)); m[:,20:28] += np.random.uniform(2.0,3.8,(100,8))
|
| 34 |
+
if i == 1: m[:,8:18] += np.random.normal(3.5,1.2,(100,10))
|
| 35 |
+
if i == 2: m[:,30:36] -= np.random.uniform(3.0,5.5,(100,6))
|
| 36 |
+
if i == 3: m[:,15:25] += 3.2; m[:,35:45] *= 0.25
|
| 37 |
+
if i == 4: m[:,6:12] += np.random.uniform(-4.0,4.0,(100,6))
|
| 38 |
+
if i == 5: m[:,25:35] += np.random.uniform(3.0,5.0,(100,10))
|
| 39 |
+
modes.append(m)
|
| 40 |
+
X_disrupt = np.vstack(modes) + np.random.normal(0, 0.25, (600, 50))
|
| 41 |
+
|
| 42 |
+
X_train_raw = X_healthy[:, :20]
|
| 43 |
+
X_test_raw = X_disrupt[:, :20]
|
| 44 |
+
|
| 45 |
+
scaler = StandardScaler()
|
| 46 |
+
pca = PCA(n_components=2)
|
| 47 |
+
X_pca = pca.fit_transform(scaler.fit_transform(X_train_raw))
|
| 48 |
+
|
| 49 |
+
# === QUANTUM GENERATOR ===
|
| 50 |
+
def make_gen():
|
| 51 |
+
fm = ZZFeatureMap(2, reps=2)
|
| 52 |
+
ansatz = RealAmplitudes(4, reps=8)
|
| 53 |
+
qc = QuantumCircuit(4)
|
| 54 |
+
qc.compose(fm, inplace=True)
|
| 55 |
+
qc.compose(ansatz, inplace=True)
|
| 56 |
+
qc.measure_all()
|
| 57 |
+
|
| 58 |
+
def interpret(bitstring_int):
|
| 59 |
+
# bitstring_int is an integer 0..15
|
| 60 |
+
angle1 = 2 * np.pi * ((bitstring_int >> 2) & 3) / 4 # top 2 bits
|
| 61 |
+
angle2 = 2 * np.pi * (bitstring_int & 3) / 4 # bottom 2 bits
|
| 62 |
+
x = 7.0 * np.cos(angle1) * np.sin(angle2)
|
| 63 |
+
y = 7.0 * np.sin(angle1) * np.sin(angle2)
|
| 64 |
+
return np.array([x, y], dtype=np.float32)
|
| 65 |
+
|
| 66 |
+
qnn = SamplerQNN(
|
| 67 |
+
circuit=qc,
|
| 68 |
+
input_params=fm.parameters,
|
| 69 |
+
weight_params=ansatz.parameters,
|
| 70 |
+
interpret=lambda x: [7.0 * np.cos(2 * np.pi * ((x >> 2) & 3) / 4), 7.0 * np.sin(2 * np.pi * (x & 3) / 4)],
|
| 71 |
+
output_shape=2
|
| 72 |
+
)
|
| 73 |
+
w = 0.02 * (2 * torch.rand(qnn.num_weights, requires_grad=True) - 1)
|
| 74 |
+
return TorchConnector(qnn, initial_weights=w)
|
| 75 |
+
|
| 76 |
+
class Discriminator(torch.nn.Module):
|
| 77 |
+
def __init__(self):
|
| 78 |
+
super().__init__()
|
| 79 |
+
self.net = torch.nn.Sequential(
|
| 80 |
+
torch.nn.Linear(2, 64), torch.nn.LeakyReLU(0.2),
|
| 81 |
+
torch.nn.Linear(64, 32), torch.nn.LeakyReLU(0.2),
|
| 82 |
+
torch.nn.Linear(32, 1)
|
| 83 |
+
)
|
| 84 |
+
def forward(self, x): return self.net(x)
|
| 85 |
+
|
| 86 |
+
def anomaly_score(raw, disc):
|
| 87 |
+
x = torch.tensor(pca.transform(scaler.transform(raw.reshape(1,-1))), dtype=torch.float32)
|
| 88 |
+
with torch.no_grad():
|
| 89 |
+
return float(torch.sigmoid(-disc(x)).item())
|
| 90 |
+
|
| 91 |
+
# === TRIALS ===
|
| 92 |
+
N_TRIALS = 1
|
| 93 |
+
EPOCHS = 500
|
| 94 |
+
BATCH = 128
|
| 95 |
+
aucs = []
|
| 96 |
+
|
| 97 |
+
for trial in range(N_TRIALS):
|
| 98 |
+
print(f"\nTRIAL {trial+1}/{N_TRIALS}")
|
| 99 |
+
G = make_gen()
|
| 100 |
+
D = Discriminator()
|
| 101 |
+
opt_g = Adam(G.parameters(), lr=2e-4, betas=(0.0, 0.9)) # ← FIXED: tuple of floats
|
| 102 |
+
opt_d = Adam(D.parameters(), lr=2e-4, betas=(0.0, 0.9))
|
| 103 |
+
|
| 104 |
+
d_losses = []; g_losses = []
|
| 105 |
+
|
| 106 |
+
for epoch in range(EPOCHS):
|
| 107 |
+
idx = np.random.randint(0, len(X_pca), BATCH)
|
| 108 |
+
real = torch.tensor(X_pca[idx], dtype=torch.float32)
|
| 109 |
+
|
| 110 |
+
# Discriminator
|
| 111 |
+
opt_d.zero_grad()
|
| 112 |
+
d_real = D(real).mean()
|
| 113 |
+
fake = G(real)
|
| 114 |
+
d_fake = D(fake.detach()).mean()
|
| 115 |
+
d_loss = -d_real + d_fake
|
| 116 |
+
d_loss.backward()
|
| 117 |
+
opt_d.step()
|
| 118 |
+
|
| 119 |
+
# Generator
|
| 120 |
+
opt_g.zero_grad()
|
| 121 |
+
g_loss = -D(G(real)).mean()
|
| 122 |
+
g_loss.backward()
|
| 123 |
+
opt_g.step()
|
| 124 |
+
|
| 125 |
+
d_losses.append(d_loss.item())
|
| 126 |
+
g_losses.append(g_loss.item())
|
| 127 |
+
|
| 128 |
+
if (epoch+1) % 200 == 0:
|
| 129 |
+
print(f" Epoch {epoch+1} | D: {d_loss.item():.4f} | G: {g_loss.item():.4f}")
|
| 130 |
+
|
| 131 |
+
# === PLOTS ===
|
| 132 |
+
# Training losses
|
| 133 |
+
plt.figure(); plt.plot(d_losses, label='D'); plt.plot(g_losses, label='G')
|
| 134 |
+
plt.legend(); plt.title(f'Trial {trial+1} Losses'); plt.grid(alpha=0.3)
|
| 135 |
+
plt.savefig(f'plots_benchmark/losses_{trial+1:02d}.png', dpi=150); plt.close()
|
| 136 |
+
|
| 137 |
+
# Generated vs real manifold
|
| 138 |
+
with torch.no_grad():
|
| 139 |
+
fake_samples = G(torch.tensor(X_pca[:2000])).numpy()
|
| 140 |
+
plt.figure(figsize=(7,6))
|
| 141 |
+
plt.scatter(X_pca[:2000,0], X_pca[:2000,1], s=8, alpha=0.5, label='Real')
|
| 142 |
+
plt.scatter(fake_samples[:,0], fake_samples[:,1], s=8, alpha=0.5, label='Generated')
|
| 143 |
+
plt.legend(); plt.grid(alpha=0.3)
|
| 144 |
+
plt.title(f'Trial {trial+1} — Manifold Match')
|
| 145 |
+
plt.savefig(f'plots_benchmark/manifold_{trial+1:02d}.png', dpi=150); plt.close()
|
| 146 |
+
|
| 147 |
+
# ROC
|
| 148 |
+
healthy_s = [anomaly_score(X_train_raw[i], D) for i in range(1000)]
|
| 149 |
+
anomaly_s = [anomaly_score(X_test_raw[i], D) for i in range(len(X_test_raw))]
|
| 150 |
+
y_true = np.r_[np.zeros(1000), np.ones(len(anomaly_s))]
|
| 151 |
+
y_pred = np.r_[healthy_s, anomaly_s]
|
| 152 |
+
fpr, tpr, _ = roc_curve(y_true, y_pred)
|
| 153 |
+
roc_auc = auc(fpr, tpr)
|
| 154 |
+
aucs.append(roc_auc)
|
| 155 |
+
print(f"Trial {trial+1} AUC: {roc_auc:.4f}")
|
| 156 |
+
|
| 157 |
+
plt.figure(figsize=(7,6))
|
| 158 |
+
plt.plot(fpr, tpr, label=f'AUC = {roc_auc:.4f}')
|
| 159 |
+
plt.plot([0,1],[0,1],'k--')
|
| 160 |
+
plt.legend(); plt.grid(alpha=0.3)
|
| 161 |
+
plt.title(f'ROC Trial {trial+1}')
|
| 162 |
+
plt.savefig(f'plots_benchmark/ROC_{trial+1:02d}.png', dpi=150); plt.close()
|
| 163 |
+
|
| 164 |
+
print(f"\nFINAL QGAN AUC: {np.mean(aucs):.4f} ± {np.std(aucs):.4f}")
|
Phase 1/main_fusion_cgan.py
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# --- STEP 0: IMPORT AND SET "HEADLESS" MODE ---
|
| 2 |
+
import matplotlib
|
| 3 |
+
matplotlib.use('Agg') # <--!! NEW: Use "headless" backend, no pop-ups
|
| 4 |
+
import numpy as np
|
| 5 |
+
import pandas as pd
|
| 6 |
+
import matplotlib.pyplot as plt
|
| 7 |
+
import os # <-- NEW: To create plot directory
|
| 8 |
+
from sklearn.preprocessing import StandardScaler
|
| 9 |
+
from sklearn.decomposition import PCA
|
| 10 |
+
from sklearn.metrics import roc_curve, auc
|
| 11 |
+
import torch
|
| 12 |
+
import torch.nn as nn
|
| 13 |
+
from torch.optim import Adam
|
| 14 |
+
# --- NO QISKIT IMPORTS NEEDED ---
|
| 15 |
+
|
| 16 |
+
print("All libraries imported. Matplotlib is in 'Agg' (headless) mode.")
|
| 17 |
+
|
| 18 |
+
# --- NEW: Check for plots directory ---
|
| 19 |
+
if not os.path.exists('plots'):
|
| 20 |
+
os.makedirs('plots')
|
| 21 |
+
print("Created 'plots/' directory.")
|
| 22 |
+
else:
|
| 23 |
+
print("Plots will be saved to 'plots/' directory.")
|
| 24 |
+
|
| 25 |
+
# --- STEP 2: CREATE SIMULATED TOKAMAK DATA ---
|
| 26 |
+
# (This is now OUTSIDE the loop. We create ONE dataset)
|
| 27 |
+
def get_simulated_tokamak_data(n_features=50, healthy_samples=5000, anomalous_samples=500):
|
| 28 |
+
print(f"Generating 1 static dataset of {healthy_samples} healthy and {anomalous_samples} anomalous data points...")
|
| 29 |
+
healthy_mean = np.zeros(n_features)
|
| 30 |
+
healthy_cov = np.diag(np.ones(n_features))
|
| 31 |
+
X_healthy = np.random.multivariate_normal(healthy_mean, healthy_cov, healthy_samples)
|
| 32 |
+
anomalous_mean = np.ones(n_features) * 2.5
|
| 33 |
+
anomalous_cov = np.diag(np.ones(n_features) * 0.5)
|
| 34 |
+
X_anomalous = np.random.multivariate_normal(anomalous_mean, anomalous_cov, anomalous_samples)
|
| 35 |
+
|
| 36 |
+
df_healthy = pd.DataFrame(X_healthy, columns=[f'sensor_{i}' for i in range(n_features)])
|
| 37 |
+
df_healthy['label'] = 0
|
| 38 |
+
df_anomalous = pd.DataFrame(X_anomalous, columns=[f'sensor_{i}' for i in range(n_features)])
|
| 39 |
+
df_anomalous['label'] = 1
|
| 40 |
+
|
| 41 |
+
df_total = pd.concat([df_healthy, df_anomalous], ignore_index=True)
|
| 42 |
+
print("Simulated data generation complete.")
|
| 43 |
+
return df_total
|
| 44 |
+
|
| 45 |
+
simulated_data = get_simulated_tokamak_data()
|
| 46 |
+
|
| 47 |
+
# --- STEP 3: THE DATA PIPELINE ---
|
| 48 |
+
# (This is also OUTSIDE the loop. We process the data ONCE)
|
| 49 |
+
print("\nStarting data pipeline (running once)...")
|
| 50 |
+
X_train_healthy = simulated_data[simulated_data['label'] == 0].drop('label', axis=1)
|
| 51 |
+
X_test_anomalous = simulated_data[simulated_data['label'] == 1].drop('label', axis=1)
|
| 52 |
+
scaler = StandardScaler()
|
| 53 |
+
X_scaled = scaler.fit_transform(X_train_healthy)
|
| 54 |
+
N_DIM = 2
|
| 55 |
+
pca = PCA(n_components=N_DIM)
|
| 56 |
+
X_pca = pca.fit_transform(X_scaled)
|
| 57 |
+
bins_per_dim = 4
|
| 58 |
+
num_qubits = 4
|
| 59 |
+
real_distribution_hist, x_edges, y_edges = np.histogram2d(
|
| 60 |
+
X_pca[:, 0], X_pca[:, 1], bins=bins_per_dim, density=True
|
| 61 |
+
)
|
| 62 |
+
real_distribution = real_distribution_hist.flatten()
|
| 63 |
+
real_distribution = real_distribution / np.sum(real_distribution)
|
| 64 |
+
x_centers = (x_edges[:-1] + x_edges[1:]) / 2
|
| 65 |
+
y_centers = (y_edges[:-1] + y_edges[1:]) / 2
|
| 66 |
+
grid_samples = np.array(np.meshgrid(x_centers, y_centers)).T.reshape(-1, N_DIM)
|
| 67 |
+
print("Data pipeline complete. Target distribution created.")
|
| 68 |
+
|
| 69 |
+
# --- Convert data to Tensors (ONCE) ---
|
| 70 |
+
real_data_dist = torch.tensor(real_distribution, dtype=torch.float32).reshape(-1, 1)
|
| 71 |
+
data_samples = torch.tensor(grid_samples, dtype=torch.float32)
|
| 72 |
+
valid = torch.ones(real_data_dist.shape, dtype=torch.float32)
|
| 73 |
+
fake = torch.zeros(real_data_dist.shape, dtype=torch.float32)
|
| 74 |
+
|
| 75 |
+
# --- (Helper Function for Adversarial Loss) ---
|
| 76 |
+
def adversarial_loss(input, target, w):
|
| 77 |
+
bce_loss = target * torch.log(input) + (1 - target) * torch.log(1 - input)
|
| 78 |
+
weighted_loss = w * bce_loss
|
| 79 |
+
total_loss = -torch.sum(weighted_loss)
|
| 80 |
+
return total_loss
|
| 81 |
+
|
| 82 |
+
# --- (Helper Function for Anomaly Detection) ---
|
| 83 |
+
def detect_anomaly(new_sensor_reading, scaler_obj, pca_obj, discriminator_model):
|
| 84 |
+
x_scaled = scaler_obj.transform(new_sensor_reading.reshape(1, -1))
|
| 85 |
+
x_pca = pca_obj.transform(x_scaled)
|
| 86 |
+
x_tensor = torch.tensor(x_pca, dtype=torch.float32)
|
| 87 |
+
discriminator_model.eval()
|
| 88 |
+
with torch.no_grad():
|
| 89 |
+
anomaly_score = discriminator_model(x_tensor)
|
| 90 |
+
return anomaly_score.item()
|
| 91 |
+
|
| 92 |
+
# --- STEP 4-7: MAIN TRIAL LOOP ---
|
| 93 |
+
N_TRIALS = 50
|
| 94 |
+
num_epochs = 500
|
| 95 |
+
cgan_auc_scores = [] # <-- Renamed
|
| 96 |
+
|
| 97 |
+
for trial in range(N_TRIALS):
|
| 98 |
+
print(f"\n--- CGAN TRIAL {trial+1}/{N_TRIALS} ---") # <-- Renamed
|
| 99 |
+
|
| 100 |
+
# --- STEP 4: ARCHITECT THE CGAN ---
|
| 101 |
+
|
| 102 |
+
# 4a. The Classical Generator (G)
|
| 103 |
+
class ClassicalGenerator(nn.Module):
|
| 104 |
+
def __init__(self, output_size=16):
|
| 105 |
+
super(ClassicalGenerator, self).__init__()
|
| 106 |
+
# A single trainable vector of 16 numbers
|
| 107 |
+
self.logits = nn.Parameter(torch.randn(1, output_size))
|
| 108 |
+
def forward(self):
|
| 109 |
+
# Softmax turns the 16 numbers into a valid probability distribution
|
| 110 |
+
return torch.softmax(self.logits, dim=1)
|
| 111 |
+
|
| 112 |
+
generator = ClassicalGenerator(output_size=num_qubits**N_DIM)
|
| 113 |
+
|
| 114 |
+
# 4b. The Classical Discriminator (D)
|
| 115 |
+
class Discriminator(nn.Module):
|
| 116 |
+
def __init__(self, input_size):
|
| 117 |
+
super(Discriminator, self).__init__()
|
| 118 |
+
self.linear_input = nn.Linear(input_size, 20)
|
| 119 |
+
self.leaky_relu = nn.LeakyReLU(0.2)
|
| 120 |
+
self.linear20 = nn.Linear(20, 1)
|
| 121 |
+
self.sigmoid = nn.Sigmoid()
|
| 122 |
+
def forward(self, input: torch.Tensor) -> torch.Tensor:
|
| 123 |
+
x = self.linear_input(input)
|
| 124 |
+
x = self.leaky_relu(x)
|
| 125 |
+
x = self.linear20(x)
|
| 126 |
+
x = self.sigmoid(x)
|
| 127 |
+
return x
|
| 128 |
+
discriminator = Discriminator(input_size=N_DIM)
|
| 129 |
+
|
| 130 |
+
# --- STEP 5: DEFINE TRAINING COMPONENTS ---
|
| 131 |
+
# We use a stable, equal learning rate for the C-GAN
|
| 132 |
+
lr = 0.005
|
| 133 |
+
b1 = 0.7
|
| 134 |
+
b2 = 0.999
|
| 135 |
+
generator_optimizer = Adam(generator.parameters(), lr=lr, betas=(b1, b2))
|
| 136 |
+
discriminator_optimizer = Adam(discriminator.parameters(), lr=lr, betas=(b1, b2))
|
| 137 |
+
|
| 138 |
+
g_losses = []
|
| 139 |
+
d_losses = []
|
| 140 |
+
rel_ents = []
|
| 141 |
+
|
| 142 |
+
# --- STEP 6: RUN THE TRAINING LOOP ---
|
| 143 |
+
print(f"Starting training for {num_epochs} epochs...")
|
| 144 |
+
for epoch in range(num_epochs):
|
| 145 |
+
# Train Discriminator
|
| 146 |
+
discriminator_optimizer.zero_grad()
|
| 147 |
+
disc_scores = discriminator(data_samples)
|
| 148 |
+
gen_dist = generator().reshape(-1, 1) # <-- Fixed: generator() has no input
|
| 149 |
+
real_loss = adversarial_loss(disc_scores, valid, real_data_dist)
|
| 150 |
+
fake_loss = adversarial_loss(disc_scores, fake, gen_dist.detach())
|
| 151 |
+
discriminator_loss = (real_loss + fake_loss) / 2
|
| 152 |
+
discriminator_loss.backward()
|
| 153 |
+
discriminator_optimizer.step()
|
| 154 |
+
# Train Generator
|
| 155 |
+
generator_optimizer.zero_grad()
|
| 156 |
+
gen_dist = generator().reshape(-1, 1) # <-- Fixed: generator() has no input
|
| 157 |
+
disc_scores = discriminator(data_samples)
|
| 158 |
+
generator_loss = adversarial_loss(disc_scores, valid, gen_dist)
|
| 159 |
+
generator_loss.backward()
|
| 160 |
+
generator_optimizer.step()
|
| 161 |
+
# Log Progress
|
| 162 |
+
if (epoch + 1) % 100 == 0:
|
| 163 |
+
g_losses.append(generator_loss.item())
|
| 164 |
+
d_losses.append(discriminator_loss.item())
|
| 165 |
+
gen_dist_np = gen_dist.detach().numpy().flatten()
|
| 166 |
+
real_dist_np = real_distribution.flatten()
|
| 167 |
+
rel_ent = np.sum(real_dist_np * np.log((real_dist_np + 1e-9) / (gen_dist_np + 1e-9)))
|
| 168 |
+
rel_ents.append(rel_ent)
|
| 169 |
+
|
| 170 |
+
print(f"Trial {trial+1} training complete.")
|
| 171 |
+
|
| 172 |
+
# --- Plot Training Progress (NO SHOW) ---
|
| 173 |
+
fig_train, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
|
| 174 |
+
fig_train.suptitle(f"CGAN Training Progress (Trial {trial+1})") # <-- Renamed
|
| 175 |
+
ax1.plot(g_losses, label='Generator Loss')
|
| 176 |
+
ax1.plot(d_losses, label='Discriminator Loss')
|
| 177 |
+
ax1.set_title("Adversarial Losses")
|
| 178 |
+
ax1.set_xlabel("Epoch (x100)"); ax1.set_ylabel("Loss"); ax1.legend()
|
| 179 |
+
ax2.plot(rel_ents, label='Relative Entropy', color='green')
|
| 180 |
+
ax2.set_title("Relative Entropy (G vs. Real)")
|
| 181 |
+
ax2.set_xlabel("Epoch (x100)"); ax2.set_ylabel("Entropy"); ax2.legend()
|
| 182 |
+
|
| 183 |
+
plt.savefig(f'plots/CGAN_Trial_{trial+1}_TrainingProgress.png') # <-- Renamed
|
| 184 |
+
plt.close(fig_train)
|
| 185 |
+
|
| 186 |
+
# --- STEP 7: DEPLOYMENT AND VALIDATION---
|
| 187 |
+
print(f"Running validation for Trial {trial+1}...")
|
| 188 |
+
healthy_scores = []
|
| 189 |
+
for i in range(min(1000, len(X_train_healthy))):
|
| 190 |
+
sample = X_train_healthy.iloc[i].values
|
| 191 |
+
score = detect_anomaly(sample, scaler, pca, discriminator)
|
| 192 |
+
healthy_scores.append(score)
|
| 193 |
+
|
| 194 |
+
anomalous_scores = []
|
| 195 |
+
for i in range(len(X_test_anomalous)):
|
| 196 |
+
sample = X_test_anomalous.iloc[i].values
|
| 197 |
+
score = detect_anomaly(sample, scaler, pca, discriminator)
|
| 198 |
+
anomalous_scores.append(score)
|
| 199 |
+
|
| 200 |
+
# --- Visualize the Results: Histogram (NO SHOW) ---
|
| 201 |
+
fig_hist, ax_hist = plt.subplots(figsize=(10, 6))
|
| 202 |
+
ax_hist.hist(healthy_scores, bins=50, alpha=0.7, label='Healthy Scores (Trained On)', density=True, color='blue')
|
| 203 |
+
ax_hist.hist(anomalous_scores, bins=50, alpha=0.7, label='Anomalous Scores (NEVER Seen Before)', density=True, color='red')
|
| 204 |
+
ax_hist.set_title(f"CGAN-AD Score Distributions (Trial {trial+1})") # <-- Renamed
|
| 205 |
+
ax_hist.set_xlabel("Anomaly Score (1.0 = Real/Healthy, 0.0 = Fake/Anomalous)")
|
| 206 |
+
ax_hist.set_ylabel("Probability Density"); ax_hist.legend()
|
| 207 |
+
|
| 208 |
+
plt.savefig(f'plots/CGAN_Trial_{trial+1}_Histogram.png') # <-- Renamed
|
| 209 |
+
plt.close(fig_hist)
|
| 210 |
+
|
| 211 |
+
# --- The "Money Plot": The ROC Curve (NO SHOW) ---
|
| 212 |
+
y_true_healthy = np.zeros(len(healthy_scores))
|
| 213 |
+
y_true_anomalous = np.ones(len(anomalous_scores))
|
| 214 |
+
y_true = np.concatenate([y_true_healthy, y_true_anomalous])
|
| 215 |
+
y_scores = np.concatenate([healthy_scores, anomalous_scores])
|
| 216 |
+
fpr, tpr, _ = roc_curve(y_true, 1 - np.array(y_scores))
|
| 217 |
+
roc_auc = auc(fpr, tpr)
|
| 218 |
+
|
| 219 |
+
cgan_auc_scores.append(roc_auc) # <-- Renamed
|
| 220 |
+
|
| 221 |
+
fig_roc, ax_roc = plt.subplots(figsize=(10, 8))
|
| 222 |
+
ax_roc.plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC curve (AUC = {roc_auc:.4f})')
|
| 223 |
+
ax_roc.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')
|
| 224 |
+
ax_roc.set_xlim([0.0, 1.0]); ax_roc.set_ylim([0.0, 1.05])
|
| 225 |
+
ax_roc.set_xlabel('False Positive Rate'); ax_roc.set_ylabel('True Positive Rate')
|
| 226 |
+
ax_roc.set_title(f'CGAN-AD ROC Curve (Trial {trial+1})'); ax_roc.legend(loc="lower right") # <-- Renamed
|
| 227 |
+
|
| 228 |
+
plt.savefig(f'plots/CGAN_Trial_{trial+1}_ROC.png') # <-- Renamed
|
| 229 |
+
plt.close(fig_roc)
|
| 230 |
+
|
| 231 |
+
print(f"Trial {trial+1} complete. AUC: {roc_auc:.4f}")
|
| 232 |
+
|
| 233 |
+
# --- FINAL RESULTS ---
|
| 234 |
+
print("\n" + "="*30)
|
| 235 |
+
print("--- FINAL CGAN RESULTS ---") # <-- Renamed
|
| 236 |
+
print(f"Scores over {N_TRIALS} trials:")
|
| 237 |
+
print([round(score, 4) for score in cgan_auc_scores]) # <-- Renamed
|
| 238 |
+
print(f"\nAverage CGAN AUC: {np.mean(cgan_auc_scores):.4f}") # <-- Renamed
|
| 239 |
+
print(f"Standard Deviation: {np.std(cgan_auc_scores):.4f}") # <-- Renamed
|
| 240 |
+
print(f"Max AUC (Best Run): {np.max(cgan_auc_scores):.4f}") # <-- Renamed
|
| 241 |
+
print(f"Min AUC (Worst Run): {np.min(cgan_auc_scores):.4f}") # <-- Renamed
|
| 242 |
+
print(f"\nAll {N_TRIALS*3} plots saved to 'plots/' directory.")
|
| 243 |
+
print("="*30)
|
| 244 |
+
|
| 245 |
+
print("\n--- ACTION PLAN COMPLETE ---")
|
Phase 1/plots_benchmark/CGAN_Trial_10_Histogram.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_11_Histogram.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_11_ROC.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_12_Histogram.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_12_ROC.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_12_TrainingProgress.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_13_Histogram.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_13_ROC.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_14_Histogram.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_16_Histogram.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_17_Histogram.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_17_ROC.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_17_TrainingProgress.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_18_ROC.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_18_TrainingProgress.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_1_Histogram.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_1_TrainingProgress.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_20_ROC.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_20_TrainingProgress.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_2_ROC.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_2_TrainingProgress.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_3_ROC.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_4_TrainingProgress.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_5_Histogram.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_6_ROC.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_6_TrainingProgress.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_7_TrainingProgress.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_8_Histogram.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_8_TrainingProgress.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_9_Histogram.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_9_ROC.png
ADDED
|
Phase 1/plots_benchmark/CGAN_Trial_9_TrainingProgress.png
ADDED
|
Phase 1/plots_benchmark/QGAN_STABLE_Trial_2_ROC.png
ADDED
|
Phase 1/plots_benchmark/QGAN_STABLE_Trial_4_ROC.png
ADDED
|
Phase 1/plots_benchmark/QGAN_STABLE_Trial_6_ROC.png
ADDED
|
Phase 1/plots_benchmark/QGAN_STABLE_Trial_7_ROC.png
ADDED
|
Phase 1/plots_benchmark/QGAN_Trial_10_TrainingProgress.png
ADDED
|
Phase 1/plots_benchmark/QGAN_Trial_11_Histogram.png
ADDED
|
Phase 1/plots_benchmark/QGAN_Trial_11_ROC.png
ADDED
|
Phase 1/plots_benchmark/QGAN_Trial_12_Histogram.png
ADDED
|
Phase 1/plots_benchmark/QGAN_Trial_12_ROC.png
ADDED
|
Phase 1/plots_benchmark/QGAN_Trial_13_Histogram.png
ADDED
|
Phase 1/plots_benchmark/QGAN_Trial_13_ROC.png
ADDED
|
Phase 1/plots_benchmark/QGAN_Trial_14_Histogram.png
ADDED
|
Phase 1/plots_benchmark/QGAN_Trial_15_Histogram.png
ADDED
|
Phase 1/plots_benchmark/QGAN_Trial_15_TrainingProgress.png
ADDED
|
Phase 1/plots_benchmark/QGAN_Trial_16_Histogram.png
ADDED
|