Instructions to use 9mark9/finbert-minilm-sentiment with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use 9mark9/finbert-minilm-sentiment with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-classification", model="9mark9/finbert-minilm-sentiment")# pip install -U transformers accelerate # Load model directly from transformers import AutoTokenizer, AutoModelForSequenceClassification tokenizer = AutoTokenizer.from_pretrained("9mark9/finbert-minilm-sentiment") model = AutoModelForSequenceClassification.from_pretrained("9mark9/finbert-minilm-sentiment", device_map="auto") - Notebooks
- Google Colab
- Kaggle
finbert-minilm-sentiment
A compact financial-sentiment classifier: 33.4M parameters, 97.9% on PhraseBank test, 86.4% on finance tweets.
finbert-minilm-sentiment classifies financial text into negative / neutral / positive sentiment.
It is a compact MiniLM encoder (microsoft/MiniLM-L12-H384-uncased, 33.4M parameters)
fine-tuned on a mix of formal financial sentences
(Financial PhraseBank),
finance tweets (zeroshot/twitter-financial-news-sentiment)
and press-release headlines (Jean-Baptiste/financial_news_sentiment).
All numbers below are measured on data held out from training and model selection.
This is version 3 (October 2026): the same architecture and file format as before, now the uniform weight average of ten fine-tuning runs, with extra PhraseBank sentences in training. Earlier weights stay available at git tags:
v2-mixed-domain: one run on the same three sources.v1-phrasebank-only: PhraseBank only; good in-domain, weak on tweets.
Companion project: the from-scratch educational transformer (~2M params, no pretrained weights, fully annotated) lives at github.com/shaikn6/nano-finbert.
Results (held-out sets)
All versions were scored with the same code (eval_model.py): max 96 tokens, highest-probability
label. Measured 2026-10-28. Cells are accuracy / macro-F1.
| Held-out set | n | v1 | v2 | v3 |
|---|---|---|---|---|
PhraseBank Sentences_AllAgree, stratified 15% test split |
340 | 95.29% / 0.9368 | 96.47% / 0.9532 | 97.94% / 0.9643 |
| Finance tweets, full validation file | 2,388 | 68.22% / 0.6165 | 83.75% / 0.7995 | 86.43% / 0.8300 |
Press-release headlines (title field), full test split |
267 | 71.16% / 0.5864 | 72.28% / 0.5594 | 75.28% / 0.5904 |
| SEntFiN news headlines, full test split, unanimous headlines | 933 | 69.67% / 0.6977 | 73.20% / 0.7361 | 75.99% / 0.7627 |
Mean macro-F1 over the four sets: v1 0.7093, v2 0.7620, v3 0.7868 (+2.48 points over v2).
Label mapping: tweets Bearish = negative, Bullish = positive, Neutral = neutral. Press-release headlines use the dataset's own class names (0 negative, 1 neutral, 2 positive). SEntFiN labels each company or entity in a headline; only headlines where every entity carries the same label are kept. SEntFiN was never used for training or model selection.
95% intervals (2,000 bootstrap resamples of the rows), v2 vs v3:
| Set | n | v2 macro-F1 | v3 macro-F1 | v2 negative recall | v3 negative recall |
|---|---|---|---|---|---|
| PhraseBank test | 340 | 0.953 (0.921 to 0.978) | 0.964 (0.934 to 0.988) | 0.933 (0.848 to 1.000) | 0.911 (0.816 to 0.979) |
| Tweets | 2,388 | 0.800 (0.781 to 0.817) | 0.830 (0.812 to 0.847) | 0.865 (0.827 to 0.900) | 0.850 (0.810 to 0.886) |
| Press-release headlines | 267 | 0.559 (0.470 to 0.648) | 0.590 (0.485 to 0.695) | 0.167 (0.000 to 0.417) | 0.167 (0.000 to 0.417) |
| SEntFiN headlines | 933 | 0.736 (0.708 to 0.763) | 0.763 (0.735 to 0.790) | 0.757 (0.703 to 0.808) | 0.722 (0.667 to 0.773) |
The intervals overlap on every set, so treat any single-set gain as uncertain; the consistent direction across all four sets is the stronger evidence.
Per-class results, v3 (v2 in brackets):
| Set | Class | Support | Precision | Recall | F1 |
|---|---|---|---|---|---|
| PhraseBank test | negative | 45 | 0.953 (0.955) | 0.911 (0.933) | 0.932 (0.944) |
| PhraseBank test | neutral | 209 | 1.000 (0.995) | 0.990 (0.971) | 0.995 (0.983) |
| PhraseBank test | positive | 86 | 0.944 (0.902) | 0.988 (0.965) | 0.966 (0.933) |
| Tweets | negative | 347 | 0.725 (0.638) | 0.850 (0.865) | 0.782 (0.734) |
| Tweets | neutral | 1,566 | 0.946 (0.941) | 0.870 (0.837) | 0.906 (0.886) |
| Tweets | positive | 475 | 0.752 (0.741) | 0.857 (0.819) | 0.801 (0.778) |
| Press-release headlines | negative | 12 | 0.667 (0.286) | 0.167 (0.167) | 0.267 (0.211) |
| Press-release headlines | neutral | 156 | 0.764 (0.774) | 0.853 (0.769) | 0.806 (0.772) |
| Press-release headlines | positive | 99 | 0.733 (0.676) | 0.667 (0.717) | 0.698 (0.696) |
| SEntFiN headlines | negative | 259 | 0.850 (0.794) | 0.722 (0.757) | 0.781 (0.775) |
| SEntFiN headlines | neutral | 342 | 0.676 (0.668) | 0.825 (0.754) | 0.743 (0.709) |
| SEntFiN headlines | positive | 332 | 0.811 (0.763) | 0.723 (0.690) | 0.764 (0.725) |
Reading the tables:
- v3 is better than v2 on macro-F1 on all four sets. On tweets it labels far fewer neutral or bullish tweets as negative (negative precision 0.638 to 0.725).
- Where negative F1 rose, it came from precision, not recall. Negative recall is slightly lower on PhraseBank (0.933 to 0.911), tweets (0.865 to 0.850) and SEntFiN (0.757 to 0.722): v3 misses a few more negatives and raises fewer false alarms on tweets and SEntFiN.
- Negative press-release headlines are still weak: v3 finds 2 of 12 (v2 found 2). With 12 negatives that figure is very uncertain. On SEntFiN (259 negative headlines) negative recall is 0.722, so the problem is mainly with this one set of press releases.
- The datasets define sentiment differently (tweets label market direction, PhraseBank labels the tone of a reporting sentence), so the numbers are not directly comparable across rows.
Release rule (and one change to it)
The rule for releasing v3 was set before training: mean macro-F1 over the held-out sets up by at least 1.5 points, no set down by more than 1.0 point, and negative-class F1 no worse on any set.
The candidate (chosen on validation slices only, before any held-out score was seen) met the first two conditions but failed the third by one sentence: PhraseBank negative F1 fell from 0.944 to 0.932 because it found 41 of the 45 negative test sentences and v2 found 42. It was therefore not released at first.
After seeing those held-out results, the third condition was amended to "negative-class F1 falls by no more than 2.0 points on any set". The reason: on PhraseBank that condition is judged on 45 negatives, so one sentence decides it, which is far inside the bootstrap noise (the negative-recall interval spans about 16 points). Changing a rule after seeing test results is a weaker form of evidence, so the original outcome is stated here. No other candidate was searched for after the amendment; the same recipe was retrained and the retrained weights re-scored before release. Before the amendment, one other candidate (a single run, picked on validation with a negative-F1 filter) was scored on the held-out sets once; it failed (mean +0.87 points) and was discarded.
Released weights against v2 (macro-F1 change in points; negative F1):
| Set | Macro-F1 change | v2 negative F1 | v3 negative F1 | Change (points) |
|---|---|---|---|---|
| PhraseBank test | +1.12 | 0.944 | 0.932 | -1.20 |
| Tweets | +3.05 | 0.734 | 0.782 | +4.81 |
| Press-release headlines | +3.09 | 0.211 | 0.267 | +5.61 |
| SEntFiN headlines | +2.67 | 0.775 | 0.781 | +0.61 |
Mean macro-F1 change +2.48 points. Under the original strict rule these released weights would still fail (PhraseBank negative F1 0.932 vs 0.944); under the amended rule they pass. The first-pass candidate scored +2.38 points mean; because Apple-GPU training is not bit-for-bit deterministic, the retrained weights differ slightly from it, and the numbers in this card are for the released file.
Training data
Held out and never used for training or model selection: the PhraseBank 15% test split, the full tweet validation file, the headline test split and all of SEntFiN. Any training text that also appears in one of these or in a validation slice (17 rows, case-insensitive) was dropped.
| Source | Used for training | Used for validation |
|---|---|---|
PhraseBank Sentences_AllAgree |
70% train split (1,584), repeated 3x per epoch | 15% validation split (340) |
| PhraseBank 50% / 66% / 75% agreement files | only sentences not in the all-agree file at all (2,582 / 1,953 / 1,189); majority label; one variant per run | - |
Tweets, sent_train.csv |
90% stratified (8,588) | 10% stratified (955) |
| Headlines, train split | 90% stratified (1,360); repeated 2x in three runs | 10% stratified (152) |
Because the extra PhraseBank sentences exclude every all-agree sentence, none of the all-agree
test or validation sentences can appear in training. PhraseBank split: stratified 70 / 15 / 15
with scikit-learn train_test_split(random_state=42), test carved first, then validation.
Tweets and headlines: one stratified 90 / 10 split (random_state=42) of each training file.
Training details
| Setting | Value |
|---|---|
| Base encoder | microsoft/MiniLM-L12-H384-uncased (12 layers, hidden 384) |
| Parameters | 33,361,155 (~33.4M) |
| Objective | class-weighted cross-entropy (inverse class frequency) |
| Optimizer | AdamW, weight decay 0.01, gradient clip 1.0 |
| Schedule | linear, 10% warmup; batch 16; max 96 tokens |
| Epochs | up to 3 (4 for the 2e-5 run); validation twice per epoch, early stopping |
| Checkpoint per run | best mean macro-F1 over the three validation slices |
| Final weights | uniform average of the ten run checkpoints below (same architecture, same file format) |
| Hardware | one laptop (Apple GPU) |
The ten runs (train.py flags):
| Run | Flags |
|---|---|
| 1 | --lr 3e-5 |
| 2 | --lr 3e-5 --pb-extra 50Agree |
| 3 | --lr 3e-5 --pb-extra 75Agree |
| 4 | --lr 3e-5 --pb-extra 66Agree |
| 5 | --lr 2e-5 --pb-extra 66Agree --epochs 4 --patience 3 |
| 6 | --lr 5e-5 --pb-extra 66Agree |
| 7 | --lr 3e-5 --pb-extra 66Agree --seed 1 |
| 8 | --lr 3e-5 --pb-extra 66Agree --jb-rep 2 --seed 2 |
| 9 | --lr 3e-5 --pb-extra 66Agree --jb-rep 2 --seed 3 |
| 10 | --lr 5e-5 --pb-extra 66Agree --jb-rep 2 --seed 4 |
Also tried and not used: label smoothing 0.1, and 8,000 short machine-labelled news snippets from NOSIBLE/financial-sentiment; neither improved the validation score.
Reproduce
train.py, common.py, eval_model.py and average.py in this repo hold the code;
requirements-train.txt lists the package versions. All datasets are downloaded from the Hub at
pinned revisions.
pip install -r requirements-train.txt
python train.py --lr 3e-5 --out run1 # one run, about 10-20 minutes on a laptop GPU
# ... the other nine runs from the table above, each with its own --out
python average.py v3 run1 run2 run3 run4 run5 run6 run7 run8 run9 run10
python eval_model.py v3 - scores.json # all held-out sets, with bootstrap intervals
Training on an Apple GPU is not bit-for-bit deterministic, so a rerun lands close to, not exactly
on, the published numbers. v2 was trained with an earlier copy of train.py that dropped 12
held-out overlaps instead of 17.
Usage
import torch
from transformers import AutoModelForSequenceClassification, AutoTokenizer
model_id = "9mark9/finbert-minilm-sentiment"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForSequenceClassification.from_pretrained(model_id)
model.eval()
texts = [
"The company reported record quarterly profit, beating analyst expectations.",
"Net sales decreased by 12% and the firm warned of further losses ahead.",
"The board will meet next Tuesday to review the quarterly schedule.",
]
enc = tokenizer(texts, padding=True, truncation=True, max_length=96, return_tensors="pt")
with torch.no_grad():
probs = model(**enc).logits.softmax(-1)
for text, p in zip(texts, probs):
label = model.config.id2label[int(p.argmax())]
print(f"[{label:8s} {p.max():.2f}] {text}")
To load an earlier version, pass revision="v2-mixed-domain" or revision="v1-phrasebank-only"
to both from_pretrained calls.
Intended use
- Sentiment scoring of short financial text: news sentences, headlines, finance tweets, analyst notes.
- Research, prototyping and teaching, as a compact CPU-friendly alternative to 110M-parameter FinBERT.
Limitations
Overlap found after release: when links and punctuation are also stripped before comparing, 80 of the tweet training rows and 8 of the headline training rows match held-out test text (for example the same headline with a different link). This model was trained on them. Worst case, if all 80 matching tweets were answered correctly only because of that, the tweet accuracy of 86.4% would be about 0.5 points lower (85.96% on the other 2,308 tweets). The newer finbert-deberta-large-sentiment was trained without those rows.
English only. Max sequence length 96 tokens; longer text is truncated.
Weak on negative press-release headlines (2 of 12 found in that test split). On a larger set of news headlines (SEntFiN, 259 negatives) negative recall is 0.722.
On finance tweets about 28% of the tweets it calls negative are labelled neutral or bullish.
PhraseBank
AllAgreeis the cleanest, easiest part of that benchmark; expect lower accuracy on ambiguous sentences.Tweet labels describe bullish / bearish market direction, which is not the same as the tone of a sentence. The model mixes both conventions.
Not financial advice. Outputs are sentiment labels, not buy/sell signals.
Citation
Dataset:
@article{malo2014good,
title={Good debt or bad debt: Detecting semantic orientations in economic texts},
author={Malo, Pekka and Sinha, Ankur and Korhonen, Pekka and Wallenius, Jyrki and Takala, Pyry},
journal={Journal of the Association for Information Science and Technology},
volume={65}, number={4}, pages={782--796}, year={2014}
}
Contact
Maintainer: nagizaazs@gmail.com
License
MIT
- Downloads last month
- 312
Datasets used to train 9mark9/finbert-minilm-sentiment
zeroshot/twitter-financial-news-sentiment
Jean-Baptiste/financial_news_sentiment
Space using 9mark9/finbert-minilm-sentiment 1
Collection including 9mark9/finbert-minilm-sentiment
Evaluation results
- Test Accuracy on Financial PhraseBank (Sentences_AllAgree)self-reported0.979
- Test Macro-F1 on Financial PhraseBank (Sentences_AllAgree)self-reported0.964
- Validation Accuracy on Twitter Financial News Sentimentvalidation set self-reported0.864
- Validation Macro-F1 on Twitter Financial News Sentimentvalidation set self-reported0.830
- Test Accuracy on SEntFiN headlines (unanimous, test split)test set self-reported0.760
- Test Macro-F1 on SEntFiN headlines (unanimous, test split)test set self-reported0.763