NLP · Transformer Fine-tuning
Indonesian Financial Text · 6,200 samples · HuggingFace Transformers · 2025
Fine-tune IndoBERT
for Financial Sentiment
My 2021 thesis used LSA + KNN. In 2025 I fine-tuned IndoBERT on financial text. Here's what four years changed — and what it didn't.
Model indobenchmark/indobert-base-p1
Dataset 6,200 Indonesian financial texts · 3 classes
Stack HuggingFace Transformers · NLTK · spaCy · Docker
Context Solo project · 2025
Same Domain
Indonesian Financial Text · 6,200 samples
Traditional
TF-IDF + LogReg
72.3%
Acc · F1 0.7018
Sequential
BiLSTM
79.1%
Acc · F1 0.7845
Transformer · Winner
IndoBERT fine-tuned
88.4%
Acc · F1 0.8791
⚠ IndoBERT without fine-tuning: 64.2% — domain-specific fine-tuning is non-negotiable for financial text
Accuracy
F1 Macro
Per-class Recall
Inference Latency
01 · Business Question
Indonesian financial text is its own language. Generic models don't know that.
A stock investor writing "saham BBRI nyangkut terus" and a food reviewer writing "makanannya enak" are both using Bahasa Indonesia. Only one is expressing financial distress. Generic sentiment models can't tell the difference.
Financial social media in Indonesia mixes formal Bahasa Indonesia, financial jargon ("bullish", "cut loss", "koreksi"), slang ("nyangkut" = stuck at a losing price, "cuan" = profit), and English ticker symbols — all in the same sentence. Pre-trained models trained on general corpora fail at this domain boundary.
The question isn't "which model is best for sentiment?" — it's "which model is best for this specific domain?" and "does fine-tuning actually close the gap?" This project answers both.
Indonesian NLP
IndoBERT
HuggingFace Transformers
NLTK
spaCy
Domain Fine-tuning
Docker
02 · Final Result
Fine-tuned IndoBERT wins — and zero-shot IndoBERT finishes last.
88.4% accuracy, F1 0.8791. The surprise: un-fine-tuned IndoBERT scores only 64.2% — worse than TF-IDF. Domain-specific fine-tuning is the entire story.
🏆 Winner
IndoBERT fine-tuned
88.4%
F1 Macro0.8791
Epochs3
Base modelindobert-base-p1
#2
BiLSTM
79.1%
F1 Macro0.7845
Embedding128-dim trained
ContextSequential only
#3 · Baseline
TF-IDF + LogReg
72.3%
F1 Macro0.7018
Features10,000 ngrams
ContextBag of words
Decision matrix
| Criterion |
TF-IDF + LogReg |
BiLSTM |
IndoBERT fine-tuned |
| Accuracy | 🔴 | 🟡 | 🟢 |
| Macro F1 | 🔴 | 🟡 | 🟢 |
| Handles negation | 🔴 | 🟡 | 🟢 |
| Financial slang | 🔴 | 🟡 | 🟢 |
| Subword tokenization | 🔴 | 🔴 | 🟢 |
| Inference speed | 🟢 | 🟢 | 🟡 |
| Training cost | 🟢 | 🟢 | 🟡 |
The key finding: IndoBERT without fine-tuning scores 64.2% — worse than TF-IDF. A pre-trained transformer is not automatically better than a baseline. Fine-tuning on domain-specific data is what closes the 24-point gap between zero-shot BERT and fine-tuned BERT.
03 · Architecture
The fine-tuning pipeline, step by step.
Every text goes through preprocessing (NLTK + spaCy) before hitting the IndoBERT tokenizer. The model is fine-tuned end-to-end — no feature extraction, no frozen layers — with a classification head on top of the [CLS] token.
01
Raw Financial Text
Indonesian social media posts, news headlines, financial forum text — mixed Bahasa Indonesia, English jargon, ticker symbols
02
Preprocessing (NLTK + spaCy)
URL removal, mention stripping, financial slang normalization (nyangkut → "harga turun"), Indonesian stopword removal via NLTK, NER via spaCy id_core_news_sm to preserve ticker symbols
03
IndoBERT WordPiece Tokenizer
indobenchmark/indobert-base-p1 tokenizer — subword tokenization handles OOV financial terms, max_length=128, padding to uniform length
04
IndoBERT Encoder (12 layers, bidirectional attention)
768-dim contextual embeddings — each token's representation is informed by all other tokens in the sequence simultaneously
05
[CLS] Pooling → Classification Head
768-dim [CLS] vector → Linear(768→3) → Softmax → {negative, neutral, positive}
# Training config
model = AutoModelForSequenceClassification.from_pretrained(
"indobenchmark/indobert-base-p1",
num_labels=3,
id2label={0:"negative", 1:"neutral", 2:"positive"},
)
training_args = TrainingArguments(
num_train_epochs=3,
per_device_train_batch_size=16,
learning_rate=2e-5,
warmup_ratio=0.1, # 10% warmup prevents early instability
weight_decay=0.01, # AdamW regularization
metric_for_best_model="f1_macro",
load_best_model_at_end=True,
)
04 · Dataset & EDA
6,200 Indonesian financial texts. Domain-specific vocabulary is the challenge.
The dataset covers Indonesian financial social media: IDX-listed company mentions, market commentary, and investor discussion. The label distribution is intentionally balanced to avoid the majority-class bias that plagues generic sentiment datasets.
Vocabulary analysis: what makes financial text hard
General Indonesian NLP vocabulary
- makan, bagus, senang, buruk
- naik, turun (ambiguous)
- tidak bisa, sangat baik
- sudah, akan, karena
Financial domain vocabulary
- nyangkut, cuan, koreksi, jeblok
- bullish, bearish, cut loss, support
- BBRI, TLKM, IDX, emiten
- BI rate, inflasi, IPO, right issue
Per-class performance (IndoBERT fine-tuned)
| Class |
Precision |
Recall |
F1 |
Support |
| Negative | 0.87 | 0.85 | 0.86 | 372 |
| Neutral | 0.88 | 0.90 | 0.89 | 372 |
| Positive | 0.90 | 0.90 | 0.90 | 496 |
05 · From LSA to BERT
What four years actually changed — and what it didn't.
My 2021 thesis built a KNN + LSA chatbot. This project fine-tunes a transformer. The architecture is completely different. The fundamental engineering questions are identical.
| Dimension |
LSA + KNN (2021 Thesis) |
IndoBERT fine-tuned (2025) |
| Tokenization |
Whitespace split, manual stopword removal |
WordPiece subword — "bullish" → ["bull", "##ish"] — OOV terms handled automatically |
| Text representation |
Term-document matrix → SVD → static latent vectors |
Contextual embeddings — same word, different vector depending on surrounding context |
| Context window |
None — bag of words, word order ignored |
Full sequence (128 tokens) — bidirectional attention over entire input simultaneously |
| Negation handling |
Fails — "tidak rugi" and "rugi" have similar vectors |
Captured by attention — "tidak" modifies the representation of the tokens around it |
| Transfer learning |
None — trained from scratch on thesis dataset |
Pre-trained on 16GB Indonesian corpora — domain fine-tuning adds 6.2K examples on top |
| Labeled data requirement |
Required — no labeled data, no model |
Still required — fine-tuning is not magic without domain labels |
| Preprocessing still matters |
Stopwords, normalization, cleaning |
Same — garbage in, garbage out regardless of model sophistication |
| Evaluation methodology |
Accuracy, F1, confusion matrix, held-out test set |
Exactly the same — the metrics haven't changed, only what we're measuring |
"BERT didn't obsolete NLP fundamentals. It made them more important — because now you're debugging a 110M-parameter black box instead of a cosine similarity matrix."
06 · What BERT Gets Right
Three cases where context is everything.
These are real patterns from the test set where TF-IDF and BiLSTM fail. BERT's bidirectional attention resolves them correctly because it reads the entire sentence before committing to a representation.
Case 01 · Contrastive negation
"Harga saham BBRI memang naik, tapi investor tetap khawatir dengan prospek jangka pendek"
True label: Negative
TF-IDF: Positive — sees "naik", ignores "tapi...khawatir"
BiLSTM: Neutral — sequential context helps, but loses the long-range contrast
IndoBERT: Negative ✓ — attention connects "naik" and "khawatir" across the "tapi" pivot
Case 02 · Slang + ticker
"TLKM lagi bearish banget, gue udah cutloss kemarin, nyangkut 2 bulan"
True label: Negative
TF-IDF: Neutral — "bearish", "cutloss", "nyangkut" are OOV in general vocabulary
BiLSTM: Negative — trained embedding partially captures slang patterns
IndoBERT: Negative ✓ — subword tokenization preserves signal from OOV financial terms
Case 03 · Qualified optimism
"Secara fundamental BBCA bagus, tapi valuasinya sudah mahal untuk dikoleksi sekarang"
True label: Neutral
TF-IDF: Positive — "bagus" dominates; "mahal" doesn't cancel it in BoW
BiLSTM: Positive — forward LSTM anchors on "bagus" early in sequence
IndoBERT: Neutral ✓ — bidirectional attention weighs "bagus" against "mahal untuk dikoleksi"
Case 04 · Code-switching
"Market lagi volatile banget, wait and see dulu, jangan FOMO masuk sekarang"
True label: Neutral / Cautious
TF-IDF: Neutral (lucky) — most tokens are OOV; result is noise
BiLSTM: Negative — interprets "jangan" as negative signal overall
IndoBERT: Neutral ✓ — cross-lingual pre-training handles English-Bahasa code-switching natively
07 · Fine-tuning Process
Three epochs. Each one mattered differently.
Fine-tuning a pre-trained transformer is not "just run more epochs." Learning rate schedule, warmup, and early stopping on validation F1 (not accuracy) are what prevent catastrophic forgetting of pre-trained representations.
Epoch 1 — Adaptation
Model adapts pre-trained weights to financial domain. High learning rate phase with warmup.
74.2%
+10pp over zero-shot baseline
Epoch 2 — Refinement
Financial jargon patterns stabilize. Per-class F1 for "negative" improves most.
83.7%
+9.5pp
Epoch 3 — Convergence
Validation F1 plateaus. Early stopping fires. Best checkpoint saved.
88.4%
Best checkpoint · EarlyStopping
Why F1 macro, not accuracy, as the stopping criterion
Accuracy can improve even when the model gets lazier — just predict the majority class more often. Macro F1 penalizes this: every class must be predicted well, not just the most common one. Monitoring F1 on the validation set caught one epoch where accuracy kept rising while neutral-class F1 was already declining.
This is not theoretical. At epoch 4 in an early experiment, accuracy reached 89.1% — but macro F1 dropped to 0.8721. The model had started over-predicting "positive" at the expense of the balanced F1. Early stopping on F1 prevented this regression from becoming the final model.
08 · What Didn't Change
The invariants that survive every architecture shift.
Going from LSA (2021) to BERT (2025) changed everything about how text is represented. It changed nothing about the engineering discipline required to do NLP well.
- Labeled data is still the bottleneck. BERT zero-shot at 64.2% is worse than TF-IDF at 72.3%. Pre-training doesn't replace domain labels — it makes them more efficient to use.
- Preprocessing still shapes what the model sees. Normalizing "nyangkut" → "harga turun" before tokenization improved neutral-class F1 by ~3 points. Garbage in, garbage out — regardless of the model size.
- Evaluation methodology hasn't changed. Macro F1 on a stratified held-out test set, per-class breakdown, confusion matrix analysis. These were correct in 2021 and remain correct in 2025.
- Domain expertise is still the multiplier. Knowing that "koreksi" is bearish context, that "nyangkut" signals losses, and that "cut loss" is investor distress — this domain knowledge informs preprocessing, label quality, and error analysis. No model learns this automatically.
- Overfitting is still the primary failure mode. The same early stopping, validation monitoring, and regularization discipline from 2021 applies directly. The parameter count changed; the problem didn't.