Text Classification
Transformers
Safetensors
English
modernbert
financial-sentiment-analysis
sentiment-analysis
financial-news
finance
stocks
trading
finbert-alternative
sentiment
fintech
news
text-embeddings-inference
Instructions to use AnkitAI/FinSense-ModernBERT-Financial-News-Sentiment-Analysis with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use AnkitAI/FinSense-ModernBERT-Financial-News-Sentiment-Analysis with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-classification", model="AnkitAI/FinSense-ModernBERT-Financial-News-Sentiment-Analysis")# Load model directly from transformers import AutoTokenizer, AutoModelForSequenceClassification tokenizer = AutoTokenizer.from_pretrained("AnkitAI/FinSense-ModernBERT-Financial-News-Sentiment-Analysis") model = AutoModelForSequenceClassification.from_pretrained("AnkitAI/FinSense-ModernBERT-Financial-News-Sentiment-Analysis", device_map="auto") - Notebooks
- Google Colab
- Kaggle
File size: 6,080 Bytes
92508af | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | """FINSENSE stage-1 trainer — deberta-v3-base on Financial PhraseBank 50agree.
Research-verified recipe (FINSENSE.md): generic encoder, no DAPT, our own
published stratified split (FPB has no official one), macro-F1 primary,
confusion matrix logged (pos/neutral confusion = the known ceiling).
Usage: python3 finsense_train.py [--seed 42] [--smoke] [--base microsoft/deberta-v3-base]
Runs on MPS (Mac) or CUDA. Smoke = 200 steps sanity.
"""
import argparse
import json
import os
import io
import zipfile
import numpy as np
import torch
from huggingface_hub import hf_hub_download
from sklearn.metrics import accuracy_score, confusion_matrix, f1_score
from transformers import (AutoModelForSequenceClassification, AutoTokenizer,
Trainer, TrainingArguments)
p = argparse.ArgumentParser()
p.add_argument("--seed", type=int, default=42)
p.add_argument("--smoke", action="store_true")
p.add_argument("--base", default="microsoft/deberta-v3-base")
p.add_argument("--corpus", default=None,
help="optional finsense_arm_*.jsonl — replaces the FPB train split "
"as training data (val/test splits stay FPB, untouched)")
args = p.parse_args()
_arm = ("-" + os.path.basename(args.corpus).replace("finsense_arm_", "arm").replace(".jsonl", "")) if args.corpus else ""
OUT = os.path.expanduser(f"~/finsense_runs/{args.base.split('/')[-1]}-s{args.seed}{_arm}")
os.makedirs(OUT, exist_ok=True)
LABELS = ["negative", "neutral", "positive"] # FPB label order 0/1/2
# parse the raw FPB zip directly (script-datasets unsupported, no parquet branch)
zp = hf_hub_download("takala/financial_phrasebank", "data/FinancialPhraseBank-v1.0.zip",
repo_type="dataset")
LAB2ID = {"negative": 0, "neutral": 1, "positive": 2}
rows = []
with zipfile.ZipFile(zp) as z:
with z.open("FinancialPhraseBank-v1.0/Sentences_50Agree.txt") as fh:
for line in io.TextIOWrapper(fh, encoding="latin-1"):
line = line.strip()
if not line or "@" not in line:
continue
sent, lab = line.rsplit("@", 1)
rows.append({"sentence": sent.strip(), "label": LAB2ID[lab.strip()]})
print(f"FPB 50agree rows: {len(rows)}", flush=True)
# published split: stratified 80/10/10, split-seed FIXED at 42 regardless of
# training seed (same rule as Parable corpus splits)
import random as _rnd
_rnd.Random(42).shuffle(rows)
by_label = {i: [] for i in range(3)}
for r in rows:
by_label[r["label"]].append(r)
train, val, test = [], [], []
for lab, rows in by_label.items():
n = len(rows)
test += rows[: int(n * 0.10)]
val += rows[int(n * 0.10): int(n * 0.20)]
train += rows[int(n * 0.20):]
print(f"split: train={len(train)} val={len(val)} test={len(test)}", flush=True)
if args.corpus:
train = [json.loads(l) for l in open(args.corpus)]
import random as _r2
_r2.Random(args.seed).shuffle(train)
print(f"corpus override: {args.corpus} -> train={len(train)}", flush=True)
_trc = {"trust_remote_code": True} if "NeoBERT" in args.base else {}
tok = AutoTokenizer.from_pretrained(args.base, **_trc)
def enc(rows):
from datasets import Dataset
d = Dataset.from_list(rows)
return d.map(lambda b: tok(b["sentence"], truncation=True, max_length=128),
batched=True, remove_columns=["sentence"])
torch.manual_seed(args.seed)
model = AutoModelForSequenceClassification.from_pretrained(
args.base, num_labels=3, **_trc,
id2label=dict(enumerate(LABELS)), label2id={l: i for i, l in enumerate(LABELS)})
def metrics(pred):
y, yhat = pred.label_ids, pred.predictions.argmax(-1)
return {"accuracy": accuracy_score(y, yhat),
"macro_f1": f1_score(y, yhat, average="macro")}
from transformers import TrainerCallback
class MPSCacheFlush(TrainerCallback):
"""torch-MPS leaks on deberta attention kernels; flush or the kernel
OOM-kills the process silently around step ~100 on a 16GB machine."""
def on_step_end(self, args, state, control, **kw):
if state.global_step % 25 == 0 and torch.backends.mps.is_available():
torch.mps.empty_cache()
import transformers as _tf
_tok_kw = {"processing_class": tok} if int(_tf.__version__.split(".")[0]) >= 5 else {"tokenizer": tok}
trainer = Trainer(
model=model,
**_tok_kw,
callbacks=[MPSCacheFlush()],
train_dataset=enc(train),
eval_dataset=enc(val),
compute_metrics=metrics,
args=TrainingArguments(
output_dir=f"{OUT}/ckpt",
num_train_epochs=1 if args.smoke else 5,
max_steps=50 if args.smoke else -1,
per_device_train_batch_size=8, gradient_accumulation_steps=2, per_device_eval_batch_size=16,
learning_rate=(1e-5 if "deberta" in args.base else 2e-5), warmup_ratio=0.1, weight_decay=0.01,
eval_strategy="epoch" if not args.smoke else "no",
save_strategy="epoch" if not args.smoke else "no",
load_best_model_at_end=not args.smoke,
metric_for_best_model="macro_f1",
logging_steps=25, seed=args.seed, report_to=[],
use_cpu=False,
# deberta-v3's disentangled attention overflows under fp16 -> class
# collapse; transformers v5 auto-enables mixed precision on CUDA
fp16=False, bf16=False,
),
)
trainer.train()
# final: held-out test (never used for selection)
pred = trainer.predict(enc(test))
y, yhat = pred.label_ids, pred.predictions.argmax(-1)
res = {"base": args.base, "seed": args.seed, "smoke": args.smoke,
"test_accuracy": float(accuracy_score(y, yhat)),
"test_macro_f1": float(f1_score(y, yhat, average="macro")),
"confusion_matrix": confusion_matrix(y, yhat).tolist(),
"labels": LABELS, "split": "stratified 80/10/10, split-seed 42",
"corpus": args.corpus or "fpb-train",
"dataset": "financial_phrasebank sentences_50agree"}
json.dump(res, open(f"{OUT}/result.json", "w"), indent=1)
print("FINSENSE_RESULT", json.dumps(res), flush=True)
if not args.smoke:
model.save_pretrained(f"{OUT}/model")
tok.save_pretrained(f"{OUT}/model")
|