Chapter 16 - Transformer¶
import sys
sys.path.insert(0, '..')Quote from https://
DistilBERT is a small, fast, cheap and light Transformer model trained by distilling BERT base. It has 40% less parameters than bert-base-uncased , runs 60% faster while preserving over 95% of BERT’s performances as measured on the GLUE language understanding benchmark.
Fine-tuning a BERT model in PyTorch¶
Loading the IMDb movie review dataset¶
We recommend to check specific versions of essential packages like PyTorch and Transformers to ensure compatibility of your CUDA devices. This step helps prevent errors caused by version mismatches.
import gzip
import shutil
import time
import pandas as pd
import requests
import torch
import torch.nn.functional as F
import torchtext
import transformers
from transformers import DistilBertTokenizerFast
from transformers import DistilBertForSequenceClassificationGeneral Settings
torch.backends.cudnn.deterministic = True
RANDOM_SEED = 123
torch.manual_seed(RANDOM_SEED)
DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
NUM_EPOCHS = 3Download Dataset
The following cells will download the IMDB movie review dataset (http://
url = "https://github.com/rasbt/machine-learning-book/raw/main/ch08/movie_data.csv.gz"
filename = url.split("/")[-1]
with open(filename, "wb") as f:
r = requests.get(url)
f.write(r.content)
with gzip.open('movie_data.csv.gz', 'rb') as f_in:
with open('movie_data.csv', 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)Check that the dataset looks okay:
df = pd.read_csv('movie_data.csv')
df.head()df.shape(50000, 2)Split Dataset into Train/Validation/Test
train_texts = df.iloc[:35000]['review'].values
train_labels = df.iloc[:35000]['sentiment'].values
valid_texts = df.iloc[35000:40000]['review'].values
valid_labels = df.iloc[35000:40000]['sentiment'].values
test_texts = df.iloc[40000:]['review'].values
test_labels = df.iloc[40000:]['sentiment'].valuesTokenizing the dataset¶
We use the DistilBertTokenizerFast tokenizer to preprocess our text data.
The tokenizer:
Splits text into subword tokens.
Converts tokens to numerical IDs based on the pre-trained DistilBERT vocabulary.
Pads or truncates sequences to a fixed length to enable batch processing.
tokenizer = DistilBertTokenizerFast.from_pretrained('distilbert-base-uncased')/home/myenv/lib/python3.10/site-packages/huggingface_hub/file_download.py:797: FutureWarning: `resume_download` is deprecated and will be removed in version 1.0.0. Downloads always resume when possible. If you want to force a new download, use `force_download=True`.
warnings.warn(
train_encodings = tokenizer(list(train_texts), truncation=True, padding=True)
valid_encodings = tokenizer(list(valid_texts), truncation=True, padding=True)
test_encodings = tokenizer(list(test_texts), truncation=True, padding=True)train_encodings[0]Encoding(num_tokens=512, attributes=[ids, type_ids, tokens, offsets, attention_mask, special_tokens_mask, overflowing])Dataset Class and Loaders
Here, we wrap our tokenized data into a torch.utils.data.Dataset object and then use DataLoader to efficiently feed data into the model during training.
The DataLoader handles batching, shuffling, and parallel loading, which helps speed up and stabilize the training process.
class IMDbDataset(torch.utils.data.Dataset):
def __init__(self, encodings, labels):
self.encodings = encodings
self.labels = labels
def __getitem__(self, idx):
item = {key: torch.tensor(val[idx]) for key, val in self.encodings.items()}
# item['labels'] = torch.tensor(self.labels[idx]) # original code (outdated)
item['labels'] = torch.tensor(int(self.labels[idx]))
return item
def __len__(self):
return len(self.labels)
train_dataset = IMDbDataset(train_encodings, train_labels)
valid_dataset = IMDbDataset(valid_encodings, valid_labels)
test_dataset = IMDbDataset(test_encodings, test_labels)train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, shuffle=True)
valid_loader = torch.utils.data.DataLoader(valid_dataset, batch_size=16, shuffle=False)
test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=16, shuffle=False)Loading and fine-tuning a pre-trained BERT model¶
We load a pre-trained DistilBERT model (DistilBertForSequenceClassification) and configure it for binary classification.
Using a pre-trained model leverages knowledge gained from large-scale text corpora, allowing us to fine-tune it on a smaller, task-specific dataset like IMDb movie reviews.
model = DistilBertForSequenceClassification.from_pretrained('distilbert-base-uncased')
model.to(DEVICE)
model.train()
optim = torch.optim.Adam(model.parameters(), lr=5e-5)Some weights of DistilBertForSequenceClassification were not initialized from the model checkpoint at distilbert-base-uncased and are newly initialized: ['classifier.bias', 'classifier.weight', 'pre_classifier.bias', 'pre_classifier.weight']
You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.
Train Model -- Manual Training Loop¶
During each epoch:
We perform a forward pass to compute model predictions.
Calculate the loss between predictions and actual labels.
Perform a backward pass to update model weights using gradients.
Optionally, evaluate the model on the validation set after each epoch to monitor progress.
After training, we evaluate the model’s performance on the validation dataset.
We calculate metrics like accuracy and loss to assess how well the model generalizes to unseen data. This step helps us identify potential overfitting or underfitting.
def compute_accuracy(model, data_loader, device):
with torch.no_grad():
correct_pred, num_examples = 0, 0
for batch_idx, batch in enumerate(data_loader):
### Prepare data
input_ids = batch['input_ids'].to(device)
attention_mask = batch['attention_mask'].to(device)
labels = batch['labels'].to(device)
outputs = model(input_ids, attention_mask=attention_mask)
logits = outputs['logits']
predicted_labels = torch.argmax(logits, 1)
num_examples += labels.size(0)
correct_pred += (predicted_labels == labels).sum()
return correct_pred.float()/num_examples * 100
start_time = time.time()
for epoch in range(NUM_EPOCHS):
model.train()
for batch_idx, batch in enumerate(train_loader):
### Prepare data
input_ids = batch['input_ids'].to(DEVICE)
attention_mask = batch['attention_mask'].to(DEVICE)
labels = batch['labels'].to(DEVICE)
### Forward
outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
loss, logits = outputs['loss'], outputs['logits']
### Backward
optim.zero_grad()
loss.backward()
optim.step()
### Logging
if not batch_idx % 250:
print (f'Epoch: {epoch+1:04d}/{NUM_EPOCHS:04d} | '
f'Batch {batch_idx:04d}/{len(train_loader):04d} | '
f'Loss: {loss:.4f}')
model.eval()
with torch.set_grad_enabled(False):
print(f'Training accuracy: '
f'{compute_accuracy(model, train_loader, DEVICE):.2f}%'
f'\nValid accuracy: '
f'{compute_accuracy(model, valid_loader, DEVICE):.2f}%')
print(f'Time elapsed: {(time.time() - start_time)/60:.2f} min')
print(f'Total Training Time: {(time.time() - start_time)/60:.2f} min')
print(f'Test accuracy: {compute_accuracy(model, test_loader, DEVICE):.2f}%')Epoch: 0001/0003 | Batch 0000/2188 | Loss: 0.6767
Epoch: 0001/0003 | Batch 0250/2188 | Loss: 0.2761
Epoch: 0001/0003 | Batch 0500/2188 | Loss: 0.2632
Epoch: 0001/0003 | Batch 0750/2188 | Loss: 0.1686
Epoch: 0001/0003 | Batch 1000/2188 | Loss: 0.0973
Epoch: 0001/0003 | Batch 1250/2188 | Loss: 0.1445
Epoch: 0001/0003 | Batch 1500/2188 | Loss: 0.1527
Epoch: 0001/0003 | Batch 1750/2188 | Loss: 0.0854
Epoch: 0001/0003 | Batch 2000/2188 | Loss: 0.1912
Training accuracy: 96.64%
Valid accuracy: 92.68%
Time elapsed: 6.92 min
Epoch: 0002/0003 | Batch 0000/2188 | Loss: 0.2476
Epoch: 0002/0003 | Batch 0250/2188 | Loss: 0.1103
Epoch: 0002/0003 | Batch 0500/2188 | Loss: 0.1080
Epoch: 0002/0003 | Batch 0750/2188 | Loss: 0.2222
Epoch: 0002/0003 | Batch 1000/2188 | Loss: 0.0294
Epoch: 0002/0003 | Batch 1250/2188 | Loss: 0.3470
Epoch: 0002/0003 | Batch 1500/2188 | Loss: 0.3448
Epoch: 0002/0003 | Batch 1750/2188 | Loss: 0.1408
Epoch: 0002/0003 | Batch 2000/2188 | Loss: 0.0051
Training accuracy: 98.16%
Valid accuracy: 91.90%
Time elapsed: 13.84 min
Epoch: 0003/0003 | Batch 0000/2188 | Loss: 0.0311
Epoch: 0003/0003 | Batch 0250/2188 | Loss: 0.0788
Epoch: 0003/0003 | Batch 0500/2188 | Loss: 0.0103
Epoch: 0003/0003 | Batch 0750/2188 | Loss: 0.1383
Epoch: 0003/0003 | Batch 1000/2188 | Loss: 0.0124
Epoch: 0003/0003 | Batch 1250/2188 | Loss: 0.2669
Epoch: 0003/0003 | Batch 1500/2188 | Loss: 0.0127
Epoch: 0003/0003 | Batch 1750/2188 | Loss: 0.0215
Epoch: 0003/0003 | Batch 2000/2188 | Loss: 0.0652
Training accuracy: 99.23%
Valid accuracy: 92.20%
Time elapsed: 20.76 min
Total Training Time: 20.76 min
Test accuracy: 92.63%
del model # free memory