AI Sentiment Analysis Optimization with Parameter-Efficient Fine-Tuning
This Article is aimed to enhance sentiment analysis using Parameter-Efficient Fine-Tuning (PEFT) techniques. The foundational model used for this task was distilbert-base-uncased, known for its efficiency in sequence classification tasks. TL;DR: Github Repo
Dependencies:
Python; torch, transformers, peft, datasets, pandas, numpy, and evaluate
# pip install -r requirements.txt
!pip install torch
!pip install transformers
!pip install peft
!pip install datasets
!pip install pandas
!pip install numpy
!pip install evaluate
PEFT Technique:
PEFT, or Parameter-Efficient Fine-Tuning, operates by unfreezing all model parameters post the initial training phase, allowing for closer adaptation to the specific task during fine-tuning. The chosen PEFT approach involved training the base model for one epoch with frozen parameters, followed by an additional two epochs with unfrozen parameters.
Model Configuration:
The distilbert-base-uncased model served as the base model for both initial training and the PEFT process. This model was fine-tuned for sentiment analysis, with the objective of classifying text sequences into positive or negative sentiment categories.
import torch
from transformers import AutoTokenizer, DataCollatorWithPadding, TrainingArguments, Trainer, AutoModelForSequenceClassification
from peft import get_peft_model, LoraConfig, AutoPeftModelForSequenceClassification #get_peft_config
from datasets import load_dataset
import pandas as pd
import numpy as np
import evaluate
# Load the train and test splits of the rotten_tomatoes dataset, Attributed to the Udacity course code and Huggingface code snippets.
splits = ["train", "test"]
ds = {split: ds for split, ds in zip(splits, load_dataset("rotten_tomatoes", split=splits))}
# Thin out the dataset to make it run faster for this example
for split in splits:
ds[split] = ds[split].shuffle(seed=42).select(range(500))
# Pre-process dataset
tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased")
def preprocess_function(examples):
return tokenizer(examples["text"], padding="max_length", truncation=True)
tokenized_ds = {}
for split in splits:
tokenized_ds[split] = ds[split].map(preprocess_function, batched=True)
# Load and set up the base model
base_model_name = "distilbert-base-uncased"
base_model = AutoModelForSequenceClassification.from_pretrained(
"distilbert-base-uncased",
num_labels=2,
id2label={0: "NEGATIVE", 1: "POSITIVE"},
label2id={"NEGATIVE": 0, "POSITIVE": 1},
)
# Freeze all the parameters of the base model
for param in base_model.base_model.parameters():
param.requires_grad = False
Evaluation Methodology:
The evaluation methodology employed the Trainer class from the Hugging Face transformers library. Evaluation was performed after each training epoch, with metrics including loss, accuracy, runtime, and throughput measured in samples and steps per second.
Here we import the accuracy evaluation metrics and test out the pre-trained model, defining a list of examples:
# Import accuracy evaluation metrics
accuracy = evaluate.load("accuracy")
def compute_metrics(eval_pred):
predictions, labels = eval_pred
predictions = np.argmax(predictions, axis=1)
return {"accuracy": (predictions == labels).mean()}
# Apply a pre-trained model to text.
id2label = {0: "Negative", 1: "Positive"}
label2id = {"Negative":0, "Positive":1}
# Untrained model preformance
# Define list of examples
text_list = ["It was amazing.", "Would recommend.", "Amazing! Loved it!", "Nope, ever again.", "Waste of time."]
print("Untrained model predictions:")
print("----------------------------")
for text in text_list:
# tokenize text
inputs = tokenizer.encode(text, return_tensors="pt")
# compute logits
logits = base_model(inputs).logits
# convert logits to label
predictions = torch.argmax(logits)
print(text + " - " + id2label[predictions.tolist()])
Now we train and evaluate the base model:
# Training the base model
trainer_base = Trainer(
model=base_model,
args=TrainingArguments(
output_dir="./data/sentiment_analysis_base",
learning_rate=2e-3,
per_device_train_batch_size=6,
per_device_eval_batch_size=6,
num_train_epochs=2,
weight_decay=0.01,
evaluation_strategy="epoch",
save_strategy="epoch",
load_best_model_at_end=True,
),
train_dataset=tokenized_ds["train"],
eval_dataset=tokenized_ds["test"],
tokenizer=tokenizer,
data_collator=DataCollatorWithPadding(tokenizer=tokenizer),
compute_metrics=compute_metrics,
)
# Training the base model
trainer_base.train()
# Evaluate the base model
base_model_evaluation = trainer_base.evaluate()
Project Implementation:
The project showcased a systematic approach to implementing PEFT for sentiment analysis. The initial steps involved loading and preprocessing the rotten_tomatoes dataset, followed by configuring and training the base model. Subsequently, PEFT was applied by unfreezing all model parameters and modifying specific configurations for efficient fine-tuning.
Preforming PEFT, and mapping out a new peft_model to use:
# Performing Parameter-Efficient Fine-Tuning (PEFT)
# Unfreeze all the model parameters.
for param in base_model.parameters():
param.requires_grad = True
# Apply PEFT to the model
lora_config = LoraConfig(
r=16,
lora_alpha=16,
use_rslora=True,
lora_dropout=0.05,
target_modules=['v_lin'], #can change depending on Model ModuleList
modules_to_save=["text"],
task_type="SEQ_CLS"
)
peft_model = get_peft_model(base_model, lora_config)
Continue by using the HuggingFace Trainer class to train and evaluate the loop with PyTorch:
# The HuggingFace Trainer class handles the training and eval loop for PyTorch, Attributed to the Udacity course code and Huggingface code snippets.
trainer = Trainer(
model=peft_model,
args=TrainingArguments(
output_dir='./data/sentiment_analysis_lora',
learning_rate=5e-4, #hyper param
per_device_train_batch_size=12, #hyper params
per_device_eval_batch_size=12,
num_train_epochs=3,
weight_decay=0.05,
evaluation_strategy='epoch',
save_strategy='epoch',
load_best_model_at_end=True,
label_names=["labels"]
),
train_dataset=tokenized_ds['train'],
eval_dataset=tokenized_ds['test'],
tokenizer=tokenizer,
data_collator=DataCollatorWithPadding(tokenizer=tokenizer),
compute_metrics=compute_metrics,
)
trainer.train()
# Saving PEFT model
peft_model.save_pretrained("sentiment_analysis_lora")
# Load the saved PEFT model, Model Class changed to AutoPeftModelForSequenceClassification
inference_model = AutoPeftModelForSequenceClassification.from_pretrained("sentiment_analysis_lora")
# Set pad_token_id to eos_token_id
inference_model.config.pad_token_id = inference_model.config.eos_token_id
Training the PEFT model and generating predictions:
trainer = Trainer(
model=inference_model,
eval_dataset=tokenized_ds["test"],
tokenizer=tokenizer,
data_collator=DataCollatorWithPadding(tokenizer=tokenizer),
compute_metrics=compute_metrics
)
trainer.evaluate()
# Generating prediction, trained model performance
peft_model.to('mps') # Transitioning to mps for Mac
print("Trained model predictions:")
print("--------------------------")
for text in text_list:
inputs = tokenizer.encode(text, return_tensors="pt").to("mps") # Transitioning to mps for Mac
logits = peft_model(inputs).logits
predictions = torch.max(logits,1).indices
print(text + " - " + id2label[predictions.tolist()[0]])
# Evaluate the PEFT model
model_evaluation = trainer.evaluate()
# Compare the results of the base model and PEFT model
print("Base Model Evaluation:")
print(base_model_evaluation)
print("\nPEFT Model Evaluation:")
print(model_evaluation)
Checking out the results:
# Review results
df = pd.DataFrame(tokenized_ds["test"])
df = df[["text", "label"]]
df["text"] = df["text"].str.replace("<br />", " ")
predictions = trainer.predict(tokenized_ds["test"])
df["predicted_label"] = np.argmax(predictions[0], axis=1)
df.head(2)
# Reviewing some of the incorrect predictions
pd.set_option("display.max_colwidth", None)
df[df["label"] != df["predicted_label"]].head(2)
Performance Evaluation:
The performance evaluation revealed promising results for the PEFT model compared to the base model. The PEFT model exhibited a reduced evaluation loss and increased accuracy over the test dataset. Additionally, slight improvements in evaluation runtimes were observed, indicating enhanced efficiency in sentiment analysis tasks.
The application of Parameter-Efficient Fine-Tuning represents a significant stride towards optimizing sentiment analysis performance. By leveraging PEFT techniques, my project showcased notable improvements in model adaptability and efficacy, underscoring its potential for advancing AI capabilities in natural language processing tasks. As the AI landscape continues to evolve, the integration of innovative techniques like PEFT will play a pivotal role in driving efficiency and performance across diverse AI applications.
TL;DR: Github Repo