Downloads · 30 days
4
5% of all-time downloads
Gigvo/teliti-job-scam-mdistilbert
teliti-job-scam-mdistilbert is a text classification model from Gigvo. Use it when you need a label for a piece of text. The card lists the license as mit.
Fine-tuned distilbert-base-multilingual-cased that scores how likely an Indonesian job advertisement is to be a scam, so a jobseeker can check it before sending a CV and personal data.
Downloads · 30 days
4
5% of all-time downloads
All-time downloads
73
Public
Parameters
135M
541 MB on disk
Likes
0
Public
Click a slice to open those files.
.safetensors541 MB · 99%
From the Hugging Face model README
Fine-tuned distilbert-base-multilingual-cased that scores how likely an Indonesian job
advertisement is to be a scam, so a jobseeker can check it before sending a
CV and personal data.
Part of TELITI (Teknologi Evaluasi Lowongan dan Integritas). Full system — API, rule engine, explanations, web app: https://github.com/Gigvo/TELITI
| File | Purpose |
|---|---|
model.safetensors, config.json, tokenizer*.json | The fine-tuned classifier |
calibrator_deployment.json | Platt scaling recalibrated for Indonesian deployment — use this one |
calibrator.json | Platt scaling fitted on EMSCAD, kept for reference |
thresholds.json | Risk-label boundaries (Rendah / Sedang / Tinggi) |
training_summary.json | Full training configuration and metrics |
Two evaluations on two datasets. The numbers are not comparable to each other — PR-AUC scales with how common the positive class is, so the higher number is not evidence of a better model.
Scored once, on a split untouched by any earlier decision.
| Metric | mDistilBERT | TF-IDF + LinearSVC |
|---|---|---|
| PR-AUC | 0.7883 | 0.8651 |
| ROC-AUC | 0.9647 | 0.9839 |
| Precision | 0.9273 | 0.8462 |
| Recall | 0.5930 | 0.7674 |
| F1 | 0.7234 | 0.8049 |
Quote 0.7883, not the validation figure. The validation score was 0.8669, but the checkpoint was selected on validation, which makes that split an optimistic estimate. The transformer drops 0.0786 PR-AUC from validation to test; the baseline, which involved no such selection, drops only 0.0118.
The transformer does not beat a TF-IDF baseline on English EMSCAD. It is used because TF-IDF trained on English cannot process Indonesian at all, and Indonesian is the deployment domain — not because of this number.
Reported for completeness. The checkpoint was selected here, so these are optimistic — see the test figures above.
| Metric | Value |
|---|---|
| PR-AUC | 0.8669 |
| TF-IDF baseline | 0.8769 |
| ROC-AUC | 0.9767 |
| Precision | 0.8571 |
| Recall | 0.7674 |
| F1 | 0.8098 |
| Brier score | 0.0160 |
Accuracy is 98.3% and is misleading — at 4.8% prevalence, predicting "not a scam" for everything scores 95.2%.
| Configuration | PR-AUC | False positives |
|---|---|---|
| model only | 0.9258 | 5 |
| model + rule layer | 0.8617 | 28 |
| rule layer only | 0.4167 | 93 |
The rule layer made things worse and is disabled in the product. Concept paper §3.6 names false positives against real businesses as the expensive error, and the rules multiplied them by five.
import json
import math
import torch
from huggingface_hub import hf_hub_download
from transformers import AutoModelForSequenceClassification, AutoTokenizer
REPO = "Gigvo/teliti-job-scam-mdistilbert"
tokenizer = AutoTokenizer.from_pretrained(REPO)
model = AutoModelForSequenceClassification.from_pretrained(REPO).eval()
# Raw logits are NOT probabilities here. Apply the deployment calibrator.
with open(hf_hub_download(REPO, "calibrator_deployment.json")) as fh:
calibrator = json.load(fh)
text = (
"Dibutuhkan admin online, gaji Rp9.000.000, tanpa pengalaman. "
"Wajib transfer biaya administrasi Rp250.000."
)
with torch.no_grad():
encoded = tokenizer(text, truncation=True, max_length=256, return_tensors="pt")
logits = model(**encoded).logits
# The calibrator is fitted on the LOGIT MARGIN, not on softmax output.
margin = (logits[0, 1] - logits[0, 0]).item()
p_scam = 1 / (1 + math.exp(-(calibrator["a"] * margin + calibrator["b"])))
print(f"p(scam) = {p_scam:.3f}")
print(f"integrity score = {round((1 - p_scam) * 100)} / 100")
Verified output — scam example above scores 0, and a legitimate posting scores 91.
Note max_length=256. The model was fine-tuned at
512, but the product serves at 256 to keep occlusion-based
explanation within its latency budget; use 256 to reproduce the product's
numbers exactly.
Raw softmax(logits) is not a usable probability here. The model was
trained where scams are 4.8% of postings and
deployed where they are ~36%. Without recalibration every Indonesian ad scored
93–100 out of 100 — technically ranked correctly, useless as a number a person
can act on.
calibrator_deployment.json corrects that shift. Use it.
| Base model | distilbert-base-multilingual-cased |
| Max sequence length | 512 |
| Batch size | 16 |
| Learning rate | 3e-05 |
| Epochs | 3 |
| Warmup ratio | 0.1 |
| Class weighting | pos_weight 19.68 |
| Seed | 42 |
| Device | cuda |
Trained on EMSCAD (Employment Scam Aegean Dataset), then recalibrated on a manually annotated Indonesian holdout.
For: helping a jobseeker decide whether to look more carefully at an advertisement before applying.
Not for: automated rejection of postings, moderation without human review, or any decision about a company or a person taken on the score alone.
Concept paper §3.6 treats false positives against legitimate businesses as the expensive error, which is why the rule layer was disabled after measurement rather than kept because it was built. The product ships a visible disclaimer and an appeal route; appeals are reviewed by a person and are not used to retrain.
@misc{teliti2026,
title = {TELITI: Teknologi Evaluasi Lowongan dan Integritas},
author = {Tim SateManaAjaDah},
year = {2026},
note = {Datathon 2026, Ristek Fasilkom UI},
url = {https://huggingface.co/Gigvo/teliti-job-scam-mdistilbert}
}