Text Classification
Transformers
Safetensors
English
roberta
data-classification
sensitivity
data-governance
Synthetic
text-embeddings-inference
Instructions to use aitrainingdatabroker/document-sensitivity-tier-classifier with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use aitrainingdatabroker/document-sensitivity-tier-classifier with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-classification", model="aitrainingdatabroker/document-sensitivity-tier-classifier")# pip install -U transformers accelerate # Load model directly from transformers import AutoTokenizer, AutoModelForSequenceClassification tokenizer = AutoTokenizer.from_pretrained("aitrainingdatabroker/document-sensitivity-tier-classifier") model = AutoModelForSequenceClassification.from_pretrained("aitrainingdatabroker/document-sensitivity-tier-classifier", device_map="auto") - Notebooks
- Google Colab
- Kaggle
Download source/train.py from aitrainingdatabroker/document-sensitivity-tier-classifier: direct link, hf CLI and curl.
- Browser
- Download file 6.99 kB
-
https://huggingface.co/aitrainingdatabroker/document-sensitivity-tier-classifier/resolve/main/source/train.py
- Command line
-
hf download hf://aitrainingdatabroker/document-sensitivity-tier-classifier/source/train.py
-
curl -L -o train.py https://huggingface.co/aitrainingdatabroker/document-sensitivity-tier-classifier/resolve/main/source/train.py
6.99 kB
| """Train one of the five models on the graphics card and write its test scores (2026-10-29). | |
| python models/train.py <name> e.g. business-pii-detector | |
| Reads models/<name>/data/{train,test}.jsonl (models/build.py), fine-tunes distilroberta-base, saves the model to | |
| models/<name>/out/ (not committed; uploaded to Hugging Face) and the scores to models/<name>/scores.json. | |
| Span models (rows with "entities") are scored by exact entity match; classifiers by accuracy and macro F1. | |
| """ | |
| import json | |
| import os | |
| import sys | |
| from collections import Counter | |
| from pathlib import Path | |
| import torch | |
| from torch.utils.data import DataLoader | |
| from transformers import (AutoModelForSequenceClassification, AutoModelForTokenClassification, AutoTokenizer, | |
| get_linear_schedule_with_warmup) | |
| BASE = "distilroberta-base" | |
| ROOT = Path(__file__).resolve().parent | |
| name = sys.argv[1] | |
| folder = ROOT / name | |
| read = lambda s: [json.loads(l) for l in open(folder / "data" / f"{s}.jsonl", encoding="utf-8")] | |
| train_rows, test_rows = read("train"), read("test") | |
| spans = "entities" in train_rows[0] | |
| dev = "cuda" if torch.cuda.is_available() else "cpu" | |
| torch.manual_seed(1029) | |
| tok = AutoTokenizer.from_pretrained(BASE, add_prefix_space=True) | |
| if spans: | |
| types = sorted({e["label"] for r in train_rows + test_rows for e in r["entities"]}) | |
| labels = ["O"] + [f"{p}-{t}" for t in types for p in ("B", "I")] | |
| else: | |
| labels = sorted({r["label"] for r in train_rows + test_rows}) | |
| lid = {l: i for i, l in enumerate(labels)} | |
| MAX = 256 if not spans else 160 | |
| def encode_spans(r): | |
| enc = tok(r["text"], truncation=True, max_length=MAX, return_offsets_mapping=True) | |
| tags = [] | |
| for s, e in enc["offset_mapping"]: | |
| if s == e: | |
| tags.append(-100) | |
| continue | |
| tag = "O" | |
| for ent in r["entities"]: | |
| if s >= ent["start"] and e <= ent["end"] + 1 and s < ent["end"]: | |
| tag = ("B-" if s <= ent["start"] else "I-") + ent["label"] | |
| break | |
| tags.append(lid[tag]) | |
| enc.pop("offset_mapping") | |
| enc["labels"] = tags | |
| return enc | |
| def encode_cls(r): | |
| enc = tok(r["text"], truncation=True, max_length=MAX) | |
| enc["labels"] = lid[r["label"]] | |
| return enc | |
| def collate(batch): | |
| n = max(len(b["input_ids"]) for b in batch) | |
| out = {"input_ids": [], "attention_mask": [], "labels": []} | |
| for b in batch: | |
| pad = n - len(b["input_ids"]) | |
| out["input_ids"].append(b["input_ids"] + [tok.pad_token_id] * pad) | |
| out["attention_mask"].append(b["attention_mask"] + [0] * pad) | |
| out["labels"].append(b["labels"] + [-100] * pad if spans else b["labels"]) | |
| return {k: torch.tensor(v) for k, v in out.items()} | |
| encode = encode_spans if spans else encode_cls | |
| train = [encode(r) for r in train_rows] | |
| Model = AutoModelForTokenClassification if spans else AutoModelForSequenceClassification | |
| model = Model.from_pretrained(BASE, num_labels=len(labels), id2label=dict(enumerate(labels)), label2id=lid).to(dev) | |
| EPOCHS = int(os.environ.get("EPOCHS", 8 if spans else 6)) # EPOCHS=20 for many-class models with few rows each | |
| loader = DataLoader(train, batch_size=16, shuffle=True, collate_fn=collate) | |
| opt = torch.optim.AdamW(model.parameters(), lr=5e-5, weight_decay=0.01) | |
| sched = get_linear_schedule_with_warmup(opt, len(loader) * EPOCHS // 10, len(loader) * EPOCHS) | |
| model.train() | |
| for ep in range(EPOCHS): | |
| total = 0.0 | |
| for batch in loader: | |
| with torch.autocast(dev, dtype=torch.float16, enabled=dev == "cuda"): | |
| loss = model(**{k: v.to(dev) for k, v in batch.items()}).loss | |
| loss.backward() | |
| torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) | |
| opt.step(), sched.step(), opt.zero_grad() | |
| total += loss.item() | |
| print(f"epoch {ep + 1}: loss {total / len(loader):.4f}") | |
| model.eval() | |
| def predict_spans(text): | |
| enc = tok(text, truncation=True, max_length=MAX, return_offsets_mapping=True, return_tensors="pt") | |
| offs = enc.pop("offset_mapping")[0].tolist() | |
| with torch.no_grad(): | |
| ids = model(**{k: v.to(dev) for k, v in enc.items()}).logits[0].argmax(-1).tolist() | |
| found, cur = [], None | |
| for (s, e), i in zip(offs, ids): | |
| if s == e: | |
| continue | |
| tag = labels[i] | |
| if tag == "O": | |
| cur = None | |
| continue | |
| kind = tag[2:] | |
| if tag.startswith("I-") and cur and cur["label"] == kind: | |
| cur["end"] = e | |
| else: | |
| cur = {"label": kind, "start": s, "end": e} | |
| found.append(cur) | |
| for f in found: # offsets from a prefix-space tokenizer can start on the space | |
| while f["start"] < f["end"] and text[f["start"]] == " ": | |
| f["start"] += 1 | |
| return found | |
| if spans: | |
| tp, fp, fn = Counter(), Counter(), Counter() | |
| for r in test_rows: | |
| gold = {(e["label"], e["start"], e["end"]) for e in r["entities"]} | |
| pred = {(e["label"], e["start"], e["end"]) for e in predict_spans(r["text"])} | |
| for g in gold: | |
| (tp if g in pred else fn)[g[0]] += 1 | |
| for p in pred - gold: | |
| fp[p[0]] += 1 | |
| f1 = lambda t, p, n: (2 * t / (2 * t + p + n)) if t else 0.0 | |
| per = {t: {"precision": round(tp[t] / (tp[t] + fp[t]), 3) if tp[t] + fp[t] else 0.0, | |
| "recall": round(tp[t] / (tp[t] + fn[t]), 3) if tp[t] + fn[t] else 0.0, | |
| "f1": round(f1(tp[t], fp[t], fn[t]), 3), "support": tp[t] + fn[t]} for t in types} | |
| T, P, N = sum(tp.values()), sum(fp.values()), sum(fn.values()) | |
| scores = {"metric": "exact entity match", "micro_precision": round(T / (T + P), 3), "micro_recall": round(T / (T + N), 3), | |
| "micro_f1": round(f1(T, P, N), 3), "per_type": per} | |
| else: | |
| pred, gold = [], [] | |
| for r in test_rows: | |
| enc = tok(r["text"], truncation=True, max_length=MAX, return_tensors="pt") | |
| with torch.no_grad(): | |
| pred.append(labels[model(**{k: v.to(dev) for k, v in enc.items()}).logits[0].argmax().item()]) | |
| gold.append(r["label"]) | |
| acc = sum(p == g for p, g in zip(pred, gold)) / len(gold) | |
| per = {} | |
| for l in labels: | |
| t = sum(p == g == l for p, g in zip(pred, gold)) | |
| p_ = sum(p == l for p in pred) | |
| g_ = sum(g == l for g in gold) | |
| per[l] = {"f1": round(2 * t / (p_ + g_), 3) if p_ + g_ else 0.0, "support": g_} | |
| scores = {"metric": "accuracy and macro F1", "accuracy": round(acc, 3), | |
| "macro_f1": round(sum(v["f1"] for v in per.values() if v["support"]) / sum(1 for v in per.values() if v["support"]), 3), | |
| "per_label": per} | |
| scores.update({"base_model": BASE, "train_rows": len(train_rows), "test_rows": len(test_rows), "epochs": EPOCHS, "device": dev}) | |
| (folder / "scores.json").write_text(json.dumps(scores, indent=2) + "\n", encoding="utf-8") | |
| print(json.dumps({k: v for k, v in scores.items() if k not in ("per_type", "per_label")})) | |
| model.save_pretrained(folder / "out", safe_serialization=True) | |
| tok.save_pretrained(folder / "out") | |