Digits

The scikit-learn digits dataset: 1,797 tiny 8x8 greyscale images of handwritten digits, and the workhorse of every sklearn tutorial that needs a multiclass problem which loads instantly and fits in L2 cache.

Provenance

The data is the Optical Recognition of Handwritten Digits (optdigits) set from the UCI machine learning repository, created by E. Alpaydin and C. Kaynak at Bogazici University and donated in July 1998. It originates in Kaynak’s 1995 MSc thesis on combining multiple classifiers, and the companion paper is Alpaydin and Kaynak, Cascading Classifiers, Kybernetika 34(4), 1998.

Handwritten digits were extracted from preprinted forms using NIST’s preprocessing programs. 43 people contributed: 30 writers for the training set and a different 13 for the test set, so the official split measures writer-independent generalisation.

Schema

  • preprocessing: each digit is a 32x32 binary bitmap, divided into non-overlapping 4x4 blocks; the count of on-pixels in each block (an integer 0 to 16) becomes one feature. This shrinks dimensionality and buys invariance to small distortions.
  • features: 64 integers in 0 to 16 (an 8x8 matrix, flattened).
  • classes: 10 (digits 0 to 9), near-balanced (174 to 183 instances per class).
  • instances: the full UCI set has 5,620 (3,823 train + 1,797 test). sklearn’s load_digits ships only the 1,797-instance test portion — worth knowing when comparing numbers against the UCI literature.

Not MNIST

Easy to confuse, materially different:

propertydigits (optdigits)mnist
resolution8x8, values 0 to 1628x28, values 0 to 255
instances1,797 (sklearn copy)70,000
originUCI / Bogazici, 1998NIST via LeCun et al., 1998

Accuracies do not transfer: “99% on digits” is a statement about a 1.8k-sample, 64-feature problem, not about MNIST. On a dataset this small, 5-fold accuracy carries error bars of roughly a percentage point, so leaderboard-style comparisons of decimals are noise.

Benchmarks

5-fold cross-validation on the raw 64 features, default-ish models:

from sklearn.datasets import load_digits
from sklearn.model_selection import cross_val_score
from sklearn.svm import SVC
from sklearn.neighbors import KNeighborsClassifier
from sklearn.linear_model import LogisticRegression

digits = load_digits()
X, y = digits.data, digits.target
for name, clf in [("svm (rbf, gamma=0.001)", SVC(gamma=0.001)),
                  ("3-nn", KNeighborsClassifier(3)),
                  ("logistic regression", LogisticRegression(max_iter=5000))]:
    acc = cross_val_score(clf, X, y, cv=5).mean()
    print(f"{name:<24} {acc:.4f}")

Kernel methods and nearest-neighbour sit in the high 90s essentially out of the box (the original UCI test-set experiments with k-NN land around 98%); a linear model gives up a few points. There is not much headroom left — this dataset is a sanity check, not a benchmark.

On this site

  • gaussian mixtures — the tagged model: fitting a GMM per class (or clustering the digits unsupervised) is the classic use of this dataset once labels are hidden.
  • pca — the 64-dimensional digit cloud is standard visualisation fodder for projection methods.

Loading

No download — the data ships inside sklearn:

from sklearn.datasets import load_digits

digits = load_digits()
X, y = digits.data, digits.target
print(X.shape, y.shape)          # flattened 8x8 -> 64 features
print(digits.images.shape)       # original 8x8 bitmaps
print(X.min(), X.max())          # 4x4 block counts, not 0-255 pixels
import numpy as np
print(np.bincount(y))            # per-class counts