multilingual-absa / src /absa /models /train_aspect_extraction.py
Aryan Mishra
Add CI, typed ORM models, and packaging cleanup
a00fee9
Raw
History Blame Contribute Delete
4.06 kB
from pathlib import Path
import mlflow
import numpy as np
from datasets import load_from_disk
from seqeval.metrics import f1_score as seqeval_f1_score
from transformers import (
AutoModelForTokenClassification,
AutoTokenizer,
DataCollatorForTokenClassification,
Trainer,
TrainingArguments,
set_seed,
)
from absa.training.mlflow_utils import setup_mlflow
def compute_metrics(p):
"""Computes evaluation metrics (F1 score) for token classification.
Args:
p: EvalPrediction tuple containing predictions and labels.
Returns:
Dictionary with 'f1' key and its computed value.
"""
predictions, labels = p
predictions = np.argmax(predictions, axis=2)
label_map = {0: "O", 1: "B-ASP", 2: "I-ASP"}
true_predictions = [
[label_map[p] for (p, lbl) in zip(prediction, label) if lbl != -100]
for prediction, label in zip(predictions, labels)
]
true_labels = [
[label_map[lbl] for (p, lbl) in zip(prediction, label) if lbl != -100]
for prediction, label in zip(predictions, labels)
]
# Seqeval F1 handles span-level scoring
f1 = seqeval_f1_score(true_labels, true_predictions)
# Calculate macro F1 roughly from classification report if needed,
# but for NER, seqeval's micro-averaged F1 (which seqeval_f1_score returns) is standard span-F1
return {"f1": f1}
def main():
"""Main function to train and evaluate the aspect extraction model.
Loads tokenized dataset, initializes XLM-RoBERTa for token classification,
configures Trainer, executes training loop, evaluates on test set,
and logs results to MLflow.
"""
set_seed(42)
setup_mlflow()
dataset_path = Path("data/tokenized/absa_ner_dataset")
print(f"Loading dataset from {dataset_path}")
dataset = load_from_disk(str(dataset_path))
tokenizer = AutoTokenizer.from_pretrained("xlm-roberta-base")
data_collator = DataCollatorForTokenClassification(tokenizer=tokenizer)
label_map = {0: "O", 1: "B-ASP", 2: "I-ASP"}
model = AutoModelForTokenClassification.from_pretrained(
"xlm-roberta-base",
num_labels=len(label_map),
id2label=label_map,
label2id={v: k for k, v in label_map.items()},
)
output_dir = "models/aspect_extraction"
training_args = TrainingArguments(
output_dir=output_dir,
learning_rate=2e-5,
num_train_epochs=5,
per_device_train_batch_size=16,
per_device_eval_batch_size=16,
warmup_ratio=0.1,
weight_decay=0.01,
evaluation_strategy="epoch",
save_strategy="epoch",
metric_for_best_model="eval_f1",
load_best_model_at_end=True,
seed=42,
report_to="mlflow",
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=dataset["train"],
eval_dataset=dataset["validation"],
tokenizer=tokenizer,
data_collator=data_collator,
compute_metrics=compute_metrics,
)
print("Training Aspect Extraction model...")
trainer.train()
print("Evaluating on test set...")
test_results = trainer.evaluate(dataset["test"], metric_key_prefix="test")
print(test_results)
best_model_path = Path(output_dir) / "best"
trainer.save_model(str(best_model_path))
print(f"Best model saved to {best_model_path}")
# Log test metric manually since trainer.train() only automatically logs eval metrics
# if report_to="mlflow" handles it, but test results we need to make sure are in the same run.
active_run = mlflow.active_run()
fallback_run_id = active_run.info.run_id if active_run else None
with mlflow.start_run(
run_id=(trainer.state.trial_params.get("mlflow_run_id") if trainer.state.trial_params else fallback_run_id)
) as run:
mlflow.log_metrics({"test_f1": test_results["test_f1"], "test_loss": test_results["test_loss"]})
print(f"Logged test metrics to run {run.info.run_id}")
if __name__ == "__main__":
main()