Publish phishing v2.1
Browse files- v2.1/config.json +35 -0
- v2.1/eval_report.json +36 -0
- v2.1/model.safetensors +3 -0
- v2.1/model_card.md +153 -0
- v2.1/threshold.json +3 -0
- v2.1/tokenizer.json +0 -0
- v2.1/tokenizer_config.json +14 -0
- v2.1/training_config.yaml +76 -0
v2.1/config.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"activation": "gelu",
|
| 3 |
+
"architectures": [
|
| 4 |
+
"DistilBertForSequenceClassification"
|
| 5 |
+
],
|
| 6 |
+
"attention_dropout": 0.1,
|
| 7 |
+
"bos_token_id": null,
|
| 8 |
+
"dim": 768,
|
| 9 |
+
"dropout": 0.1,
|
| 10 |
+
"dtype": "float32",
|
| 11 |
+
"eos_token_id": null,
|
| 12 |
+
"hidden_dim": 3072,
|
| 13 |
+
"id2label": {
|
| 14 |
+
"0": "legit",
|
| 15 |
+
"1": "phishing"
|
| 16 |
+
},
|
| 17 |
+
"initializer_range": 0.02,
|
| 18 |
+
"label2id": {
|
| 19 |
+
"legit": 0,
|
| 20 |
+
"phishing": 1
|
| 21 |
+
},
|
| 22 |
+
"max_position_embeddings": 512,
|
| 23 |
+
"model_type": "distilbert",
|
| 24 |
+
"n_heads": 12,
|
| 25 |
+
"n_layers": 6,
|
| 26 |
+
"pad_token_id": 0,
|
| 27 |
+
"qa_dropout": 0.1,
|
| 28 |
+
"seq_classif_dropout": 0.2,
|
| 29 |
+
"sinusoidal_pos_embds": false,
|
| 30 |
+
"tie_weights_": true,
|
| 31 |
+
"tie_word_embeddings": true,
|
| 32 |
+
"transformers_version": "5.0.0",
|
| 33 |
+
"use_cache": false,
|
| 34 |
+
"vocab_size": 30522
|
| 35 |
+
}
|
v2.1/eval_report.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"checkpoint": "checkpoints/phishing_v2",
|
| 3 |
+
"test_split": "data/processed/phishing/test",
|
| 4 |
+
"metrics": {
|
| 5 |
+
"recall_phishing": 0.9903169014084507,
|
| 6 |
+
"precision_phishing": 0.9774109470026064,
|
| 7 |
+
"f1_phishing": 0.9838216003498033,
|
| 8 |
+
"confusion_matrix": [
|
| 9 |
+
[
|
| 10 |
+
1339,
|
| 11 |
+
26
|
| 12 |
+
],
|
| 13 |
+
[
|
| 14 |
+
11,
|
| 15 |
+
1125
|
| 16 |
+
]
|
| 17 |
+
],
|
| 18 |
+
"threshold": 0.06,
|
| 19 |
+
"n_test": 2501,
|
| 20 |
+
"n_phishing_test": 1136,
|
| 21 |
+
"n_legit_test": 1365,
|
| 22 |
+
"p95_ms": 242.0971423996889,
|
| 23 |
+
"latency": {
|
| 24 |
+
"mean_ms": 100.66626771500523,
|
| 25 |
+
"p50_ms": 59.67643049984872,
|
| 26 |
+
"p95_ms": 242.0971423996889,
|
| 27 |
+
"n": 200.0
|
| 28 |
+
}
|
| 29 |
+
},
|
| 30 |
+
"gates": {
|
| 31 |
+
"recall": true,
|
| 32 |
+
"precision": true,
|
| 33 |
+
"latency_p95": true
|
| 34 |
+
},
|
| 35 |
+
"all_passed": true
|
| 36 |
+
}
|
v2.1/model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:7692afc2685e34b5736a8af98aa889929c6c4a7ce2850adfce009d32e82ecfc5
|
| 3 |
+
size 267832560
|
v2.1/model_card.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
language:
|
| 3 |
+
- en
|
| 4 |
+
license: other
|
| 5 |
+
tags:
|
| 6 |
+
- text-classification
|
| 7 |
+
- phishing-detection
|
| 8 |
+
- distilbert
|
| 9 |
+
base_model: distilbert-base-uncased
|
| 10 |
+
pipeline_tag: text-classification
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
# Model Card — PriorMail Phishing Detector v2.1
|
| 14 |
+
|
| 15 |
+
**Model ID:** `phishing/v2.1`
|
| 16 |
+
**Owner:** Faiz (PJK-GM095)
|
| 17 |
+
**Date:** 2026-06-19
|
| 18 |
+
**Git SHA:** `7cdeae7` (branch `feat/phishing-distilbert-v2`)
|
| 19 |
+
**Status:** In-distribution §8 gates **PASS**; real-world acceptance gate **FAILS on false-negative rate** (documented known-limitation below). Ship decision is deliberate, scoped, and pending sign-off from Insan + Syafiq — this is **not** an automatic promotion.
|
| 20 |
+
|
| 21 |
+
---
|
| 22 |
+
|
| 23 |
+
## What This Model Does
|
| 24 |
+
|
| 25 |
+
Binary email classifier: **legit (0)** vs **phishing (1)**.
|
| 26 |
+
|
| 27 |
+
Used by `prior-mail-backend` to flag potentially malicious emails. Intended as **one layer** of defense — see Known Limitations for why it should not be the sole line.
|
| 28 |
+
|
| 29 |
+
---
|
| 30 |
+
|
| 31 |
+
## Base Model
|
| 32 |
+
|
| 33 |
+
`distilbert-base-uncased` (~67M params).
|
| 34 |
+
|
| 35 |
+
Replaces v1.0's `bert-base-multilingual-cased`: the training data is English-only, and DistilBERT is ~3× smaller and faster on CPU (the backend runs CPU on Render), easing the 500 ms latency gate and the checkpoint-size constraint.
|
| 36 |
+
|
| 37 |
+
---
|
| 38 |
+
|
| 39 |
+
## Training Data
|
| 40 |
+
|
| 41 |
+
| Source | Role | Notes |
|
| 42 |
+
|---|---|---|
|
| 43 |
+
| `ealvaradob/phishing-dataset` (`texts`) | Phishing + benign legit | ~20,137 rows; ~62% benign |
|
| 44 |
+
| `insanar/prior-mail-priority` (`v4`) | Legit negatives | Product-distribution legit mail (transactional/banking) |
|
| 45 |
+
| Synthetic augmentation (`src/data/augment.py`) | Both classes | 600 rows/class, header-complete, seed 42 |
|
| 46 |
+
|
| 47 |
+
Enron (`emails.csv`) is **off** in v2 (it made v1 learn "non-Enron-style ⇒ phishing" → over-flagging). Legit pool downsampled to ~1.2× phishing; deduplicated on the first 200 chars of body before splitting to prevent leakage.
|
| 48 |
+
|
| 49 |
+
**After dedup:** ~16,669 rows (7,572 phishing / 9,097 legit)
|
| 50 |
+
**Split (stratified, seed 42):** train 11,667 / val 2,501 / test 2,501
|
| 51 |
+
|
| 52 |
+
The synthetic augmentation covers tactics the public corpora miss (BEC/CEO-fraud, fake invoice, brand- and IT-helpdesk credential harvest, delivery). It is **header-complete on both classes** so header *presence* cannot become a class proxy (the v1 leak).
|
| 53 |
+
|
| 54 |
+
---
|
| 55 |
+
|
| 56 |
+
## Input / Output Format
|
| 57 |
+
|
| 58 |
+
**Input** (`src/data/preprocess.build_phishing_input()`):
|
| 59 |
+
```
|
| 60 |
+
{sender display-name + domain} [SEP] {subject} [SEP] {body}
|
| 61 |
+
```
|
| 62 |
+
Text is cleaned with `keep_domains=True`: URL **host** and email **domain** are kept as signal (lookalike domains, odd TLDs) while the PII-bearing URL path/query and email local-part are masked. The `[SEP]` scaffolding is always present (header-less rows carry empty fields) — this is what makes the sender/subject reinstatement leak-safe.
|
| 63 |
+
|
| 64 |
+
**Output:**
|
| 65 |
+
- Softmax probability for class 1 (phishing), range [0, 1].
|
| 66 |
+
- Label `phishing` if probability ≥ threshold, else `legit`.
|
| 67 |
+
- **Threshold: 0.06**, stored in `threshold.json`. Selected on the validation set to maximize F-beta (β=2, recall-weighted — false negatives are the worst case) subject to precision ≥ 0.80; median of the optimal plateau.
|
| 68 |
+
|
| 69 |
+
---
|
| 70 |
+
|
| 71 |
+
## Training Config
|
| 72 |
+
|
| 73 |
+
See [`configs/phishing_v2.yaml`](../configs/phishing_v2.yaml). Key hyperparameters:
|
| 74 |
+
|
| 75 |
+
| Parameter | Value |
|
| 76 |
+
|---|---|
|
| 77 |
+
| Learning rate | 2e-5 (AdamW) |
|
| 78 |
+
| Batch size | 16 per device |
|
| 79 |
+
| Max sequence length | 512 |
|
| 80 |
+
| Epochs | 4 (early stopping, patience 2) |
|
| 81 |
+
| Early stopping metric | `f1_phishing` |
|
| 82 |
+
| Loss | Weighted cross-entropy, `phishing_class_multiplier=1.0` (threshold drives recall, not the loss) |
|
| 83 |
+
| Mixed precision | bf16 |
|
| 84 |
+
| Seed | 42 |
|
| 85 |
+
|
| 86 |
+
---
|
| 87 |
+
|
| 88 |
+
## Evaluation Results
|
| 89 |
+
|
| 90 |
+
### In-distribution test set (n=2,501) — §8 corpus gates
|
| 91 |
+
|
| 92 |
+
| Metric | Result | Gate | Status |
|
| 93 |
+
|---|---|---|---|
|
| 94 |
+
| Recall | **0.990** | ≥ 0.95 | PASS ✅ |
|
| 95 |
+
| Precision | **0.977** | ≥ 0.80 | PASS ✅ |
|
| 96 |
+
| Latency p95 (CPU) | **242 ms** | < 500 ms | PASS ✅ |
|
| 97 |
+
| F1 | 0.984 | — | — |
|
| 98 |
+
|
| 99 |
+
Confusion matrix (test):
|
| 100 |
+
|
| 101 |
+
| | Predicted Legit | Predicted Phishing |
|
| 102 |
+
|---|---|---|
|
| 103 |
+
| **Actual Legit** | 1,339 (TN) | 26 (FP) |
|
| 104 |
+
| **Actual Phishing** | 11 (FN) | 1,125 (TP) |
|
| 105 |
+
|
| 106 |
+
> The test split is drawn from the **same corpora** as training, so it measures in-distribution fit, not real-world behaviour (v1.0 passed these yet over-flagged real mail). The acceptance set below is the real-world judge.
|
| 107 |
+
|
| 108 |
+
### Real-world acceptance set (36 hand-curated `.eml`, 18 legit / 18 phishing)
|
| 109 |
+
|
| 110 |
+
| Metric | Result | Gate | Status |
|
| 111 |
+
|---|---|---|---|
|
| 112 |
+
| False-positive rate | **0.00** (0/18) | ≤ 0.20 | PASS ✅ |
|
| 113 |
+
| False-negative rate | **0.33** (6/18) | ≤ 0.05 | **FAIL ❌** |
|
| 114 |
+
| Accuracy | 0.833 | — | — |
|
| 115 |
+
|
| 116 |
+
- **Zero false positives** — including deliberately "scary but legit" mail (a real new-sign-in security alert, bank statement, password-reset notice, CI/Slack/GitHub notifications). The v1/v2 over-flagging problem is resolved.
|
| 117 |
+
- **Caught (12/18):** credential harvest (PayPal, IT-helpdesk), BEC/CEO-fraud, fake invoice, bank/card suspension, lottery, package delivery, tax refund, crypto wallet, job-offer scam, voicemail lure.
|
| 118 |
+
- **Missed (6/18), all scored ≈0.00:** phishing that **mimics a routine SaaS/workflow notification** — DocuSign signature request, MFA-reset notice, payroll direct-deposit redirect, shared-document lure, subscription-renewal scare, and an Office 365 lookalike-domain login.
|
| 119 |
+
|
| 120 |
+
---
|
| 121 |
+
|
| 122 |
+
## Known Limitations
|
| 123 |
+
|
| 124 |
+
- **Workflow-mimicking phishing (primary gap).** The model learned "phishing = urgent threat + suspicious URL." Phishing that impersonates a *calm, routine* notification (DocuSign/MFA/payroll/shared-document/subscription) whose only tell is a lookalike sender domain is scored confidently legit. This is the **ceiling of synthetic-template augmentation** — the model reliably catches tactics it was trained on and misses novel ones. Closing it requires **diverse real phishing data** (e.g. Nazario / PhishTank email corpora), not more synthetic templates, and/or a **header/auth-signal layer (SPF/DKIM/DMARC, display-name↔domain mismatch) at the backend** — which is the right home for sender-spoofing detection.
|
| 125 |
+
- **Acceptance FN gate fails (0.33 > 0.05).** Per CLAUDE.md §8/§12 this is *not* a clean promotion. It is shipped as a scoped candidate with this limitation documented; do **not** treat the model as a sole line of defense.
|
| 126 |
+
- **English only.** Indonesian-language phishing is not represented and not validated.
|
| 127 |
+
- **Adversarial robustness** (obfuscated URLs, Unicode/homoglyph tricks beyond simple lookalikes) is not systematically evaluated.
|
| 128 |
+
|
| 129 |
+
---
|
| 130 |
+
|
| 131 |
+
## Artifacts
|
| 132 |
+
|
| 133 |
+
| File | Description |
|
| 134 |
+
|---|---|
|
| 135 |
+
| `model.safetensors` | Fine-tuned weights (~256 MB) |
|
| 136 |
+
| `config.json` | HuggingFace model config (`id2label`: 0=legit, 1=phishing) |
|
| 137 |
+
| `tokenizer.json`, `tokenizer_config.json`, `vocab.txt` | Tokenizer (distilbert-base-uncased) |
|
| 138 |
+
| `threshold.json` | `{"threshold": 0.06}` |
|
| 139 |
+
| `training_config.yaml` | Full training config + git SHA + timestamp |
|
| 140 |
+
| `eval_report.json` | In-distribution test metrics + confusion matrix + latency |
|
| 141 |
+
| `acceptance_report.json` | 36-file real-world acceptance results (per-file table) |
|
| 142 |
+
| `val_metrics.json` | Validation metrics at best epoch |
|
| 143 |
+
|
| 144 |
+
---
|
| 145 |
+
|
| 146 |
+
## Changelog
|
| 147 |
+
|
| 148 |
+
| Version | Date | Notes |
|
| 149 |
+
|---|---|---|
|
| 150 |
+
| v1.0 | 2026-06-13 | Initial release. mBERT. Passed corpus gates; over-flagged real mail at inference. |
|
| 151 |
+
| v2.1 | 2026-06-19 | DistilBERT rework. Domain-aware cleaning (A1), header-complete synthetic augmentation + sender/subject input (B1/B2), FN-averse threshold selection, real-world `.eml` acceptance gate. Corpus gates pass, FP eliminated (0%); documented FN gap on workflow-mimicking phishing. |
|
| 152 |
+
|
| 153 |
+
> Reproduce: `make data-phishing && make train-phishing config=configs/phishing_v2.yaml && make eval-phishing config=configs/phishing_v2.yaml` then `python -m src.eval.acceptance_phishing --config configs/phishing_v2.yaml` (git `7cdeae7`, seed 42).
|
v2.1/threshold.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"threshold": 0.06
|
| 3 |
+
}
|
v2.1/tokenizer.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
v2.1/tokenizer_config.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"backend": "tokenizers",
|
| 3 |
+
"cls_token": "[CLS]",
|
| 4 |
+
"do_lower_case": true,
|
| 5 |
+
"is_local": false,
|
| 6 |
+
"mask_token": "[MASK]",
|
| 7 |
+
"model_max_length": 512,
|
| 8 |
+
"pad_token": "[PAD]",
|
| 9 |
+
"sep_token": "[SEP]",
|
| 10 |
+
"strip_accents": null,
|
| 11 |
+
"tokenize_chinese_chars": true,
|
| 12 |
+
"tokenizer_class": "BertTokenizer",
|
| 13 |
+
"unk_token": "[UNK]"
|
| 14 |
+
}
|
v2.1/training_config.yaml
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Phishing detector — v2 config (DistilBERT rework).
|
| 2 |
+
#
|
| 3 |
+
# v2.1 update (2026-06-19): the v2 run passed the §8 in-distribution gates but
|
| 4 |
+
# missed half the real .eml acceptance phishing (FN rate 0.50). Fixes landed in
|
| 5 |
+
# the data/preprocess layer (no hyperparameter change needed):
|
| 6 |
+
# A1 — clean_text(keep_domains=True): URL host + email domain kept as signal
|
| 7 |
+
# (lookalike domains / odd TLDs) instead of an opaque [URL].
|
| 8 |
+
# B1 — synthetic augmentation: prepare adds 600 rows/class (header-complete,
|
| 9 |
+
# both classes) covering BEC / fake-invoice / brand- AND IT-helpdesk
|
| 10 |
+
# credential tactics the corpora miss. Tune via --augmentation-size.
|
| 11 |
+
# B2 — input is now {sender_domain} [SEP] {subject} [SEP] {body} (was body-only);
|
| 12 |
+
# leak-safe because the [SEP] scaffolding is always present and the
|
| 13 |
+
# augmentation gives BOTH classes realistic headers.
|
| 14 |
+
# Real-world promotion is now also gated on the .eml acceptance set
|
| 15 |
+
# (src/eval/acceptance_phishing.py), not just the in-distribution §8 metrics.
|
| 16 |
+
#
|
| 17 |
+
# Why v2 (see feat/phishing-distilbert-v2 analysis, 2026-06-18):
|
| 18 |
+
# v1.0 passed test gates (recall 0.970 / precision 0.928) but over-flagged on
|
| 19 |
+
# real .eml at inference. Root causes addressed here:
|
| 20 |
+
# #1 Base model : distilbert-base-uncased — on-spec (ML_PIPELINE.md §3, mBERT
|
| 21 |
+
# dropped: data is English-only; DistilBERT is faster on CPU
|
| 22 |
+
# and ~3× smaller, easing the 500 ms gate + checkpoint size).
|
| 23 |
+
# #4 Class weight : multiplier dropped 3.0 -> 1.0. v1 stacked balanced (inverse-
|
| 24 |
+
# frequency) AND a 3× multiplier => ~6× gradient pressure on
|
| 25 |
+
# phishing, saturating its probability for almost any input.
|
| 26 |
+
# Pure inverse-frequency now; the THRESHOLD does the recall
|
| 27 |
+
# work, not the loss.
|
| 28 |
+
# #5 Early stop : monitor f1_phishing, not recall_phishing. Monitoring raw
|
| 29 |
+
# recall picked the most trigger-happy checkpoint; F1 balances
|
| 30 |
+
# recall against precision. Recall is still guaranteed by the
|
| 31 |
+
# eval-time threshold + gate.
|
| 32 |
+
# (#2 legit diversity + #3 body-only input are in loaders.py / preprocess.py.)
|
| 33 |
+
#
|
| 34 |
+
# Run via:
|
| 35 |
+
# make data-phishing
|
| 36 |
+
# make train-phishing config=configs/phishing_v2.yaml
|
| 37 |
+
|
| 38 |
+
model_type: phishing
|
| 39 |
+
model_name: distilbert-base-uncased # §3 decision: English-only data, on-spec
|
| 40 |
+
|
| 41 |
+
dataset: ealvaradob/phishing-dataset # primary phishing training data (HF)
|
| 42 |
+
legit_source: emails.csv # Enron corpus — negative class source
|
| 43 |
+
|
| 44 |
+
num_labels: 2 # legit (0) | phishing (1) — matches PHISHING_LABELS in constants.py
|
| 45 |
+
|
| 46 |
+
hyperparameters:
|
| 47 |
+
learning_rate: 2.0e-5 # AdamW (mirrors priority protocol, ML_PIPELINE.md §3)
|
| 48 |
+
weight_decay: 0.01
|
| 49 |
+
batch_size: 16
|
| 50 |
+
gradient_accumulation_steps: 1
|
| 51 |
+
num_epochs: 4 # 3–5 with early stopping
|
| 52 |
+
warmup_ratio: 0.10
|
| 53 |
+
lr_scheduler_type: linear
|
| 54 |
+
max_seq_length: 512
|
| 55 |
+
# Early stopping on F1 — balances recall vs precision (fix #5). Recall is still
|
| 56 |
+
# enforced by the post-training threshold + the >=0.95 eval gate.
|
| 57 |
+
early_stopping_metric: f1_phishing
|
| 58 |
+
early_stopping_patience: 2
|
| 59 |
+
class_weights: balanced # inverse-frequency base weights from train dist
|
| 60 |
+
phishing_class_multiplier: 1.0 # fix #4: no extra stacking — let threshold drive recall
|
| 61 |
+
mixed_precision: bf16 # falls back to fp16 / none as needed
|
| 62 |
+
|
| 63 |
+
seed: 42
|
| 64 |
+
|
| 65 |
+
output_dir: checkpoints/phishing_v2
|
| 66 |
+
|
| 67 |
+
wandb:
|
| 68 |
+
project: priormail
|
| 69 |
+
tags:
|
| 70 |
+
- model:phishing
|
| 71 |
+
- stage:exp
|
| 72 |
+
|
| 73 |
+
# --- run provenance (auto-recorded) ---
|
| 74 |
+
_git_sha: 7cdeae7
|
| 75 |
+
_git_dirty: False
|
| 76 |
+
_trained_at: 2026-06-19T03:00:44
|