SavienWalker commited on
Commit
917841b
·
verified ·
1 Parent(s): 9e8d5fd

Bulwark scam detector v1

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ tokenizer.json filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ base_model: Horizon-Labs/phishing-scam-detector-base
4
+ language:
5
+ - tr
6
+ - en
7
+ - multilingual
8
+ pipeline_tag: text-classification
9
+ tags:
10
+ - scam-detection
11
+ - phishing
12
+ - smishing
13
+ - fraud
14
+ - spam
15
+ - turkish
16
+ - modernbert
17
+ - mmbert
18
+ datasets:
19
+ - SavienWalker/bulwark-bench-tr
20
+ metrics:
21
+ - accuracy
22
+ ---
23
+
24
+ # Bulwark Scam Detector
25
+
26
+ A multilingual scam / spam / legitimate message classifier, **specialised for Turkish**.
27
+ Fine-tuned from [Horizon-Labs/phishing-scam-detector-base](https://huggingface.co/Horizon-Labs/phishing-scam-detector-base)
28
+ (mmBERT-base, 307M parameters) to fix one specific weakness: **false alarms on real Turkish institutional
29
+ messages** (bank, cargo, e-Devlet, operator, OTP notifications).
30
+
31
+ > 🇹🇷 **Türkçe özet:** Bulwark; SMS, e-posta ve kısa mesajları *dolandırıcılık / spam / temiz* olarak ayırır.
32
+ > Gerçek Türk kurum mesajlarında mevcut modelin temiz mesajların yarısına verdiği yanlış alarmı %14'e indirir,
33
+ > istenmeyen mesajları yakalama oranı ise %98'de kalır. Diğer dillerdeki temel yeteneği korunmuştur.
34
+
35
+ ## Headline result
36
+
37
+ On a held-out half of **real Turkish SMS from a personal inbox** (opened once, after all model choices were made):
38
+
39
+ | | Base model (threshold 0.5) | **Bulwark (threshold 0.6)** |
40
+ |---|---|---|
41
+ | False alarm on legitimate messages ↓ | 53% (54/102) | **14% (14/102)** |
42
+ | Legitimate message labelled *fraud* ↓ | 26% | **9%** |
43
+ | Unwanted (spam + fraud) caught ↑ | 100% (52/52) | 98% (51/52) |
44
+ | 3-class accuracy ↑ | 64% | **87%** |
45
+
46
+ The base model flags roughly every second real bank / cargo / e-Devlet notification as unwanted because it
47
+ was trained on synthetic scams plus generic web text, and has rarely seen what a *real* institutional message
48
+ looks like. Bulwark keeps the base model's recall while learning the difference.
49
+
50
+ ## Labels and usage
51
+
52
+ | id | label | meaning |
53
+ |---|---|---|
54
+ | 0 | `legitimate` | genuine personal or institutional message |
55
+ | 1 | `spam` | advertising from a real business |
56
+ | 2 | `fraud` | tries to steal money, card data, passwords, codes, identity or trust |
57
+
58
+ **Recommended decision rule:** treat a message as unwanted when `p(spam) + p(fraud) > 0.6`
59
+ (chosen on the development split; stored in `bulwark_config.json`).
60
+
61
+ ```python
62
+ import torch
63
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification
64
+
65
+ model_id = "SavienWalker/bulwark-scam-detector"
66
+ tok = AutoTokenizer.from_pretrained(model_id)
67
+ model = AutoModelForSequenceClassification.from_pretrained(model_id).eval()
68
+
69
+ text = "PTT: Gonderiniz adres eksikligi nedeniyle bekletilmektedir. 0,99 TL odeme yapin: ptt-gonderitakip.com"
70
+ with torch.no_grad():
71
+ p = torch.softmax(model(**tok(text, return_tensors="pt", truncation=True, max_length=256)).logits, -1)[0]
72
+ probs = {model.config.id2label[i]: round(v.item(), 3) for i, v in enumerate(p)}
73
+ unwanted = probs["spam"] + probs["fraud"] > 0.6
74
+ print(probs, "unwanted" if unwanted else "ok")
75
+ ```
76
+
77
+ ## Evaluation
78
+
79
+ All numbers compare the base model and Bulwark on the same messages. Sets are described in
80
+ [bulwark-bench-tr](https://huggingface.co/datasets/SavienWalker/bulwark-bench-tr).
81
+ Messages marked `ambiguous` (mostly illegal-betting ads) are excluded from metrics.
82
+
83
+ | Set | Type | Metric | Base | Bulwark |
84
+ |---|---|---|---|---|
85
+ | Real SMS, test half (212) | real, held out | false alarm ↓ | 53% | **14%** |
86
+ | | | unwanted caught ↑ | 100% | 98% |
87
+ | Real e-mail (40) | real, **development** set | false alarm ↓ | 33% | **19%** |
88
+ | Hard set v2 (60) | synthetic, human-reviewed | fraud caught ↑ | 82% | **95%** |
89
+ | | | false alarm ↓ | 25% | **0%** |
90
+ | | | contrast pairs fully correct ↑ | 9/13 | **12/13** |
91
+ | Hard set v1 (42) | synthetic, human-reviewed | contrast pairs fully correct ↑ | 5/7 | **7/7** |
92
+ | UCI SMS Spam, held-out half (2,787) | real, English | unwanted caught ↑ | 87% | 96% |
93
+ | Multilingual probe (54; de, fr, es, pt, ru, ar) | synthetic, easy | 3-class accuracy | 54/54 | 54/54 |
94
+
95
+ *Contrast pairs* are a fake message and the genuine message it imitates (e.g. fake vs. real HGS toll notice);
96
+ a pair counts only if **both** are classified correctly — the clearest test of whether a model reads the
97
+ signals (domain, request, tone) rather than the topic.
98
+
99
+ How to read these numbers honestly:
100
+ - **The only fully clean real-world number is the SMS test half.** The e-mail set was used for error analysis,
101
+ so it is reported as development data.
102
+ - **Hard sets and training templates were written by the same author (an LLM, Claude).** A style overlap
103
+ could inflate those scores. They are reported separately and labelled synthetic.
104
+ - **The SMS test half contains no non-ambiguous real fraud** — real fraud in a personal inbox is almost all
105
+ illegal betting. Fraud recall is therefore measured on the (synthetic) hard sets only.
106
+ - **English:** half of the UCI SMS Spam Collection was used as replay data during training, so the English
107
+ gain is in-distribution; the honest claim is "no regression", not "better than the base model".
108
+ - **Other languages:** a small, easy probe shows no regression. Hard cases outside Turkish were not measured.
109
+
110
+ ## Training
111
+
112
+ - **Base:** Horizon-Labs/phishing-scam-detector-base (Apache-2.0), itself built on jhu-clsp/mmBERT-base (MIT).
113
+ - **Data (~9k messages):**
114
+ - ~8k Turkish synthetic messages from 190 hand-written templates with slot filling (institution, amount,
115
+ date, code, look-alike domain). Covers real institutional notifications, real-business ads, link-based
116
+ phishing, and link-free social engineering. Patterns were drawn from public warnings (KGM, SGK, PTT, EGM)
117
+ and public complaint narratives; no complaint text was copied.
118
+ - ~1k English messages from the UCI SMS Spam Collection (CC BY 4.0) as replay against forgetting; spam vs.
119
+ fraud for these was taken from the base model's own prediction (distillation).
120
+ - **Leakage control:** every training message was compared with every evaluation message (token-set
121
+ Jaccard); anything ≥ 0.5 was removed.
122
+ - **Personalisation balancing:** scammers now use leaked personal data (name, card last 4 digits, plate,
123
+ old address). These details were injected into legitimate *and* fraudulent training messages at equal
124
+ rates, so that "the message knows my name" is **not** learned as a sign of legitimacy.
125
+ - **Recipe:** word embeddings frozen (110M of 307M parameters trained), bf16, AdamW lr 2e-5, 2 epochs,
126
+ max length 256, class-weighted loss, fraud examples down-sampled to the legitimate count. Runs on a single
127
+ 6 GB laptop GPU (RTX 4050) in about 6 minutes.
128
+ - **Model selection:** on a development half of the real SMS, split by sender so the same institution never
129
+ appears in both halves.
130
+
131
+ ## Scope and limitations
132
+
133
+ - **One message at a time.** Many real scams unfold over a phone call, a long WhatsApp conversation, a fake
134
+ website or an ad. Bulwark sees only the text it is given; the first message of a slow scam often looks
135
+ harmless.
136
+ - **A message containing your real personal details is not proof that it is genuine.** Leaked databases make
137
+ this trivial for scammers.
138
+ - Illegal-betting ads are labelled `fraud` but treated as ambiguous in evaluation.
139
+ - Small real evaluation sets (hundreds, not thousands). Treat the numbers as indicative.
140
+ - Not a replacement for a bank's or carrier's own fraud systems. Do not use it to make decisions about
141
+ people.
142
+
143
+ ## License and attribution
144
+
145
+ Apache-2.0. Derived from [Horizon-Labs/phishing-scam-detector-base](https://huggingface.co/Horizon-Labs/phishing-scam-detector-base)
146
+ (Apache-2.0) and [jhu-clsp/mmBERT-base](https://huggingface.co/jhu-clsp/mmBERT-base) (MIT).
147
+ English replay data: Almeida, T.A. & Gómez Hidalgo, J.M., *SMS Spam Collection* (UCI, CC BY 4.0).
bulwark_config.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "recommended_threshold": 0.6,
3
+ "threshold_applies_to": "p_spam + p_fraud",
4
+ "base_model": "Horizon-Labs/phishing-scam-detector-base",
5
+ "checkpoint": "checkpoints/v5"
6
+ }
config.json ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "ModernBertForSequenceClassification"
4
+ ],
5
+ "attention_bias": false,
6
+ "attention_dropout": 0.0,
7
+ "bos_token_id": 2,
8
+ "classifier_activation": "gelu",
9
+ "classifier_bias": false,
10
+ "classifier_dropout": 0.0,
11
+ "classifier_pooling": "mean",
12
+ "cls_token_id": 1,
13
+ "decoder_bias": true,
14
+ "deterministic_flash_attn": false,
15
+ "dtype": "float32",
16
+ "embedding_dropout": 0.0,
17
+ "eos_token_id": 1,
18
+ "global_attn_every_n_layers": 3,
19
+ "gradient_checkpointing": false,
20
+ "hidden_activation": "gelu",
21
+ "hidden_size": 768,
22
+ "id2label": {
23
+ "0": "legitimate",
24
+ "1": "spam",
25
+ "2": "fraud"
26
+ },
27
+ "initializer_cutoff_factor": 2.0,
28
+ "initializer_range": 0.02,
29
+ "intermediate_size": 1152,
30
+ "label2id": {
31
+ "fraud": 2,
32
+ "legitimate": 0,
33
+ "spam": 1
34
+ },
35
+ "layer_norm_eps": 1e-05,
36
+ "layer_types": [
37
+ "full_attention",
38
+ "sliding_attention",
39
+ "sliding_attention",
40
+ "full_attention",
41
+ "sliding_attention",
42
+ "sliding_attention",
43
+ "full_attention",
44
+ "sliding_attention",
45
+ "sliding_attention",
46
+ "full_attention",
47
+ "sliding_attention",
48
+ "sliding_attention",
49
+ "full_attention",
50
+ "sliding_attention",
51
+ "sliding_attention",
52
+ "full_attention",
53
+ "sliding_attention",
54
+ "sliding_attention",
55
+ "full_attention",
56
+ "sliding_attention",
57
+ "sliding_attention",
58
+ "full_attention"
59
+ ],
60
+ "local_attention": 128,
61
+ "mask_token_id": 4,
62
+ "max_position_embeddings": 8192,
63
+ "mlp_bias": false,
64
+ "mlp_dropout": 0.0,
65
+ "model_type": "modernbert",
66
+ "norm_bias": false,
67
+ "norm_eps": 1e-05,
68
+ "num_attention_heads": 12,
69
+ "num_hidden_layers": 22,
70
+ "pad_token_id": 0,
71
+ "position_embedding_type": "sans_pos",
72
+ "rope_parameters": {
73
+ "full_attention": {
74
+ "rope_theta": 160000,
75
+ "rope_type": "default"
76
+ },
77
+ "sliding_attention": {
78
+ "rope_theta": 160000,
79
+ "rope_type": "default"
80
+ }
81
+ },
82
+ "sep_token_id": 1,
83
+ "sparse_pred_ignore_index": -100,
84
+ "sparse_prediction": false,
85
+ "tie_word_embeddings": true,
86
+ "transformers_version": "5.21.0",
87
+ "vocab_size": 256000
88
+ }
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:60bf6102bb35c62941b01eac2bc4d367d5395a81e6fdc13c8bfb793d92197351
3
+ size 1230144500
tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0a5069feab370ece9f452dcda41c0ba2c216eebf6cb49b06893698f53cdc2b0b
3
+ size 34363441
tokenizer_config.json ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "backend": "tokenizers",
3
+ "bos_token": "<bos>",
4
+ "clean_up_tokenization_spaces": false,
5
+ "cls_token": "<bos>",
6
+ "eos_token": "<eos>",
7
+ "extra_special_tokens": [
8
+ "<start_of_turn>",
9
+ "<end_of_turn>"
10
+ ],
11
+ "is_local": false,
12
+ "local_files_only": false,
13
+ "mask_token": "<mask>",
14
+ "max_length": 384,
15
+ "model_input_names": [
16
+ "input_ids",
17
+ "attention_mask"
18
+ ],
19
+ "model_max_length": 8192,
20
+ "pad_token": "<pad>",
21
+ "padding_side": "right",
22
+ "sep_token": "<eos>",
23
+ "spaces_between_special_tokens": false,
24
+ "stride": 0,
25
+ "tokenizer_class": "TokenizersBackend",
26
+ "truncation_side": "right",
27
+ "truncation_strategy": "longest_first",
28
+ "unk_token": "<unk>"
29
+ }