Problem
A model improves by being scored against something, so training needs a signal. The expensive part is rarely the model; it is the signal. Labelling a million photos with the objects in them takes people and money, while a trillion words of raw text cost almost nothing. Which setup you are in decides how much data you can use, what the model can learn, and what the loss measures (see loss functions).
Confusing the setups causes real mistakes: expecting clustering to recover the categories you had in mind, treating a pretraining loss as a quality score, or paying for labels a free objective could have supplied.
Intuition
Think of studying for an exam. Supervised: you have worked problems with answer keys. Unsupervised: you have a pile of problems with no answers and sort them into piles that look alike. Self-supervised: you cover a word in a textbook sentence and try to recall it; the book itself is the answer key, so you can quiz yourself for free, endlessly.
The only difference between the three is the source of the target.
Mechanics
Supervised learning
You have pairs (xi, yi) and fit a function f so that f(xi) ≈ yi by minimizing a loss. If y is a number it is regression (linear regression); if y is a category it is classification (logistic regression). The label defines the task: change it and the same inputs teach something else.
Unsupervised learning
You have only x. The goal is structure: groups (clustering), a compact description (dimensionality reduction), or the density p(x) itself. k-means is the standard example. It alternates two steps until nothing changes: assign each point to its nearest centre, then move each centre to the mean of its points. The quantity it lowers is inertia, the sum of squared distances from each point to its centre. Neither step can raise it, so it settles, though possibly in a poor spot that depends on the start. Nothing says the groups it finds match any human categories.
Self-supervised learning
You still only have x, but you manufacture a supervised problem from it by hiding a piece and predicting it. For text there are two standard choices:
- Next-token prediction: the input is tokens up to position t and the target is token t+1. A sequence of n tokens yields n − 1 labelled examples. This is how a decoder-only model is pretrained, and what decoding later samples from.
- Masked prediction: replace some tokens with a mask symbol and predict the originals from the surrounding words on both sides. The loss is a cross-entropy at the masked positions only.
The loss is the same cross-entropy as in supervised classification; only the labels' origin differs. For images, audio and video the hidden piece is a patch, a frame or the other view of the same item (the contrastive losses on the loss functions page).
In between
Semi-supervised learning has a few labels and many unlabelled points, and uses the unlabelled ones to learn the shape of the data, as in the worked example below. Weak supervision uses labels that are cheap but noisy: keywords, rules, links, or another model's guesses. Pretrain-then-fine-tune chains self-supervised pretraining on lots of raw data with supervised training on a small labelled set. Reinforcement learning is another case with its own signal, a reward, not covered here.
The estimator API
scikit-learn expresses the setups through one interface. Every estimator has fit(X, y=None); supervised ones
need y, unsupervised ones ignore it. Then predict(X) for a supervised model,
or labels_ and predict for a clusterer. Mixins add behaviour by role:
ClassifierMixin.score is accuracy, RegressorMixin.score is R², and
ClusterMixin adds fit_predict. PyTorch has no such interface; you write the loop yourself
(see the training loop).
Feature 2 is the vertical axis. Colour shows the cluster k-means found; the two circled points are the only ones with labels.
| Component | Shape/Params | Meaning | Source |
|---|---|---|---|
BaseEstimator |
get_params / set_params |
Base of every estimator; constructor arguments are the hyperparameters, learned values end in an underscore. | base.py |
ClassifierMixin.score |
X [n, d], y [n] |
Marks the estimator as a classifier; score defaults to accuracy. |
base.py |
RegressorMixin.score |
X [n, d], y [n] |
Marks a regressor; score defaults to R². |
base.py |
ClusterMixin.fit_predict |
X [n, d], no y |
Fits and returns a cluster id per point. | base.py |
KMeans.fit |
n_clusters, n_init; sets cluster_centers_, labels_, inertia_ |
Lloyd's alternation, repeated from several starts, keeping the lowest inertia. | _kmeans.py |
nn.CrossEntropyLoss |
logits [N, C], class indices [N] |
The loss for supervised classification, and for next-token and masked prediction alike. | loss.py |
nn.MSELoss |
prediction and target, same shape | The usual supervised regression loss. | loss.py |
Worked example
k-means with no labels
Nine points, five near the lower left and four upper right (see the figure). Start the two centres badly, on the first two points (0.5, 1.0) and (1.0, 1.4). Each row is one pass: assign, record the inertia, then move the centres.
| Pass | Points in cluster 1 | Inertia | New centre 1 | New centre 2 |
|---|---|---|---|---|
| 1 | 8 | 74.92 | (0.5, 1.0) | (3.0, 2.7) |
| 2 | 5 | 19.23 | (1.2, 1.2) | (3.9, 3.6) |
| 3 | 4 | 7.43 | (1.4, 1.4) | (4.3, 3.9) |
| 4 | 4 | 5.89 | (1.4, 1.4) | (4.3, 3.9) |
Inertia falls every pass and the last pass leaves the centres unchanged, so it stops. The clusters match the two visible groups, but the algorithm only produced the numbers 0 and 1; nothing says which is "small". Starting from the first and last points instead, it settles on the same answer in 2 passes.
Two labels and the unlabelled points
Suppose only points 1 and 6 have labels (class 0 and class 1). Option one: give each point the label of its nearest labelled point. Option two: cluster all nine points as above, then give each cluster the label of the labelled point inside it. Against the true labels:
| Method | Uses unlabelled structure | Wrong points | Accuracy |
|---|---|---|---|
| Nearest labelled point | no | 1 | 0.89 |
| Cluster, then name the cluster | yes | 0 | 1.00 |
Point 5, (2.4, 2.2), is nearer to the labelled class-1 point (3.6, 3.0) than to the class-0 point (0.5, 1.0), so the first method calls it class 1. But it sits in the lower-left cluster, which holds the class-0 label, so the second method gets it right. The unlabelled points told the method where the groups are. This only works if clusters line up with classes; when they do not, the extra data misleads.
Labels from the text itself
Take the 10-token text "the cat sat on the mat and the cat ran". With no human involved, it gives 9 next-token examples: input "the" → target "cat", input "cat" → target "sat", and so on. Masking position 2 gives the input "the [MASK] sat on the mat and the cat ran" with target "cat"; masking position 6 gives "the cat sat on the [MASK] and the cat ran" with target "mat".
A model that just counts which token follows which (a bigram table, fit on this same text) has a mean cross-entropy of 0.37 nats on these 9 examples. Guessing uniformly among the 7 distinct tokens costs ln 7 = 1.95. The gap is the structure learned. Measured on its own training text, though, this number is optimistic; a real evaluation uses held-out text.
Implementation
import torch
import torch.nn.functional as F
from sklearn.cluster import KMeans
from sklearn.linear_model import LogisticRegression
# supervised: the labels y come from people
clf = LogisticRegression().fit(X, y)
clf.score(X_test, y_test) # accuracy
# unsupervised: no y; the labels_ are arbitrary cluster ids
km = KMeans(n_clusters=2, n_init=10).fit(X)
km.labels_, km.inertia_
# self-supervised: the targets are the inputs shifted by one
def next_token_loss(model, tokens): # tokens: [B, T] ints
logits = model(tokens[:, :-1]) # [B, T-1, V]
return F.cross_entropy(
logits.reshape(-1, logits.size(-1)), tokens[:, 1:].reshape(-1)
)
# masked prediction: loss only where a token was hidden
def masked_loss(model, tokens, mask): # mask: [B, T] bool
corrupted = tokens.masked_fill(mask, MASK_ID)
logits = model(corrupted)
targets = tokens.masked_fill(~mask, -100) # ignore_index default
return F.cross_entropy(logits.transpose(1, 2), targets)
Note that KMeans.fit(X) takes no y, whereas LogisticRegression.fit(X, y) requires it, which is the
whole distinction in one line. The self-supervised loss is the same cross_entropy call as the supervised
one; -100 is its default ignore_index, which is what lets unmasked positions contribute nothing.
Tradeoffs
- Cost of labels. Supervised data is limited by annotation; self-supervised data is limited only by what you can collect. The price is that the objective is a proxy: predicting words is not the same as being helpful or correct.
- Evaluation. Supervised models have an obvious test, accuracy on held-out labels. Unsupervised ones have no ground truth, so you judge by indirect measures (inertia, downstream use) that can be gamed or misleading.
- Label quality. A supervised model learns the labellers' mistakes and biases. Weak labels scale but add noise the model can fit.
- k-means pitfalls. You must choose k; it assumes roughly round, similar-sized groups; and different starts give different results, which is why
n_initexists. Lower inertia with larger k is not evidence of better clusters, since k = n gives zero. - Mixing setups. Pretraining then fine-tuning gets the best of both but inherits the pretraining data's biases, and a fine-tuned model can forget what it learned in pretraining if tuned too hard.
Check yourself
Is next-token prediction supervised or unsupervised?
Mechanically it is supervised (inputs, targets, a cross-entropy loss), but the targets cost nothing because they are part of the input. That is why it is called self-supervised.
Why can k-means never raise its inertia from one pass to the next?
Reassigning each point to its nearest centre cannot increase total distance, and the mean is the point that minimizes squared distance to a set, so moving a centre to it cannot either.
In the worked example, how many next-token examples does a text of 100 tokens give?
99. Each position except the last is the input for the token after it.
Connections
Read first
Nothing: this page is a starting point.
Read next
- Generalization intro
References
- Hastie, Tibshirani and Friedman. The Elements of Statistical Learning, 2nd ed., chapters 2 and 14. Springer, 2009.
- Lloyd. Least Squares Quantization in PCM. IEEE Transactions on Information Theory, 1982 (circulated as a Bell Labs memo in 1957).
- Devlin et al. BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding. 2018 (arXiv 1810.04805), for masked prediction.
- Radford et al. Language Models are Unsupervised Multitask Learners (GPT-2). OpenAI, 2019, for next-token pretraining.
- scikit-learn 1.9.1:
BaseEstimator,KMeans.fit. - PyTorch v2.15.0:
CrossEntropyLoss.