Views
No views yet
filename.txt). At inference time, it scores any filename by computing its negative log-likelihood (NLL) — how "surprised" the model is by each character given the preceding context.| Component | Details |
|---|---|
| Model | GPT-2 style (RMSNorm, no biases, ReLU) |
| Embedding dim | 32 |
| Attention heads | 4 |
| Layers | 2 |
| Max sequence length | 96 characters |
| Tokenizer | Character-level |
| Autograd | Scalar-valued (each Value wraps a single float) |
| Training | Adam optimizer with early stopping |
| File | Description |
|---|---|
filename_anomaly_detector.py | Training script — autograd engine, model, training loop with early stopping, anomaly scoring |
test_model.py | Inference-only test script — loads model.json and scores filenames |
model.json | Pre-trained weights (can be regenerated by running the training script) |
filename.txt | Training data — 88 filenames following a naming convention |
training_loss.svg | Train vs validation loss curve from the last training run |
main.py | Original Karpathy GPT (trains on baby names dataset, for reference) |
input.txt | Names dataset used by main.py |
python test_model.pyModel loaded: 32d, 4h, 2L, vocab=45
--- Normal filenames (should have LOW NLL) ---
NLL 16.93 | acr_banner_spring25_enUS_v01.png
NLL 18.76 | acr_email_bf24_enGB_v02.jpg
NLL 18.65 | acr_video_demo_enUS_v01.mp4
NLL 23.63 | acr_logo_primary_enUS_v03.svg
NLL 53.87 | acr_report_fy24q4_enUS_v01.pdf
--- Anomalous filenames (should have HIGH NLL) ---
NLL 101.55 | DELETE_THIS_NOW.exe
NLL 118.09 | ..hidden_config.bat
NLL 164.49 | photo_2024_vacation_IMG_3847.HEIC
NLL 168.67 | meeting notes final FINAL v2 (1).docx
NLL 68.93 | acr banner spring enUS v01.png1import json, math
2
3# 1. Load the model
4with open('model.json') as f:
5 payload = json.load(f)
6
7hp = payload['hyperparams']
8n_embd, n_head, n_layer = hp['n_embd'], hp['n_head'], hp['n_layer']
9block_size, head_dim = hp['block_size'], hp['head_dim']
10uchars = payload['vocab']
11vocab_size = payload['vocab_size']
12weights = payload['weights']
13BOS = vocab_size - 1
14stoi = {ch: i for i, ch in enumerate(uchars)}
15
16# 2. Define the forward pass (float-only, no autograd needed)
17def linear(x, w):
18 return [sum(wi * xi for wi, xi in zip(wo, x)) for wo in w]
19
20def rmsnorm(x):
21 ms = sum(xi * xi for xi in x) / len(x)
22 return [xi * (ms + 1e-5) ** -0.5 for xi in x]
23
24def softmax(logits):
25 m = max(logits)
26 exps = [math.exp(v - m) for v in logits]
27 s = sum(exps)
28 return [e / s for e in exps]
29
30def gpt_forward(token_id, pos_id, keys, values):
31 x = [t + p for t, p in zip(weights['wte'][token_id], weights['wpe'][pos_id])]
32 x = rmsnorm(x)
33 for li in range(n_layer):
34 x_res = x
35 x = rmsnorm(x)
36 q = linear(x, weights[f'layer{li}.attn_wq'])
37 k = linear(x, weights[f'layer{li}.attn_wk'])
38 v = linear(x, weights[f'layer{li}.attn_wv'])
39 keys[li].append(k); values[li].append(v)
40 x_attn = []
41 for h in range(n_head):
42 hs = h * head_dim
43 q_h = q[hs:hs+head_dim]
44 k_h = [ki[hs:hs+head_dim] for ki in keys[li]]
45 v_h = [vi[hs:hs+head_dim] for vi in values[li]]
46 attn = [sum(q_h[j]*k_h[t][j] for j in range(head_dim)) / head_dim**0.5
47 for t in range(len(k_h))]
48 aw = softmax(attn)
49 x_attn.extend([sum(aw[t]*v_h[t][j] for t in range(len(v_h)))
50 for j in range(head_dim)])
51 x = linear(x_attn, weights[f'layer{li}.attn_wo'])
52 x = [a + b for a, b in zip(x, x_res)]
53 x_res = x
54 x = rmsnorm(x)
55 x = [max(0, xi) for xi in linear(x, weights[f'layer{li}.mlp_fc1'])]
56 x = linear(x, weights[f'layer{li}.mlp_fc2'])
57 x = [a + b for a, b in zip(x, x_res)]
58 return linear(x, weights['lm_head'])
59
60# 3. Score a filename
61def score_filename(name):
62 """Returns NLL (lower = more normal, higher = more anomalous)."""
63 toks = [BOS] + [stoi[c] for c in name if c in stoi] + [BOS]
64 keys = [[] for _ in range(n_layer)]
65 vals = [[] for _ in range(n_layer)]
66 nll = 0.0
67 for pos in range(len(toks) - 1):
68 probs = softmax(gpt_forward(toks[pos], pos, keys, vals))
69 p = probs[toks[pos + 1]]
70 nll += -math.log(p) if p > 0 else 1e6
71 return nll
72
73# 4. Use it
74nll = score_filename("acr_banner_spring25_enUS_v01.png")
75print(f"NLL: {nll:.2f}") # Low NLL = normal
76
77nll = score_filename("DELETE_THIS_NOW.exe")
78print(f"NLL: {nll:.2f}") # High NLL = anomalous1known_good = [line.strip() for line in open('filename.txt')]
2scores = [score_filename(fn) for fn in known_good]
3scores.sort()
4threshold = scores[int(len(scores) * 0.95)]
5print(f"Threshold: {threshold:.2f}")
6
7# Flag anomalies
8test_file = "suspicious_file.exe"
9nll = score_filename(test_file)
10print(f"{'ANOMALY' if nll > threshold else 'NORMAL'}: {test_file} (NLL={nll:.2f})")filename.txt with your filenames (one per line)model.json (so the script trains from scratch)python filename_anomaly_detector.pytraining_loss.svg plot is updated during training.Note: Training uses scalar autograd (every multiply/add creates aValuenode), so it's slow by design — this is an educational implementation. For production use, port the forward pass to NumPy/PyTorch.