Study interactive :: Progress tools open in the Study Hub reader.

Transformer Fine-Tuning Guide

Fine-tuning transformer models (T5, BERT, GPT) using the Hugging Face library for various NLP tasks.

Table of Contents


Introduction to Transformer Fine-Tuning

What is Fine-Tuning?

Fine-tuning is the process of taking a pre-trained transformer model and adapting it to a specific task by training it further on task-specific data.

Why Fine-Tune?

Pre-Trained Models

Popular Models:

Hugging Face Transformers

The Hugging Face transformers library provides easy access to pre-trained models and fine-tuning capabilities.

Installation:

pip install transformers torch datasets accelerate

Understanding Transformer Architecture

Key Components

1. Encoder (BERT, T5 Encoder)

2. Decoder (GPT, T5 Decoder)

3. Encoder-Decoder (T5)

Attention Mechanism

Self-Attention:

Multi-Head Attention:


Fine-Tuning T5 for Text Summarization

What is T5?

T5 (Text-to-Text Transfer Transformer) is a unified framework that treats all NLP tasks as text-to-text problems.

Key Features:

Fine-Tuning T5 for Summarization

Step 1: Load Pre-Trained Model and Tokenizer

from transformers import T5ForConditionalGeneration, T5Tokenizer
import torch

# Load pre-trained T5 model and tokenizer
model_name = "t5-small"  # Options: t5-small, t5-base, t5-large, t5-3b, t5-11b
tokenizer = T5Tokenizer.from_pretrained(model_name)
model = T5ForConditionalGeneration.from_pretrained(model_name)

# Move model to GPU if available
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)

Step 2: Prepare Dataset

from datasets import load_dataset

# Load dataset (example: CNN/DailyMail for summarization)
dataset = load_dataset("cnn_dailymail", "3.0.0")

# Or use your own dataset
# dataset = load_dataset("csv", data_files={"train": "train.csv", "validation": "val.csv"})

# Preprocess function
def preprocess_function(examples):
    # Prefix for T5 (required for summarization)
    inputs = ["summarize: " + article for article in examples["article"]]
    targets = examples["highlights"]
    
    # Tokenize inputs
    model_inputs = tokenizer(
        inputs,
        max_length=512,
        truncation=True,
        padding="max_length"
    )
    
    # Tokenize targets
    with tokenizer.as_target_tokenizer():
        labels = tokenizer(
            targets,
            max_length=128,
            truncation=True,
            padding="max_length"
        )
    
    # Replace padding token id's of the labels by -100 so it's ignored by the loss function
    labels["input_ids"] = [
        [(l if l != tokenizer.pad_token_id else -100) for l in label]
        for label in labels["input_ids"]
    ]
    
    model_inputs["labels"] = labels["input_ids"]
    return model_inputs

# Apply preprocessing
tokenized_dataset = dataset.map(preprocess_function, batched=True)

Step 3: Fine-Tune with Trainer

from transformers import Trainer, TrainingArguments
from transformers import DataCollatorForSeq2Seq

# Data collator
data_collator = DataCollatorForSeq2Seq(
    tokenizer=tokenizer,
    model=model,
    padding=True
)

# Training arguments
training_args = TrainingArguments(
    output_dir="./t5-summarization",
    overwrite_output_dir=True,
    num_train_epochs=3,
    per_device_train_batch_size=8,
    per_device_eval_batch_size=8,
    warmup_steps=500,
    weight_decay=0.01,
    logging_dir="./logs",
    logging_steps=100,
    evaluation_strategy="epoch",
    save_strategy="epoch",
    load_best_model_at_end=True,
    push_to_hub=False,  # Set to True to push to Hugging Face Hub
)

# Initialize Trainer
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_dataset["train"],
    eval_dataset=tokenized_dataset["validation"],
    data_collator=data_collator,
    tokenizer=tokenizer,
)

# Fine-tune
trainer.train()

# Save model
trainer.save_model("./t5-summarization-final")
tokenizer.save_pretrained("./t5-summarization-final")

Step 4: Generate Summaries

# Load fine-tuned model
model = T5ForConditionalGeneration.from_pretrained("./t5-summarization-final")
tokenizer = T5Tokenizer.from_pretrained("./t5-summarization-final")
model = model.to(device)

# Generate summary
def summarize(text, max_length=128, min_length=30):
    # Add prefix
    input_text = "summarize: " + text
    
    # Tokenize
    inputs = tokenizer(
        input_text,
        max_length=512,
        truncation=True,
        return_tensors="pt"
    ).to(device)
    
    # Generate
    outputs = model.generate(
        inputs["input_ids"],
        max_length=max_length,
        min_length=min_length,
        num_beams=4,
        early_stopping=True,
        no_repeat_ngram_size=2
    )
    
    # Decode
    summary = tokenizer.decode(outputs[0], skip_special_tokens=True)
    return summary

# Example
article = """
Artificial intelligence has transformed many industries, from healthcare to finance.
Machine learning models can now diagnose diseases, predict stock prices, and even
drive cars. However, with great power comes great responsibility. We must ensure
that AI systems are fair, transparent, and beneficial to all of humanity.
"""

summary = summarize(article)
print("Summary:", summary)

Fine-Tuning BERT for Classification

What is BERT?

BERT (Bidirectional Encoder Representations from Transformers) is a transformer-based model that uses bidirectional context to understand language.

Key Features:

Fine-Tuning BERT for Text Classification

Step 1: Load Pre-Trained Model

from transformers import BertForSequenceClassification, BertTokenizer
from transformers import AutoModelForSequenceClassification, AutoTokenizer
import torch

# Option 1: Use BERT directly
model_name = "bert-base-uncased"
tokenizer = BertTokenizer.from_pretrained(model_name)
model = BertForSequenceClassification.from_pretrained(
    model_name,
    num_labels=2  # Binary classification
)

# Option 2: Use Auto classes (more flexible)
model_name = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(
    model_name,
    num_labels=2
)

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)

Step 2: Prepare Dataset

from datasets import load_dataset
import pandas as pd

# Load dataset (example: IMDB for sentiment analysis)
dataset = load_dataset("imdb")

# Or use your own CSV
# df = pd.read_csv("your_data.csv")
# dataset = Dataset.from_pandas(df)

# Preprocess function
def preprocess_function(examples):
    return tokenizer(
        examples["text"],
        truncation=True,
        padding="max_length",
        max_length=512
    )

# Apply preprocessing
tokenized_dataset = dataset.map(preprocess_function, batched=True)

# Rename columns if needed
tokenized_dataset = tokenized_dataset.rename_column("label", "labels")
tokenized_dataset.set_format("torch", columns=["input_ids", "attention_mask", "labels"])

Step 3: Fine-Tune

from transformers import Trainer, TrainingArguments
from transformers import DataCollatorWithPadding

# Data collator
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)

# Training arguments
training_args = TrainingArguments(
    output_dir="./bert-classification",
    num_train_epochs=3,
    per_device_train_batch_size=16,
    per_device_eval_batch_size=16,
    warmup_steps=500,
    weight_decay=0.01,
    logging_dir="./logs",
    logging_steps=100,
    evaluation_strategy="epoch",
    save_strategy="epoch",
    load_best_model_at_end=True,
)

# Metrics function
from sklearn.metrics import accuracy_score, precision_recall_fscore_support

def compute_metrics(eval_pred):
    predictions, labels = eval_pred
    predicti axis=1)
    precision, recall, f1, _ = precision_recall_fscore_support(labels, predictions, average='weighted')
    acc = accuracy_score(labels, predictions)
    return {
        'accuracy': acc,
        'f1': f1,
        'precision': precision,
        'recall': recall
    }

# Initialize Trainer
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_dataset["train"],
    eval_dataset=tokenized_dataset["test"],
    data_collator=data_collator,
    tokenizer=tokenizer,
    compute_metrics=compute_metrics,
)

# Fine-tune
trainer.train()

# Save model
trainer.save_model("./bert-classification-final")
tokenizer.save_pretrained("./bert-classification-final")

Step 4: Make Predictions

# Load fine-tuned model
model = BertForSequenceClassification.from_pretrained("./bert-classification-final")
tokenizer = BertTokenizer.from_pretrained("./bert-classification-final")
model = model.to(device)
model.eval()

def predict(text):
    # Tokenize
    inputs = tokenizer(
        text,
        truncation=True,
        padding="max_length",
        max_length=512,
        return_tensors="pt"
    ).to(device)
    
    # Predict
    with torch.no_grad():
        outputs = model(**inputs)
        logits = outputs.logits
        probabilities = torch.softmax(logits, dim=-1)
        predicted_class = torch.argmax(probabilities, dim=-1).item()
    
    return predicted_class, probabilities[0].cpu().numpy()

# Example
text = "This movie was absolutely fantastic! I loved every minute of it."
predicted_class, probabilities = predict(text)
print(f"Predicted Class: {predicted_class}")
print(f"Probabilities: {probabilities}")

Fine-Tuning GPT for Text Generation

What is GPT?

GPT (Generative Pre-trained Transformer) is an autoregressive language model that generates text one token at a time.

Key Features:

Fine-Tuning GPT-2 for Text Generation

Step 1: Load Pre-Trained Model

from transformers import GPT2LMHeadModel, GPT2Tokenizer
import torch

# Load GPT-2
model_name = "gpt2"  # Options: gpt2, gpt2-medium, gpt2-large, gpt2-xl
tokenizer = GPT2Tokenizer.from_pretrained(model_name)
model = GPT2LMHeadModel.from_pretrained(model_name)

# Add padding token (GPT-2 doesn't have one by default)
tokenizer.pad_token = tokenizer.eos_token

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)

Step 2: Prepare Dataset

from datasets import load_dataset

# Load dataset (example: your text generation dataset)
# dataset = load_dataset("your_dataset")

# Or create from text file
def load_text_file(file_path):
    with open(file_path, 'r', encoding='utf-8') as f:
        texts = f.readlines()
    return {"text": texts}

# Preprocess function
def preprocess_function(examples):
    return tokenizer(
        examples["text"],
        truncation=True,
        padding="max_length",
        max_length=512
    )

# Apply preprocessing
tokenized_dataset = dataset.map(preprocess_function, batched=True)

Step 3: Fine-Tune

from transformers import Trainer, TrainingArguments
from transformers import DataCollatorForLanguageModeling

# Data collator for language modeling
data_collator = DataCollatorForLanguageModeling(
    tokenizer=tokenizer,
    mlm=False  # GPT-2 is not masked language model
)

# Training arguments
training_args = TrainingArguments(
    output_dir="./gpt2-generation",
    num_train_epochs=3,
    per_device_train_batch_size=4,
    per_device_eval_batch_size=4,
    warmup_steps=500,
    weight_decay=0.01,
    logging_dir="./logs",
    logging_steps=100,
    evaluation_strategy="epoch",
    save_strategy="epoch",
    load_best_model_at_end=True,
)

# Initialize Trainer
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_dataset["train"],
    eval_dataset=tokenized_dataset["validation"],
    data_collator=data_collator,
    tokenizer=tokenizer,
)

# Fine-tune
trainer.train()

# Save model
trainer.save_model("./gpt2-generation-final")
tokenizer.save_pretrained("./gpt2-generation-final")

Step 4: Generate Text

# Load fine-tuned model
model = GPT2LMHeadModel.from_pretrained("./gpt2-generation-final")
tokenizer = GPT2Tokenizer.from_pretrained("./gpt2-generation-final")
model = model.to(device)
model.eval()

def generate_text(prompt, max_length=100, temperature=0.7, top_k=50, top_p=0.95):
    # Tokenize prompt
    inputs = tokenizer.encode(prompt, return_tensors="pt").to(device)
    
    # Generate
    with torch.no_grad():
        outputs = model.generate(
            inputs,
            max_length=max_length,
            temperature=temperature,
            top_k=top_k,
            top_p=top_p,
            do_sample=True,
            pad_token_id=tokenizer.eos_token_id
        )
    
    # Decode
    generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
    return generated_text

# Example
prompt = "The future of artificial intelligence"
generated = generate_text(prompt, max_length=150)
print("Generated Text:")
print(generated)

Optimization Techniques

Learning Rate Scheduling

from transformers import get_linear_schedule_with_warmup

# Create optimizer
optimizer = torch.optim.AdamW(model.parameters(), lr=2e-5)

# Create scheduler
num_training_steps = len(train_dataset) * num_epochs
scheduler = get_linear_schedule_with_warmup(
    optimizer,
    num_warmup_steps=500,
    num_training_steps=num_training_steps
)

# Use in training loop
for epoch in range(num_epochs):
    for batch in train_dataloader:
        loss = model(**batch).loss
        loss.backward()
        optimizer.step()
        scheduler.step()  # Update learning rate
        optimizer.zero_grad()

Gradient Accumulation

For larger effective batch sizes when GPU memory is limited:

training_args = TrainingArguments(
    output_dir="./model",
    per_device_train_batch_size=4,  # Small batch size
    gradient_accumulation_steps=4,   # Effective batch size = 4 * 4 = 16
    # ... other arguments
)

Mixed Precision Training

For faster training and lower memory usage:

training_args = TrainingArguments(
    output_dir="./model",
    fp16=True,  # Enable mixed precision
    # ... other arguments
)

Freezing Layers

Freeze early layers and only fine-tune later layers:

# Freeze all layers
for param in model.parameters():
    param.requires_grad = False

# Unfreeze last few layers
for param in model.bert.encoder.layer[-2:].parameters():
    param.requires_grad = True

# Unfreeze classifier head
for param in model.classifier.parameters():
    param.requires_grad = True

Learning Rate Finder

Find optimal learning rate:

from transformers import TrainerCallback

class LRFinderCallback(TrainerCallback):
    def __init__(self):
        self.lrs = []
        self.losses = []
    
    def on_step_end(self, args, state, control, **kwargs):
        self.lrs.append(args.learning_rate)
        self.losses.append(state.log_history[-1].get('loss', 0))

# Use in training
trainer = Trainer(
    # ... other arguments
    callbacks=[LRFinderCallback()]
)

Best Practices

1. Data Preparation

2. Model Selection

3. Hyperparameter Tuning

4. Monitoring

5. Evaluation

6. Deployment


Resources

Official Documentation

Tutorials

Books

Community


Try next: Fine-tune a small model for one epoch on a held-out split. Stop if val loss rises.