Frodo commited on
Commit
e3f783d
·
1 Parent(s): 072a58b

Add Benchmarks tab with public dataset results

Browse files
Files changed (2) hide show
  1. README.md +1 -1
  2. app.py +191 -68
README.md CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: GnaniNet Fraud Classifier
3
  emoji: 🔍
4
  colorFrom: yellow
5
  colorTo: red
 
1
  ---
2
+ title: KestrelNet Fraud Classifier
3
  emoji: 🔍
4
  colorFrom: yellow
5
  colorTo: red
app.py CHANGED
@@ -1,5 +1,5 @@
1
  """
2
- GnaniNet Fraud Classifier — Gradio Space
3
 
4
  Interactive demo for a 1,059-parameter fraud detection model.
5
  Pure NumPy inference, no GPU required.
@@ -21,7 +21,7 @@ def _softmax(logits):
21
  return e / e.sum()
22
 
23
 
24
- class GnaniNet:
25
  def __init__(self, input_dim, hidden_dims, output_dim):
26
  self.input_dim = input_dim
27
  self.hidden_dims = list(hidden_dims)
@@ -58,7 +58,7 @@ class GnaniNet:
58
 
59
  # ── Load model ──────────────────────────────────────────────────────────────
60
 
61
- model = GnaniNet(14, [32, 16], 3).load("weights.txt")
62
 
63
 
64
  # ── Feature normalization ───────────────────────────────────────────────────
@@ -190,8 +190,126 @@ A fully-connected classifier smaller than most models' bias vectors.
190
  </div>
191
  """
192
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  with gr.Blocks(
194
- title="GnaniNet Fraud Classifier",
195
  theme=gr.themes.Base(
196
  primary_hue=gr.themes.colors.orange,
197
  neutral_hue=gr.themes.colors.gray,
@@ -202,81 +320,86 @@ with gr.Blocks(
202
  footer { display: none !important; }
203
  """,
204
  ) as demo:
205
- gr.Markdown("# GnaniNet", elem_id="title")
206
  gr.HTML(DESCRIPTION)
207
 
208
- with gr.Row():
209
- preset = gr.Dropdown(
210
- choices=list(PRESETS.keys()),
211
- label="Load preset",
212
- interactive=True,
213
- scale=2,
214
- )
215
- threshold = gr.Radio(
216
- choices=["Standard", "Conservative", "Strict"],
217
- value="Standard",
218
- label="Threshold mode",
219
- scale=3,
220
- )
221
-
222
- with gr.Row():
223
- with gr.Column(scale=3):
224
- gr.Markdown("### Transaction Features")
225
- with gr.Row():
226
- amount_ratio = gr.Number(label="Amount / 90-day avg", value=1.0, minimum=0, maximum=100)
227
- hour = gr.Slider(label="Hour", value=14, minimum=0, maximum=23, step=1)
228
- day_of_week = gr.Slider(label="Day of week (0=Mon)", value=2, minimum=0, maximum=6, step=1)
229
-
230
- with gr.Row():
231
- location_delta = gr.Number(label="Location delta (σ)", value=0.1, minimum=0, maximum=10)
232
- velocity_1h = gr.Slider(label="Txns past hour", value=1, minimum=0, maximum=99, step=1)
233
- velocity_24h = gr.Slider(label="Txns past 24h", value=3, minimum=0, maximum=999, step=1)
234
-
235
  with gr.Row():
236
- merchant_risk = gr.Slider(label="Merchant risk", value=0.05, minimum=0, maximum=1, step=0.01)
237
- account_age_days = gr.Number(label="Account age (days)", value=1200, minimum=0, maximum=36500)
238
- prev_fraud_score = gr.Slider(label="Prev fraud score", value=0.0, minimum=0, maximum=1, step=0.01)
 
 
 
 
 
 
 
 
 
239
 
240
  with gr.Row():
241
- international = gr.Checkbox(label="International", value=False)
242
- card_present = gr.Checkbox(label="Card present", value=True)
243
- device_match = gr.Checkbox(label="Device match", value=True)
244
-
245
- with gr.Column(scale=2):
246
- gr.Markdown("### Result")
247
- verdict_output = gr.HTML()
248
- scores_output = gr.Label(label="Class probabilities", num_top_classes=3)
249
-
250
- feature_inputs = [
251
- amount_ratio, hour, day_of_week, location_delta,
252
- velocity_1h, velocity_24h, merchant_risk,
253
- international, card_present, device_match,
254
- account_age_days, prev_fraud_score,
255
- ]
256
-
257
- all_inputs = feature_inputs + [threshold]
258
-
259
- # Preset loader
260
- preset.change(fn=apply_preset, inputs=[preset], outputs=feature_inputs)
261
-
262
- # Auto-classify on any change
263
- for inp in all_inputs:
264
- inp.change(fn=classify, inputs=all_inputs, outputs=[verdict_output, scores_output])
265
-
266
- # Classify button as fallback
267
- btn = gr.Button("Classify", variant="primary")
268
- btn.click(fn=classify, inputs=all_inputs, outputs=[verdict_output, scores_output])
269
-
270
- # Run once on load
271
- demo.load(fn=classify, inputs=all_inputs, outputs=[verdict_output, scores_output])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
 
273
  gr.Markdown("""
274
  ---
275
  <div style="text-align:center; font-size: 0.8em; color: #888;">
276
  Architecture: 14 → 32 → 16 → 3 &nbsp;·&nbsp; ReLU activations &nbsp;·&nbsp; Softmax output &nbsp;·&nbsp; Analytic backprop training<br>
277
  No PyTorch. No TensorFlow. No ONNX. Just NumPy.<br><br>
278
- <a href="https://huggingface.co/gnaninet/fraud-classifier">Model Card</a> &nbsp;·&nbsp;
279
- <a href="https://gnaninet.ai">Website</a>
280
  </div>
281
  """)
282
 
 
1
  """
2
+ KestrelNet Fraud Classifier — Gradio Space
3
 
4
  Interactive demo for a 1,059-parameter fraud detection model.
5
  Pure NumPy inference, no GPU required.
 
21
  return e / e.sum()
22
 
23
 
24
+ class KestrelNet:
25
  def __init__(self, input_dim, hidden_dims, output_dim):
26
  self.input_dim = input_dim
27
  self.hidden_dims = list(hidden_dims)
 
58
 
59
  # ── Load model ──────────────────────────────────────────────────────────────
60
 
61
+ model = KestrelNet(14, [32, 16], 3).load("weights.txt")
62
 
63
 
64
  # ── Feature normalization ───────────────────────────────────────────────────
 
190
  </div>
191
  """
192
 
193
+ BENCHMARKS_HTML = """
194
+ <div style="max-width: 780px; margin: 0 auto; font-family: Inter, sans-serif;">
195
+
196
+ <h3 style="margin-bottom: 4px;">Verified on Public Kaggle Datasets</h3>
197
+ <p style="color: #888; font-size: 0.85em; margin-top: 0;">
198
+ All datasets publicly available. Results independently reproducible. Pure NumPy, CPU only.
199
+ </p>
200
+
201
+ <table style="width:100%; border-collapse: collapse; font-size: 0.9em; margin: 16px 0;">
202
+ <thead>
203
+ <tr style="border-bottom: 2px solid #444; text-align: left;">
204
+ <th style="padding: 8px;">Dataset</th>
205
+ <th style="padding: 8px;">Task</th>
206
+ <th style="padding: 8px;">Accuracy</th>
207
+ <th style="padding: 8px;">F1 / AUC</th>
208
+ <th style="padding: 8px;">Params</th>
209
+ <th style="padding: 8px;">Latency</th>
210
+ </tr>
211
+ </thead>
212
+ <tbody>
213
+ <tr style="border-bottom: 1px solid #333;">
214
+ <td style="padding: 8px;"><strong>ECG Heartbeat</strong><br><span style="color:#888;font-size:0.8em;">MIT-BIH Arrhythmia</span></td>
215
+ <td style="padding: 8px;">5-class arrhythmia</td>
216
+ <td style="padding: 8px; color: #22c55e; font-weight: 700;">97.2%</td>
217
+ <td style="padding: 8px;">F1 0.853</td>
218
+ <td style="padding: 8px;">12,756</td>
219
+ <td style="padding: 8px;">56 μs</td>
220
+ </tr>
221
+ <tr style="border-bottom: 1px solid #333;">
222
+ <td style="padding: 8px;"><strong>EEG Emotions</strong><br><span style="color:#888;font-size:0.8em;">Brainwave Sentiment</span></td>
223
+ <td style="padding: 8px;">3-class emotion</td>
224
+ <td style="padding: 8px; color: #22c55e; font-weight: 700;">99.1%</td>
225
+ <td style="padding: 8px;">F1 0.991</td>
226
+ <td style="padding: 8px;">163,788</td>
227
+ <td style="padding: 8px;">1.3 ms</td>
228
+ </tr>
229
+ <tr style="border-bottom: 1px solid #333;">
230
+ <td style="padding: 8px;"><strong>EEG Eye State</strong><br><span style="color:#888;font-size:0.8em;">Roesler / UCI</span></td>
231
+ <td style="padding: 8px;">Binary open/closed</td>
232
+ <td style="padding: 8px; color: #22c55e; font-weight: 700;">94.2%</td>
233
+ <td style="padding: 8px;">AUC 0.986</td>
234
+ <td style="padding: 8px;">1,576</td>
235
+ <td style="padding: 8px;">17 μs</td>
236
+ </tr>
237
+ <tr style="border-bottom: 1px solid #333;">
238
+ <td style="padding: 8px;"><strong>Seizure Prediction</strong><br><span style="color:#888;font-size:0.8em;">Bonn University EEG</span></td>
239
+ <td style="padding: 8px;">Binary seizure</td>
240
+ <td style="padding: 8px; color: #22c55e; font-weight: 700;">97.1%</td>
241
+ <td style="padding: 8px;">AUC 0.988</td>
242
+ <td style="padding: 8px;">12,072</td>
243
+ <td style="padding: 8px;">—</td>
244
+ </tr>
245
+ <tr style="border-bottom: 1px solid #333;">
246
+ <td style="padding: 8px;"><strong>HAR Smartphones</strong><br><span style="color:#888;font-size:0.8em;">UCI Activity Recognition</span></td>
247
+ <td style="padding: 8px;">6-class activity</td>
248
+ <td style="padding: 8px; color: #22c55e; font-weight: 700;">94.9%</td>
249
+ <td style="padding: 8px;">F1 0.949</td>
250
+ <td style="padding: 8px;">15,416</td>
251
+ <td style="padding: 8px;">70 μs</td>
252
+ </tr>
253
+ <tr style="border-bottom: 2px solid #444;">
254
+ <td style="padding: 8px;"><strong>Fraud Detection</strong><br><span style="color:#888;font-size:0.8em;">Proprietary</span></td>
255
+ <td style="padding: 8px;">3-class fraud</td>
256
+ <td style="padding: 8px; color: #22c55e; font-weight: 700;">91.6%</td>
257
+ <td style="padding: 8px;">—</td>
258
+ <td style="padding: 8px;">1,059</td>
259
+ <td style="padding: 8px;">5 μs</td>
260
+ </tr>
261
+ </tbody>
262
+ </table>
263
+
264
+ <h3 style="margin-bottom: 4px;">Parameter Efficiency vs Typical Models</h3>
265
+
266
+ <table style="width:100%; border-collapse: collapse; font-size: 0.9em; margin: 16px 0;">
267
+ <thead>
268
+ <tr style="border-bottom: 2px solid #444; text-align: left;">
269
+ <th style="padding: 8px;">Dataset</th>
270
+ <th style="padding: 8px;">Typical CNN/LSTM</th>
271
+ <th style="padding: 8px;">Ours</th>
272
+ <th style="padding: 8px;">Reduction</th>
273
+ </tr>
274
+ </thead>
275
+ <tbody>
276
+ <tr style="border-bottom: 1px solid #333;">
277
+ <td style="padding: 8px;">ECG Heartbeat</td>
278
+ <td style="padding: 8px; color: #888;">500K – 2M</td>
279
+ <td style="padding: 8px; font-weight: 700;">12,756</td>
280
+ <td style="padding: 8px; color: #f59e0b; font-weight: 700;">40–160x smaller</td>
281
+ </tr>
282
+ <tr style="border-bottom: 1px solid #333;">
283
+ <td style="padding: 8px;">EEG Emotions</td>
284
+ <td style="padding: 8px; color: #888;">1M+</td>
285
+ <td style="padding: 8px; font-weight: 700;">163,788</td>
286
+ <td style="padding: 8px; color: #f59e0b; font-weight: 700;">6x smaller</td>
287
+ </tr>
288
+ <tr style="border-bottom: 1px solid #333;">
289
+ <td style="padding: 8px;">EEG Eye State</td>
290
+ <td style="padding: 8px; color: #888;">100K+</td>
291
+ <td style="padding: 8px; font-weight: 700;">1,576</td>
292
+ <td style="padding: 8px; color: #f59e0b; font-weight: 700;">63x smaller</td>
293
+ </tr>
294
+ <tr style="border-bottom: 2px solid #444;">
295
+ <td style="padding: 8px;">HAR Smartphones</td>
296
+ <td style="padding: 8px; color: #888;">200K – 1M</td>
297
+ <td style="padding: 8px; font-weight: 700;">15,416</td>
298
+ <td style="padding: 8px; color: #f59e0b; font-weight: 700;">13–65x smaller</td>
299
+ </tr>
300
+ </tbody>
301
+ </table>
302
+
303
+ <p style="color: #666; font-size: 0.8em; text-align: center; margin-top: 24px;">
304
+ Two model families: <strong>KestrelNet</strong> (standard FC, minimal params) and <strong>GoshawkNet</strong> (multivector products, deeper pattern capture).<br>
305
+ Named after raptors — bird size matches model size, hunting style matches classification style.
306
+ </p>
307
+
308
+ </div>
309
+ """
310
+
311
  with gr.Blocks(
312
+ title="KestrelNet Fraud Classifier",
313
  theme=gr.themes.Base(
314
  primary_hue=gr.themes.colors.orange,
315
  neutral_hue=gr.themes.colors.gray,
 
320
  footer { display: none !important; }
321
  """,
322
  ) as demo:
323
+ gr.Markdown("# KestrelNet", elem_id="title")
324
  gr.HTML(DESCRIPTION)
325
 
326
+ with gr.Tabs():
327
+ with gr.TabItem("Classify"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  with gr.Row():
329
+ preset = gr.Dropdown(
330
+ choices=list(PRESETS.keys()),
331
+ label="Load preset",
332
+ interactive=True,
333
+ scale=2,
334
+ )
335
+ threshold = gr.Radio(
336
+ choices=["Standard", "Conservative", "Strict"],
337
+ value="Standard",
338
+ label="Threshold mode",
339
+ scale=3,
340
+ )
341
 
342
  with gr.Row():
343
+ with gr.Column(scale=3):
344
+ gr.Markdown("### Transaction Features")
345
+ with gr.Row():
346
+ amount_ratio = gr.Number(label="Amount / 90-day avg", value=1.0, minimum=0, maximum=100)
347
+ hour = gr.Slider(label="Hour", value=14, minimum=0, maximum=23, step=1)
348
+ day_of_week = gr.Slider(label="Day of week (0=Mon)", value=2, minimum=0, maximum=6, step=1)
349
+
350
+ with gr.Row():
351
+ location_delta = gr.Number(label="Location delta (σ)", value=0.1, minimum=0, maximum=10)
352
+ velocity_1h = gr.Slider(label="Txns past hour", value=1, minimum=0, maximum=99, step=1)
353
+ velocity_24h = gr.Slider(label="Txns past 24h", value=3, minimum=0, maximum=999, step=1)
354
+
355
+ with gr.Row():
356
+ merchant_risk = gr.Slider(label="Merchant risk", value=0.05, minimum=0, maximum=1, step=0.01)
357
+ account_age_days = gr.Number(label="Account age (days)", value=1200, minimum=0, maximum=36500)
358
+ prev_fraud_score = gr.Slider(label="Prev fraud score", value=0.0, minimum=0, maximum=1, step=0.01)
359
+
360
+ with gr.Row():
361
+ international = gr.Checkbox(label="International", value=False)
362
+ card_present = gr.Checkbox(label="Card present", value=True)
363
+ device_match = gr.Checkbox(label="Device match", value=True)
364
+
365
+ with gr.Column(scale=2):
366
+ gr.Markdown("### Result")
367
+ verdict_output = gr.HTML()
368
+ scores_output = gr.Label(label="Class probabilities", num_top_classes=3)
369
+
370
+ feature_inputs = [
371
+ amount_ratio, hour, day_of_week, location_delta,
372
+ velocity_1h, velocity_24h, merchant_risk,
373
+ international, card_present, device_match,
374
+ account_age_days, prev_fraud_score,
375
+ ]
376
+
377
+ all_inputs = feature_inputs + [threshold]
378
+
379
+ # Preset loader
380
+ preset.change(fn=apply_preset, inputs=[preset], outputs=feature_inputs)
381
+
382
+ # Auto-classify on any change
383
+ for inp in all_inputs:
384
+ inp.change(fn=classify, inputs=all_inputs, outputs=[verdict_output, scores_output])
385
+
386
+ # Classify button as fallback
387
+ btn = gr.Button("Classify", variant="primary")
388
+ btn.click(fn=classify, inputs=all_inputs, outputs=[verdict_output, scores_output])
389
+
390
+ # Run once on load
391
+ demo.load(fn=classify, inputs=all_inputs, outputs=[verdict_output, scores_output])
392
+
393
+ with gr.TabItem("Benchmarks"):
394
+ gr.HTML(BENCHMARKS_HTML)
395
 
396
  gr.Markdown("""
397
  ---
398
  <div style="text-align:center; font-size: 0.8em; color: #888;">
399
  Architecture: 14 → 32 → 16 → 3 &nbsp;·&nbsp; ReLU activations &nbsp;·&nbsp; Softmax output &nbsp;·&nbsp; Analytic backprop training<br>
400
  No PyTorch. No TensorFlow. No ONNX. Just NumPy.<br><br>
401
+ <a href="https://huggingface.co/reddysama/gnaninet-fraud-classifier">Model Card</a> &nbsp;·&nbsp;
402
+ <a href="https://naninet.ai">Website</a>
403
  </div>
404
  """)
405