Multi-task NLP pipeline for Indonesian disaster tweets using IndoBERTTweet and ONNX Runtime.
- Download the models folder Gemma-Models and add it in the repo
- Folder Train and Test is in
- Notebook of Train and Inference
- Donwload the raw data zip
- Download the gemma_train.ipynb notebook
- Import the notebook and Upload the dataset
- Change the input path accordingly
- Add hugging face token to read repository
GEMMA is built as a two-stage system:
- Training pipeline (domain adaptation + multi-task fine-tuning + export)
- Offline inference pipeline (privacy-first preprocessing + dual-head ONNX inference)
Training flow
Raw token-level SRL labels
-> dataset reconstruction (tweet-level seq labels + token-level NER)
-> text_id-aware split (zero leakage)
-> TAPT (masked language modeling on disaster corpus)
-> multi-task fine-tuning (shared backbone + 2 heads)
-> threshold optimization per label
-> ONNX export + quantization + label_maps.json
Inference flow
Raw tweet
-> PII censor (NIK/phone/email/bank)
-> paper-aligned normalization
-> strict filter gate (optional)
-> tokenizer
-> ONNX runtime (seq head + NER head)
-> urgency scoring + entity decoding
-> structured JSON output
Shared Backbone
IndoBERTTweet (TAPT-adapted, ~110M params)
|
---------------------------
| |
Head 1: Sequence Head 2: Token/Ner
Classification (12) Classification (17)
| |
5 event + 6 assistance + BIO tags for place/street/
1 is_urgent signal time/org/event/argument
Model parameter budget is approximately 111M, which satisfies the <= 4B SLM constraint.
Primary implementation: src/notebooks/gemma_training.py
- Data reconstruction
- Rebuild tweet text by grouping token-level rows by
text_id. - Build Head 1 multi-label targets: 5 event labels + 6 assistance labels +
is_urgent. - Build Head 2 token labels (BIO, 17 tags) with argument-to-NER routing.
- Split strategy and leakage control
- Stratified split is performed on tweet IDs (not per token).
- Train/test IDs are asserted disjoint to enforce zero leakage.
- Split CSVs are written to
data/processed/train_dataanddata/processed/test_data.
- TAPT (Task-Adaptive Pretraining)
- Corpus is built from disaster-domain preprocessed CSV files.
- MLM pretraining runs for one epoch on IndoBERTTweet to adapt to disaster language.
- TAPT artifacts are stored under
models/tapt.
- Multi-task optimization
- Shared encoder with two heads:
- Sequence head: multi-label logits
- NER head: token logits
- Combined objective:
- Class imbalance is handled with positive class weights (sequence) and class weights (NER).
- Best checkpoint is selected by weighted combined F1 and saved to
models/finetuned/best_model.pt.
- Decision threshold calibration
- Per-label optimal thresholds are derived from precision-recall curves.
- These thresholds are persisted into
models/onnx/label_maps.jsonand used at inference.
- Export and verification
- ONNX graph exports two outputs:
seq_logitsandner_logits. - Quantization paths:
- FP16 model for GPU-oriented deployment
- Numerical equivalence is checked against PyTorch outputs.
Primary implementation: src/notebooks/gemma_inference.py
- Runtime initialization
- Loads local tokenizer from
models/tapt. - Loads ONNX model and
label_maps.jsonfrommodels/onnx. - Chooses provider order automatically:
CUDAExecutionProvider+CPUExecutionProviderwhen CUDA is availableCPUExecutionProviderotherwise
- Privacy-first preprocessing
- Every tweet passes through PII censoring before normalization.
- PII types: NIK, phone, email, bank account.
- 16-digit values are context-disambiguated (NIK vs bank account) using nearby keywords.
- Paper-aligned normalization and gating
- Steps: lowercase, remove URL/mention/hashtag/emoji/punctuation, slang normalization, whitespace cleanup.
- Strict filter can skip inference when:
- text has fewer than 4 words
- text is classified as non-Indonesia context (heuristic)
- Skip behavior is explicit in output via
skippedandskip_reasons.
- Dual-head ONNX inference
- Sequence logits -> sigmoid -> thresholding via persisted per-label thresholds.
- NER logits -> argmax -> BIO span decoding.
- Weighted urgency scoring
- Urgency score combines label probabilities with domain weights and false-event penalty:
- Tier mapping from
label_maps.json:- CRITICAL >= 70
- HIGH >= 40
- MEDIUM >= 15
- LOW < 15
- Output contract
- Structured response includes:
- original/censored/preprocessed text
- PII findings
- predicted crisis types and assistance needs
- binary urgency (
is_urgent) and weighted urgency (urgency_score,urgency_tier) - model confidence
- extracted entities
- preprocessing metadata and skip reasons
| Module | Responsibility |
|---|---|
src/core/pii_filter.py |
Offline regex-based PII detection/censoring and 16-digit context disambiguation |
src/core/preprocessing.py |
Privacy-first text normalization, slang mapping, strict filtering heuristics |
src/utils/model.py |
Canonical dual-head MTL model + combined loss definition |
src/utils/data_utils.py |
Dataset reconstruction, stratified splitting, TAPT corpus creation |
src/utils/onnx_utils.py |
ONNX export, quantization, verification, model-size reporting |
src/notebooks/gemma_training.py |
End-to-end training notebook/script (Kaggle-oriented) |
src/notebooks/gemma_inference.py |
End-to-end offline inference notebook/script |
| Artifact | Path | Description |
|---|---|---|
| TAPT checkpoint | models/tapt/ |
Domain-adapted tokenizer + backbone weights |
| Best fine-tuned checkpoint | models/finetuned/best_model.pt |
Best validation model state dict |
| ONNX FP32 | models/onnx/gemma_mtl.onnx |
Reference ONNX graph with dual outputs |
| ONNX FP16 | models/onnx/gemma_mtl_fp16.onnx |
Reduced-precision model for GPU inference |
| Label metadata | models/onnx/label_maps.json |
Seq labels, per-label thresholds, urgency weights/tiers, NER maps |
| Constraint | Status |
|---|---|
| Model <= 4B params | ~111M params |
| 100% Offline inference | ONNX Runtime on localhost |
| PII Filter (Privacy Brain) | Regex-based: NIK, phone, email, bank account |
| Data segregation | Disjoint split IDs with leakage checks + split artifacts |
# 1) Install dependencies
uv sync
# 2) Build processed splits/manifests from raw dataset
python -c "from src.utils.data_utils import run_data_pipeline; run_data_pipeline()"
# 3) Training (GPU recommended)
# Open and run: src/notebooks/gemma_training.ipynb
# (or execute src/notebooks/gemma_training.py in a notebook-compatible flow)
# 4) Offline inference
# Open and run: src/notebooks/gemma_inference.ipynb
# (or execute src/notebooks/gemma_inference.py after model artifacts exist)Run these commands from the project root (GEMMA/).
# Run all test targets (batch + stream + stress)
uv run python src/test/run_test_suite.py --target all --stress-mode sequential --stress-texts 16
# Run only stream test (local default input: data/processed/test_data/seq_test.csv)
uv run python src/test/test_ingest_stream.py
# Run stream self-check
uv run python src/test/test_ingest_stream.py --self-check
# Run only batch self-check
uv run python src/test/run_test_suite.py --target batch
# Run only stress tests
uv run python src/test/run_test_suite.py --target stress --stress-mode allGEMMA/
├── data/
│ ├── raw/ # Source SRL and preprocessed corpora
│ └── processed/ # Split CSVs, manifests, and inference outputs
├── models/
│ ├── tapt/ # TAPT tokenizer + checkpoint artifacts
│ ├── finetuned/ # best_model.pt
│ └── onnx/ # ONNX models + label_maps.json
├── src/
│ ├── core/
│ │ ├── pii_filter.py
│ │ └── preprocessing.py
│ ├── utils/
│ │ ├── data_utils.py
│ │ ├── model.py
│ │ └── onnx_utils.py
│ ├── notebooks/
│ │ ├── gemma_training.ipynb
│ │ ├── gemma_training.py
│ │ ├── gemma_inference.ipynb
│ │ └── gemma_inference.py
│ └── test/
└── pyproject.toml
Based on Semantic Role Labeling Datasets for Crisis Event, with approximately 4,150 annotated Indonesian tweets covering floods, fires, earthquakes, and accidents.
Dataset: CC BY-NC 4.0