BuildITSystems commited on
Commit
80bd85c
·
verified ·
1 Parent(s): ed8f615

Upload BITS phishing detector

Browse files
Files changed (4) hide show
  1. README.md +251 -0
  2. config.json +467 -0
  3. model.safetensors +3 -0
  4. tokenizer.json +0 -0
README.md ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ datasets:
3
+ - puyang2025/seven-phishing-email-datasets
4
+ - corbt/enron-emails
5
+ - kudzaiprichard/aura-phishing-email-corpus
6
+ language: en
7
+ library_name: pytorch
8
+ license: mit
9
+ pipeline_tag: text-classification
10
+ tags:
11
+ - phishing
12
+ - email-security
13
+ - text-classification
14
+ - from-scratch
15
+ - spam-detection
16
+ model-index:
17
+ - name: phishing-detector-encoder
18
+ results:
19
+ - task:
20
+ type: text-classification
21
+ dataset:
22
+ name: TREC-06 holdout (puyang2025), binary roll-up
23
+ type: puyang2025/seven-phishing-email-datasets
24
+ metrics:
25
+ - type: recall
26
+ value: 0.9741
27
+ name: Recall (unwanted)
28
+ - type: precision
29
+ value: 0.8794
30
+ name: Precision (unwanted)
31
+ - type: f1
32
+ value: 0.9243
33
+ name: F1 (unwanted)
34
+ - type: accuracy
35
+ value: 0.9619
36
+ name: Accuracy
37
+ - type: roc_auc
38
+ value: 0.9946
39
+ name: ROC-AUC
40
+ - task:
41
+ type: text-classification
42
+ dataset:
43
+ name: three-way holdout (Nazario + Nigerian Fraud phishing, SpamAssassin / Enron-Spam
44
+ spam, CEAS-08 / TREC-07 / SpamAssassin ham)
45
+ type: kudzaiprichard/aura-phishing-email-corpus
46
+ metrics:
47
+ - type: f1
48
+ value: 0.9867
49
+ name: Macro-F1 (legitimate / phishing / spam)
50
+ - type: accuracy
51
+ value: 0.9885
52
+ name: Accuracy (3-way)
53
+ ---
54
+
55
+ # phishing-detector-encoder: legitimate / phishing / spam email classifier trained from scratch
56
+
57
+ > **No external pretrained weights were used.** The encoder, the tokenizer and every parameter in `model.safetensors`
58
+ > were trained from random initialisation inside this project, in plain PyTorch, without the `transformers`
59
+ > library. The only external inputs are the public email corpora listed below.
60
+ > Initialisation of this checkpoint: `pretrained:/home/nathank/BITS/dev/Phishing Model/checkpoints/pretrain/large-24L/final`.
61
+
62
+ ## What it is
63
+
64
+ A 337,444,867-parameter BERT-style **pre-LayerNorm Transformer encoder** (24 layers, d_model 1024,
65
+ 16 heads, FFN 4096, byte-level BPE vocabulary of 32,768, 512 positions) with a
66
+ CLS-pooled classification head that maps an email (`subject + "\n\n" + body`) to three classes: `0 = Legitimate`, `1 = Phishing`, `2 = Spam`. The binary verdict shown by the demo is the roll-up **Unwanted (phishing or spam)** = P(phishing) + P(spam) (`config.json["binary_rollup"]`). Phishing = Nazario phishing corpus + Nigerian-fraud (advance-fee) corpus + synthetic BITS-style credential/BEC lures; Spam = SpamAssassin and Enron-Spam spam folders; the TREC-05 / TREC-07 / CEAS-08 positives never separated spam from phishing and were trained as 'unwanted' only (probability mass of phishing + spam), never as either class.
67
+ The architecture is defined in `model.py`, which ships with the weights. Weights are stored in **bf16** safetensors
68
+ (~675 MB); the header carries the architecture config and the label names as JSON.
69
+
70
+ ## How it was trained
71
+
72
+ 1. **Masked-language-model pretraining from scratch** on the deduplicated Enron email corpus (`corbt/enron-emails`: 517,401 rows -> 226,273 documents after exact/near-duplicate removal and quote stripping), 15% dynamic masking (80/10/10), sequence packing to 512 tokens, AdamW (betas (0.9, 0.98), weight decay 0.01), peak LR 0.00015, bf16 autocast on one RTX 3090 Ti. Run summary (from the pretraining run's `final/config.json`):
73
+ - run_name: large-24L
74
+ - status: complete
75
+ - source_checkpoint: step-0009552
76
+ - steps: 9552
77
+ - total_steps_planned: 9552
78
+ - steps_per_epoch: 796
79
+ - epochs_planned: 12
80
+ - epochs_completed: 12.0
81
+ - tokens_seen: 1291124736
82
+ - total_training_seconds: 54185.9
83
+ - total_training_time: 15h 03m 05s
84
+ - final_loss: 1.523698394948786
85
+ - val_loss: 1.9862932266429436
86
+ - val_masked_acc: 0.6291136684837473
87
+ - peak_vram_reserved_gb: 17.939
88
+ - peak_vram_allocated_gb: 17.494
89
+ - micro_batch: 24
90
+ - grad_accumulation: 11
91
+ - effective_batch: 264
92
+ - precision: bf16_autocast
93
+ - optimizer: AdamW(fused) {"lr": 0.00015, "betas": [0.9, 0.98], "eps": 1e-06, "weight_decay": 0.01, "grad_clip": 1.0, "mlm_probability": 0.15, "mask_replace_probs": [0.8, 0.1, 0.1], "schedule": "cosine_to_10pct", "effective_batch_sequences": 256}
94
+ - schedule: {'kind': 'cosine', 'peak_lr': 0.00015, 'warmup_steps': 573, 'total_steps': 9552, 'floor_ratio': 0.1}
95
+ - mean_tokens_per_s: 23889
96
+ - data: {'n_windows': 210368, 'n_val_windows': 1999, 'tokenizer_sha1': '9f50c5e775089d2ca3efc82e100cd6b3396ed7e7', 'n_docs': 226273, 'limit_docs': None, 'window': '[CLS] + 511 stream tokens'}
97
+ - weights_dtype: float32
98
+ - exported_at: 2026-10-09T03:31:14
99
+ 2. **Fine-tuning** on `puyang2025/seven-phishing-email-datasets` with our own split: TREC-05, TREC-07 and CEAS-08 (positives trained as *unwanted* only), Enron-Spam and SpamAssassin (spam folders = `Spam`), the Nazario and Nigerian-fraud corpora of `kudzaiprichard/aura-phishing-email-corpus` (= `Phishing`) and synthetic BITS-style lures (= `Phishing`) form train (157,113) / val (17,643), grouped by
100
+ near-duplicate cluster so no template straddles the two; **TREC-06 (16,302 rows, 23.9% positive) is held out
101
+ entirely** as the unseen binary test set and never touched by the tokenizer, pretraining or fine-tuning (1,652 pool rows that
102
+ near-duplicate TREC-06 templates were removed). A second holdout, **test_holdout3**, holds clean-provenance three-way rows never seen by the tokenizer or the pretraining corpus for the 3x3 confusion matrix. Counts come from `data/processed/splits_manifest.json`.
103
+ Rows with a clean three-way label train with 3-class cross-entropy; TREC / CEAS positives train on the probability mass of {phishing, spam} (partial-label loss: -log(P(phishing) + P(spam))), so they shape the legitimate-vs-unwanted decision without asserting a kind they were never labelled with.
104
+ Fine-tune dials (from the checkpoint's config.json):
105
+ - run_name: ft-focal
106
+ - init: pretrained:checkpoints/pretrain/large-24L/final
107
+ - n_layers: 24
108
+ - freeze_layers: 0
109
+ - loss: focal
110
+ - focal_gamma: 2.0
111
+ - lr: 3e-05
112
+ - batch_size: 32
113
+ - micro_batch: 16
114
+ - epochs: 2
115
+ - max_steps: None
116
+ - eval_every: 1250
117
+ - log_every: 20
118
+ - eval_batch_size: 64
119
+ - warmup_fraction: 0.06
120
+ - weight_decay: 0.01
121
+ - betas: [0.9, 0.98]
122
+ - eps: 1e-06
123
+ - grad_clip: 1.0
124
+ - patience: 2
125
+ - schedule: linear_decay
126
+ - selection_metric: val_f1_unwanted
127
+ - pooling: cls
128
+ - num_labels: 3
129
+ - synthetic: off
130
+ - limit_rows: None
131
+ - seed: 42
132
+ - cpu: False
133
+
134
+ ## Metrics
135
+
136
+ ### TREC-06 binary holdout (Unwanted (phishing or spam))
137
+
138
+ Evaluated by `src/evaluate.py` on the **test** split (`test_holdout.parquet`, 16,302 rows, threshold 0.5). Positive class = **Unwanted (phishing or spam)**, P(unwanted) = P(phishing) + P(spam); these definitions are identical to the binary-era runs, so the numbers are comparable.
139
+
140
+ | metric | value |
141
+ |---|---:|
142
+ | **Recall (unwanted)** | **0.9741** (101 missed of 3,894) |
143
+ | Precision (unwanted) | 0.8794 |
144
+ | F1 (unwanted) | 0.9243 |
145
+ | Accuracy | 0.9619 |
146
+ | Specificity | 0.9581 |
147
+ | ROC-AUC | 0.9946 |
148
+ | PR-AUC | 0.9848 |
149
+ | Recall at 1% FPR | 0.8955 |
150
+ | Recall at 0.1% FPR | 0.6497 |
151
+ | Credential-lure slice recall / precision (n=398) | 0.9959 / 0.9758 |
152
+ | Latency, batch 1, GPU bf16 (avg / p50) | 10.08 ms / 8.9 ms |
153
+ | Latency, batch 1, CPU fp32, 16 threads (avg / p50) | 458.57 ms / 445.01 ms |
154
+ | Instructor benchmark | 74.0 ms per sample |
155
+
156
+ Recall on the unwanted class is the headline number: in a defensive triage aid a missed phish is the costly error, while a false positive costs a human one glance.
157
+
158
+ TREC-06 carries no phishing-vs-spam ground truth, so the three-way head can only be *described* here: of the 3,793 true positives it flagged, the model called 974 phishing and 2,819 spam (informational, not a metric).
159
+
160
+ ### Three-way holdout (legitimate / phishing / spam)
161
+
162
+ Evaluated by `src/evaluate.py --split test3` on `test_holdout3.parquet` (3,220 rows with a full three-way label, threshold 0.5). Decision rule `rollup_then_argmax`: a row is unwanted iff P(phishing) + P(spam) >= threshold, then the kind is argmax(P(phishing), P(spam)), so this table collapses exactly onto the binary roll-up.
163
+
164
+ | metric | value |
165
+ |---|---:|
166
+ | **Macro-F1 (3 classes)** | **0.9867** |
167
+ | Accuracy | 0.9885 |
168
+ | Legitimate: precision / recall / F1 (n=1,999) | 0.9900 / 0.9935 / 0.9918 |
169
+ | Phishing: precision / recall / F1 (n=521) | 0.9961 / 0.9866 / 0.9913 |
170
+ | Spam: precision / recall / F1 (n=700) | 0.9785 / 0.9757 / 0.9771 |
171
+ | Phishing-vs-spam accuracy on the 1,221 true unwanted rows (kind decision alone) | 0.9959 (4 phishing read as spam, 1 spam read as phishing) |
172
+ | Binary roll-up on this split: recall / precision / F1 | 0.9836 / 0.9893 / 0.9864 |
173
+
174
+ Confusion matrix (rows = true label by corpus provenance, columns = predicted):
175
+
176
+ | true \ predicted | Legitimate | Phishing | Spam |
177
+ |---|---:|---:|---:|
178
+ | **Legitimate** | 1,986 | 1 | 12 |
179
+ | **Phishing** | 4 | 514 | 3 |
180
+ | **Spam** | 16 | 1 | 683 |
181
+
182
+ | source | rows | Legitimate | Phishing | Spam | accuracy |
183
+ |---|---:|---:|---:|---:|---:|
184
+ | Assassin | 236 | 164 | 0 | 72 | 0.9534 |
185
+ | CEAS-08 | 736 | 736 | 0 | 0 | 0.9905 |
186
+ | Enron | 628 | 0 | 0 | 628 | 0.9841 |
187
+ | Nazario | 222 | 0 | 222 | 0 | 0.9865 |
188
+ | Nigerian_Fraud | 299 | 0 | 299 | 0 | 0.9866 |
189
+ | TREC-07 | 1,099 | 1,099 | 0 | 0 | 0.9982 |
190
+
191
+ **Label provenance.** Phishing = Nazario phishing corpus + Nigerian-fraud (advance-fee) corpus + synthetic BITS-style credential/BEC lures; Spam = SpamAssassin and Enron-Spam spam folders; the TREC-05 / TREC-07 / CEAS-08 positives never separated spam from phishing and were trained as 'unwanted' only (probability mass of phishing + spam), never as either class. The three-way holdout is drawn from text the tokenizer never saw and the pretraining corpus does not contain (Nazario / Nigerian-fraud phishing, SpamAssassin / Enron-Spam spam, CEAS-08 / TREC-07 / SpamAssassin legitimate mail from the former validation split). Because the kind labels come from which corpus a message sits in, part of any phishing-vs-spam confusion measures corpus style rather than lure content.
192
+
193
+ ## Intended use
194
+
195
+ A **defensive triage aid for small-business inboxes**: rank or flag incoming mail for a human to review, surface a
196
+ probability and the attention-highlighted tokens that drove it, and shorten the time between a phishing wave
197
+ landing and someone noticing. It is a classroom/final-project model and is not a substitute for a mail gateway.
198
+
199
+ ## Limitations
200
+
201
+ - **Era**: the labelled data is 1998-2008 spam-track mail (TREC, CEAS, SpamAssassin, Enron), plus Nazario phishing (1995-2022) and Nigerian-fraud mail.
202
+ Modern lures (OAuth consent phishing, QR codes, MFA fatigue, SaaS-brand impersonation) are under-represented; expect lower recall on them.
203
+ - **Labels follow corpus provenance**: Phishing = Nazario phishing corpus + Nigerian-fraud (advance-fee) corpus + synthetic BITS-style credential/BEC lures; Spam = SpamAssassin and Enron-Spam spam folders; the TREC-05 / TREC-07 / CEAS-08 positives never separated spam from phishing and were trained as 'unwanted' only (probability mass of phishing + spam), never as either class.
204
+ A phishing-vs-spam confusion therefore partly measures corpus style, and the credential-lure slice remains the closest proxy for phishing-proper recall.
205
+ - **Length confound**: legitimate emails in these corpora average ~2x the tokens of positives. Inputs are truncated to
206
+ 512 tokens, and the error analysis checks whether errors follow length, but a short legitimate note can
207
+ still look suspicious.
208
+ - **English only**; byte-level BPE will tokenize other scripts but the model has not seen them.
209
+ - **Headers are not used** (TREC rewrote them); the model sees subject and body text only. Sender reputation,
210
+ SPF/DKIM and URL reputation are out of scope and should be combined with this score in any real deployment.
211
+ - Attention highlights are a signal of what the model attended to, not an explanation of why it decided.
212
+
213
+ ## How to load
214
+
215
+ `model.py` from this repository is the only code dependency (plain PyTorch).
216
+
217
+ ```python
218
+ import json, torch
219
+ from safetensors.torch import load_file
220
+ from tokenizers import Tokenizer
221
+ from model import EncoderConfig, EncoderForClassification # model.py, shipped with the weights
222
+
223
+ cfg = json.load(open("config.json"))
224
+ model = EncoderForClassification(EncoderConfig.from_dict(cfg["model"])) # num_labels = 3
225
+ model.load_state_dict(load_file("model.safetensors"), strict=True) # bf16 weights cast into the fp32 module
226
+ model.eval()
227
+
228
+ tok = Tokenizer.from_file("tokenizer.json")
229
+ tok.enable_truncation(max_length=cfg["max_seq_len"])
230
+ enc = tok.encode("Your account has been suspended\n\nClick here to verify your identity within 24 hours.")
231
+ ids = torch.tensor([enc.ids])
232
+ mask = torch.ones_like(ids, dtype=torch.bool)
233
+ with torch.inference_mode():
234
+ probs = model(ids, mask).float().softmax(-1)[0]
235
+ p_unwanted = sum(float(probs[i]) for i in cfg["binary_rollup"]["unwanted"]) # P(phishing) + P(spam)
236
+ print("unwanted" if p_unwanted >= 0.5 else "legitimate", {cfg["labels"][str(i)]: round(float(p), 4) for i, p in enumerate(probs)})
237
+ ```
238
+
239
+ On a GPU wrap the forward in `torch.autocast("cuda", dtype=torch.bfloat16)`. Measured single-email latency (batch 1, tokenize + forward + softmax, see the metrics table): GPU bf16 10.08 ms avg on NVIDIA GeForce RTX 3090 Ti, CPU fp32 (16 threads) 458.57 ms avg.
240
+
241
+ ## Files
242
+
243
+ - `model.safetensors` — bf16 weights, metadata `format=pt, dtype=bf16, architecture=EncoderForClassification, config=<json>, labels=<json>`
244
+ - `config.json` — model config, num_labels, labels, binary_rollup, label provenance, max_seq_len, export dtype, source checkpoint, fine-tune init, binary metrics (TREC-06), three-way metrics (test_holdout3), pretraining summary (null for scratch init)
245
+ - `tokenizer.json` — byte-level BPE, NFKC, case preserved, specials `[PAD]=0 [UNK]=1 [CLS]=2 [SEP]=3 [MASK]=4`
246
+ - `README.md` — this card
247
+
248
+ ## Provenance
249
+
250
+ Built for the Skillspire AI & Machine Learning course final project (BITS, 2026). Data decisions, measured VRAM /
251
+ throughput / latency and every challenge hit along the way are recorded in the project's RUN_LOG.md.
config.json ADDED
@@ -0,0 +1,467 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architecture": "EncoderForClassification",
3
+ "model": {
4
+ "vocab_size": 32768,
5
+ "max_seq_len": 512,
6
+ "d_model": 1024,
7
+ "n_layers": 24,
8
+ "n_heads": 16,
9
+ "d_ff": 4096,
10
+ "dropout": 0.1,
11
+ "attention_dropout": 0.1,
12
+ "layer_norm_eps": 1e-05,
13
+ "pad_token_id": 0,
14
+ "tie_mlm_head": true,
15
+ "grad_checkpointing": false,
16
+ "num_labels": 3
17
+ },
18
+ "num_labels": 3,
19
+ "labels": {
20
+ "0": "Legitimate",
21
+ "1": "Phishing",
22
+ "2": "Spam"
23
+ },
24
+ "binary_rollup": {
25
+ "unwanted": [
26
+ 1,
27
+ 2
28
+ ]
29
+ },
30
+ "unwanted_name": "Unwanted (phishing or spam)",
31
+ "label_provenance": "Phishing = Nazario phishing corpus + Nigerian-fraud (advance-fee) corpus + synthetic BITS-style credential/BEC lures; Spam = SpamAssassin and Enron-Spam spam folders; the TREC-05 / TREC-07 / CEAS-08 positives never separated spam from phishing and were trained as 'unwanted' only (probability mass of phishing + spam), never as either class.",
32
+ "max_seq_len": 512,
33
+ "export_dtype": "bf16",
34
+ "source_checkpoint": "/home/nathank/BITS/dev/Phishing Model/checkpoints/finetune/ft-focal/best",
35
+ "exported": "2026-10-12T11:28:39",
36
+ "tokenizer": "tokenizer.json",
37
+ "model_input": "subject + \"\\n\\n\" + body",
38
+ "finetune_checkpoint": {
39
+ "tokenizer": "tokenizer.json",
40
+ "finetune": {
41
+ "run_name": "ft-focal",
42
+ "init": "pretrained:checkpoints/pretrain/large-24L/final",
43
+ "n_layers": 24,
44
+ "freeze_layers": 0,
45
+ "loss": "focal",
46
+ "focal_gamma": 2.0,
47
+ "lr": 3e-05,
48
+ "batch_size": 32,
49
+ "micro_batch": 16,
50
+ "epochs": 2,
51
+ "max_steps": null,
52
+ "eval_every": 1250,
53
+ "log_every": 20,
54
+ "eval_batch_size": 64,
55
+ "warmup_fraction": 0.06,
56
+ "weight_decay": 0.01,
57
+ "betas": [
58
+ 0.9,
59
+ 0.98
60
+ ],
61
+ "eps": 1e-06,
62
+ "grad_clip": 1.0,
63
+ "patience": 2,
64
+ "schedule": "linear_decay",
65
+ "selection_metric": "val_f1_unwanted",
66
+ "pooling": "cls",
67
+ "num_labels": 3,
68
+ "synthetic": "off",
69
+ "limit_rows": null,
70
+ "seed": 42,
71
+ "cpu": false
72
+ },
73
+ "best_epoch": 1,
74
+ "best_step": 8750,
75
+ "num_labels": 3,
76
+ "label_names": [
77
+ "Legitimate",
78
+ "Phishing",
79
+ "Spam"
80
+ ],
81
+ "binary_rollup_name": "Unwanted (phishing or spam)",
82
+ "selection_metric": "val_f1_unwanted",
83
+ "selection_value": 0.9951084002657166,
84
+ "val_metrics": {
85
+ "accuracy": 0.9947003402250719,
86
+ "precision": 0.9954089646007007,
87
+ "recall": 0.9948080173871046,
88
+ "f1": 0.9951084002657166,
89
+ "roc_auc": 0.9998186946414248,
90
+ "pr_auc": 0.9998464119156345,
91
+ "tn": 6964,
92
+ "fp": 38,
93
+ "fn": 43,
94
+ "tp": 8239,
95
+ "threshold": 0.5,
96
+ "f1_optimal_threshold": 0.42074909806251526,
97
+ "f1_at_optimal_threshold": 0.9952995058454863,
98
+ "f1_unwanted": 0.9951084002657166,
99
+ "n_3way": 8105,
100
+ "accuracy_3way": 0.994694632942628,
101
+ "macro_f1_3way": 0.9868751212108838,
102
+ "precision_legitimate": 0.9988533753762362,
103
+ "recall_legitimate": 0.99528706083976,
104
+ "f1_legitimate": 0.9970670291151013,
105
+ "precision_phishing": 0.9853372434017595,
106
+ "recall_phishing": 0.9882352941176471,
107
+ "f1_phishing": 0.986784140969163,
108
+ "precision_spam": 0.9618805590851334,
109
+ "recall_spam": 0.9921363040629095,
110
+ "f1_spam": 0.9767741935483871,
111
+ "cm3_ll": 6969,
112
+ "cm3_lp": 4,
113
+ "cm3_ls": 29,
114
+ "cm3_pl": 3,
115
+ "cm3_pp": 336,
116
+ "cm3_ps": 1,
117
+ "cm3_sl": 5,
118
+ "cm3_sp": 1,
119
+ "cm3_ss": 757,
120
+ "n_unwanted_3way": 1103,
121
+ "pvs_phish_as_phish": 339,
122
+ "pvs_phish_as_spam": 1,
123
+ "pvs_spam_as_phish": 1,
124
+ "pvs_spam_as_spam": 762,
125
+ "val_loss": 0.030296484990695233,
126
+ "n": 15284,
127
+ "eval_seconds": 48.0
128
+ },
129
+ "val_metric_sets": {
130
+ "binary_unwanted": {
131
+ "accuracy": 0.9947003402250719,
132
+ "precision": 0.9954089646007007,
133
+ "recall": 0.9948080173871046,
134
+ "f1": 0.9951084002657166,
135
+ "roc_auc": 0.9998186946414248,
136
+ "pr_auc": 0.9998464119156345,
137
+ "tn": 6964,
138
+ "fp": 38,
139
+ "fn": 43,
140
+ "tp": 8239,
141
+ "threshold": 0.5,
142
+ "f1_optimal_threshold": 0.42074909806251526,
143
+ "f1_at_optimal_threshold": 0.9952995058454863,
144
+ "n": 15284,
145
+ "positive_class": "Unwanted (phishing or spam)"
146
+ },
147
+ "three_way": {
148
+ "n": 8105,
149
+ "accuracy": 0.994694632942628,
150
+ "macro_f1": 0.9868751212108838,
151
+ "per_class": {
152
+ "Legitimate": {
153
+ "precision": 0.9988533753762362,
154
+ "recall": 0.99528706083976,
155
+ "f1": 0.9970670291151013
156
+ },
157
+ "Phishing": {
158
+ "precision": 0.9853372434017595,
159
+ "recall": 0.9882352941176471,
160
+ "f1": 0.986784140969163
161
+ },
162
+ "Spam": {
163
+ "precision": 0.9618805590851334,
164
+ "recall": 0.9921363040629095,
165
+ "f1": 0.9767741935483871
166
+ }
167
+ },
168
+ "confusion": {
169
+ "order": [
170
+ "Legitimate",
171
+ "Phishing",
172
+ "Spam"
173
+ ],
174
+ "rows_true_cols_pred": [
175
+ [
176
+ 6969,
177
+ 4,
178
+ 29
179
+ ],
180
+ [
181
+ 3,
182
+ 336,
183
+ 1
184
+ ],
185
+ [
186
+ 5,
187
+ 1,
188
+ 757
189
+ ]
190
+ ]
191
+ },
192
+ "phish_vs_spam_on_true_unwanted": {
193
+ "n": 1103,
194
+ "order": [
195
+ "Phishing",
196
+ "Spam"
197
+ ],
198
+ "rows_true_cols_pred": [
199
+ [
200
+ 339,
201
+ 1
202
+ ],
203
+ [
204
+ 1,
205
+ 762
206
+ ]
207
+ ]
208
+ }
209
+ }
210
+ },
211
+ "thresholds": {
212
+ "decision": 0.5,
213
+ "decision_applies_to": "P(unwanted) = sum of non-legitimate probabilities",
214
+ "val_f1_optimal": 0.42074909806251526,
215
+ "val_f1_at_optimal": 0.9952995058454863
216
+ },
217
+ "init": "pretrained:/home/nathank/BITS/dev/Phishing Model/checkpoints/pretrain/large-24L/final",
218
+ "params": {
219
+ "total": 337444867,
220
+ "trainable": 337444867,
221
+ "embeddings": 34080768,
222
+ "encoder_layers": 302309376,
223
+ "final_norm": 2048,
224
+ "head": 1052675
225
+ }
226
+ },
227
+ "finetune_init": "pretrained:/home/nathank/BITS/dev/Phishing Model/checkpoints/pretrain/large-24L/final",
228
+ "mlm_pretraining": "summary below",
229
+ "metrics": {
230
+ "split": "test",
231
+ "split_file": "/home/nathank/BITS/dev/Phishing Model/data/processed/test_holdout.parquet",
232
+ "n_rows": 16302,
233
+ "dev_run": false,
234
+ "threshold": 0.5,
235
+ "timestamp": "2026-10-09 10:46",
236
+ "num_labels": 3,
237
+ "positive_class": "Unwanted (phishing or spam)",
238
+ "accuracy": 0.9619065145380935,
239
+ "precision": 0.8794342684906098,
240
+ "recall": 0.9740626605033385,
241
+ "f1": 0.9243328865602534,
242
+ "specificity": 0.9580915538362347,
243
+ "confusion_matrix": {
244
+ "tn": 11888,
245
+ "fp": 520,
246
+ "fn": 101,
247
+ "tp": 3793
248
+ },
249
+ "roc_auc": 0.9946343971962354,
250
+ "pr_auc": 0.9847899987683836,
251
+ "recall_at_fpr_1pct": 0.8954802259887006,
252
+ "recall_at_fpr_0_1pct": 0.6497175141242938,
253
+ "best_f1_threshold": 0.6815109252929688,
254
+ "credential_lure_recall": 0.9958847736625515,
255
+ "credential_lure_precision": 0.9758064516129032,
256
+ "credential_lure_n": 398,
257
+ "predicted_kind_of_flagged_true_positives": {
258
+ "n": 3793,
259
+ "predicted_phishing": 974,
260
+ "predicted_spam": 2819
261
+ },
262
+ "latency_ms": {
263
+ "gpu_bf16_avg": 10.08,
264
+ "gpu_bf16_p50": 8.9,
265
+ "cpu_fp32_avg": 458.57,
266
+ "cpu_fp32_p50": 445.01,
267
+ "cpu_threads": 16,
268
+ "instructor_benchmark_ms": 74.0
269
+ }
270
+ },
271
+ "metrics_source": "/home/nathank/BITS/dev/Phishing Model/reports/metrics.json",
272
+ "metrics_checkpoint_matches": true,
273
+ "metrics_mismatch": null,
274
+ "metrics_threeway": {
275
+ "split": "test3",
276
+ "split_file": "/home/nathank/BITS/dev/Phishing Model/data/processed/test_holdout3.parquet",
277
+ "n_rows": 3220,
278
+ "dev_run": false,
279
+ "threshold": 0.5,
280
+ "timestamp": "2026-10-09 10:46",
281
+ "rows_with_full_label": 3220,
282
+ "label3_counts": {
283
+ "legit": 1999,
284
+ "phish": 521,
285
+ "spam": 700,
286
+ "unwanted": 0
287
+ },
288
+ "decision_rule": "rollup_then_argmax",
289
+ "accuracy": 0.9885093167701864,
290
+ "macro_f1": 0.9867305252015051,
291
+ "per_class": {
292
+ "Legitimate": {
293
+ "precision": 0.9900299102691924,
294
+ "recall": 0.9934967483741871,
295
+ "f1": 0.9917602996254682,
296
+ "support": 1999
297
+ },
298
+ "Phishing": {
299
+ "precision": 0.9961240310077519,
300
+ "recall": 0.9865642994241842,
301
+ "f1": 0.991321118611379,
302
+ "support": 521
303
+ },
304
+ "Spam": {
305
+ "precision": 0.9785100286532952,
306
+ "recall": 0.9757142857142858,
307
+ "f1": 0.977110157367668,
308
+ "support": 700
309
+ }
310
+ },
311
+ "confusion_matrix": {
312
+ "labels": [
313
+ "Legitimate",
314
+ "Phishing",
315
+ "Spam"
316
+ ],
317
+ "matrix": [
318
+ [
319
+ 1986,
320
+ 1,
321
+ 12
322
+ ],
323
+ [
324
+ 4,
325
+ 514,
326
+ 3
327
+ ],
328
+ [
329
+ 16,
330
+ 1,
331
+ 683
332
+ ]
333
+ ],
334
+ "cells": {
335
+ "true_legitimate": {
336
+ "pred_legitimate": 1986,
337
+ "pred_phishing": 1,
338
+ "pred_spam": 12
339
+ },
340
+ "true_phishing": {
341
+ "pred_legitimate": 4,
342
+ "pred_phishing": 514,
343
+ "pred_spam": 3
344
+ },
345
+ "true_spam": {
346
+ "pred_legitimate": 16,
347
+ "pred_phishing": 1,
348
+ "pred_spam": 683
349
+ }
350
+ }
351
+ },
352
+ "kind_accuracy_on_true_unwanted": 0.9959049959049959,
353
+ "phishing_as_spam": 4,
354
+ "spam_as_phishing": 1,
355
+ "n_true_unwanted": 1221,
356
+ "per_source": {
357
+ "Assassin": {
358
+ "n": 236,
359
+ "counts": {
360
+ "Legitimate": 164,
361
+ "Phishing": 0,
362
+ "Spam": 72
363
+ },
364
+ "accuracy": 0.9533898305084746
365
+ },
366
+ "CEAS-08": {
367
+ "n": 736,
368
+ "counts": {
369
+ "Legitimate": 736,
370
+ "Phishing": 0,
371
+ "Spam": 0
372
+ },
373
+ "accuracy": 0.9904891304347826
374
+ },
375
+ "Enron": {
376
+ "n": 628,
377
+ "counts": {
378
+ "Legitimate": 0,
379
+ "Phishing": 0,
380
+ "Spam": 628
381
+ },
382
+ "accuracy": 0.9840764331210191
383
+ },
384
+ "Nazario": {
385
+ "n": 222,
386
+ "counts": {
387
+ "Legitimate": 0,
388
+ "Phishing": 222,
389
+ "Spam": 0
390
+ },
391
+ "accuracy": 0.9864864864864865
392
+ },
393
+ "Nigerian_Fraud": {
394
+ "n": 299,
395
+ "counts": {
396
+ "Legitimate": 0,
397
+ "Phishing": 299,
398
+ "Spam": 0
399
+ },
400
+ "accuracy": 0.9866220735785953
401
+ },
402
+ "TREC-07": {
403
+ "n": 1099,
404
+ "counts": {
405
+ "Legitimate": 1099,
406
+ "Phishing": 0,
407
+ "Spam": 0
408
+ },
409
+ "accuracy": 0.9981801637852593
410
+ }
411
+ },
412
+ "binary_rollup_on_this_split": {
413
+ "recall": 0.9836199836199836,
414
+ "precision": 0.9892915980230642,
415
+ "f1": 0.986447638603696,
416
+ "accuracy": 0.989751552795031
417
+ }
418
+ },
419
+ "metrics_threeway_source": "/home/nathank/BITS/dev/Phishing Model/reports/metrics_test3.json",
420
+ "metrics_threeway_reason": null,
421
+ "metrics_threeway_checkpoint_matches": true,
422
+ "metrics_threeway_mismatch": null,
423
+ "training": {
424
+ "run_name": "large-24L",
425
+ "status": "complete",
426
+ "source_checkpoint": "step-0009552",
427
+ "steps": 9552,
428
+ "total_steps_planned": 9552,
429
+ "steps_per_epoch": 796,
430
+ "epochs_planned": 12,
431
+ "epochs_completed": 12.0,
432
+ "tokens_seen": 1291124736,
433
+ "total_training_seconds": 54185.9,
434
+ "total_training_time": "15h 03m 05s",
435
+ "final_loss": 1.523698394948786,
436
+ "val_loss": 1.9862932266429436,
437
+ "val_masked_acc": 0.6291136684837473,
438
+ "peak_vram_reserved_gb": 17.939,
439
+ "peak_vram_allocated_gb": 17.494,
440
+ "micro_batch": 24,
441
+ "grad_accumulation": 11,
442
+ "effective_batch": 264,
443
+ "precision": "bf16_autocast",
444
+ "optimizer": "AdamW(fused) {\"lr\": 0.00015, \"betas\": [0.9, 0.98], \"eps\": 1e-06, \"weight_decay\": 0.01, \"grad_clip\": 1.0, \"mlm_probability\": 0.15, \"mask_replace_probs\": [0.8, 0.1, 0.1], \"schedule\": \"cosine_to_10pct\", \"effective_batch_sequences\": 256}",
445
+ "schedule": {
446
+ "kind": "cosine",
447
+ "peak_lr": 0.00015,
448
+ "warmup_steps": 573,
449
+ "total_steps": 9552,
450
+ "floor_ratio": 0.1
451
+ },
452
+ "mean_tokens_per_s": 23889,
453
+ "data": {
454
+ "n_windows": 210368,
455
+ "n_val_windows": 1999,
456
+ "tokenizer_sha1": "9f50c5e775089d2ca3efc82e100cd6b3396ed7e7",
457
+ "n_docs": 226273,
458
+ "limit_docs": null,
459
+ "window": "[CLS] + 511 stream tokens"
460
+ },
461
+ "weights_dtype": "float32",
462
+ "exported_at": "2026-10-09T03:31:14"
463
+ },
464
+ "training_source": "/home/nathank/BITS/dev/Phishing Model/checkpoints/pretrain/large-24L/final/config.json",
465
+ "from_scratch": true,
466
+ "external_pretrained_weights_used": "none"
467
+ }
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9f23caf1836250c83ad5a29a4fd5931bcd33a3dae4877120a6254a1e465017ff
3
+ size 674920638
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff