IMDb Sentiment Analysis with DistilBERT LoRA, TF-IDF Baselines, Calibration, Interpretability, Robustness Testing, and Semi-Supervised Learning A tutorial by an unnamed author demonstrates an end-to-end sentiment analysis workflow on the Stanford NLP IMDb Large Movie Review Dataset, comparing a TF-IDF and Logistic Regression baseline with a DistilBERT model fine-tuned using LoRA via PEFT. The workflow evaluates models using accuracy, macro-F1, ROC-AUC, calibration metrics, and robustness tests, and incorporates semi-supervised learning with pseudo-labeling on the unlabeled IMDb split. The tutorial reports that the transformer approach achieves higher performance than the baseline, with detailed analysis of confident errors, length-based performance, and truncation effects. In this tutorial, we develop an end-to-end sentiment analysis workflow using the Stanford NLP IMDb https://huggingface.co/datasets/stanfordnlp/imdb Large Movie Review Dataset and compare classical machine learning with parameter-efficient transformer fine-tuning. We begin by establishing a reproducible environment and auditing the dataset for class ordering, review-length skew, duplicate leakage, and preprocessing artifacts before training a strong TF-IDF and Logistic Regression baseline. We then fine-tune DistilBERT with LoRA through PEFT, evaluate it using accuracy, macro-F1, ROC-AUC, confusion matrices, and ROC curves, and examine threshold selection and probability calibration through Expected Calibration Error and reliability analysis. Beyond headline metrics, we investigate confident errors, performance across review lengths, word-level occlusion saliency, and head-versus-tail truncation to understand how the model reaches its predictions and where long-context limitations affect performance. Finally, we use the unlabeled IMDb split for confidence-based pseudo-labeling, compare the resulting semi-supervised model against our baseline, and save the merged transformer for reusable sentiment inference. python import importlib.util, subprocess, sys, os, time, random, warnings, inspect, hashlib warnings.filterwarnings "ignore" os.environ "TOKENIZERS PARALLELISM" = "false" os.environ "WANDB DISABLED" = "true" REQUIRED = { "transformers": "transformers", "datasets": "datasets", "peft": "peft", "accelerate": "accelerate", "sklearn": "scikit-learn", } missing = pkg for mod, pkg in REQUIRED.items if importlib.util.find spec mod is None if missing: print f"Installing: {', '.join missing } ..." subprocess.run sys.executable, "-m", "pip", "install", "-q", missing , check=True print "Done. If imports fail below, restart the runtime and re-run. \n" import numpy as np import pandas as pd import torch import matplotlib.pyplot as plt from datasets import load dataset from sklearn.feature extraction.text import TfidfVectorizer from sklearn.linear model import LogisticRegression from sklearn.pipeline import make pipeline from sklearn.metrics import accuracy score, f1 score, roc auc score, classification report, confusion matrix, roc curve from transformers import AutoTokenizer, AutoModelForSequenceClassification, TrainingArguments, Trainer, DataCollatorWithPadding, EarlyStoppingCallback, set seed from peft import LoraConfig, get peft model, TaskType def disable torchao probe : patched = try: import peft.import utils as piu piu.is torchao available = lambda: False patched.append "peft.import utils" except Exception: pass for name, mod in list sys.modules.items : if name.startswith "peft" and hasattr mod, "is torchao available" : mod.is torchao available = lambda: False patched.append name return patched try: import torchao as tao v = getattr tao, " version ", "?" if tuple int x for x in v.split "." :2 < 0, 16 : print f" compat torchao { v} < 0.16 - disabling PEFT's torchao probe: " f"{', '.join disable torchao probe }" except Exception: disable torchao probe SEED = 42 MODEL NAME = "distilbert-base-uncased" MAX LEN = 256 N TRAIN = 5000 N EVAL = 2000 N UNSUP = 3000 EPOCHS = 2 BATCH = 16 LR = 3e-4 FULL RUN = False if FULL RUN: N TRAIN, N EVAL, EPOCHS = 25000, 25000, 3 set seed SEED ; random.seed SEED ; np.random.seed SEED ; torch.manual seed SEED DEVICE = "cuda" if torch.cuda.is available else "cpu" print "=" 79 print f"device={DEVICE} | torch={torch. version } | " f"gpu={torch.cuda.get device name 0 if DEVICE=='cuda' else 'n/a'}" print "=" 79 t0 = time.time raw = load dataset "stanfordnlp/imdb" print raw, f"\nloaded in {time.time -t0:.1f}s\n" print "--- example truncated ---" print "label:", raw "train" 0 "label" , "|", raw "train" 0 "text" :300 , "...\n" first labels = np.array raw "train" "label" :5 last labels = np.array raw "train" "label" -5: print f"TRAP 1 - split ordering: first 5 labels {first labels}, " f"last 5 labels {last labels} - ALWAYS shuffle before subsampling." train full = raw "train" .shuffle seed=SEED test full = raw "test" .shuffle seed=SEED train ds = train full.select range min N TRAIN, len train full eval ds = test full.select range min N EVAL, len test full print f" after shuffle+subsample: train balance = " f"{np.bincount train ds 'label' }, eval balance = {np.bincount eval ds 'label' }" lens = np.array len t.split for t in train full "text" q = np.percentile lens, 50, 75, 90, 95, 99 print f"\nTRAP 2 - length words : median={q 0 :.0f} p75={q 1 :.0f} p90={q 2 :.0f} " f"p95={q 3 :.0f} p99={q 4 :.0f} max={lens.max }" print f" ~{ lens MAX LEN 0.75 .mean 100:.1f}% of reviews exceed MAX LEN={MAX LEN} " f"tokens rough words- tokens factor 1.3 . Section 9 measures what that costs." h tr = {hashlib.md5 t.encode .hexdigest for t in raw "train" "text" } h te = {hashlib.md5 t.encode .hexdigest for t in raw "test" "text" } print f"\nTRAP 3 - leakage: {len h tr & h te } exact duplicate reviews across " f"train/test; {len raw 'train' -len h tr } dupes inside train itself." def clean t : return t.replace "