peyterho commited on
Commit
915188e
Β·
verified Β·
1 Parent(s): 18d4ac4

Improve model card with comprehensive documentation

Browse files
Files changed (1) hide show
  1. README.md +376 -37
README.md CHANGED
@@ -2,65 +2,404 @@
2
  library_name: transformers
3
  base_model: ProsusAI/finbert
4
  tags:
5
- - generated_from_trainer
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  metrics:
7
  - accuracy
 
 
8
  model-index:
9
  - name: macro-sentiment-finbert
10
- results: []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  ---
12
 
13
- <!-- This model card has been generated automatically according to the information the Trainer had access to. You
14
- should probably proofread and complete it, then remove this comment. -->
15
 
16
- # macro-sentiment-finbert
17
 
18
- This model is a fine-tuned version of [ProsusAI/finbert](https://huggingface.co/ProsusAI/finbert) on the None dataset.
19
- It achieves the following results on the evaluation set:
20
- - Loss: 0.3450
21
- - Accuracy: 0.8486
22
- - F1 Macro: 0.8325
23
- - F1 Weighted: 0.8515
24
 
25
- ## Model description
26
 
27
- More information needed
 
 
 
 
28
 
29
- ## Intended uses & limitations
30
 
31
- More information needed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
- ## Training and evaluation data
34
 
35
- More information needed
36
 
37
- ## Training procedure
 
 
 
 
 
38
 
39
- ### Training hyperparameters
 
 
 
 
 
40
 
41
- The following hyperparameters were used during training:
42
- - learning_rate: 2e-05
43
- - train_batch_size: 16
44
- - eval_batch_size: 32
45
- - seed: 42
46
- - gradient_accumulation_steps: 4
47
- - total_train_batch_size: 64
48
- - optimizer: Use OptimizerNames.ADAMW_TORCH_FUSED with betas=(0.9,0.999) and epsilon=1e-08 and optimizer_args=No additional optimizer arguments
49
- - lr_scheduler_type: cosine
50
- - lr_scheduler_warmup_steps: 31
51
- - num_epochs: 2
52
 
53
- ### Training results
54
 
55
- | Training Loss | Epoch | Step | Validation Loss | Accuracy | F1 Macro | F1 Weighted |
56
- |:-------------:|:-----:|:----:|:---------------:|:--------:|:--------:|:-----------:|
57
- | 1.6807 | 1.0 | 314 | 0.3761 | 0.8246 | 0.8058 | 0.8300 |
58
- | 1.1679 | 2.0 | 628 | 0.3450 | 0.8486 | 0.8325 | 0.8515 |
59
 
 
 
 
 
 
60
 
61
- ### Framework versions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
  - Transformers 5.6.2
64
- - Pytorch 2.11.0+cu130
65
  - Datasets 4.8.4
66
  - Tokenizers 0.22.2
 
2
  library_name: transformers
3
  base_model: ProsusAI/finbert
4
  tags:
5
+ - sentiment-analysis
6
+ - finance
7
+ - macroeconomics
8
+ - climate
9
+ - esg
10
+ - policy
11
+ - ensemble
12
+ - dictionary
13
+ - finbert
14
+ - text-classification
15
+ language:
16
+ - en
17
+ - ar
18
+ - fr
19
+ - de
20
+ - hi
21
+ - it
22
+ - pt
23
+ - es
24
+ license: apache-2.0
25
+ datasets:
26
+ - nickmuchi/financial-classification
27
+ - zeroshot/twitter-financial-news-sentiment
28
+ - FinanceInc/auditor_sentiment
29
+ - pauri32/fiqa-2018
30
+ - climatebert/climate_sentiment
31
  metrics:
32
  - accuracy
33
+ - f1
34
+ pipeline_tag: text-classification
35
  model-index:
36
  - name: macro-sentiment-finbert
37
+ results:
38
+ - task:
39
+ type: text-classification
40
+ name: Sentiment Analysis
41
+ dataset:
42
+ name: Combined Financial Sentiment (5 datasets)
43
+ type: custom
44
+ metrics:
45
+ - type: accuracy
46
+ value: 0.8973
47
+ name: Accuracy
48
+ - type: f1
49
+ value: 0.8813
50
+ name: F1 (macro)
51
+ - type: f1
52
+ value: 0.8984
53
+ name: F1 (weighted)
54
+ - task:
55
+ type: text-classification
56
+ name: Sentiment Analysis (OOD)
57
+ dataset:
58
+ name: Jean-Baptiste/financial_news_sentiment_mixte_with_phrasebank_75
59
+ type: Jean-Baptiste/financial_news_sentiment_mixte_with_phrasebank_75
60
+ metrics:
61
+ - type: accuracy
62
+ value: 0.9236
63
+ name: Accuracy
64
+ - type: f1
65
+ value: 0.9134
66
+ name: F1 (macro)
67
+ - task:
68
+ type: text-classification
69
+ name: Sentiment Analysis (OOD)
70
+ dataset:
71
+ name: ic-fspml/stock_news_sentiment
72
+ type: ic-fspml/stock_news_sentiment
73
+ metrics:
74
+ - type: accuracy
75
+ value: 0.6781
76
+ name: Accuracy
77
+ - type: f1
78
+ value: 0.6765
79
+ name: F1 (macro)
80
  ---
81
 
82
+ # Macro Sentiment FinBERT
 
83
 
84
+ **A multi-signal macroeconomic sentiment pipeline** that combines fine-tuned transformer ensembles with financial dictionaries and topic routing to produce structured sentiment analysis for financial news, central bank communications, climate/ESG reports, and social media.
85
 
86
+ This repo hosts the **FinBERT head** (109M params, fine-tuned from [ProsusAI/finbert](https://huggingface.co/ProsusAI/finbert)) β€” the default routing target and backbone of the full pipeline. The complete system includes three additional transformer heads, four dictionary scorers, and a keyword-based topic router.
 
 
 
 
 
87
 
88
+ ## Key Features
89
 
90
+ - 🏦 **Macro-aware** β€” outputs financial sentiment, monetary policy stance (dovish ↔ hawkish), crisis signals, and uncertainty
91
+ - 🌱 **Climate/ESG scoring** β€” dedicated ClimateBERT head + Sautner-style exposure dictionary for climate risk vs. opportunity
92
+ - πŸ”€ **Topic routing** β€” automatically selects the best transformer head based on text content (policy β†’ RoBERTa-Large, climate β†’ ClimateBERT, financial news β†’ FinBERT)
93
+ - 🌍 **Multilingual** β€” non-English text auto-routes to XLM-RoBERTa (8+ languages: EN, AR, FR, DE, HI, IT, PT, ES)
94
+ - πŸ“– **Dictionary layer** β€” Loughran-McDonald, Henry earnings tone, climate exposure, and macro policy dictionaries provide interpretable feature signals alongside neural predictions
95
 
96
+ ## Architecture
97
 
98
+ ```
99
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
100
+ β”‚ Input Text β”‚
101
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
102
+ β”‚
103
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
104
+ β–Ό β–Ό β–Ό
105
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
106
+ β”‚ Topic Router β”‚ β”‚ Dictionary β”‚ β”‚ Language β”‚
107
+ β”‚ (keywords) β”‚ β”‚ Scorers (Γ—4) β”‚ β”‚ Detection β”‚
108
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
109
+ β”‚ β”‚ β”‚
110
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
111
+ β”‚ Head Selection β”‚
112
+ β”‚ policy β†’ RoBERTa-Large β”‚ climate β†’ ClimateBERT β”‚
113
+ β”‚ financial β†’ FinBERT β˜… β”‚ non-English β†’ XLM-RoBERTa β”‚
114
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
115
+ β”‚ β”‚
116
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
117
+ β”‚ Transformer β”‚ β”‚ Dictionary β”‚
118
+ β”‚ Score [-1,+1] β”‚ β”‚ Composite β”‚
119
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
120
+ β”‚ β”‚
121
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
122
+ β–Ό
123
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
124
+ β”‚ Weighted Fusion β”‚
125
+ β”‚ (crisis-adaptive weights) β”‚
126
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
127
+ β–Ό
128
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
129
+ β”‚ MacroSentimentResult β”‚
130
+ β”‚ β€’ macro_sentiment [-1,+1] β”‚
131
+ β”‚ β€’ policy_stance [-1,+1] β”‚
132
+ β”‚ β€’ crisis_signal [0,1] β”‚
133
+ β”‚ β€’ climate_sentiment [-1,+1] β”‚
134
+ β”‚ β€’ confidence [0,1] β”‚
135
+ β”‚ β€’ detected_domain β”‚
136
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
137
+ ```
138
 
139
+ The fusion weights are **crisis-adaptive**: when the crisis dictionary fires strongly, more weight shifts to the dictionary composite (up to 75% dict / 25% transformer), since crisis language often carries clearer signal through keywords than neural softmax probabilities.
140
 
141
+ ## Ensemble Components
142
 
143
+ | Head | Model | Params | Base | Role |
144
+ |---|---|---|---|---|
145
+ | **FinBERT** β˜… | [peyterho/finbert-macro-sentiment](https://huggingface.co/peyterho/finbert-macro-sentiment) | 109M | [ProsusAI/finbert](https://huggingface.co/ProsusAI/finbert) | Default β€” financial news, tweets |
146
+ | **RoBERTa-Large** | [peyterho/financial-roberta-large-macro-sentiment](https://huggingface.co/peyterho/financial-roberta-large-macro-sentiment) | 355M | [soleimanian/financial-roberta-large-sentiment](https://huggingface.co/soleimanian/financial-roberta-large-sentiment) | Policy/macro text |
147
+ | **ClimateBERT** | [peyterho/climatebert-macro-sentiment](https://huggingface.co/peyterho/climatebert-macro-sentiment) | 82M | [climatebert/distilroberta-base-climate-sentiment](https://huggingface.co/climatebert/distilroberta-base-climate-sentiment) | Climate/ESG text |
148
+ | **XLM-RoBERTa** | [cardiffnlp/twitter-xlm-roberta-base-sentiment-multilingual](https://huggingface.co/cardiffnlp/twitter-xlm-roberta-base-sentiment-multilingual) | 278M | β€” | Non-English text (8+ languages) |
149
 
150
+ | Dictionary | Based On | Signal |
151
+ |---|---|---|
152
+ | **Loughran-McDonald** | Loughran & McDonald (2011) | Financial polarity, subjectivity |
153
+ | **Henry** | Henry (2008) β€” earnings tone | Earnings press release tone |
154
+ | **Climate Exposure** | Sautner et al. (2023) style | Climate risk vs. opportunity density |
155
+ | **Macro Policy** | Custom | Hawkish/dovish stance, crisis intensity, uncertainty |
156
 
157
+ ## Quick Start
 
 
 
 
 
 
 
 
 
 
158
 
159
+ ### Standalone FinBERT (this model only)
160
 
161
+ ```python
162
+ from transformers import pipeline
 
 
163
 
164
+ classifier = pipeline("text-classification", model="peyterho/macro-sentiment-finbert", top_k=None)
165
+ result = classifier("The Federal Reserve signaled a pause in rate hikes amid cooling inflation.")
166
+ print(result)
167
+ # [[{'label': 'positive', 'score': 0.72}, {'label': 'neutral', 'score': 0.21}, {'label': 'negative', 'score': 0.07}]]
168
+ ```
169
 
170
+ ### Full Pipeline (recommended)
171
+
172
+ ```python
173
+ from macro_sentiment import MacroSentimentPipeline
174
+
175
+ pipe = MacroSentimentPipeline(device="cpu")
176
+
177
+ # Financial news β€” auto-routes to FinBERT
178
+ result = pipe("Markets rallied on strong earnings, with the S&P 500 hitting record highs.")
179
+ print(result.summary())
180
+ # Sentiment: Positive (+0.612) | Policy: Neutral (+0.000) | Crisis: Normal (0.000) | Domain: financial_news
181
+
182
+ # Central bank communication β€” auto-routes to RoBERTa-Large
183
+ result = pipe("The ECB raised rates by 25 basis points, citing persistent inflation pressures.")
184
+ print(result.summary())
185
+ # Sentiment: Negative (-0.348) | Policy: Hawkish (+0.714) | Crisis: Normal (0.000) | Domain: policy
186
+
187
+ # Climate/ESG text β€” auto-routes to ClimateBERT
188
+ result = pipe("The company committed to net-zero emissions by 2040 through renewable energy investments.")
189
+ print(result.summary())
190
+ # Sentiment: Positive (+0.445) | Policy: Neutral (+0.000) | Crisis: Normal (0.000) | Domain: climate | Climate: Opportunity (exp=0.60)
191
+
192
+ # Non-English text β€” auto-routes to XLM-RoBERTa
193
+ result = pipe("Die EZB signalisiert Geduld bei kΓΌnftigen Zinssenkungen.")
194
+ print(result.summary())
195
+ ```
196
+
197
+ ### Structured Output
198
+
199
+ Every call returns a `MacroSentimentResult` with these fields:
200
+
201
+ | Field | Range | Description |
202
+ |---|---|---|
203
+ | `macro_sentiment` | [-1, +1] | Overall macroeconomic sentiment (weighted fusion of transformer + dictionary) |
204
+ | `financial_sentiment` | [-1, +1] | Financial-domain sentiment from the selected transformer head |
205
+ | `policy_stance` | [-1, +1] | Monetary policy orientation: -1 = very dovish, +1 = very hawkish |
206
+ | `climate_sentiment` | [-1, +1] | Climate outlook: -1 = risk, +1 = opportunity |
207
+ | `crisis_signal` | [0, 1] | Crisis language intensity (recession, contagion, bank failure, etc.) |
208
+ | `uncertainty` | [0, 1] | Uncertainty/volatility language density |
209
+ | `confidence` | [0, 1] | Pipeline confidence (based on head agreement, topic match, uncertainty) |
210
+ | `detected_domain` | str | Routed domain: `financial_news`, `policy`, `climate`, `social`, `ensemble` |
211
+ | `head_used` | str | Which transformer head was selected |
212
+ | `lm_polarity` | [-1, +1] | Loughran-McDonald polarity score |
213
+ | `henry_polarity` | [-1, +1] | Henry earnings tone score |
214
+ | `climate_exposure` | [0, 1] | Climate keyword density (Sautner-style) |
215
+
216
+ ### Batch Scoring
217
+
218
+ ```python
219
+ texts = [
220
+ "GDP growth exceeded expectations at 3.2% annualized.",
221
+ "Credit markets froze as contagion fears spread across European banks.",
222
+ "Solar installations surged 40% year-over-year globally.",
223
+ ]
224
+ results = pipe.score_batch(texts, mode="routed")
225
+ for text, r in zip(texts, results):
226
+ print(f"{r.summary()}")
227
+ print(f" β†’ {text[:80]}...\n")
228
+ ```
229
+
230
+ ### Scoring Modes
231
+
232
+ ```python
233
+ # "routed" (default) β€” topic router selects best head
234
+ result = pipe.score("text", mode="routed")
235
+
236
+ # "all" β€” runs ALL heads and averages composite scores
237
+ result = pipe.score("text", mode="all")
238
+
239
+ # "dict_only" β€” dictionary signals only, no transformer inference
240
+ result = pipe.score("text", mode="dict_only")
241
+ ```
242
+
243
+ ## Training Data
244
+
245
+ All three transformer heads were fine-tuned on the same combined dataset of **5 public financial/climate sentiment corpora**:
246
+
247
+ | Dataset | Domain | Samples | Label Mapping |
248
+ |---|---|---|---|
249
+ | [nickmuchi/financial-classification](https://huggingface.co/datasets/nickmuchi/financial-classification) | Financial PhraseBank | ~4,800 train / ~1,200 test | negative / neutral / positive |
250
+ | [zeroshot/twitter-financial-news-sentiment](https://huggingface.co/datasets/zeroshot/twitter-financial-news-sentiment) | Financial tweets | ~9,900 train / ~2,500 val | bearish β†’ neg, bullish β†’ pos, neutral |
251
+ | [FinanceInc/auditor_sentiment](https://huggingface.co/datasets/FinanceInc/auditor_sentiment) | Auditor reports | ~3,600 train / ~900 test | negative / neutral / positive |
252
+ | [pauri32/fiqa-2018](https://huggingface.co/datasets/pauri32/fiqa-2018) | Financial QA + microblog | ~938 train+val / ~235 test | Continuous score thresholded at Β±0.15 |
253
+ | [climatebert/climate_sentiment](https://huggingface.co/datasets/climatebert/climate_sentiment) | Climate reports | ~1,000 train / ~500 test | risk β†’ neg, neutral, opportunity β†’ pos |
254
+
255
+ All datasets were unified to a consistent 3-class schema: `0=negative`, `1=neutral`, `2=positive`.
256
+
257
+ ## Training Details
258
+
259
+ ### FinBERT Head (this model)
260
+
261
+ | Hyperparameter | Value |
262
+ |---|---|
263
+ | Base model | [ProsusAI/finbert](https://huggingface.co/ProsusAI/finbert) |
264
+ | Learning rate | 2e-5 |
265
+ | Batch size | 16 Γ— 4 gradient accumulation = 64 effective |
266
+ | Epochs | 2 |
267
+ | Scheduler | Cosine with 31 warmup steps |
268
+ | Optimizer | AdamW (fused) |
269
+ | Max length | 512 |
270
+ | Seed | 42 |
271
+
272
+ ### Training Curve
273
+
274
+ | Epoch | Train Loss | Val Loss | Accuracy | F1 (macro) | F1 (weighted) |
275
+ |:---:|:---:|:---:|:---:|:---:|:---:|
276
+ | 1 | 1.6807 | 0.3761 | 0.8246 | 0.8058 | 0.8300 |
277
+ | 2 | 1.1679 | 0.3450 | 0.8486 | 0.8325 | 0.8515 |
278
+
279
+ > Note: The higher training loss reflects the FinBERT label ordering (positive=0, negative=1, neutral=2) which differs from the unified schema β€” a label remapping is applied at training time.
280
+
281
+ ## Evaluation Results
282
+
283
+ ### In-Domain (Combined Test Set β€” 4,333 samples)
284
+
285
+ | Model | Accuracy | F1 (macro) | F1 (weighted) |
286
+ |---|:---:|:---:|:---:|
287
+ | **RoBERTa-Large (355M)** | **0.9130** | **0.9023** | **0.9137** |
288
+ | FinBERT (109M) β˜… | 0.8973 | 0.8813 | 0.8984 |
289
+ | ClimateBERT (82M) | 0.8885 | 0.8716 | 0.8898 |
290
+ | Dict-only baseline (GBT) | 0.6693 | 0.5781 | 0.6500 |
291
+ | Dict-only baseline (rules) | 0.5684 | 0.5277 | 0.5784 |
292
+
293
+ ### Out-of-Domain: Financial News Phrasebank (785 samples)
294
+
295
+ Evaluated on [Jean-Baptiste/financial_news_sentiment_mixte_with_phrasebank_75](https://huggingface.co/datasets/Jean-Baptiste/financial_news_sentiment_mixte_with_phrasebank_75) β€” **not in the training mix**.
296
+
297
+ | Model | Accuracy | F1 (macro) |
298
+ |---|:---:|:---:|
299
+ | **RoBERTa-Large** | **0.9414** | **0.9357** |
300
+ | ClimateBERT | 0.9248 | 0.9213 |
301
+ | FinBERT β˜… | 0.9236 | 0.9134 |
302
+
303
+ ### Out-of-Domain: Stock News Headlines (30,150 samples)
304
+
305
+ Evaluated on [ic-fspml/stock_news_sentiment](https://huggingface.co/datasets/ic-fspml/stock_news_sentiment) β€” 5-class mapped to 3-class. **Not in the training mix**.
306
+
307
+ | Model | Accuracy | F1 (macro) |
308
+ |---|:---:|:---:|
309
+ | **RoBERTa-Large** | **0.7211** | **0.7265** |
310
+ | FinBERT β˜… | 0.6781 | 0.6765 |
311
+ | ClimateBERT | 0.6472 | 0.6441 |
312
+
313
+ Performance drops on stock news headlines are expected β€” these are short, noisy texts with 5β†’3 class mapping, representing a significant domain shift from the training data.
314
+
315
+ ## Custom Fine-Tuning
316
+
317
+ You can fine-tune any head on your own labelled data using the included `finetune.py` script:
318
+
319
+ ```bash
320
+ pip install -r requirements.txt
321
+
322
+ python -m macro_sentiment.finetune \
323
+ --data my_labels.csv \
324
+ --text-column headline \
325
+ --label-column sentiment \
326
+ --base-model peyterho/macro-sentiment-finbert \
327
+ --output my-org/my-custom-model \
328
+ --push-to-hub \
329
+ --epochs 4 \
330
+ --lr 2e-5 \
331
+ --batch-size 32
332
+ ```
333
+
334
+ Accepts CSV, TSV, JSON, or JSONL. Labels can be strings (`"positive"`, `"negative"`, `"neutral"`, `"bullish"`, `"bearish"`, `"risk"`, `"opportunity"`) or integers (0/1/2). Automatic class-weighted loss handles imbalanced datasets.
335
+
336
+ ## Limitations
337
+
338
+ - **English-centric** β€” the fine-tuned heads (FinBERT, RoBERTa, ClimateBERT) are English-only. Non-English text falls back to the pre-trained XLM-RoBERTa multilingual model, which was not fine-tuned on the macro sentiment training mix.
339
+ - **Domain-specific** β€” trained on financial news, earnings reports, climate disclosures, and financial tweets. Performance on general-purpose sentiment tasks (movie reviews, product reviews) will be lower.
340
+ - **Dictionary lag** β€” the Loughran-McDonald and Henry dictionaries use static word lists that may not capture emerging financial terminology or evolving language patterns.
341
+ - **No temporal awareness** β€” the model treats each text independently and does not incorporate time-series context or market state.
342
+ - **Topic router is keyword-based** β€” the routing heuristic may misclassify ambiguous texts. Use `mode="all"` for important decisions to get ensemble averaging across all heads.
343
+ - **Label noise** β€” the FiQA dataset uses continuous sentiment scores thresholded at Β±0.15 to create discrete labels, introducing boundary noise for scores near the thresholds.
344
+
345
+ ## Citation
346
+
347
+ If you use this model or pipeline, please cite the base models:
348
+
349
+ ```bibtex
350
+ @article{araci2019finbert,
351
+ title={FinBERT: Financial Sentiment Analysis with Pre-Trained Language Models},
352
+ author={Araci, Dogu},
353
+ journal={arXiv preprint arXiv:1908.10063},
354
+ year={2019}
355
+ }
356
+
357
+ @article{loughran2011liability,
358
+ title={When is a liability not a liability? Textual analysis, dictionaries, and 10-Ks},
359
+ author={Loughran, Tim and McDonald, Bill},
360
+ journal={The Journal of Finance},
361
+ volume={66},
362
+ number={1},
363
+ pages={35--65},
364
+ year={2011}
365
+ }
366
+
367
+ @article{henry2008earnings,
368
+ title={Are investors influenced by how earnings press releases are written?},
369
+ author={Henry, Elaine},
370
+ journal={Journal of Business Communication},
371
+ volume={45},
372
+ number={4},
373
+ pages={363--407},
374
+ year={2008}
375
+ }
376
+ ```
377
+
378
+ ## Repository Contents
379
+
380
+ ```
381
+ β”œβ”€β”€ model.safetensors # FinBERT fine-tuned weights (109M params)
382
+ β”œβ”€β”€ config.json # Model config (3-class: positive/negative/neutral)
383
+ β”œβ”€β”€ tokenizer.json # BERT tokenizer
384
+ β”œβ”€β”€ eval_results.json # In-domain benchmarks for all heads
385
+ β”œβ”€β”€ eval_ood_results.json # Out-of-domain benchmarks
386
+ β”œβ”€β”€ artifacts/
387
+ β”‚ β”œβ”€β”€ meta_classifier.joblib # GradientBoosting meta-classifier on dictionary features
388
+ β”‚ └─��� smart_router.joblib # Topic routing model
389
+ β”œβ”€β”€ macro_sentiment/
390
+ β”‚ β”œβ”€β”€ __init__.py # Public API exports
391
+ β”‚ β”œβ”€β”€ pipeline.py # MacroSentimentPipeline β€” main entry point
392
+ β”‚ β”œβ”€β”€ transformers_ensemble.py # Multi-head transformer ensemble + topic router
393
+ β”‚ β”œβ”€β”€ dictionaries.py # LM, Henry, Climate, Macro dictionary scorers
394
+ β”‚ β”œβ”€β”€ data_prep.py # Dataset loading and combination (5 datasets)
395
+ β”‚ β”œβ”€β”€ finetune.py # Custom fine-tuning script
396
+ β”‚ └── train_meta.py # Meta-classifier training script
397
+ └── requirements.txt
398
+ ```
399
+
400
+ ## Framework Versions
401
 
402
  - Transformers 5.6.2
403
+ - PyTorch 2.11.0+cu130
404
  - Datasets 4.8.4
405
  - Tokenizers 0.22.2