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)

class v2 (published) v4 (this version)
Positive accuracy_ovr 0.902 0.900
Negative accuracy_ovr 0.872 0.876
Neutral accuracy_ovr 0.822 0.817
Overall Accuracy / Macro F1 0.798 / 0.779 0.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)

class v2 (published) v4 (this version)
Positive accuracy_ovr 0.93 0.92
Negative accuracy_ovr 0.90 0.87
Neutral accuracy_ovr 0.91 0.91
Overall Accuracy / Macro F1 0.870 / 0.759 0.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:

class v2 (published) v4 (this version)
Positive accuracy_ovr 0.878 0.870
Negative accuracy_ovr 0.882 0.877
Neutral accuracy_ovr 0.811 0.803
Overall agreement_rate 0.701 0.674
Overall macro_f1_vs_judge 0.731 0.724
Overall mean_severity (lower is better) 0.341 0.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

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

Downloads last month
11
Safetensors
Model size
0.1B params
Tensor type
F32
Β·
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Model tree for gamug/FinBERT-financial-news

Base model

ProsusAI/finbert
Finetuned
(114)
this model