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
88.4%
Best model accuracy
IndoBERT fine-tuned
+16.1pp
Improvement over
BiLSTM baseline
6.2K
Indonesian financial
text samples
3
Epochs to convergence
with early stopping
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
Negative0.870.850.86372
Neutral0.880.900.89372
Positive0.900.900.90496
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.

09 · Lessons Learned

What fine-tuning actually taught me.

On zero-shot BERT

A large model without fine-tuning is not a smart model. IndoBERT zero-shot scored 64.2% — worse than a TF-IDF baseline. Pre-training is a starting point, not an endpoint. The domain gap between general corpora and Indonesian financial text is real and measurable.

On learning rate

2e-5 is not a magic number — it's a starting point. At 5e-5, the model catastrophically forgot pre-trained representations within one epoch. At 1e-5, convergence was too slow and the model was still adapting at epoch 5. The warmup ratio matters as much as the peak LR.

On evaluation metrics

Accuracy is a lie you tell your stakeholders. At epoch 4, accuracy was 89.1% but macro F1 was declining. The model was getting "more accurate" by abandoning the harder classes. Macro F1 caught this; accuracy didn't.

On pre-LLM fundamentals

Understanding attention is more useful than knowing prompt syntax. When IndoBERT got a financial contrastive sentence wrong, I could debug it by reasoning about attention patterns. You can't debug a prompt-engineered system the same way — it's a black box with no error gradient.

"The gap between calling a BERT API and actually fine-tuning one is the same gap as between driving a car and understanding why it turns. Both get you somewhere. Only one tells you what went wrong."
10 · References

Papers & resources.

  1. [1]Devlin, J., Chang, M.-W., Lee, K., & Toutanova, K. (2019). BERT: Pre-training of deep bidirectional transformers for language understanding. NAACL 2019.
  2. [2]Wilie, B., et al. (2020). IndoNLU: Benchmark and resources for evaluating Indonesian natural language understanding. AACL-IJCNLP 2020. Source of indobert-base-p1.
  3. [3]Wolf, T., et al. (2020). Transformers: State-of-the-art natural language processing. EMNLP 2020 (System Demo). HuggingFace Transformers library.
  4. [4]Vaswani, A., et al. (2017). Attention is all you need. NeurIPS 2017. Foundational transformer architecture.
  5. [5]Sun, C., et al. (2019). How to fine-tune BERT for text classification? CCL 2019. Hyperparameter guidance for downstream task fine-tuning.
  6. [6]Loshchilov, I., & Hutter, F. (2019). Decoupled weight decay regularization. ICLR 2019. AdamW optimizer.
  7. [7]Honnibal, M., & Montani, I. (2017). spaCy 2: Natural language understanding with Bloom embeddings, convolutional neural networks and incremental parsing. Source of id_core_news_sm.
  8. [8]Bird, S., Klein, E., & Loper, E. (2009). Natural Language Processing with Python. O'Reilly Media. NLTK Indonesian stopwords corpus.
See the code

Full training pipeline, preprocessing, and evaluation code on GitHub.

Repository includes: fine-tuning script with HuggingFace Trainer, NLTK + spaCy preprocessing pipeline, Docker container for reproducible training, and the full evaluation comparison against TF-IDF and BiLSTM baselines.