Approach: Load a pretrained BERTForSequenceClassification, freeze all encoder params, unfreeze classifier head and any LayerNorm parameters (often named "LayerNorm" or ".layer_norm"). Build optimizer on model.parameters() filtered for requires_grad, train for epochs with standard forward/backward, and run validation computing accuracy.python
import torch
from torch.utils.data import DataLoader
from transformers import AutoTokenizer, AutoModelForSequenceClassification, get_linear_schedule_with_warmup
from datasets import load_dataset
import torch.nn.functional as F
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model_name = "bert-base-uncased"
# Data (example using glue/mrpc)
dataset = load_dataset("glue", "mrpc")
tokenizer = AutoTokenizer.from_pretrained(model_name)
def preprocess(batch):
toks = tokenizer(batch["sentence1"], batch["sentence2"], truncation=True, padding="max_length", max_length=128)
toks["labels"] = batch["label"]
return toks
train_ds = dataset["train"].map(preprocess, batched=True)
val_ds = dataset["validation"].map(preprocess, batched=True)
cols = ["input_ids","attention_mask","labels"]
train_dl = DataLoader(train_ds.remove_columns([c for c in train_ds.column_names if c not in cols]), batch_size=16, shuffle=True)
val_dl = DataLoader(val_ds.remove_columns([c for c in val_ds.column_names if c not in cols]), batch_size=32)
# Model
model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=2)
model.to(device)
# Freeze all params first
for p in model.parameters():
p.requires_grad = False
# Unfreeze classifier head and LayerNorm parameters
for name, p in model.named_parameters():
if name.startswith("classifier") or "layernorm" in name.lower() or "layer_norm" in name.lower() or "LayerNorm" in name:
p.requires_grad = True
# Prepare optimizer with only trainable params
trainable_params = [p for p in model.parameters() if p.requires_grad]
optimizer = torch.optim.AdamW(trainable_params, lr=2e-5, weight_decay=0.01)
total_steps = len(train_dl) * 3
scheduler = get_linear_schedule_with_warmup(optimizer, num_warmup_steps=0, num_training_steps=total_steps)
# Training loop with validation accuracy
def evaluate(model, dataloader):
model.eval()
correct = total = 0
with torch.no_grad():
for batch in dataloader:
input_ids = batch["input_ids"].to(device)
attention_mask = batch["attention_mask"].to(device)
labels = batch["labels"].to(device)
outputs = model(input_ids=input_ids, attention_mask=attention_mask)
preds = outputs.logits.argmax(dim=-1)
correct += (preds == labels).sum().item()
total += labels.size(0)
return correct / total
for epoch in range(3):
model.train()
for batch in train_dl:
input_ids = batch["input_ids"].to(device)
attention_mask = batch["attention_mask"].to(device)
labels = batch["labels"].to(device)
outputs = model(input_ids=input_ids, attention_mask=attention_mask, labels=labels)
loss = outputs.loss
loss.backward()
optimizer.step()
scheduler.step()
optimizer.zero_grad()
val_acc = evaluate(model, val_dl)
print(f"Epoch {epoch+1} — val_acc: {val_acc:.4f}")
Key points:- Freezing reduces compute/memory and preserves pretrained features.- Ensure LayerNorm params are trainable for stability when adapting to new tasks.- Filter optimizer to only update parameters with requires_grad=True.