AnkitAI/Sensible-ModernBERT-Sentiment-Analysis
143
1"""General-sentiment trainer — SST-2, targeting the 3.9M-dl/mo distilbert-sst2 incumbent.2 3Same discipline as finsense_train.py: fp32, best-ckpt by val metric, held-out4reporting. SST-2 official: train 67,349 / validation 872 (the reported split —5incumbent's 91.3 is on validation; test is unlabeled). We carve 5% of train as6our early-stop val and report on the official validation set, untouched.7 8Usage: python sst2_train.py [--seed 42] [--base answerdotai/ModernBERT-base]9"""10import argparse11import json12import os13 14import torch15from datasets import load_dataset16from sklearn.metrics import accuracy_score, f1_score17from transformers import (AutoModelForSequenceClassification, AutoTokenizer,18 Trainer, TrainingArguments)19 20p = argparse.ArgumentParser()21p.add_argument("--seed", type=int, default=42)22p.add_argument("--base", default="answerdotai/ModernBERT-base")23p.add_argument("--epochs", type=int, default=2)24args = p.parse_args()25 26OUT = os.path.expanduser(f"~/finsense_runs/sst2-{args.base.split('/')[-1]}-s{args.seed}")27os.makedirs(OUT, exist_ok=True)28LABELS = ["negative", "positive"]29 30ds = load_dataset("nyu-mll/glue", "sst2")31full_train = ds["train"].shuffle(seed=42)32n_val = int(len(full_train) * 0.05)33early_val = full_train.select(range(n_val))34train = full_train.select(range(n_val, len(full_train)))35report_val = ds["validation"] # incumbent's benchmark split — never used for selection36print(f"train={len(train)} early_val={len(early_val)} report_val={len(report_val)}", flush=True)37 38tok = AutoTokenizer.from_pretrained(args.base)39 40def enc(d):41 return d.map(lambda b: tok(b["sentence"], truncation=True, max_length=128),42 batched=True, remove_columns=[c for c in d.column_names if c not in ("label",)])43 44torch.manual_seed(args.seed)45model = AutoModelForSequenceClassification.from_pretrained(46 args.base, num_labels=2,47 id2label=dict(enumerate(LABELS)), label2id={l: i for i, l in enumerate(LABELS)})48 49def metrics(pred):50 y, yhat = pred.label_ids, pred.predictions.argmax(-1)51 return {"accuracy": accuracy_score(y, yhat)}52 53import transformers as _tf54_tok_kw = {"processing_class": tok} if int(_tf.__version__.split(".")[0]) >= 5 else {"tokenizer": tok}55trainer = Trainer(56 model=model, **_tok_kw,57 train_dataset=enc(train), eval_dataset=enc(early_val),58 compute_metrics=metrics,59 args=TrainingArguments(60 output_dir=f"{OUT}/ckpt",61 num_train_epochs=args.epochs,62 per_device_train_batch_size=32, per_device_eval_batch_size=64,63 learning_rate=2e-5, warmup_ratio=0.06, weight_decay=0.01,64 eval_strategy="steps", eval_steps=500,65 save_strategy="steps", save_steps=500, save_total_limit=1,66 load_best_model_at_end=True, metric_for_best_model="accuracy",67 logging_steps=100, seed=args.seed, report_to=[],68 fp16=False, bf16=False,69 ),70)71trainer.train()72 73pred = trainer.predict(enc(report_val))74y, yhat = pred.label_ids, pred.predictions.argmax(-1)75res = {"base": args.base, "seed": args.seed, "task": "sst2",76 "val_accuracy": float(accuracy_score(y, yhat)),77 "val_f1": float(f1_score(y, yhat)),78 "incumbent": "distilbert-sst2 = 0.913 on this same validation split"}79json.dump(res, open(f"{OUT}/result.json", "w"), indent=1)80print("SST2_RESULT", json.dumps(res), flush=True)81model.save_pretrained(f"{OUT}/model")82tok.save_pretrained(f"{OUT}/model")83 