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()