File size: 5,552 Bytes
4d67ec7 | 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 | """
train.py — Fine-tune bert-base-uncased for NEET Biology chapter classification
Flow:
1. Load config from config.json
2. Load and split the labeled dataset (80/20 train/val split)
3. Load bert-base-uncased with a classification head
4. Fine-tune using HuggingFace Trainer API
5. Evaluate & save the best checkpoint
Run:
python -m Classification.train
When you have your real labeled data, replace the path in DATA_PATH.
"""
import json
import os
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, f1_score
import torch
from transformers import (
AutoTokenizer,
AutoModelForSequenceClassification,
TrainingArguments,
Trainer,
EarlyStoppingCallback,
)
from Classification.dataset import NEETDataset, build_label_map
# ==============================
# CONFIG
# ==============================
CONFIG_PATH = "Classification/config.json"
DATA_PATH = "Classification/data/generated_questions.json"
with open(CONFIG_PATH, encoding="utf-8") as f:
cfg = json.load(f)
MODEL_NAME = cfg["model_name"] # "bert-base-uncased"
MAX_LENGTH = cfg["max_length"] # 128
BATCH_SIZE = cfg["batch_size"] # 16
LR = cfg["learning_rate"] # 2e-5
EPOCHS = cfg["num_epochs"] # 5
OUTPUT_DIR = cfg["output_dir"] # "Classification/checkpoints"
SEED = cfg["seed"] # 42
# ==============================
# STEP 1: Build label map
# ==============================
print("Loading label map...")
with open("Classification/data/label_map.json", "r", encoding="utf-8") as f:
label_map = json.load(f)
num_labels = len(label_map)
print(f"Number of classes (chapters): {num_labels}")
# Update config num_labels dynamically
cfg["num_labels"] = num_labels
# ==============================
# STEP 2: Load & split data
# ==============================
with open(DATA_PATH, encoding="utf-8") as f:
all_data = json.load(f)
train_data, val_data = train_test_split(
all_data, test_size=0.2, random_state=SEED, stratify=[d["chapter_id"] for d in all_data]
)
# Save splits for transparency
os.makedirs("Classification/data", exist_ok=True)
with open("Classification/data/train_split.json", "w", encoding="utf-8") as f:
json.dump(train_data, f, indent=2)
with open("Classification/data/val_split.json", "w", encoding="utf-8") as f:
json.dump(val_data, f, indent=2)
print(f"Train: {len(train_data)} | Val: {len(val_data)}")
# ==============================
# STEP 3: Tokenizer & Datasets
# ==============================
print(f"Loading tokenizer: {MODEL_NAME}")
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
# Write splits to temp files (NEETDataset reads from JSON path)
train_dataset = NEETDataset("Classification/data/train_split.json", tokenizer, MAX_LENGTH)
val_dataset = NEETDataset("Classification/data/val_split.json", tokenizer, MAX_LENGTH)
# ==============================
# STEP 4: Load BERT Model
# ==============================
print(f"Loading {MODEL_NAME} with {num_labels} output labels...")
model = AutoModelForSequenceClassification.from_pretrained(
MODEL_NAME,
num_labels=num_labels,
)
# ==============================
# STEP 5: Metrics
# ==============================
def compute_metrics(eval_pred):
logits, labels = eval_pred
preds = np.argmax(logits, axis=-1)
acc = accuracy_score(labels, preds)
f1 = f1_score(labels, preds, average="macro", zero_division=0)
return {"accuracy": acc, "f1": f1}
# ==============================
# STEP 6: Training Arguments
# ==============================
training_args = TrainingArguments(
output_dir=OUTPUT_DIR,
num_train_epochs=EPOCHS,
per_device_train_batch_size=BATCH_SIZE,
per_device_eval_batch_size=BATCH_SIZE,
learning_rate=LR,
warmup_ratio=cfg["warmup_ratio"],
weight_decay=cfg["weight_decay"],
evaluation_strategy=cfg["evaluation_strategy"],
save_strategy=cfg["save_strategy"],
load_best_model_at_end=cfg["load_best_model_at_end"],
metric_for_best_model=cfg["metric_for_best_model"],
logging_steps=cfg["logging_steps"],
seed=SEED,
report_to="none", # set to "wandb" or "tensorboard" if you want tracking
)
# ==============================
# STEP 7: Train
# ==============================
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=val_dataset,
tokenizer=tokenizer,
compute_metrics=compute_metrics,
callbacks=[EarlyStoppingCallback(early_stopping_patience=2)],
)
print("\nStarting training...")
trainer.train()
# ==============================
# STEP 8: Save best model
# ==============================
best_model_dir = os.path.join(OUTPUT_DIR, "best_model")
trainer.save_model(best_model_dir)
tokenizer.save_pretrained(best_model_dir)
print(f"\n✅ Best model saved to: {best_model_dir}")
# Save final eval metrics to Evaluation_metrics folder
metrics = trainer.evaluate()
print(f"\nFinal Eval Metrics: {metrics}")
eval_dir = "Classification/Evaluation_metrics"
os.makedirs(eval_dir, exist_ok=True)
with open(os.path.join(eval_dir, "eval_metrics.json"), "w") as f:
json.dump(metrics, f, indent=2)
print(f"📊 Metrics saved to {eval_dir}/eval_metrics.json")
# Also save training history
log_history = trainer.state.log_history
with open(os.path.join(eval_dir, "training_log.json"), "w") as f:
json.dump(log_history, f, indent=2)
print(f"📊 Training log saved to {eval_dir}/training_log.json")
|