Deevyankar commited on
Commit
ebe7f62
·
verified ·
1 Parent(s): b956a4c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +147 -101
app.py CHANGED
@@ -1,99 +1,134 @@
 
 
 
 
 
1
  import numpy as np
2
  import pandas as pd
 
3
  from sklearn.cluster import KMeans
 
4
  import torch
5
  from torch import nn
6
  from torch.utils.data import TensorDataset, DataLoader
 
7
  import matplotlib.pyplot as plt
8
  import gradio as gr
9
 
10
  # -------------------------------------------------------
11
- # 1. Load dataset (Facebook Metrics of Cosmetic Brand)
12
  # -------------------------------------------------------
13
- DATA_PATH = "dataset_Facebook.csv" # semicolon-separated file
14
-
15
- df = pd.read_csv(DATA_PATH, sep=";")
16
- df = df.fillna(0)
17
-
18
- # Rename important columns if needed
19
- df = df.rename(columns={
20
- "Page total likes": "page_likes",
21
- "Lifetime Post Total Impressions": "impressions",
22
- "Lifetime Engaged Users": "engaged_users",
23
- "comment": "comments",
24
- "like": "likes",
25
- "share": "shares"
26
- })
27
-
28
- # Fallback if some names are missing in your copy
29
- for col in ["comments", "likes", "shares"]:
30
- if col not in df.columns:
31
- df[col] = 0
 
 
 
 
 
 
 
 
32
 
33
  # -------------------------------------------------------
34
- # 2. Real behavioural features from dataset
35
  # -------------------------------------------------------
36
 
37
- engagement = df["comments"] + df["likes"] + df["shares"]
 
38
 
39
- impressions = df["impressions"].replace(0, 1)
40
- interaction_rate = df["engaged_users"] / impressions
 
 
 
 
 
 
 
 
 
41
 
42
  def minmax(x):
43
  x = np.asarray(x, dtype=float)
44
  return (x - x.min()) / (x.max() - x.min() + 1e-8)
45
 
46
- eng_norm = minmax(engagement)
47
- interaction_norm = minmax(interaction_rate)
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
- # Trust-like base: higher interaction => more trusted
50
- trust_base = interaction_norm.copy()
 
51
 
52
- # Suspicious: high impressions but low engagement
53
- imp_norm = minmax(df["impressions"])
54
- suspicious_score = imp_norm * (1.0 - trust_base)
55
- susp_norm = minmax(suspicious_score)
56
 
57
- # Activity regularity: posts around midday more "regular"
58
- if "Post Hour" in df.columns:
59
- post_hour = df["Post Hour"]
60
- else:
61
- post_hour = pd.Series([12] * len(df)) # default midday if missing
62
 
63
- activity_reg = 1.0 - (np.abs(post_hour - 12) / 12.0).clip(0, 1)
64
- act_norm = minmax(activity_reg)
65
 
66
- # -------------------------------------------------------
67
- # 3. Synthetic FRR & MFR
68
- # -------------------------------------------------------
 
69
 
70
- rng = np.random.default_rng(42)
 
 
 
71
 
72
- # Friend requests sent (more for engaged posts)
73
- base_sent = rng.poisson(lam=3 + 20 * eng_norm)
74
- sent_requests = np.maximum(base_sent, 1)
75
 
76
- # Acceptance probability depends on trust_base (0.2 to 0.9)
77
- accepted_prob = 0.2 + 0.7 * trust_base
78
  accepted_prob = np.clip(accepted_prob, 0.0, 1.0)
79
  accepted_requests = rng.binomial(sent_requests, accepted_prob)
80
  friend_request_ratio = accepted_requests / (sent_requests + 1e-8)
81
  frr_norm = minmax(friend_request_ratio)
82
 
83
- # Synthetic total friends
84
- total_friends = rng.integers(low=50, high=2000, size=len(df))
85
-
86
- # Mutual friends probability depends on trust_base
87
- mutual_prob = 0.1 + 0.6 * trust_base
88
- mutual_prob = np.clip(mutual_prob, 0.0, 1.0)
89
- mutual_friends = rng.binomial(total_friends, mutual_prob)
90
- mutual_friends_ratio = mutual_friends / (total_friends + 1e-8)
91
  mfr_norm = minmax(mutual_friends_ratio)
92
 
93
- friends_norm = minmax(total_friends)
94
 
95
  # -------------------------------------------------------
96
- # 4. Build S, T, B scores
97
  # -------------------------------------------------------
98
 
99
  # S: social / structural (FRR, MFR, friends)
@@ -106,7 +141,7 @@ T_score = (trust_base + frr_norm + (1.0 - susp_norm)) / 3.0
106
  B_score = (eng_norm + act_norm + susp_norm) / 3.0
107
 
108
  # -------------------------------------------------------
109
- # 5. Fused features with variance-based weights
110
  # -------------------------------------------------------
111
 
112
  varS = np.var(S_score)
@@ -121,8 +156,12 @@ F = np.vstack([
121
  wB * B_score
122
  ]).T # shape (N, 3)
123
 
 
 
 
124
  # -------------------------------------------------------
125
- # 6. Unsupervised clustering -> pseudo labels
 
126
  # -------------------------------------------------------
127
 
128
  kmeans = KMeans(n_clusters=3, random_state=42, n_init=10)
@@ -134,9 +173,9 @@ for c in range(3):
134
  cluster_means_sorted = sorted(cluster_means, key=lambda x: x[1])
135
 
136
  label_map = {
137
- cluster_means_sorted[0][0]: 2, # lowest trust => Intruder
138
- cluster_means_sorted[1][0]: 1, # mid => Under Observation
139
- cluster_means_sorted[2][0]: 0 # highest => Trusted
140
  }
141
 
142
  cluster_labels = np.array([label_map[c] for c in cluster_raw], dtype=int)
@@ -151,22 +190,22 @@ status_counts = np.bincount(cluster_labels, minlength=3)
151
 
152
  def make_status_bar_plot():
153
  fig, ax = plt.subplots()
154
- labels = ["Trusted", "Under Observation", "Intruder"]
155
- ax.bar(labels, status_counts)
156
- ax.set_ylabel("Number of posts")
157
- ax.set_title("Global distribution of statuses (on dataset)")
158
  fig.tight_layout()
159
  return fig
160
 
161
  # -------------------------------------------------------
162
- # 7. Train MLP on fused features
163
  # -------------------------------------------------------
164
 
165
  X = torch.tensor(F, dtype=torch.float32)
166
  y = torch.tensor(cluster_labels, dtype=torch.long)
167
 
168
  dataset = TensorDataset(X, y)
169
- loader = DataLoader(dataset, batch_size=64, shuffle=True)
170
 
171
  class MLPClassifier(nn.Module):
172
  def __init__(self, in_dim, hidden_dim=32, num_classes=3):
@@ -195,6 +234,8 @@ for epoch in range(20):
195
  loss.backward()
196
  optimizer.step()
197
  total_loss += loss.item() * xb.size(0)
 
 
198
 
199
  model.eval()
200
  with torch.no_grad():
@@ -216,7 +257,7 @@ eng_min = engagement.min()
216
  eng_max = engagement.max()
217
 
218
  # -------------------------------------------------------
219
- # 8. Build S, T, B from UI inputs
220
  # -------------------------------------------------------
221
 
222
  def build_scores_from_user_input(
@@ -226,6 +267,7 @@ def build_scores_from_user_input(
226
  frr_input,
227
  mfr_input
228
  ):
 
229
  eng_norm_ui = (engagement_input - eng_min) / (eng_max - eng_min + 1e-8)
230
  eng_norm_ui = float(np.clip(eng_norm_ui, 0.0, 1.0))
231
 
@@ -234,10 +276,13 @@ def build_scores_from_user_input(
234
  frr_norm_ui = float(np.clip(frr_input, 0.0, 1.0))
235
  mfr_norm_ui = float(np.clip(mfr_input, 0.0, 1.0))
236
 
237
- friends_norm_ui = 0.5 # fixed average friends
 
238
 
 
239
  trust_norm_ui = (eng_norm_ui + (1.0 - susp_norm_ui)) / 2.0
240
 
 
241
  S_ui = (frr_norm_ui + mfr_norm_ui + friends_norm_ui) / 3.0
242
  T_ui = (trust_norm_ui + frr_norm_ui + (1.0 - susp_norm_ui)) / 3.0
243
  B_ui = (eng_norm_ui + act_norm_ui + susp_norm_ui) / 3.0
@@ -245,7 +290,7 @@ def build_scores_from_user_input(
245
  return S_ui, T_ui, B_ui, eng_norm_ui, susp_norm_ui, act_norm_ui
246
 
247
  # -------------------------------------------------------
248
- # 9. Timeline helpers (T1–T5)
249
  # -------------------------------------------------------
250
 
251
  def make_timeline_plot(timeline_state):
@@ -257,9 +302,9 @@ def make_timeline_plot(timeline_state):
257
  return fig
258
 
259
  steps = [i + 1 for i in range(len(timeline_state))]
260
- trusted = [e["probs"][0] for e in timeline_state]
261
- obs = [e["probs"][1] for e in timeline_state]
262
- intr = [e["probs"][2] for e in timeline_state]
263
 
264
  ax.plot(steps, trusted, marker="o", label="Trusted")
265
  ax.plot(steps, obs, marker="o", label="Under Observation")
@@ -296,8 +341,9 @@ def simulate_week(
296
  pred, probs = predict_from_fused(S_ui, T_ui, B_ui)
297
  status = label_names[pred]
298
 
 
299
  if len(timeline_state) >= 5:
300
- timeline_state = timeline_state[1:] # keep only last 4
301
  timeline_state.append({
302
  "status": status,
303
  "probs": probs.tolist(),
@@ -308,9 +354,9 @@ def simulate_week(
308
 
309
  step_num = len(timeline_state)
310
 
311
- # Current step summary
312
  lines = []
313
- lines.append(f"### Current Week: T{step_num}")
314
  lines.append(f"**Predicted Status:** **{status}**")
315
  lines.append("")
316
  lines.append("**Probabilities:**")
@@ -354,7 +400,7 @@ def reset_timeline():
354
  )
355
 
356
  # -------------------------------------------------------
357
- # 10. Example table: real Trusted / Intruder-like samples
358
  # -------------------------------------------------------
359
 
360
  def build_example_table(n_per_class=5):
@@ -365,11 +411,10 @@ def build_example_table(n_per_class=5):
365
  continue
366
  sel = rng.choice(idxs, size=min(n_per_class, len(idxs)), replace=False)
367
  tmp = pd.DataFrame({
 
368
  "Status": [label_names[lbl]] * len(sel),
369
- "Comments": df["comments"].values[sel],
370
- "Likes": df["likes"].values[sel],
371
- "Shares": df["shares"].values[sel],
372
- "Engagement": engagement.values[sel],
373
  "S_score": S_score[sel],
374
  "T_score": T_score[sel],
375
  "B_score": B_score[sel]
@@ -379,7 +424,7 @@ def build_example_table(n_per_class=5):
379
  return pd.concat(rows, ignore_index=True)
380
  else:
381
  return pd.DataFrame(columns=[
382
- "Status", "Comments", "Likes", "Shares", "Engagement",
383
  "S_score", "T_score", "B_score"
384
  ])
385
 
@@ -388,7 +433,6 @@ examples_df = build_example_table()
388
  def refresh_examples():
389
  return build_example_table()
390
 
391
- # Precompute global status plot
392
  global_status_fig = make_status_bar_plot()
393
 
394
  # -------------------------------------------------------
@@ -396,26 +440,28 @@ global_status_fig = make_status_bar_plot()
396
  # -------------------------------------------------------
397
 
398
  with gr.Blocks() as demo:
399
- gr.Markdown("# Trust-Based Intrusion Detection Demo (Facebook Cosmetic Brand Metrics)")
400
  gr.Markdown(
401
- "This app is trained on the **Facebook Metrics of a Cosmetic Brand** dataset.\n\n"
402
- "- Real post metrics (comments, likes, shares, impressions, engaged users) are used to derive\n"
403
- " engagement, suspiciousness, and trust-like scores.\n"
404
- "- Two social features **Friend Request Ratio (FRR)** and **Mutual Friends Ratio (MFR)** –\n"
405
- " are generated synthetically but consistently with behaviour.\n\n"
406
- "Use the sliders to change user behaviour. Each click on **Next week (T+1)** simulates\n"
407
- "the same user at a new time step T1..T5, so you can see how their status changes over time."
 
 
408
  )
409
 
410
  with gr.Row():
411
  with gr.Column():
412
- gr.Markdown("### Behaviour Inputs")
413
  engagement_slider = gr.Slider(
414
  minimum=float(eng_min),
415
  maximum=float(eng_max),
416
  value=float((eng_min + eng_max) / 2.0),
417
- step=10.0,
418
- label="Engagement level (comments + likes + shares)"
419
  )
420
  suspicious_slider = gr.Slider(
421
  minimum=0.0,
@@ -443,7 +489,7 @@ with gr.Blocks() as demo:
443
  maximum=1.0,
444
  value=0.6,
445
  step=0.01,
446
- label="Mutual Friends Ratio"
447
  )
448
 
449
  next_button = gr.Button("Next week (T+1)")
@@ -451,7 +497,7 @@ with gr.Blocks() as demo:
451
 
452
  with gr.Column():
453
  current_box = gr.Markdown(
454
- "Current week status will appear here after you click **Next week (T+1)**."
455
  )
456
  timeline_box = gr.Markdown(
457
  "## Timeline (T1–T5)\n(No entries yet)"
@@ -461,13 +507,13 @@ with gr.Blocks() as demo:
461
  label="Timeline probabilities (T1–T5)"
462
  )
463
 
464
- gr.Markdown("### Global Status Distribution on Real Dataset")
465
  status_plot = gr.Plot(value=global_status_fig)
466
 
467
- gr.Markdown("### Example Posts (Real Trusted vs Intruder-like)")
468
  examples_table = gr.Dataframe(
469
  value=examples_df,
470
- label="Sample posts from dataset",
471
  interactive=False
472
  )
473
  refresh_button = gr.Button("Refresh examples")
 
1
+ import os
2
+ import urllib.request
3
+ import gzip
4
+ import io
5
+
6
  import numpy as np
7
  import pandas as pd
8
+ import networkx as nx
9
  from sklearn.cluster import KMeans
10
+
11
  import torch
12
  from torch import nn
13
  from torch.utils.data import TensorDataset, DataLoader
14
+
15
  import matplotlib.pyplot as plt
16
  import gradio as gr
17
 
18
  # -------------------------------------------------------
19
+ # 1. Download and load SNAP Facebook combined graph
20
  # -------------------------------------------------------
21
+
22
+ SNAP_URL = "https://snap.stanford.edu/data/facebook_combined.txt.gz"
23
+ DATA_DIR = "data"
24
+ os.makedirs(DATA_DIR, exist_ok=True)
25
+ LOCAL_PATH = os.path.join(DATA_DIR, "facebook_combined.txt.gz")
26
+
27
+ if not os.path.exists(LOCAL_PATH):
28
+ print("Downloading SNAP Facebook dataset...")
29
+ urllib.request.urlretrieve(SNAP_URL, LOCAL_PATH)
30
+ else:
31
+ print("Using cached SNAP dataset.")
32
+
33
+ print("Loading graph...")
34
+ with gzip.open(LOCAL_PATH, "rt") as f:
35
+ G = nx.read_edgelist(f, nodetype=int)
36
+
37
+ print(f"Graph loaded: {G.number_of_nodes()} nodes, {G.number_of_edges()} edges")
38
+
39
+ # Ensure largest connected component (should already be connected in this dataset)
40
+ if not nx.is_connected(G):
41
+ largest_cc = max(nx.connected_components(G), key=len)
42
+ G = G.subgraph(largest_cc).copy()
43
+ print(f"After LCC: {G.number_of_nodes()} nodes, {G.number_of_edges()} edges")
44
+
45
+ nodes = list(G.nodes())
46
+ node_index = {n: i for i, n in enumerate(nodes)}
47
+ N = len(nodes)
48
 
49
  # -------------------------------------------------------
50
+ # 2. Real structural features from SNAP graph
51
  # -------------------------------------------------------
52
 
53
+ # Degree
54
+ deg = np.array([G.degree(n) for n in nodes], dtype=float)
55
 
56
+ # Clustering coefficient
57
+ cc_dict = nx.clustering(G)
58
+ cc = np.array([cc_dict[n] for n in nodes], dtype=float)
59
+
60
+ # Average neighbor degree
61
+ avg_nd_dict = nx.average_neighbor_degree(G)
62
+ avg_nd = np.array([avg_nd_dict[n] for n in nodes], dtype=float)
63
+
64
+ # PageRank
65
+ pr_dict = nx.pagerank(G, alpha=0.85)
66
+ pr = np.array([pr_dict[n] for n in nodes], dtype=float)
67
 
68
  def minmax(x):
69
  x = np.asarray(x, dtype=float)
70
  return (x - x.min()) / (x.max() - x.min() + 1e-8)
71
 
72
+ deg_norm = minmax(deg)
73
+ cc_norm = minmax(cc)
74
+ avg_nd_norm = minmax(avg_nd)
75
+ pr_norm = minmax(pr)
76
+
77
+ print("Sample structural features for first 5 nodes:")
78
+ for i in range(5):
79
+ print(
80
+ nodes[i],
81
+ "deg=", deg[i],
82
+ "deg_norm=", round(deg_norm[i], 3),
83
+ "cc_norm=", round(cc_norm[i], 3),
84
+ "avg_nd_norm=", round(avg_nd_norm[i], 3),
85
+ "pr_norm=", round(pr_norm[i], 3),
86
+ )
87
 
88
+ # -------------------------------------------------------
89
+ # 3. Paper-style behavioural features (synthetic but graph-driven)
90
+ # -------------------------------------------------------
91
 
92
+ rng = np.random.default_rng(42)
 
 
 
93
 
94
+ # Engagement: central users are more "engaged"
95
+ engagement = 50 * (0.6 * deg_norm + 0.4 * avg_nd_norm) + rng.normal(0, 3, size=N)
96
+ engagement = np.clip(engagement, 0, None)
97
+ eng_norm = minmax(engagement)
 
98
 
99
+ # Trust base: users with higher PageRank and clustering are more trusted
100
+ trust_base = (pr_norm + cc_norm) / 2.0
101
 
102
+ # Suspicious: high degree but low clustering and low PageRank
103
+ suspicious_raw = deg_norm * (1.0 - cc_norm) * (1.0 - pr_norm)
104
+ suspicious_raw += 0.1 * rng.random(N)
105
+ susp_norm = minmax(suspicious_raw)
106
 
107
+ # Activity regularity: more regular if clustering is high (stable community)
108
+ activity_reg = cc_norm + rng.normal(0, 0.05, size=N)
109
+ activity_reg = np.clip(activity_reg, 0.0, 1.0)
110
+ act_norm = minmax(activity_reg)
111
 
112
+ # Friend requests sent: more for high degree, but bounded
113
+ sent_requests = rng.poisson(lam=2 + 15 * deg_norm)
114
+ sent_requests = np.maximum(sent_requests, 1)
115
 
116
+ # Acceptance probability: higher for trusted, lower for suspicious
117
+ accepted_prob = 0.1 + 0.7 * ((trust_base + (1.0 - susp_norm)) / 2.0)
118
  accepted_prob = np.clip(accepted_prob, 0.0, 1.0)
119
  accepted_requests = rng.binomial(sent_requests, accepted_prob)
120
  friend_request_ratio = accepted_requests / (sent_requests + 1e-8)
121
  frr_norm = minmax(friend_request_ratio)
122
 
123
+ # Mutual friends ratio (approx): we use clustering coefficient as a proxy
124
+ # because high clustering means many mutual connections among friends.
125
+ mutual_friends_ratio = cc_norm.copy()
 
 
 
 
 
126
  mfr_norm = minmax(mutual_friends_ratio)
127
 
128
+ friends_norm = minmax(deg) # total friends ≈ degree
129
 
130
  # -------------------------------------------------------
131
+ # 4. Build S, T, B scores (in spirit of your paper)
132
  # -------------------------------------------------------
133
 
134
  # S: social / structural (FRR, MFR, friends)
 
141
  B_score = (eng_norm + act_norm + susp_norm) / 3.0
142
 
143
  # -------------------------------------------------------
144
+ # 5. Fuse S, T, B with variance-based weights
145
  # -------------------------------------------------------
146
 
147
  varS = np.var(S_score)
 
156
  wB * B_score
157
  ]).T # shape (N, 3)
158
 
159
+ print("Fusion weights:", wS, wT, wB)
160
+ print("F shape:", F.shape)
161
+
162
  # -------------------------------------------------------
163
+ # 6. KMeans clustering -> pseudo labels
164
+ # (0 = Trusted, 1 = Under Observation, 2 = Intruder)
165
  # -------------------------------------------------------
166
 
167
  kmeans = KMeans(n_clusters=3, random_state=42, n_init=10)
 
173
  cluster_means_sorted = sorted(cluster_means, key=lambda x: x[1])
174
 
175
  label_map = {
176
+ cluster_means_sorted[0][0]: 2, # lowest trust Intruder
177
+ cluster_means_sorted[1][0]: 1, # medium Under Observation
178
+ cluster_means_sorted[2][0]: 0 # highest Trusted
179
  }
180
 
181
  cluster_labels = np.array([label_map[c] for c in cluster_raw], dtype=int)
 
190
 
191
  def make_status_bar_plot():
192
  fig, ax = plt.subplots()
193
+ labels_txt = ["Trusted", "Under Observation", "Intruder"]
194
+ ax.bar(labels_txt, status_counts)
195
+ ax.set_ylabel("Number of users")
196
+ ax.set_title("Global distribution of user statuses (SNAP graph)")
197
  fig.tight_layout()
198
  return fig
199
 
200
  # -------------------------------------------------------
201
+ # 7. Train small MLP on fused features -> status
202
  # -------------------------------------------------------
203
 
204
  X = torch.tensor(F, dtype=torch.float32)
205
  y = torch.tensor(cluster_labels, dtype=torch.long)
206
 
207
  dataset = TensorDataset(X, y)
208
+ loader = DataLoader(dataset, batch_size=128, shuffle=True)
209
 
210
  class MLPClassifier(nn.Module):
211
  def __init__(self, in_dim, hidden_dim=32, num_classes=3):
 
234
  loss.backward()
235
  optimizer.step()
236
  total_loss += loss.item() * xb.size(0)
237
+ # optional print, can be commented on HF to reduce logs
238
+ print(f"Epoch {epoch+1:02d} - loss = {total_loss / len(dataset):.4f}")
239
 
240
  model.eval()
241
  with torch.no_grad():
 
257
  eng_max = engagement.max()
258
 
259
  # -------------------------------------------------------
260
+ # 8. Map UI sliders -> S/T/B (paper-style logic)
261
  # -------------------------------------------------------
262
 
263
  def build_scores_from_user_input(
 
267
  frr_input,
268
  mfr_input
269
  ):
270
+ # Normalize engagement using dataset range
271
  eng_norm_ui = (engagement_input - eng_min) / (eng_max - eng_min + 1e-8)
272
  eng_norm_ui = float(np.clip(eng_norm_ui, 0.0, 1.0))
273
 
 
276
  frr_norm_ui = float(np.clip(frr_input, 0.0, 1.0))
277
  mfr_norm_ui = float(np.clip(mfr_input, 0.0, 1.0))
278
 
279
+ # Assume average number of friends ~ 0.5 normalized
280
+ friends_norm_ui = 0.5
281
 
282
+ # Trust estimate from engagement & suspiciousness
283
  trust_norm_ui = (eng_norm_ui + (1.0 - susp_norm_ui)) / 2.0
284
 
285
+ # Construct S / T / B
286
  S_ui = (frr_norm_ui + mfr_norm_ui + friends_norm_ui) / 3.0
287
  T_ui = (trust_norm_ui + frr_norm_ui + (1.0 - susp_norm_ui)) / 3.0
288
  B_ui = (eng_norm_ui + act_norm_ui + susp_norm_ui) / 3.0
 
290
  return S_ui, T_ui, B_ui, eng_norm_ui, susp_norm_ui, act_norm_ui
291
 
292
  # -------------------------------------------------------
293
+ # 9. Timeline (T1–T5) helpers
294
  # -------------------------------------------------------
295
 
296
  def make_timeline_plot(timeline_state):
 
302
  return fig
303
 
304
  steps = [i + 1 for i in range(len(timeline_state))]
305
+ trusted = [entry["probs"][0] for entry in timeline_state]
306
+ obs = [entry["probs"][1] for entry in timeline_state]
307
+ intr = [entry["probs"][2] for entry in timeline_state]
308
 
309
  ax.plot(steps, trusted, marker="o", label="Trusted")
310
  ax.plot(steps, obs, marker="o", label="Under Observation")
 
341
  pred, probs = predict_from_fused(S_ui, T_ui, B_ui)
342
  status = label_names[pred]
343
 
344
+ # Keep only last 5 time steps (T1–T5)
345
  if len(timeline_state) >= 5:
346
+ timeline_state = timeline_state[1:]
347
  timeline_state.append({
348
  "status": status,
349
  "probs": probs.tolist(),
 
354
 
355
  step_num = len(timeline_state)
356
 
357
+ # Current week summary
358
  lines = []
359
+ lines.append(f"### Current Time Step: T{step_num}")
360
  lines.append(f"**Predicted Status:** **{status}**")
361
  lines.append("")
362
  lines.append("**Probabilities:**")
 
400
  )
401
 
402
  # -------------------------------------------------------
403
+ # 10. Example table: real Trusted vs Intruder-like nodes
404
  # -------------------------------------------------------
405
 
406
  def build_example_table(n_per_class=5):
 
411
  continue
412
  sel = rng.choice(idxs, size=min(n_per_class, len(idxs)), replace=False)
413
  tmp = pd.DataFrame({
414
+ "NodeID": [nodes[i] for i in sel],
415
  "Status": [label_names[lbl]] * len(sel),
416
+ "Degree": deg[sel],
417
+ "Clustering": cc[sel],
 
 
418
  "S_score": S_score[sel],
419
  "T_score": T_score[sel],
420
  "B_score": B_score[sel]
 
424
  return pd.concat(rows, ignore_index=True)
425
  else:
426
  return pd.DataFrame(columns=[
427
+ "NodeID", "Status", "Degree", "Clustering",
428
  "S_score", "T_score", "B_score"
429
  ])
430
 
 
433
  def refresh_examples():
434
  return build_example_table()
435
 
 
436
  global_status_fig = make_status_bar_plot()
437
 
438
  # -------------------------------------------------------
 
440
  # -------------------------------------------------------
441
 
442
  with gr.Blocks() as demo:
443
+ gr.Markdown("# Trust-Based Intrusion Detection on SNAP Facebook Graph")
444
  gr.Markdown(
445
+ "This demo uses the **SNAP Facebook combined graph** as a real online social network.\n\n"
446
+ "- Structural features (degree, clustering, PageRank, neighbour degree) come from the real graph.\n"
447
+ "- Behavioural features (engagement, suspiciousness, activity regularity, friend-request ratio, "
448
+ "mutual-friends ratio) are generated **synthetically but guided by the graph structure**, following the "
449
+ "spirit of your paper.\n\n"
450
+ "We fuse these into **S (Social)**, **T (Trust)** and **B (Behaviour)** scores, cluster users into "
451
+ "**Trusted / Under Observation / Intruder**, and train a small neural network to replicate this.\n\n"
452
+ "**Use the sliders** to simulate how a user changes behaviour over time. Each click on "
453
+ "**Next week (T+1)** advances the time step T1..T5 and updates the model's judgement."
454
  )
455
 
456
  with gr.Row():
457
  with gr.Column():
458
+ gr.Markdown("### Behaviour Inputs (for one user)")
459
  engagement_slider = gr.Slider(
460
  minimum=float(eng_min),
461
  maximum=float(eng_max),
462
  value=float((eng_min + eng_max) / 2.0),
463
+ step=1.0,
464
+ label="Engagement level (synthetic, based on graph centrality)"
465
  )
466
  suspicious_slider = gr.Slider(
467
  minimum=0.0,
 
489
  maximum=1.0,
490
  value=0.6,
491
  step=0.01,
492
+ label="Mutual Friends Ratio (proxy)"
493
  )
494
 
495
  next_button = gr.Button("Next week (T+1)")
 
497
 
498
  with gr.Column():
499
  current_box = gr.Markdown(
500
+ "Current time-step status will appear here after you click **Next week (T+1)**."
501
  )
502
  timeline_box = gr.Markdown(
503
  "## Timeline (T1–T5)\n(No entries yet)"
 
507
  label="Timeline probabilities (T1–T5)"
508
  )
509
 
510
+ gr.Markdown("### Global Status Distribution on the SNAP Graph")
511
  status_plot = gr.Plot(value=global_status_fig)
512
 
513
+ gr.Markdown("### Example Users (Real graph nodes: Trusted vs Intruder-like)")
514
  examples_table = gr.Dataframe(
515
  value=examples_df,
516
+ label="Sample nodes from SNAP Facebook",
517
  interactive=False
518
  )
519
  refresh_button = gr.Button("Refresh examples")