| |
| |
| |
| |
| |
|
|
| import argparse |
|
|
| import numpy as np |
| import pandas as pd |
| from datasets import Dataset |
| from sklearn.metrics import accuracy_score, f1_score |
| from sklearn.model_selection import train_test_split |
| from sklearn.preprocessing import LabelEncoder |
| from transformers import ( |
| AutoModelForSequenceClassification, |
| AutoTokenizer, |
| Trainer, |
| TrainingArguments, |
| ) |
|
|
|
|
| def compute_metrics(p): |
| """Compute accuracy and weighted F1.""" |
| preds = np.argmax(p.predictions, axis=1) |
| f1 = f1_score(p.label_ids, preds, average="weighted", zero_division=0) |
| acc = accuracy_score(p.label_ids, preds) |
| return {"accuracy": acc, "f1": f1} |
|
|
|
|
| def parse_args(): |
| parser = argparse.ArgumentParser(description="Fine-tune FinBERT for sentiment.") |
| parser.add_argument( |
| "--data", |
| default="Data/emotion_dataset.csv", |
| help="Path to the training CSV (needs Text/Clean_Text and Emotion columns).", |
| ) |
| parser.add_argument( |
| "--output", |
| default="finbert_emotion_model", |
| help="Directory to save the fine-tuned model.", |
| ) |
| parser.add_argument( |
| "--model-name", |
| default="yiyanghkust/finbert-pretrain", |
| help="Base model to fine-tune.", |
| ) |
| parser.add_argument("--epochs", type=int, default=3) |
| parser.add_argument("--batch-size", type=int, default=16) |
| parser.add_argument("--max-length", type=int, default=128) |
| return parser.parse_args() |
|
|
|
|
| def main(): |
| args = parse_args() |
|
|
| print(f"Loading dataset from: {args.data}") |
| try: |
| df = pd.read_csv(args.data) |
| except FileNotFoundError: |
| print(f"ERROR: '{args.data}' not found.") |
| return |
|
|
| print("Dataset loaded successfully. Preprocessing data...") |
| df["text"] = df["Clean_Text"].fillna(df["Text"]) if "Clean_Text" in df else df["Text"] |
| df.dropna(subset=["text", "Emotion"], inplace=True) |
|
|
| label_encoder = LabelEncoder() |
| df["labels"] = label_encoder.fit_transform(df["Emotion"]) |
| num_labels = len(label_encoder.classes_) |
| id2label = {i: label for i, label in enumerate(label_encoder.classes_)} |
| label2id = {label: i for i, label in enumerate(label_encoder.classes_)} |
|
|
| print(f"Found {num_labels} unique emotions.") |
| train_df, val_df = train_test_split( |
| df, test_size=0.2, random_state=42, stratify=df["labels"] |
| ) |
| train_dataset = Dataset.from_pandas(train_df) |
| val_dataset = Dataset.from_pandas(val_df) |
|
|
| print("Loading FinBERT tokenizer and model...") |
| tokenizer = AutoTokenizer.from_pretrained(args.model_name) |
| model = AutoModelForSequenceClassification.from_pretrained( |
| args.model_name, |
| num_labels=num_labels, |
| id2label=id2label, |
| label2id=label2id, |
| ignore_mismatched_sizes=True, |
| ) |
|
|
| def tokenize_function(examples): |
| return tokenizer( |
| examples["text"], |
| padding="max_length", |
| truncation=True, |
| max_length=args.max_length, |
| ) |
|
|
| tokenized_train_dataset = train_dataset.map(tokenize_function, batched=True) |
| tokenized_val_dataset = val_dataset.map(tokenize_function, batched=True) |
|
|
| print(f"Starting model fine-tuning... (output: {args.output})") |
| training_args = TrainingArguments( |
| output_dir=args.output, |
| num_train_epochs=args.epochs, |
| per_device_train_batch_size=args.batch_size, |
| per_device_eval_batch_size=args.batch_size, |
| logging_steps=100, |
| eval_strategy="epoch", |
| save_strategy="epoch", |
| save_total_limit=1, |
| load_best_model_at_end=True, |
| ) |
|
|
| trainer = Trainer( |
| model=model, |
| args=training_args, |
| train_dataset=tokenized_train_dataset, |
| eval_dataset=tokenized_val_dataset, |
| compute_metrics=compute_metrics, |
| ) |
|
|
| trainer.train() |
|
|
| print(f"Training complete. Saving final model to '{args.output}'...") |
| trainer.save_model(args.output) |
| tokenizer.save_pretrained(args.output) |
| print("Model and tokenizer saved successfully.") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|