CoolFace
Modelpublic

gamug/FinBERT-financial-news

sourceHugging Facecc-by-nc-4.0updated 6d agoView on Hugging Face
0likes66downloads
Model Card

FinBERT-financial-news

A continued fine-tune of `ProsusAI/finbert` on sentences from real English-language financial news articles (2010s-2020s), labeled by an LLM (DeepSeek deepseek-chat) from an investor/price-impact perspective — not a from-scratch retrain: the label space (positive/negative/neutral) and task framing are unchanged from the base model, only the training sentences (and, as of v4, the loss weighting) differ.

v4 (2026-09-15): class-weighted loss — this is the version this repo's pipeline runs

v1/v2's training data was 56.1% neutral / 22.8% negative / 21.1% positive — never a deliberate target. Two fixes were tried for it: v3 downsampled neutral (a real trade — see that version's own notes, still published here as an available checkpoint but not adopted); v4 instead keeps the full original pool and applies an inverse-class-frequency-weighted CrossEntropyLoss (train_sentiment.py --weighted) so no training sentence is discarded, only reweighted. A third approach — swapping the base checkpoint to nlpaueb/sec-bert-base instead of a data-side fix — was also tried and rejected before a downstream eval, since it lost to v2 on every sentence-level and idiom-probe metric (see the portfolio-nlp repo's docs/evaluation.md, 2026-09-15).

This version (v4) is the one `portfolio-nlp`'s production pipeline actually pins — src/pipeline.py's MODEL_REVISIONS was updated to this commit in the same change that published it, a deliberate adoption decision, not a default. Read the honest trade below before assuming "newest = strictly better."

One-vs-rest accuracy per class — held-out sentence-level test set (n=579, same split as v2)

classv2 (published)**v4 (this version)**
Positiveaccuracy_ovr 0.9020.900
Negativeaccuracy_ovr 0.8720.876
Neutralaccuracy_ovr 0.8220.817
OverallAccuracy / Macro F1 0.798 / 0.7790.796 / 0.778

Unlike v3, v4 doesn't meaningfully move any class here — every number sits within ~0.01 of v2.

One-vs-rest accuracy per class — idiom probe (n=100, held out of training)

classv2 (published)**v4 (this version)**
Positiveaccuracy_ovr 0.930.92
Negativeaccuracy_ovr 0.900.87
Neutralaccuracy_ovr 0.910.91
OverallAccuracy / Macro F1 0.870 / 0.7590.850 / 0.745

The number that matters most here: v4's idiom-probe neutral accuracy_ovr (0.91) lands exactly on v2's (0.91) — the catastrophic collapse to 0.0 (measured in F1) that made v3 a real regression simply doesn't happen with class weighting, since no neutral training sentence is ever discarded.

One-vs-rest accuracy per class — downstream, real-traffic production-pipeline eval (n=2000, LLM-judge)

The evaluation that actually validated v2 in the first place (entity-scoped, chunk-level aggregation — the real run_sentiment_stage code path, not sentence-level scoring in isolation). Full per-class precision/recall/F1 breakdown (not just accuracy_ovr) is in the portfolio-nlp repo's docs/evaluation.md, 2026-09-15 follow-up:

classv2 (published)**v4 (this version)**
Positiveaccuracy_ovr 0.8780.870
Negativeaccuracy_ovr 0.8820.877
Neutralaccuracy_ovr 0.8110.803
Overallagreement_rate 0.7010.674
Overallmacro_f1_vs_judge 0.7310.724
Overallmean_severity (lower is better) 0.3410.369

Why v4: recall_negative is this pipeline's stated priority metric — missing a real negative-sentiment article is a worse failure mode than a false alarm for this use case — and v4 delivers a real gain there (0.808→0.832, full precision/recall/F1 breakdown in docs/evaluation.md). The cost is real too, not hidden: every other per-class cell in that breakdown moves slightly in v2's favor, and both agreement_rate and mean_severity above get worse. This is a deliberate, disclosed trade, not a strict improvement — made with the full breakdown in hand, not before it.

Why this exists (v1, unchanged)

ProsusAI/finbert was fine-tuned on Financial PhraseBank — ~4,840 sentences from 2014 English-language news about OMX Helsinki (Nordic) listed companies, via LexisNexis. That's a real, measurable domain/vocabulary gap for a pipeline scoring 2010s-2020s English-language financial news on globally-listed companies: real disagreement cases found during evaluation included the base model missing "crushed" as a positive idiom ("Amazon and Alphabet crushed earnings"), and terms/instruments (e.g. cryptocurrency) that didn't meaningfully exist in the base model's training window. This model targets that specific gap — a vocabulary/domain refresh, not an architecture or label-space change.

Training data

Full sourcing (base draw, idiom-augmentation round) documented in the companion dataset: `gamug/FinBERT-financial-news-data`. v4 trains on the original, unbalanced train split (4,642 rows: 56.1% neutral / 22.8% negative / 21.1% positive — same pool as v1/v2, not v3's rebalanced one), validates on validation (579), tests on test (579), and is additionally evaluated (never trained) on idiom_probe (100). Class imbalance is corrected at the loss level instead (inverse-frequency weights: neutral 0.594, negative 1.464, positive 1.581 — computed from the train split's own label counts), not by discarding data.

Important limitation, unchanged from v1/v2/v3: these labels are LLM-generated (silver-standard), not human-annotated ground truth. Treat the metrics above as "agreement with this specific LLM's sentence-level judgment" (offline) or "agreement with the downstream LLM judge" (production-pipeline table), not an absolute accuracy figure — the same caveat this project applies to every LLM-as-judge number in its own evaluation docs.

Training procedure

Identical to v2's procedure, except the loss function — via Hugging Face Trainer subclassed to apply the class weights above to CrossEntropyLoss:

  • —learning rate: 2e-5
  • —batch size: 16 (train) / 32 (eval)
  • —epochs: 4
  • —weight decay: 0.01
  • —mixed precision (fp16)
  • —load_best_model_at_end=True, selected by validation macro F1
  • —seed: 42

Usage

python
from transformers import AutoTokenizer, AutoModelForSequenceClassification, pipeline

tokenizer = AutoTokenizer.from_pretrained("gamug/FinBERT-financial-news")
model = AutoModelForSequenceClassification.from_pretrained("gamug/FinBERT-financial-news")

classifier = pipeline("text-classification", model=model, tokenizer=tokenizer)
classifier("Acme Corp reported record profit and raised its full-year guidance.")

Note: this loads whatever is currently the latest published commit (v4, as of this publish, and the commit portfolio-nlp's production pipeline actually pins). v2/v3 remain available at their own earlier commits in this repo's history if you need to reproduce an older comparison.

Recommended pre/post-processing: same as the base model — chunk long documents rather than truncating (this checkpoint keeps BERT-base's 512-token limit), and if aggregating multiple chunks/sentences per document yourself, consider scoping/weighting toward the actual subject of the document rather than a plain average (see the portfolio-nlp repo above for one worked design and its measured trade-offs).

License

Derivative of two upstream works with different licenses:

  • —Base model ProsusAI/finbert.
  • —Training data here: LLM-generated from real news article text; the underlying articles are not redistributed, only derived per-sentence sentiment labels used for training.

Released under `CC-BY-NC-4.0`, matching the base model's own license: attribution required, non-commercial use only.

Related