An open-source MobileBERT-based AI model for detecting SMS scams in the wild, with a focus on Indian telecom (DLT headers, UPI fraud, lottery scams). Trained on public datasets, synthetic data, and real personal messages for near-perfect real-world accuracy.
- Overview
- How It Works
- Model Details
- Project Structure
- Getting Started
- File Reference
- Performance
- Contributing a Better Model
- Troubleshooting
- License
Silver Guard is a lightweight SMS scam classifier built for real-world Indian telecom. The base model is trained on:
- Public UCI & India SMS spam datasets (~8,000 messages)
- Synthetic scam/ham data (~5,000 messages)
- Personal messages labeled with the included tooling (~5,000+ messages)
The model runs inference via ONNX (silver_guard.onnx) and uses MobileBERT's tokenizer, making it fast enough to run on-device or in a server with minimal overhead.
Why personal messages matter: Public datasets have a ~40% false-positive rate on real Indian SMS. Adding your own labeled messages drops that below 1%. The scripts in this repo make that process as fast as possible.
┌─────────────────────────────────────────────────────────┐
│ AUTO-LABELING PIPELINE │
├─────────────────────────────────────────────────────────┤
│ DLT Header? ──────────────────────────► HAM (auto) │
│ (XX-XXXXXX-X) │
│ │
│ Known Contact? ────────────────────────► HAM (auto) │
│ (matches contacts.csv / contacts.vcf) │
│ │
│ Unknown Number ────────────────────────► REVIEW │
│ (+91XXXXXXXXXX) (manual) │
└─────────────────────────────────────────────────────────┘
label_sms.py → review.json → merge_labels.py
↓
training_data.json
↓
main.ipynb on Google Colab
↓
silver_guard.onnx + vocab.txt
Indian businesses are legally required to use TRAI DLT-registered sender IDs (XX-XXXXXX-T/P/S/G). Since scammers cannot register DLT IDs, all DLT-format senders are auto-labeled as ham with no manual review needed.
| Property | Value |
|---|---|
| Architecture | MobileBERT (fine-tuned for binary classification) |
| Export format | ONNX (silver_guard.onnx) |
| Tokenizer | WordPiece (vocab.txt, lowercase, max length 128) |
| Input format | SENDER_HEADER [SEP] message_text |
| Output | threat_score — float in [0, 1] (0 = ham, 1 = scam) |
| Labels | 0 = ham (safe), 1 = scam |
Silver Guard AI Model Training/
├── label_sms.py # Auto-labels SMS exports using DLT + contact matching
├── merge_labels.py # Merges auto-labeled + manually reviewed data
├── main.ipynb # MobileBERT training notebook (run on Google Colab)
├── TRAINING_GUIDE.md # Detailed step-by-step training guide
└── model/
├── silver_guard.onnx # Trained model (ONNX)
├── vocab.txt # MobileBERT tokenizer vocabulary
└── model_config.json # Model metadata and I/O spec
- Python 3.8+ installed
- Google account (for Colab — free tier works)
- Kaggle account with API key (setup guide)
- Basic familiarity with the command line
Install Python dependencies:
pip install pandasExport your SMS messages as a JSON file named data.json and place it in the project folder.
- Open Phone Link on Windows and go to Messages
- Use Phone Link Exporter to export
- Save the output as
data.jsonin the project folder
- Install SMS Backup & Restore from Google Play
- Back up messages in JSON format
- Transfer to PC and rename the file to
data.json
Any tool that produces JSON in this structure works:
{
"messages": [
{
"address": "+919876543210",
"text": "Your OTP is 123456",
"kind": "received"
},
{
"address": "JK-HDFCBK-T",
"text": "Rs.5000 debited from A/c",
"kind": "received"
}
]
}Required fields:
| Field | Description |
|---|---|
address |
Sender ID (phone number or DLT header) |
text |
Message content |
kind |
"received" or "sent" (sent messages are ignored) |
Contacts allow the auto-labeler to mark messages from known people as ham automatically, significantly reducing manual review.
- Go to contacts.google.com
- Click Export in the left sidebar
- Select Google CSV
- Save as
contacts.csvin the project folder
- Open your phone's Contacts app
- Go to Settings → Export
- Export as VCF (vCard)
- Transfer to PC as
contacts.vcf
If no contacts file is found, all phone numbers go to
review.jsonfor manual labeling. This is fine — just more work.
cd "path\to\Silver Guard AI Model Training"
python label_sms.pyIndian businesses must use TRAI DLT-registered sender IDs. Since scammers cannot register them, all DLT-format senders are auto-labeled as ham:
| Format | Example | Meaning |
|---|---|---|
XX-XXXXXX-T |
JK-HDFCBK-T |
Transactional (OTPs, alerts) |
XX-XXXXXX-P |
DL-AMAZON-P |
Promotional (ads, offers) |
XX-XXXXXX-S |
MH-AIRTEL-S |
Service messages |
XX-XXXXXX-G |
KA-GOVTIN-G |
Government messages |
Loaded 5847 messages
Received messages (excluding sent): 5234
============================================================
AUTO-LABELING RESULTS
============================================================
Auto-labeled as HAM:
- DLT headers (businesses): 4012
- Known contacts: 287
- Total auto-labeled: 4299
Needs manual review: 935
- Frequent senders (3+ msgs): 89
- Unknown/rare senders: 846
| File | Contents |
|---|---|
output.json |
Auto-labeled ham messages |
review.json |
Unknown senders that need your labels |
Open review.json in VS Code or any text editor. Change "label": null to:
"label": 0— Ham (safe, legitimate)"label": 1— Scam (fraud, phishing, spam)
{
"instructions": "Set label to 0 for ham (safe), 1 for scam.",
"total": 935,
"messages": [
{
"text": "Hi, are you coming to the party tomorrow?",
"address": "+919876543210",
"label": null,
"contact_frequency": 15,
"hint": "frequent_contact"
},
{
"text": "CONGRATS! You won Rs.50,00,000! Click here: bit.ly/xxx",
"address": "+917890123456",
"label": null,
"contact_frequency": 1,
"hint": "unknown"
}
]
}| Hint | What to Do |
|---|---|
frequent_contact (3+ messages) |
Almost always ham (0) — it's someone you text regularly |
unknown with personal content |
Probably ham (0) — friend/family not in contacts |
unknown with links/offers |
Probably scam (1) — especially crypto/lottery/loans |
| OTP from unknown number | Usually ham (0) — delivery OTPs, etc. |
Open Find & Replace (Ctrl+H, enable Regex):
Find: "label": null
Replace: "label": 0
Then manually flip the obvious scams back to 1.
python merge_labels.py============================================================
FINAL TRAINING DATA
============================================================
Total messages: 5154
Ham (safe): 5012
Scam: 142
Skipped (unlabeled): 80
Saved to: training_data.json
Messages with
"label": nullare skipped. Go back and label them if you want them included.
{
"total": 5154,
"ham_count": 5012,
"scam_count": 142,
"messages": [
{ "text": "Your OTP is 123456", "address": "JK-HDFCBK-T", "label": 0 },
{
"text": "Click here to claim prize!",
"address": "+917890123456",
"label": 1
}
]
}- Open main.ipynb in Google Colab
- Upload
training_data.jsonandkaggle.jsonwhen prompted:
from google.colab import files
files.upload() # Select training_data.json, then kaggle.json- Run all cells in order:
| Cell | Action |
|---|---|
| 1 | Install dependencies |
| 2 | Download Kaggle datasets |
| 3 | Generate synthetic data |
| 4 | Merge all data (including your training_data.json) |
| 5 | Fine-tune MobileBERT |
| 6 | Export to ONNX |
| 7 | Test inference |
| 8 | Download silver_guard.onnx + vocab.txt |
Cell 4 automatically detects and merges your personal data:
PERSONAL_DATA = "training_data.json"
if os.path.exists(PERSONAL_DATA):
with open(PERSONAL_DATA, 'r') as f:
personal = json.load(f)
personal_msgs = personal.get('messages', [])
print(f"✓ Loaded {len(personal_msgs)} personal messages")- Replace
model/silver_guard.onnxandmodel/vocab.txtwith the downloaded files.
| File | Purpose | When Created |
|---|---|---|
data.json |
Your exported SMS messages | You create (Step 1) |
contacts.csv |
Your contacts (optional) | You create (Step 2) |
contacts.vcf |
Your contacts (optional) | You create (Step 2) |
output.json |
Auto-labeled ham messages | label_sms.py creates |
review.json |
Messages for manual review | label_sms.py creates |
training_data.json |
Final training data | merge_labels.py creates |
main.ipynb |
Training notebook | Already in repo |
kaggle.json |
Kaggle API credentials | You create (guide) |
| Metric | Base model (no personal data) | With personal data |
|---|---|---|
| Test accuracy | ~95% | ~99% |
| Real-world FP rate | ~40% | <1% |
| Edge case accuracy | 14 / 20 | 19 / 20 |
For best results, aim for:
Total training data: 15,000+ messages
├── Public datasets: ~8,000 (UCI + India SMS Spam)
├── Synthetic: ~5,000 (auto-generated)
└── Personal: ~5,000+ (your labeled messages)
├── Ham: ~4,800 (96%)
└── Scam: ~200 (4%)
Having real ham examples is crucial — the model needs to learn what your normal messages look like.
Silver Guard improves with more diverse, real-world training data. Contributions are very welcome.
🔬 Submit a retrained model
Train a better version of silver_guard.onnx using your own labeled data and open a pull request replacing the model files. Include your accuracy metrics and a brief description of your training data (no raw personal messages needed — just the trained artifact).
📊 Contribute labeled data
If you have anonymized, labeled SMS data you are comfortable sharing, add it as a JSON file under data/ and open a pull request. Use the same format as training_data.json.
🛠️ Improve the tooling
Found a bug in label_sms.py or merge_labels.py? Have an idea for better scam heuristics? Fix it and send a PR.
📓 Improve the training notebook
Suggestions for better hyperparameters, augmentation strategies, or alternative architectures are welcome via issues or PRs against main.ipynb.
- Fork this repository
- Create a feature branch:
git checkout -b improve/better-scam-detection - Make your changes (model files, scripts, or data)
- Commit with a descriptive message:
git commit -m "feat: retrain with 3k additional scam examples, FPR drops to 0.3%" - Push and open a pull request against
main
Please describe in your PR:
- What you changed and why
- Any accuracy / performance numbers you measured
- How the training data was sourced (no need to share raw personal messages)
ERROR: data.json not found
Make sure data.json is in the same folder as label_sms.py and is valid UTF-8 JSON.
dir # should show data.json
python label_sms.pyNo contacts file found
This is a warning, not an error. All phone numbers will go to review.json for manual labeling.
X messages still have label=null
You didn't finish labeling review.json. Either go back and label all messages, or run merge_labels.py anyway — unlabeled messages are skipped.
JSON decode error
Validate your data.json at jsonlint.com and check that it is UTF-8 encoded with no trailing commas.
Model still shows high false positives
You need more real scam examples. Options:
- Label more messages as scam in
review.json - Find scam SMS datasets online and add them to
training_data.json - Contribute scam datasets via a pull request
This project is open source and available under the MIT License.
Made with ❤️ by Tanishq Mudaliar
🛡️ Silver Guard — Protecting India from SMS Scams.Better data = better protection. Open a PR and help make it smarter.