File size: 9,069 Bytes
22d3c3e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | ---
license: apache-2.0
language:
- hi
- bn
- en
base_model: google/muril-base-cased
tags:
- text-classification
- fact-verification
- code-mixed
- indic-languages
pipeline_tag: text-classification
---
# MuRIL Fine-Tuned for Claim-Evidence Classification (SUPPORTS / REFUTES)
Fine-tuned [`google/muril-base-cased`](https://huggingface.co/google/muril-base-cased) for binary claim-evidence fact verification on code-mixed Hindi–Bengali–English text. Given a **claim** and a piece of **evidence**, the model predicts whether the evidence **SUPPORTS** or **REFUTES** the claim.
## How to Use
```python
from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch
MODEL_NAME = "nirnit-13/muril-supports-refutes"
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
model = AutoModelForSequenceClassification.from_pretrained(MODEL_NAME)
model.eval()
claim = "भारत दुनिया में दूध का सबसे बड़ा उत्पादक देश है।"
evidence = "भारत विश्व में दुग्ध उत्पादन में प्रथम स्थान पर है।"
inputs = tokenizer(claim, evidence, truncation=True, max_length=192, padding="max_length", return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs)
probs = torch.softmax(outputs.logits, dim=-1)
pred_id = torch.argmax(probs, dim=-1).item()
id2label = {0: "SUPPORTS", 1: "REFUTES"}
print(id2label[pred_id], "| confidence:", probs[0][pred_id].item())
```
## Performance (Dev Set)
| Metric | Value |
|---|---|
| Accuracy | 0.822 |
| Macro F1 | 0.801 |
| F1 (SUPPORTS) | 0.865 |
| F1 (REFUTES) | 0.738 |
## Limitations
- Trained on informal, code-mixed, social-media-style claims. Performance degrades on out-of-domain, formally-written text or differently-structured evidence sources (see "Domain Generalization Caveat" below).
- Best used with `max_length=192` tokenization to match training conditions.
---
## Training & Tuning Details
The sections below document the hyperparameter tuning methodology used to produce this model.
## Overview
This document describes the hyperparameter tuning process used to fine-tune `google/muril-base-cased` for a binary claim-evidence classification task (SUPPORTS vs. REFUTES) on code-mixed Hindi–Bengali–English data.
- **Task:** Given a claim and a piece of evidence, classify whether the evidence SUPPORTS or REFUTES the claim.
- **Model:** `google/muril-base-cased` (encoder-only, fine-tuned via `AutoModelForSequenceClassification`)
- **Framework:** HuggingFace `transformers` + `datasets`, run on Google Colab (free-tier T4 GPU)
- **Search library:** [Optuna](https://optuna.org/) (TPE sampler), integrated via `Trainer.hyperparameter_search()`
## Dataset
| Split | File | Size | Notes |
|---|---|---|---|
| Train | `train_subtask1.json` | 4,536 | Fields: `ID`, `Text`, `Evidence`, `Label` |
| Dev | `dev_subtask1.json` | 1,134 | Same structure; used for validation during tuning |
| Label distribution (train) | — | SUPPORTS: 3,040 / REFUTES: 1,496 | ~2:1 class imbalance |
## Why Hyperparameter Tuning Was Needed
Initial manually-configured training runs plateaued around **Macro F1 ≈ 0.79–0.80**, with a persistent gap between SUPPORTS and REFUTES F1 scores (minority class underperforming). Manual tuning (adjusting one variable at a time — dropout, label smoothing, class weights) was slow and occasionally made results worse by stacking multiple untested changes at once. Optuna was introduced to systematically and efficiently search the hyperparameter space using smarter sampling (TPE) and early pruning of poor trials.
## Search Setup
### Tunable Hyperparameters and Search Space
```python
def optuna_hp_space(trial):
return {
"learning_rate": trial.suggest_float("learning_rate", 1e-5, 5e-5, log=True),
"per_device_train_batch_size": trial.suggest_categorical("per_device_train_batch_size", [4, 8, 16, 32]),
"num_train_epochs": trial.suggest_int("num_train_epochs", 3, 16),
"weight_decay": trial.suggest_float("weight_decay", 0.0, 0.1),
"warmup_ratio": trial.suggest_float("warmup_ratio", 0.0, 0.2),
"max_grad_norm": trial.suggest_float("max_grad_norm", 0.5, 2.0),
}
```
### Fixed Settings (not tuned)
- `max_length = 192` (tokenization)
- `fp16 = True` (mixed precision, required for free-tier Colab GPU memory limits)
- `eval_strategy = "epoch"`, `save_strategy = "epoch"`
- `save_total_limit = 1` (only the best/most recent checkpoint retained)
- `metric_for_best_model = "f1"` (macro F1 used as the trial objective)
- Class-weighted `CrossEntropyLoss` (weights computed automatically from train label frequencies) to address the ~2:1 SUPPORTS:REFUTES imbalance
### Persistence
Since Colab's free tier disconnects after a time/usage limit, the Optuna study was configured with persistent SQLite storage so the search could resume across sessions without losing trial history:
```python
storage_path = "sqlite:////content/drive/MyDrive/muril_ckpt/optuna_study.db"
best_trial = trainer.hyperparameter_search(
direction="maximize",
backend="optuna",
hp_space=optuna_hp_space,
compute_objective=lambda metrics: metrics["eval_f1"],
n_trials=15,
study_name="muril_hp_search",
storage=storage_path,
load_if_exists=True,
)
```
- **Only the `.db` file is required to resume a search** across sessions/accounts — model checkpoints are not needed for this, since each trial trains a fresh model via `model_init()`.
- `n_trials` is a cumulative budget across all resumed sessions, not per-session.
## Results
**Total trials run:** 15 (several pruned early by Optuna due to clearly poor intermediate performance, e.g. high learning rates that failed to converge).
### Best Trial
| Hyperparameter | Value |
|---|---|
| `learning_rate` | 1.7991465194475932e-05 |
| `per_device_train_batch_size` | 32 |
| `num_train_epochs` | 16 |
| `weight_decay` | 0.0764751706565559 |
| `warmup_ratio` | 0.026509480810828625 |
| `max_grad_norm` | 1.2004964752527867 |
**Dev set performance (best trial):**
| Metric | Value |
|---|---|
| Accuracy | 0.822 |
| Macro F1 | 0.801 |
| F1 (SUPPORTS) | 0.865 |
| F1 (REFUTES) | 0.738 |
### Key Findings from the Search
- **Higher batch size (32) + moderate-high learning rate (~1.5–1.8e-5) + more epochs (10–16)** consistently produced the best results.
- **Very high learning rates (>3e-5) combined with small batch sizes** caused training to fail to converge (loss stuck near the random-guessing baseline of ~1.386) — these trials were pruned or scored near F1 ≈ 0.39–0.50.
- Smaller batch sizes (4–8) produced noisier, less reliable gradients and were more prone to early pruning.
## Known Limitation: Overfitting at High Epoch Counts
A full training run at the best trial's settings (16 epochs) showed a classic overfitting pattern when loss curves were inspected in detail:
- Training loss decreased continuously throughout all 16 epochs.
- Validation loss reached its minimum around **epoch 5–6** and increased steadily afterward.
- Despite this, Macro F1 continued to creep upward slightly through epoch 16 — meaning `metric_for_best_model="f1"` may have selected an already-overfit checkpoint rather than the true best-generalizing one (epoch 5–6, by validation loss).
**Recommendation for future runs:** either cap `num_train_epochs` closer to 6–8, or switch `metric_for_best_model` to `"eval_loss"` (with `greater_is_better=False`) to select the checkpoint with the best generalization rather than the highest (potentially overfit) F1.
## Domain Generalization Caveat
The tuned model performs well on data matching the training distribution (informal, code-mixed social-media-style claims) but was observed to generalize poorly to:
1. Clean, formally-written factual claims (out-of-domain test set) — near-total prediction collapse toward REFUTES.
2. A structurally different evidence-retrieval pipeline (news-article snippets with fusion-ranked retrieval scores, from a separate subtask) — Macro F1 dropped to ~0.60, with SUPPORTS recall collapsing to 0.45.
This indicates the tuned hyperparameters optimize well for the training/dev distribution, but do not by themselves solve domain-shift generalization — a separate concern from hyperparameter tuning.
## Reproducing This Search
1. Mount Google Drive and place `train_subtask1.json` / `dev_subtask1.json` in the configured folder.
2. Run the Optuna search cell — it will create (or resume, if `optuna_study.db` already exists at the given path) the study and run trials up to `n_trials`.
3. Retrieve `best_trial.hyperparameters` and plug them into a final `TrainingArguments` for a full training run.
4. Save the resulting model (`trainer.save_model(...)`) for downstream inference. |