Fine-tune BERT Transformers for Text Classification: Microsoft AI for Beginners Guide
You can fine-tune BERT for text classification by loading a pretrained checkpoint, adding a classification head, and training on labeled data using the PyTorch or TensorFlow notebooks in the Microsoft AI-For-Beginners repository.
The Microsoft AI-For-Beginners curriculum introduces practical natural language processing concepts in lesson 5-NLP → 18-Transformers, demonstrating how to adapt pretrained BERT models for specific text classification tasks. This guide walks through the complete fine-tuning workflow implemented in the repository's source code, covering architecture components, training loops, and implementation details from both framework variants.
Understanding BERT Architecture Components
The fine-tuning pipeline consists of three core components that transform raw text into classification predictions. Each component is implemented with specific library calls in the repository's notebooks.
Tokenization with BertTokenizer
The BertTokenizer converts raw text into WordPiece tokens, automatically inserting [CLS] and [SEP] markers and generating attention masks for the model. According to lessons/5-NLP/18-Transformers/TransformersPyTorch.ipynb (line ≈ 552), initialize the tokenizer using:
tokenizer = transformers.BertTokenizer.from_pretrained('bert-base-uncased')
This tokenizer handles truncation to max_length, padding to uniform sequence lengths, and conversion to numerical input IDs required by the encoder.
The Pre-trained Encoder
BERT base (12 transformer layers, 768 hidden units) provides contextualized word embeddings learned from massive corpora. Both notebooks instantiate the encoder at lines ≈ 546-550 using the checkpoint bert_model = 'bert-base-uncased'. Unlike left-to-right language models, BERT's bidirectional attention mechanism processes both preceding and following tokens simultaneously, yielding richer feature representations for classification tasks.
The Classification Head
The repository implements task-specific heads using framework-specific wrapper classes:
- PyTorch:
BertForSequenceClassification(seeTransformersPyTorch.ipynb, line ≈ 186) - TensorFlow:
TFBertForSequenceClassification(seeTransformersTF.ipynb, line ≈ 651)
These classes add a dense linear layer on top of the [CLS] token's final hidden state, followed by softmax activation to produce class probabilities. Instantiate the model with num_labels set to your target class count:
# PyTorch implementation from source
model = transformers.BertForSequenceClassification.from_pretrained(
bert_model, num_labels=4
).to(device)
Step-by-Step Fine-Tuning Process
Follow these seven steps implemented in the Microsoft AI-For-Beginners notebooks to adapt BERT to your classification task:
- Select a pretrained checkpoint (
bert-base-uncasedrecommended for beginners). - Instantiate the tokenizer using
BertTokenizer.from_pretrained(). - Load the classification model with
num_labelsmatching your target class count. - Create a DataLoader (PyTorch) or
tf.data.Dataset(TensorFlow) yielding token tensors and labels. - Define an optimizer using AdamW with a linear warm-up learning rate schedule (typically 2e-5).
- Run the training loop for 2-4 epochs on your labeled dataset.
- Evaluate accuracy and F1-score on a held-out validation set.
Complete Fine-Tuning Implementation
Below is a minimal, runnable example mirroring the notebook workflow. Save this as fine_tune_bert.py:
import torch
from transformers import BertTokenizer, BertForSequenceClassification
from torch.utils.data import DataLoader, Dataset
from torch.optim import AdamW
# 1. Load pretrained tokenizer
BERT_MODEL = "bert-base-uncased"
tokenizer = BertTokenizer.from_pretrained(BERT_MODEL)
# 2. Prepare dataset
class TextDataset(Dataset):
def __init__(self, texts, labels):
self.encodings = tokenizer(
texts,
truncation=True,
padding="max_length",
max_length=128,
return_tensors="pt"
)
self.labels = torch.tensor(labels)
def __len__(self):
return len(self.labels)
def __getitem__(self, idx):
item = {k: v[idx] for k, v in self.encodings.items()}
item["labels"] = self.labels[idx]
return item
# Example sentiment data
texts = ["I love this movie!", "Terrible plot and bad acting."]
labels = [1, 0] # 1 = positive, 0 = negative
dataset = TextDataset(texts, labels)
loader = DataLoader(dataset, batch_size=2, shuffle=True)
# 3. Initialize model with classification head
model = BertForSequenceClassification.from_pretrained(
BERT_MODEL,
num_labels=2
)
model.train()
# 4. Configure optimizer
optimizer = AdamW(model.parameters(), lr=2e-5)
# 5. Training loop
for batch in loader:
optimizer.zero_grad()
outputs = model(**batch)
loss = outputs.loss
loss.backward()
optimizer.step()
print(f"Loss: {loss.item():.4f}")
# 6. Inference
model.eval()
with torch.no_grad():
test_input = tokenizer(
["What a fantastic experience!"],
return_tensors="pt",
truncation=True,
padding=True
)
logits = model(**test_input).logits
prediction = torch.argmax(logits, dim=1).item()
print(f"Predicted class: {prediction}")
PyTorch vs TensorFlow Implementation Details
The repository provides equivalent implementations in both deep learning frameworks:
- PyTorch version:
lessons/5-NLP/18-Transformers/TransformersPyTorch.ipynbuses theTrainerAPI and standard PyTorch training loops withBertForSequenceClassification. - TensorFlow version:
lessons/5-NLP/18-Transformers/TransformersTF.ipynbimplementsTFBertForSequenceClassificationwithtf.kerastraining loops andoutput_attentions=Falsefor memory efficiency.
Both versions process identical tokenized inputs (input_ids, attention_mask) and target labels, allowing seamless framework selection based on your existing infrastructure.
Why Fine-Tuning BERT Works
Transfer learning enables rapid adaptation because BERT's encoder has already captured general language patterns (syntax, semantics, and contextual relationships) from massive pretraining corpora. By exposing the model to a small, task-specific dataset, you adjust these high-level representations to your specific categories while introducing only a few additional parameters through the classification head. This approach achieves state-of-the-art performance on sentiment analysis and topic categorization benchmarks with minimal training data.
For a non-transformer baseline before implementing BERT, examine examples/04-text-sentiment.py, which demonstrates classic bag-of-words sentiment analysis using traditional machine learning techniques.
Summary
- BertTokenizer processes raw text into WordPiece tokens with
[CLS]and[SEP]markers, implemented at line ≈ 552 inTransformersPyTorch.ipynb. - Load BertForSequenceClassification (PyTorch, line ≈ 186) or TFBertForSequenceClassification (TensorFlow, line ≈ 651) with
num_labelsmatching your class count. - Use AdamW optimizer with a 2e-5 learning rate and train for 2-4 epochs using the implementations in
lessons/5-NLP/18-Transformers/. - Both PyTorch and TensorFlow notebooks reference
bert-base-uncasedat lines ≈ 546-550 for the encoder weights. - Consider freezing early BERT layers (
model.bert.encoder.layer[-X:].requires_grad = True) to reduce training time, or start withexamples/04-text-sentiment.pyfor simpler baselines.
Frequently Asked Questions
How do I choose between PyTorch and TensorFlow for BERT fine-tuning?
Both frameworks offer identical model capabilities through the Hugging Face transformers library. Choose PyTorch if you prefer explicit training loops and dynamic computation graphs, or TensorFlow if you favor the Keras API and built-in distribution strategies. The Microsoft AI-For-Beginners repository provides complete, functional notebooks for both approaches at lessons/5-NLP/18-Transformers/TransformersPyTorch.ipynb and TransformersTF.ipynb.
Can I fine-tune BERT on a CPU instead of GPU?
Yes, but training will be significantly slower. The notebooks automatically detect CUDA availability with .to(device) (PyTorch) or default device placement (TensorFlow). For CPU training, reduce batch_size and max_length to prevent out-of-memory errors, or freeze all BERT layers and only train the classification head to speed up computation.
What is the difference between bert-base-uncased and bert-large-uncased?
BERT base uses 12 transformer layers with 768 hidden units (110M parameters), while BERT large uses 24 layers with 1024 hidden units (340M parameters). Beginners should start with bert-base-uncased as shown in lines ≈ 546-550 of both notebooks, since it trains faster and requires less memory while maintaining strong performance on most text classification tasks.
How do I handle multi-class classification with more than two labels?
Set num_labels to your class count when instantiating BertForSequenceClassification. The model automatically adjusts the output dimension of the classification head, and the loss function adapts from binary cross-entropy to categorical cross-entropy. Ensure your labels are zero-indexed integers (ranging from 0 to N-1) in your dataset preparation to match the expected format in the training loop.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →