Instructions to use thealper2/SpamShield-Qwen3.5-0.8B with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use thealper2/SpamShield-Qwen3.5-0.8B with PEFT:
from peft import PeftModel from transformers import AutoModelForCausalLM base_model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3.5-0.8B") model = PeftModel.from_pretrained(base_model, "thealper2/SpamShield-Qwen3.5-0.8B") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- Unsloth Desktop
SpamShield-Qwen3.5-0.8B
A small (0.8B) instruction-following classifier that labels a message (SMS, e-mail, chat or social post) as HAM, SPAM, PHISHING, SCAM, TOXIC and returns a strict JSON object with an is_spam flag and a one-sentence, evidence-based reason. It is a LoRA adapter for Qwen/Qwen3.5-0.8B trained with Unsloth on a deduplicated union of 17 public, predominantly English spam / phishing / scam corpora.
{"label": "PHISHING", "is_spam": true, "reason": "The message appears to be a phishing attempt: it asks for account details or verification, contains a link and uses urgent or time-pressure language."}
Model details
| Developed for | Message spam & safety triage research (SpamShield project) |
| Model type | Causal LM + LoRA adapter, generative classifier with JSON output |
| Base model | Qwen/Qwen3.5-0.8B (Apache-2.0) — used in non-thinking mode |
| Output labels | HAM, SPAM, PHISHING, SCAM, TOXIC, UNKNOWN (schema only, see below) |
| Language | English (sources were restricted to predominantly English corpora) |
| License | Adapter code: Apache-2.0. Weights inherit the most restrictive training-data terms (see Training data). |
Intended use
- Flagging unsolicited bulk messages, phishing and scams in research prototypes, mail/SMS triage pipelines and as a pre-filter for LLM agents that read untrusted messages.
- Returning a structured, machine-parseable verdict with a short human-readable justification.
Out of scope: sole decision-maker for blocking/deleting user mail, legal or forensic attribution, malware/attachment analysis, URL reputation, sender authentication (SPF/DKIM/DMARC), prompt-injection detection, and content moderation of toxic speech (TOXIC has only ~20 training examples).
Output schema and label policy
| label | is_spam | meaning |
|---|---|---|
PHISHING |
true | tries to obtain credentials, account access or sensitive data |
SCAM |
true | fraud, fake prizes, requests for money |
SPAM |
true | unsolicited advertising / bulk unwanted messages (also used when a source merges spam and phishing) |
HAM |
false | legitimate message |
TOXIC |
false | abusive language without spam intent |
UNKNOWN |
– | part of the schema; rows whose label could not be mapped reliably were excluded from training, so the model practically never emits it |
Exactly one primary label per message; is_spam is derived from the label. Sources that only provide spam/ham keep that granularity — no finer category is inferred from the text.
How to use
transformers + peft
import json, torch
from transformers import AutoModelForImageTextToText, AutoTokenizer
from peft import PeftModel
adapter = "thealper2/SpamShield-Qwen3.5-0.8B"
tok = AutoTokenizer.from_pretrained(adapter)
# Load the full Qwen3.5 class: the LoRA keys target model.language_model.* (not Qwen3_5ForCausalLM).
model = AutoModelForImageTextToText.from_pretrained("Qwen/Qwen3.5-0.8B", dtype=torch.bfloat16, device_map="auto")
model = PeftModel.from_pretrained(model, adapter).eval()
SYSTEM = "You are a message spam and safety classifier. Analyze the supplied message and return only a valid JSON object using the specified schema. Do not infer facts that are not supported by the message.\n\nSchema: {\"label\": one of \"HAM\", \"SPAM\", \"PHISHING\", \"SCAM\", \"TOXIC\", \"UNKNOWN\", \"is_spam\": true or false, \"reason\": one short sentence citing evidence from the message}.\nLabel policy: PHISHING = tries to obtain credentials, account access or sensitive data; SCAM = fraud, fake prizes or requests for money; SPAM = unsolicited advertising or bulk messages; HAM = legitimate message; TOXIC = abusive language without spam intent. is_spam is true exactly for SPAM, PHISHING and SCAM."
def classify(message: str) -> dict:
msgs = [{"role": "system", "content": SYSTEM},
{"role": "user", "content": "Classify the following message.\n\nMessage:\n\"\"\"\n{message}\n\"\"\"".format(message=message)}]
prompt = tok.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True, enable_thinking=False)
ids = tok(prompt, return_tensors="pt", add_special_tokens=False).to(model.device)
out = model.generate(**ids, max_new_tokens=96, do_sample=False,
eos_token_id=tok.convert_tokens_to_ids("<|im_end|>"))
raw = tok.decode(out[0, ids["input_ids"].shape[1]:], skip_special_tokens=True).strip()
return json.loads(raw) # validate! see predict.py for a strict parser
print(classify("URGENT: your bank account has been suspended. Verify your password at http://secure-login.example.com"))
The merged checkpoint (make push-merged) loads with plain AutoModelForCausalLM. Truncate long messages to ~640 tokens before formatting (training used head truncation with a [...] marker). Use greedy decoding.
Project CLI (strict JSON validation + limited retries)
make predict TEXT="Congratulations! You have won a free prize."
python -m src.inference.predict --adapter outputs/final_adapter "Your parcel is held, pay 1.99 GBP at http://..."
predict.py returns label, is_spam, reason, raw_output, json_parse_success, schema_valid, consistent (label ↔ is_spam), attempts and the first primary_raw_output. Invalid JSON is never returned as a prediction.
Prompt format
The official Qwen3.5 chat template is used through tokenizer.apply_chat_template(..., enable_thinking=False), which emits an empty <think>\n\n</think> block before the answer — identical in training and inference.
[system]
You are a message spam and safety classifier. Analyze the supplied message and return only a valid JSON object using the specified schema. Do not infer facts that are not supported by the message.
Schema: {"label": one of "HAM", "SPAM", "PHISHING", "SCAM", "TOXIC", "UNKNOWN", "is_spam": true or false, "reason": one short sentence citing evidence from the message}.
Label policy: PHISHING = tries to obtain credentials, account access or sensitive data; SCAM = fraud, fake prizes or requests for money; SPAM = unsolicited advertising or bulk messages; HAM = legitimate message; TOXIC = abusive language without spam intent. is_spam is true exactly for SPAM, PHISHING and SCAM.
[user]
Classify the following message.
Message:
"""
<message>
"""
Target: one line of compact JSON with keys in the order label, is_spam, reason, followed by <|im_end|>.
Training data
Only corpora whose text is predominantly English were kept (language share measured with py3langid). Six requested repositories were removed for that reason: KumarSahil299885/SMS_Spam_Multilingual_Collection_Dataset (machine translations, 6% English), anilguven/turkish_spam_email (Turkish), M-Arjun/SpamShield-Datasets (23 languages), thematters/matters-spam (mostly Chinese), paulkm/chinese_conversation_and_spam (Chinese) and abeeranajam31/urdu-spam-dataset (Urdu).
| Source (Hub id) | Channel | License | Status | Rows | Eligible after dedup | SFT train rows |
|---|---|---|---|---|---|---|
| ucirvine/sms_spam | sms | SMS Spam Collection v.1 (free use, attribution; HF card: unknown) | ok | 5,574 | 5,574 | 618 |
| mshenoda/spam-messages | unknown | mit | ok | 59,241 | 59,229 | 3,937 |
| thehamkercat/telegram-spam-ham | chat | wtfpl | ok | 20,348 | 20,342 | 2,941 |
| jngb-labs/sms-spam | sms | SMS Spam Collection v.1 (custom: free use, attribution) | ok | 5,159 | 5,159 | 615 |
| SetFit/enron_spam | unspecified (Enron-Spam corpus, research use) | ok | 33,716 | 33,665 | 2,381 | |
| bvk/SpamAssassin-spam | apache-2.0 | ok | 6,046 | 6,046 | 867 | |
| yxzwayne/email-spam-10k | apache-2.0 | ok | 10,899 | 10,899 | 771 | |
| AbdulHadi806/mail_spam_ham_dataset | sms | mit | ok | 5,613 | 5,613 | 654 |
| VoltageVagabond/spam-email-dataset | mit (educational senior project) | ok | 217,976 | 217,208 | 9,011 | |
| locuoco/the-biggest-spam-ham-phish-email-dataset-300000 | unknown | mit | ok | 365,448 | 365,246 | 13,719 |
| LHRS-UM-FERI/MENTHOS-dataset-spam | unknown | unspecified | ok | 87,276 | 87,257 | 7,728 |
| weijianzhg/email-safety-triage-10k | other (composite: MIT / Apache-2.0 upstreams) | ok | 10,000 | 6,095 | – | |
| puyang2025/seven-phishing-email-datasets | other (union of upstream corpus licenses) | ok | 203,017 | 202,916 | 11,463 | |
| Febriyansyah/phishing-emails-multilingual | mit | ok | 600 | 600 | – | |
| zefang-liu/phishing-email-dataset | lgpl-3.0 | ok | 18,650 | 18,096 | 1,811 | |
| alissonpadua/ham-spam-scam-toxic-parquet | sms | mit (declared on the JSON original) | ok | 40,000 | 40,000 | 116 |
| FredZhang7/all-scam-spam | unknown | apache-2.0 | ok | 42,619 | 42,618 | 3,369 |
Notes:
alissonpadua/ham-spam-scam-toxic-parquetis the split release ofalissonpadua/ham-spam-scam-toxic(both downloaded and compared; only the parquet one is used).- The 19,528
.emlfiles ofVoltageVagabond/spam-email-datasetwere fetched from their MIT upstream (github.com/realprogrammersusevim/email-dataset) to avoid Hub rate limits. email-safety-triage-10k: only rows with content typeemailare used; prompt-injection, malware, SOC-review and 'suspicious' rows are out of scope (mapped to UNKNOWN and excluded).phishing-emails-multilingual(53% English, synthetic) andemail-safety-triage-10kare held out completely and only used as the unseen-source test.- Several corpora are aggregates of the same originals (UCI SMS, Enron, SpamAssassin, TREC, CEAS); cross-source duplicates are grouped before splitting.
Processing
- Normalisation – HTML → text (link targets kept as
text (url)), raw e-mail headers split off (subject kept, header block never shown to the model), Unicode NFC, control characters removed; URLs, e-mail addresses, phone numbers and non-ASCII text are preserved. Language from the source, orpy3langid(p ≥ 0.8) where unknown. - Deduplication – exact hash → skeleton hash (NFKC, case-folded, punctuation/markup removed, digit runs collapsed) → character-5-gram MinHash LSH (64 perms, verified Jaccard ≥ 0.85) + explicit translation groups; connected components define
duplicate_group. - Label conflicts – inside a group the
is_spamside backed by more distinct sources wins; minority rows and exact ties are dropped and reported; the group label is the most specific agreeing label (PHISHING > SCAM > SPAM, TOXIC > HAM). - Leakage-free splits – whole groups are hashed into train/validation/test (80/10/10); held-out sources (phishing_multilingual_febri, email_safety_triage) never enter training and form the unseen-source test.
- Class imbalance – the SFT sample (60,000 examples) uses class budgets ∝ √n_c (equivalent to weights w_c = √(N / (K·n_c))), source budgets ∝ n_source^0.5, ≤ 2.0× repetition of real minority examples, one representative per duplicate group. Test data keeps its natural distribution.
- Reasons – no source ships rationales, so target reasons are assembled from cues verified to be present in the message (link, phone number, money/prize words, credential request, urgency, promo wording, shouting). A cue is never mentioned unless its pattern matched; otherwise a neutral label-level sentence is used. Reasons are explanations of the label, not independent evidence.
| Deduplication statistic | Value |
|---|---|
| Rows in pool | 1,132,182 |
| Unique exact texts | 699,452 |
| Unique skeletons | 551,175 |
| Duplicate groups | 364,700 |
| Groups spanning several sources | 224,835 |
| Groups with is_spam conflicts | 97 |
| Eligible rows / groups | 1,126,563 / 361,209 |
| Split | Rows | Groups |
|---|---|---|
| train | 897,179 | 286,159 |
| test | 112,390 | 35,759 |
| validation | 110,299 | 35,724 |
| cross_source_test | 3,875 | 3,567 |
| excluded_holdout_overlap | 2,820 | 2,673 |
SFT training sample label mix: HAM 26,893, SPAM 20,933, PHISHING 12,129, TOXIC 30, SCAM 16.
Leakage checks (train vs. evaluation splits): validation: 0 shared groups, 0 shared exact texts, 0 shared skeletons; test: 0 shared groups, 0 shared exact texts, 0 shared skeletons; cross_source_test: 0 shared groups, 0 shared exact texts, 0 shared skeletons.
Training procedure
| Base model | Qwen/Qwen3.5-0.8B (bf16, hybrid Gated-DeltaNet + gated attention, 24 layers) |
| Method | LoRA SFT with Unsloth (QLoRA avoided: Unsloth reports large 4-bit quantisation error for every Qwen3.5 model) |
| LoRA | r=16, alpha=32, dropout=0.0, targets=q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj, in_proj_qkv, in_proj_z, out_proj |
| Loss | cross-entropy on the assistant JSON only (system/user prompt masked) |
| Sequence length | 1024 tokens; message head-truncated to 640 tokens |
| Batch | 16 × grad-accum 2 = 32 |
| Optimiser | adamw_8bit, lr=0.0002, cosine schedule, warmup=4%, weight decay=0.01, clip=1.0 |
| Epochs | 2 |
| Precision | bf16 |
| Checkpoint selection | best eval_macro_f1 on decoded validation sample (2 evaluations per epoch) |
| Packing | disabled (recurrent Gated-DeltaNet state would leak across packed sequences) |
| Seed | 3407 |
| Training examples | 60,001 |
| Optimiser steps | 1,876 |
| Wall-clock | 1.46 h |
| Peak GPU memory | 7.6 GB |
| Best checkpoint | checkpoint-1876 (eval_macro_f1=0.9011951197080905) |
Hardware: single NVIDIA RTX 5060 Ti GPU (16 GB), WSL2, PyTorch 2.11 + CUDA 12.8, transformers 5.20, TRL 0.24, Unsloth 2026.10.3.
Evaluation
All numbers in %. natural re-weights the stratified evaluation sample to the natural label/source distribution of the held-out test split; sample is the unweighted stratified sample (at most N examples per source × label). Core macro-F1 averages HAM/SPAM/PHISHING only, because SCAM and TOXIC have only a handful of distinct test messages.
| Split | View | n | Accuracy | Macro-F1 | Core macro-F1 | Weighted-F1 | Spam P | Spam R | Spam F1 | HAM FPR | JSON valid |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Combined test | natural | 6913 | 95.20 | 95.91 | 93.18 | 95.22 | 97.28 | 97.30 | 97.29 | 2.22 | 100.00 |
| Combined test | sample | 6913 | 91.03 | 93.04 | 88.40 | 91.01 | 97.45 | 97.04 | 97.24 | 2.44 | 100.00 |
| Unseen sources | natural | 1921 | 75.64 | 41.04 | 41.04 | 79.28 | 82.86 | 92.13 | 87.25 | 5.86 | 100.00 |
| Unseen sources | sample | 1921 | 57.94 | 39.89 | 39.89 | 63.15 | 88.65 | 92.13 | 90.36 | 9.15 | 100.00 |
Per-class (combined test, natural weighting)
| Label | Precision | Recall | F1 | Support (weighted) |
|---|---|---|---|---|
| HAM | 97.79 | 97.78 | 97.79 | 19677 |
| SPAM | 94.15 | 92.47 | 93.30 | 11990 |
| PHISHING | 86.23 | 90.80 | 88.46 | 4088 |
| SCAM | 100.00 | 100.00 | 100.00 | 1 |
| TOXIC | 100.00 | 100.00 | 100.00 | 3 |
Confusion matrix (combined test, natural weighting; INVALID = unparseable output)
| gold \ pred | HAM | SPAM | PHISHING | SCAM | TOXIC |
|---|---|---|---|---|---|
| HAM | 19240 | 361 | 76 | 0 | 0 |
| SPAM | 386 | 11087 | 517 | 0 | 0 |
| PHISHING | 48 | 328 | 3712 | 0 | 0 |
| SCAM | 0 | 0 | 0 | 1 | 0 |
| TOXIC | 0 | 0 | 0 | 0 | 3 |
Breakdown and comparison with baselines
Baselines: TF-IDF (word 1–2-grams + char 2–4-grams) with Logistic Regression and Linear SVM, trained on the same SFT sample (same_data) and on up to 400k train-split groups (large); base_zero_shot is the untuned Qwen3.5-0.8B with the same prompt.
Cells: macro-F1 / spam-F1 / HAM false-positive rate (n), re-weighted to the natural test distribution. base_zero_shot is evaluated on the first records only (make evaluate-base, strict JSON schema; see its metrics file for lenient label accuracy).
| Group | base_zero_shot: macro-F1 / spam-F1 / HAM-FPR (n) | baseline_tfidf_linear_svm_same_data: macro-F1 / spam-F1 / HAM-FPR (n) | baseline_tfidf_logreg_same_data: macro-F1 / spam-F1 / HAM-FPR (n) | smoke: macro-F1 / spam-F1 / HAM-FPR (n) |
|---|---|---|---|---|
| all (test) | 0.070 / 0.192 / 0.061 (300) | 0.949 / 0.970 / 0.025 (6913) | 0.947 / 0.963 / 0.035 (6913) | 0.646 / 0.774 / 0.190 (300) |
| SMS | 0.333 / 0.667 / 0.000 (8) | 0.957 / 0.861 / 0.054 (223) | 0.957 / 0.861 / 0.054 (223) | 0.833 / 1.000 / 0.000 (8) |
| 0.072 / 0.218 / 0.058 (123) | 0.792 / 0.970 / 0.019 (2685) | 0.792 / 0.965 / 0.026 (2685) | 0.516 / 0.813 / 0.117 (123) | |
| Chat | 0.074 / 0.200 / 0.000 (27) | 0.694 / 0.879 / 0.077 (608) | 0.655 / 0.867 / 0.095 (608) | 0.587 / 0.878 / 0.056 (27) |
| Channel unknown | 0.064 / 0.148 / 0.066 (142) | 0.922 / 0.972 / 0.027 (3397) | 0.918 / 0.964 / 0.040 (3397) | 0.677 / 0.719 / 0.254 (142) |
| Phishing (gold) | 0.000 / 0.109 / – (53) | 0.956 / 0.998 / – (1302) | 0.960 / 0.998 / – (1302) | 0.753 / 0.902 / – (53) |
| Scam (gold) | – | 1.000 / 1.000 / – (1) | 1.000 / 1.000 / – (1) | – |
| Non-English (detected) | 0.000 / 0.000 / 0.000 (6) | 0.751 / 0.908 / 0.157 (154) | 0.763 / 0.906 / 0.160 (154) | 0.356 / 0.667 / 0.000 (6) |
| Unseen sources (all) | 0.007 / 0.170 / 0.068 (300) | 0.400 / 0.744 / 0.134 (1921) | 0.391 / 0.730 / 0.172 (1921) | 0.524 / 0.657 / 0.288 (300) |
Unweighted (stratified sample)
| Group | base_zero_shot: macro-F1 / spam-F1 / HAM-FPR (n) | baseline_tfidf_linear_svm_same_data: macro-F1 / spam-F1 / HAM-FPR (n) | baseline_tfidf_logreg_same_data: macro-F1 / spam-F1 / HAM-FPR (n) | smoke: macro-F1 / spam-F1 / HAM-FPR (n) |
|---|---|---|---|---|
| all (test) | 0.074 / 0.202 / 0.041 (300) | 0.923 / 0.966 / 0.036 (6913) | 0.923 / 0.962 / 0.046 (6913) | 0.661 / 0.815 / 0.130 (300) |
| SMS | 0.333 / 0.667 / 0.000 (8) | 0.957 / 0.861 / 0.054 (223) | 0.957 / 0.861 / 0.054 (223) | 0.833 / 1.000 / 0.000 (8) |
| 0.062 / 0.214 / 0.059 (123) | 0.824 / 0.974 / 0.022 (2685) | 0.827 / 0.972 / 0.027 (2685) | 0.553 / 0.809 / 0.118 (123) | |
| Chat | 0.074 / 0.200 / 0.000 (27) | 0.701 / 0.894 / 0.077 (608) | 0.660 / 0.884 / 0.095 (608) | 0.589 / 0.889 / 0.056 (27) |
| Channel unknown | 0.078 / 0.173 / 0.042 (142) | 0.886 / 0.970 / 0.035 (3397) | 0.885 / 0.966 / 0.048 (3397) | 0.704 / 0.806 / 0.169 (142) |
| Phishing (gold) | 0.000 / 0.172 / – (53) | 0.913 / 0.997 / – (1302) | 0.919 / 0.998 / – (1302) | 0.623 / 0.896 / – (53) |
| Scam (gold) | – | 1.000 / 1.000 / – (1) | 1.000 / 1.000 / – (1) | – |
| Non-English (detected) | 0.000 / 0.000 / 0.000 (6) | 0.752 / 0.912 / 0.154 (154) | 0.765 / 0.912 / 0.154 (154) | 0.356 / 0.667 / 0.000 (6) |
| Unseen sources (all) | 0.009 / 0.190 / 0.065 (300) | 0.386 / 0.827 / 0.160 (1921) | 0.379 / 0.832 / 0.201 (1921) | 0.534 / 0.812 / 0.304 (300) |
Per source (test split)
| Source | base_zero_shot: acc / macro-F1 (n) | baseline_tfidf_linear_svm_same_data: acc / macro-F1 (n) | baseline_tfidf_logreg_same_data: acc / macro-F1 (n) | smoke: acc / macro-F1 (n) |
|---|---|---|---|---|
| all_scam_spam | 0.000 / 0.000 (26) | 0.838 / 0.733 (594) | 0.827 / 0.725 (594) | 0.769 / 0.647 (26) |
| biggest_spam_ham_phish | 0.036 / 0.064 (55) | 0.932 / 0.921 (1200) | 0.929 / 0.919 (1200) | 0.702 / 0.693 (55) |
| email_spam_10k | 0.000 / 0.000 (2) | 0.925 / 0.740 (93) | 0.935 / 0.775 (93) | 1.000 / 1.000 (2) |
| ham_spam_scam_toxic | 0.500 / 0.500 (2) | 1.000 / 1.000 (8) | 1.000 / 1.000 (8) | 1.000 / 1.000 (2) |
| jngb_sms_spam | 0.000 / 0.000 (2) | 0.917 / 0.810 (60) | 0.917 / 0.810 (60) | 1.000 / 1.000 (2) |
| mail_spam_ham_abdulhadi | 0.000 / 0.000 (2) | 0.974 / 0.961 (77) | 0.974 / 0.961 (77) | 1.000 / 1.000 (2) |
| menthos_spam | 0.006 / 0.036 (27) | 0.980 / 0.799 (821) | 0.972 / 0.816 (821) | 0.678 / 0.475 (27) |
| mshenoda_spam_messages | 0.076 / 0.126 (34) | 0.865 / 0.744 (782) | 0.866 / 0.752 (782) | 0.863 / 0.558 (34) |
| phishing_email_zefang | 0.000 / 0.000 (17) | 0.920 / 0.913 (350) | 0.917 / 0.909 (350) | 0.588 / 0.554 (17) |
| setfit_enron_spam | 0.083 / 0.083 (12) | 0.706 / 0.647 (343) | 0.706 / 0.649 (343) | 0.667 / 0.509 (12) |
| seven_phishing | 0.115 / 0.095 (39) | 0.961 / 0.766 (878) | 0.956 / 0.783 (878) | 0.702 / 0.530 (39) |
| spamassassin_bvk | 0.000 / 0.000 (7) | 0.889 / 0.623 (108) | 0.889 / 0.623 (108) | 0.857 / 0.788 (7) |
| telegram_spam_ham | 0.032 / 0.074 (27) | 0.913 / 0.694 (608) | 0.903 / 0.655 (608) | 0.896 / 0.587 (27) |
| uci_sms_spam | 0.000 / 0.000 (2) | 0.949 / 0.902 (78) | 0.949 / 0.902 (78) | 0.500 / 0.500 (2) |
| voltagevagabond_spam_email | 0.020 / 0.026 (46) | 0.931 / 0.754 (913) | 0.927 / 0.743 (913) | 0.680 / 0.487 (46) |
Unseen sources (cross-source test)
| Source | base_zero_shot: acc / macro-F1 (n) | baseline_tfidf_linear_svm_same_data: acc / macro-F1 (n) | baseline_tfidf_logreg_same_data: acc / macro-F1 (n) | smoke: acc / macro-F1 (n) |
|---|---|---|---|---|
| email_safety_triage | 0.002 / 0.008 (240) | 0.776 / 0.448 (1546) | 0.744 / 0.437 (1546) | 0.679 / 0.461 (240) |
| phishing_multilingual_febri | 0.017 / 0.091 (60) | 0.072 / 0.171 (375) | 0.053 / 0.158 (375) | 0.883 / 0.731 (60) |
Limitations and known failure modes
- Dated, English-heavy data. Most e-mail corpora are 2000–2008 (Enron, SpamAssassin, TREC, CEAS); modern smishing (parcel/toll scams, QR codes, crypto, brand impersonation) is under-represented.
- Pre-processed inputs. Several corpora are lower-cased, stop-word-filtered or stemmed (e.g. the MENTHOS e-mails,
escapenumbertokens). The model has seen both raw and pre-processed text, but accuracy on raw modern messages may differ from the test numbers. - Coarse phishing/scam labels. Many sources merge phishing into a generic spam class, so PHISHING vs. SPAM confusions are partly label noise; rely on
is_spamwhen the fine label matters less. - Almost no TOXIC / SCAM data. SCAM comes from one templated synthetic SMS corpus that collapses to 9 distinct message families after deduplication; TOXIC has 20. Both labels are effectively unlearned and unmeasured — the core macro-F1 (HAM/SPAM/PHISHING) is the reliable headline number.
- English only. Non-English messages were removed from the training sources; behaviour on other languages is untested.
- Long messages are head-truncated to ~640 tokens; evidence at the end of long e-mails is not seen.
- Template reasons. Reasons are generated from verified surface cues, so they are short and generic; they may omit the decisive signal and must not be treated as forensic evidence.
- Adversarial robustness (obfuscation, prompt injection inside the message) was not evaluated.
- A small generative model can still emit invalid JSON; always validate (see
json_validrate above).
Licensing of the training data
Training sources carry MIT, Apache-2.0, WTFPL, LGPL-3.0, custom (SMS Spam Collection: free use with attribution) and unspecified terms. Redistribution of the datasets is not part of this release. Check every source before any commercial use of the weights.
Citation
@misc{spamshield_qwen35_08b,
title = {SpamShield-Qwen3.5-0.8B: a multi-source spam, phishing and scam classifier},
year = {2026},
howpublished = {\url{https://huggingface.co/thealper2/SpamShield-Qwen3.5-0.8B}}
}
Please also cite the base model (Qwen3.5) and the datasets listed above, in particular the SMS Spam Collection (Almeida & Gómez Hidalgo, 2011), the Enron-Spam corpus (Metsis et al., 2006), the SpamAssassin public corpus.
- Downloads last month
- 13
Model tree for thealper2/SpamShield-Qwen3.5-0.8B
Datasets used to train thealper2/SpamShield-Qwen3.5-0.8B
ucirvine/sms_spam
zefang-liu/phishing-email-dataset
Evaluation results
- Macro F1 on SpamShield combined English testself-reported0.959
- Accuracy on SpamShield combined English testself-reported0.952
- Spam F1 (binary) on SpamShield combined English testself-reported0.973