Evaluation & Metrics

LPIPS (Learned Perceptual Image Patch Similarity)

A full-reference perceptual distance that compares unit-normalized deep-network features of two images, with per-channel weights calibrated on human similarity judgments; its formula and lin, tune and scratch variants, the 2AFC and JND evaluation on BAPPS, the conventions that change its value, its weaknesses as a loss, and its successors E-LPIPS, ST-LPIPS and DISTS.

intermediate

LPIPS (Learned Perceptual Image Patch Similarity) measures how different two images look by passing both through a fixed convolutional neural network and comparing the activations at several layers, after normalizing each feature vector and weighting its channels with coefficients fitted to human similarity judgments. Zhang, Isola, Efros, Shechtman and Wang (2018) introduced it with a dataset of human two-alternative choices, BAPPS, on which classification networks’ features agreed with people far more often than the mean squared error or SSIM. Lower values mean more similar images. It is a full-reference metric: like PSNR and SSIM it needs a pixel-aligned reference, and view synthesis, super-resolution and image-to-image translation papers report it beside them. Its value depends on the network, the input range and the image size, which must be stated for two numbers to be comparable.

Formula

A network FF maps each image to feature stacks at LL chosen layers. For layer ll, with Hl×WlH_l \times W_l positions and ClC_l channels, the feature vector yhwl∈RCly^l_{hw} \in \mathbb{R}^{C_l} at each position is unit-normalized in the channel dimension, y^hwl=yhwl/∥yhwl∥2\hat{y}^l_{hw} = y^l_{hw} / \lVert y^l_{hw} \rVert_2 (the code adds 10−1010^{-10} to the norm). The distance between a reference xx and a distorted image x0x_0 is Eq. 1 of the paper:

d(x,x0)=∑l1HlWl∑h,w∥wl⊙(y^hwl−y^0hwl)∥22,d(x, x_0) = \sum_l \frac{1}{H_l W_l} \sum_{h,w} \big\lVert w_l \odot \big(\hat{y}^l_{hw} - \hat{y}^l_{0hw}\big) \big\rVert_2^2 ,

where wl∈RClw_l \in \mathbb{R}^{C_l} scales each channel. Per layer: a squared, channel-weighted Euclidean distance at every position, averaged over positions; the layers are then summed. The prose of the paper speaks of averaging across layers, but Eq. 1 and the released code sum them. With wl=1w_l = 1 the per-position term is ∥a^−b^∥2=2−2cos⁡(a,b)\lVert \hat{a} - \hat{b} \rVert^2 = 2 - 2\cos(a, b), so the unweighted distance is a cosine distance, as the paper notes. In the official code the learned weights are a bias-free 1×11 \times 1 convolution applied to the squared differences, ∑cwlc′(y^hwcl−y^0hwcl)2\sum_c w'_{lc} (\hat{y}^l_{hwc} - \hat{y}^l_{0hwc})^2, so wlc′w'_{lc} plays the role of wlc2w_{lc}^2 (our reading of lpips/lpips.py).

Networks and layers. The paper uses three ImageNet classifiers. For AlexNet it takes conv1–conv5 of the single-tower version (64, 192, 384, 256 and 256 channels; see AlexNet); for VGG, five layers labelled conv1_2, conv2_2, conv3_3, conv4_3 and conv5_3 in the paper’s weight plots; for SqueezeNet, the first convolution and six of its eight fire modules (seven taps in the code). The code takes each tap after its ReLU, so the features are non-negative.

Variants. Zhang et al. trained on their judgments in three ways (§3):

  • lin: the network stays frozen and only the non-negative weights ww are learned, 1,472 parameters for VGG and 1,152 for AlexNet. It is the variant the official package loads by default and the one usually meant by “LPIPS”.
  • tune: initialized from the classifier, with all network weights fine-tuned.
  • scratch: the network is initialized with random Gaussian weights and trained only on the judgments.

Training. For a triplet of reference xx and two distorted patches x0,x1x_0, x_1, a small network G\mathcal{G} (two 32-channel fully connected ReLU layers, then one output and a sigmoid) maps the two distances to a predicted probability that a person chose x1x_1, trained with cross-entropy against the fraction hh of judges who did (arXiv v2, Appendix B, Eq. 2). Negative weights are clipped to zero after every step, since a larger feature difference should never make patches closer. Training ran 5 epochs at learning rate 10−410^{-4} and 5 with linear decay, batch size 50. G\mathcal{G} is used only for training; the metric is dd.

Intuition

Why deep features. The paper’s central finding is that the features of networks trained on hard visual tasks already agree with human similarity judgments, whatever the architecture and supervision. In the arXiv v2 Table 5 (2AFC agreement averaged over the six test sets), the classic metrics range from 61.4% (HDR-VDP) to 63.8% (FSIMc); features of a randomly initialized network score 64.3%, an unsupervised stacked k-means initialization 66.6%, and four self-supervised networks 67.2% to 68.4%, about the level of the supervised classifiers (the LPIPS paper article gives the full table). Training signal, not architecture alone, makes the difference. Across AlexNet-like networks, 2AFC scores correlated with PASCAL classification and detection performance at .640 and .363 (Table 4, which the text calls Table 5), and the authors concluded that perceptual similarity is an emergent property of representations tuned to predict structure in the world.

What normalization and weighting do. Unit normalization makes each position’s comparison depend on the direction of the feature vector, not its magnitude, so strong and weak responses count equally (our reading). The learned weights then decide which channels matter: in the AlexNet lin model, 79.7%, 71.4%, 56.8%, 46.5% and 27.7% of the weights in conv1 to conv5 are zero, so most early-layer channels are ignored and almost all of conv5 is used (arXiv v2, Appendix A). Deeper layers have larger receptive fields, which in our reasoning is why the metric tolerates small displacements and texture changes better than pixel differences do, and why it punishes blur, which removes the fine structure early and middle layers respond to; the paper’s qualitative comparison found a BiGAN network’s features more sensitive to blur than SSIM (Fig. 6).

Interpretation

Range. LPIPS is non-negative and 0 for identical inputs; lower is better. Because the features are non-negative, cos⁡≥0\cos \ge 0 and each layer’s unweighted term is at most 2, so the uncalibrated distance is bounded by 2L2L; the learned weights rescale this (our arithmetic). There is no fixed scale, and typical numbers depend on the task: in NeRF’s Table 1, LPIPS was 0.081 on synthetic scenes and 0.250 on real ones, while in image-to-image translation it measures the diversity between outputs, where higher means more varied.

A squared distance. Eq. 1 is the squared Euclidean distance between two embeddings: the concatenated, channel-scaled, normalized feature maps, each layer scaled by 1/HlWl1/\sqrt{H_l W_l}. Its square root therefore satisfies the triangle inequality and is a pseudometric; LPIPS itself, like 1−SSIM1 - \mathrm{SSIM}, need not (our reasoning). Distinct images can have distance 0 if the network maps them to the same normalized features.

How it was validated. BAPPS (Berkeley-Adobe Perceptual Patch Similarity) consists of 64×6464 \times 64 patches: training triplets from the MIT-Adobe 5k images with 2 judgments each, and validation triplets from RAISE1k with 5, collected on Amazon Mechanical Turk, 484k two-alternative judgments in all (§1–2). Distortions are traditional (photometric, noise, blur, spatial warps, JPEG, composed in pairs) or produced by 96 small CNNs, and a separate test set holds outputs of real super-resolution, frame interpolation, video deblurring and colorization methods. The 2AFC score credits a metric with the fraction of judges who chose the patch it rates closer: if 4 of 5 chose x0x_0 and the metric does too, it scores 80% on that triplet; the official code counts ties as 50%. A judge drawn from the same population scores p2+(1−p)2p^2 + (1-p)^2 in expectation, which sets the human ceiling. The JND test showed a reference and a distorted patch for one second each and asked whether they were the same; metrics were scored by the average precision of ranking pairs by distance against “same” responses. The two tests correlated at ρ=.928\rho = .928 across methods, and the JND results are given only as a plot (Fig. 5).

The numbers for LPIPS. In the arXiv v2 Table 5, the lin variants reached 69.8% (AlexNet), 69.2% (VGG) and 70.0% (SqueezeNet) averaged over all six test sets. On the two distortion test sets they reach 78.7–79.8%, but the table greys these values out, since the metrics were trained on the same distortion types. The real-algorithm sets are the fairer test: there, linear calibration raised the raw networks from 65.0%, 62.6% and 64.0% to 65.3%, 64.1% and 65.1% for AlexNet, VGG and SqueezeNet, improving 11 of 12 per-task scores (§4.1). Fine-tuning the whole network scored highest on the distortion sets but below lin on the real algorithms (64.3%, 64.0% and 64.5%), which the authors note as a negative result. Differences of a point or two on these sets separate methods; the gap to the human ceiling of the real-algorithm sets, 69.5%, remains.

When to Use It

LPIPS fits when an output should match a reference but per-pixel errors misjudge what viewers see, above all when one method is sharper and another blurrier.

  • View synthesis: renderings at held-out poses are scored by PSNR, SSIM and LPIPS together (see neural rendering and novel view synthesis); the metrics can rank methods differently.
  • Super-resolution, restoration and inpainting, where it is reported beside PSNR and SSIM; image inpainting gives a case where LPIPS and human preference disagreed.
  • Diversity of generated outputs: the mean LPIPS between outputs for the same input, as in multimodal image-to-image translation.
  • Smoothness of generative models: StyleGAN’s perceptual path length averages LPIPS between nearby interpolated images (see StyleGAN).
  • Training loss: the README notes that, for backpropagation, the VGG version is closer to the traditional perceptual loss (see loss function), while AlexNet performs best as a forward metric.

It does not replace distribution metrics such as the Fréchet Inception Distance when there is no single correct output, or no-reference scores such as NIQE when there is no reference at all.

Failure Cases

Conventions change the number. As of October 2026, the latest release of the official package is lpips 0.1.4 on PyPI, and its behaviour defines the conventions:

  • Input range. Images must be RGB in [−1,1][-1, 1]; a scaling layer then maps them to the statistics the networks were trained on. Images in [0,1][0, 1] need normalize=True. Passing [0,1][0, 1] data silently gives different values: in our reading of the code, the 3D Gaussian splatting evaluation script (commit 54c035f) passes images converted to [0,1][0, 1] to an LPIPS copy with the same scaling layer, so its values are not on the official convention.
  • Network. net='alex' is the default and, per the README, the fastest and best as a forward metric; net='vgg' is used, for example, by that same 3DGS script. The networks give different values for the same pair, and “the default” is ambiguous in the literature (the DISTS paper calls VGG-lin the default configuration), so a table must say which one it used.
  • Version. Version 0.1, the default, fixed a bug in the initial release 0.0, which did not rescale the input; the README asks users to report the version.
  • Spatial output. With spatial=True, each layer’s map is bilinearly upsampled to the input size and the maps summed, giving a per-pixel distance map instead of a scalar.
  • Image size. The weights were fitted on 64×6464 \times 64 patches (the official 2AFC loader resizes BAPPS images to 64), but the network runs on any size, and a pair resized or evaluated at full resolution gives a different value, because the same distortion covers a different fraction of each receptive field (our reasoning; the example below shows it).

Adversarial fragility. LPIPS is a neural network, and Kettunen, Härkönen and Lehtinen (2019) showed that it can be attacked in both directions. Constraining LPIPS to stay within a small anchor distance ε\varepsilon of a source image while moving towards a different target, their optimizer reached images 16.2 ± 0.8 times ε\varepsilon away in ℓ2\ell_2 for LPIPS-VGG and 15.2 ± 0.7 for LPIPS-SqueezeNet, visibly different images that LPIPS still rated close; conversely, images within a small ℓ2\ell_2 ball could be made far in LPIPS (30 OpenImages images, §3.1). In their denoising and super-resolution training experiments, plain LPIPS as a loss sometimes fell into strange local minima with some architectures, for example transposed convolutions.

Small shifts. Ghildyal and Liu (2022) asked 30 people whether 256×256256 \times 256 crops shifted by nn pixels were the same image: 80.7% of responses called a 1-pixel shift the same, 56.0% a 2-pixel shift (Table 1; one sentence of the text says a 2-pixel shift was detected in 50% of cases). Yet shifting the distorted images of BAPPS by one pixel flipped the 2AFC choice of LPIPS (AlexNet) on 2.81% of triplets at 256×253256 \times 253 and 6.79% at 64×6164 \times 61, against 3.59% and 12.27% for ℓ2\ell_2 (their Tables 2 and 3, rank-flip rate, authors’ released models). LPIPS flips less often than ℓ2\ell_2 but is not shift-invariant, and at 256×253256 \times 253 MS-SSIM flipped less often still (2.22%). The authors’ remedy, anti-aliasing, targets the aliasing of strided convolutions and max pooling.

Reporting what you trained on. A model trained with an LPIPS loss and then evaluated by LPIPS is graded by its own objective; together with the attacks above, this makes a low LPIPS weaker evidence of perceptual quality for such models (our reasoning). An independent metric or a user study is then needed.

Patches, not whole images. BAPPS judgments are about low-level similarity of small patches; the authors chose 64×6464 \times 64 patches to limit high-level, semantic respects of similarity (§2.2). LPIPS is not calibrated for whole-image aesthetics or semantic correctness, and it shares the full-reference metrics’ blind spot for plausible content that differs from the reference.

Variants

  • Baseline (uncalibrated): lpips=False weights all channels equally, the cosine distance of the paper’s “Net” rows.
  • E-LPIPS (Kettunen et al., 2019) takes the expectation of LPIPS-VGG over random transformations applied identically to both images (translations, flips, axis swaps, colour-channel permutations, brightness scaling, downscaling), with dropout at keep probability 0.99, average instead of max pooling and weights on all layers including the input. The same attack then reached only 3.0 ± 0.2 times ε\varepsilon; its 2AFC score was 69.16%, between the values they give for LPIPS-VGG (68.65%; Zhang et al.’s table has 69.2%) and LPIPS-SqueezeNet (70.07%). They suggest one random sample per training step and at least 300 for a precise evaluation.
  • ST-LPIPS (Ghildyal & Liu, 2022) modifies the AlexNet-based LPIPS with the elements its study found to help, anti-aliased strided convolution and pooling (BlurPool), larger reflection padding and a smaller conv1 stride, and trains it on BAPPS without augmentation. At 256×256256 \times 256 its 1-pixel rank-flip rate was 0.57–0.66%, with 2AFC 70.4–70.5% (Table 3).
  • DISTS (Ding, Ma, Wang & Simoncelli, 2022) builds on VGG16 with ℓ2\ell_2 pooling instead of max pooling and adds the input image as a zeroth layer, and combines a texture term comparing the spatial means of the two images’ corresponding feature maps with a structure term comparing the maps themselves through their correlation; it is trained on the KADID-10k quality database while keeping crops of the same texture close, to tolerate texture resampling. Without training on BAPPS it scored 0.689 on the 2AFC test against 0.692 for LPIPS-VGG-lin, the values of the original paper’s VGG-lin row (Table 2 of the arXiv v3 copy of the journal paper).

Example

To stay self-contained, without downloading pretrained weights, the code below builds a random-feature stand-in, not LPIPS: a fixed, randomly initialized five-layer convolutional network in PyTorch with a strided first layer and two max-pooling steps, tapped after each ReLU. On its features it implements Eq. 1 exactly and applies four distortions to a synthetic 64×6464 \times 64 RGB patch in [0,1][0, 1]: Gaussian noise, Gaussian blur, 8 × 8 block-DCT quantization and a one-pixel shift. The toy 2AFC test that follows has labels fixed by construction, not by people: in each triplet the one-pixel shift is the closer patch, following the shift study above, and the other patch carries Gaussian noise or block-DCT artifacts whose MSE is 0.5 to 2 times the shift’s, so ℓ2\ell_2 is right only when the ratio exceeds 1. For five random networks it scores the unweighted stand-in (w=1w = 1) and a “lin” version whose non-negative channel weights are fitted to 600 training triplets with a logistic link in place of G\mathcal{G}.

import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F

torch.set_num_threads(4)
H = 64                                            # BAPPS evaluates 64 x 64 patches

# ---------- Synthetic RGB patches in [0, 1] and distortions with known strength ----------
def make_patch(rng, size=H):
    """Smooth colour texture + an oriented grating + a disk edge."""
    yy, xx = np.mgrid[0:size, 0:size] / size
    k = np.exp(-np.linspace(-2, 2, 9) ** 2 / (2 * 0.8 ** 2)); k /= k.sum()
    tex = rng.standard_normal((3, size, size))
    for ax in (1, 2):                             # separable Gaussian smoothing
        tex = np.apply_along_axis(lambda v: np.convolve(v, k, "same"), ax, tex)
    th, f = rng.uniform(0, np.pi), rng.uniform(3, 10)
    grating = 0.25 * np.sin(2 * np.pi * f * (np.cos(th) * xx + np.sin(th) * yy))
    disk = ((xx - rng.uniform(.2, .8)) ** 2 + (yy - rng.uniform(.2, .8)) ** 2) < rng.uniform(.02, .08)
    img = 0.5 + 0.6 * tex + grating * rng.uniform(.3, 1, (3, 1, 1)) + 0.3 * disk * rng.uniform(-1, 1, (3, 1, 1))
    return np.clip(img, 0, 1)

def blur(x, s):
    r = np.arange(-6, 7); k = np.exp(-r ** 2 / (2 * s ** 2)); k /= k.sum()
    for ax in (1, 2):
        x = np.apply_along_axis(lambda v: np.convolve(np.pad(v, 6, mode="reflect"), k, "valid"), ax, x)
    return x

C8 = np.array([[np.sqrt((1 if u == 0 else 2) / 8) * np.cos((2 * i + 1) * u * np.pi / 16)
                for i in range(8)] for u in range(8)])          # orthonormal 8-point DCT-II
def block_dct(x, q):            # quantize every 8 x 8 DCT coefficient with step q (uniform table)
    c = np.einsum("ui,ajibk,vk->ajbuv", C8, x.reshape(3, H // 8, 8, H // 8, 8), C8)
    c = q * np.round(c / q)
    return np.clip(np.einsum("ui,ajbuv,vk->ajibk", C8, c, C8).reshape(3, H, H), 0, 1)

def shift1(x):                  # one-pixel horizontal shift, edge column repeated
    return np.concatenate([x[:, :, :1], x[:, :, :-1]], axis=2)

mse = lambda a, b: float(np.mean((a - b) ** 2))

# ---------- A RANDOM-FEATURE STAND-IN, NOT LPIPS: fixed random conv net, five taps ----------
class RandomNet(nn.Module):
    def __init__(self, seed):
        super().__init__()
        torch.manual_seed(seed)
        ch = [3, 32, 64, 96, 96, 64]
        self.convs = nn.ModuleList([nn.Conv2d(ch[0], ch[1], 5, stride=2, padding=2)] +
                                   [nn.Conv2d(ch[i], ch[i + 1], 3, padding=1) for i in range(1, 5)])
        for p in self.parameters():
            p.requires_grad_(False)
    def forward(self, x):                       # x: N x 3 x H x W, expected in [-1, 1]
        feats = []
        for i, conv in enumerate(self.convs):
            x = F.relu(conv(x))
            feats.append(x)                     # tap after each ReLU, as LPIPS taps relu1..relu5
            if i in (0, 1):
                x = F.max_pool2d(x, 2)
        return feats

def unit(f, eps=1e-10):                         # unit-normalize in the channel dimension
    return f / (f.pow(2).sum(1, keepdim=True).sqrt() + eps)

def per_channel_diffs(net, x0, x1):
    """Per layer l: mean over (h, w) of (yhat0 - yhat1)^2, one value per channel -> N x C_l."""
    return [(unit(a) - unit(b)).pow(2).mean((2, 3)) for a, b in zip(net(x0), net(x1))]

def distance(net, x0, x1, w=None, spatial=False):
    """Eq. 1 of Zhang et al. (2018) on the stand-in's features: weight channels, sum over
    channels, average over space (or upsample each layer's map), sum over layers. As in the
    official code, w multiplies the squared differences."""
    out = 0
    for l, (a, b) in enumerate(zip(net(x0), net(x1))):
        d2 = (unit(a) - unit(b)).pow(2)
        wl = torch.ones(d2.shape[1]) if w is None else w[l]
        m = (d2 * wl[None, :, None, None]).sum(1, keepdim=True)
        out = out + (F.interpolate(m, size=x0.shape[2:], mode="bilinear", align_corners=False)
                     if spatial else m.mean((2, 3)))
    return out.squeeze(1)

to_t = lambda arrs: torch.tensor(np.stack(arrs), dtype=torch.float32) * 2 - 1   # [0,1] -> [-1,1]

# ---------- 1. Mechanics on one patch ----------
rng = np.random.default_rng(0)
x = make_patch(rng)
net = RandomNet(seed=0)
a = unit(net(to_t([x]))[2]); b = unit(net(to_t([np.clip(x + 0.1 * rng.standard_normal(x.shape), 0, 1)]))[2])
cos = (a * b).sum(1)
print(f"w = 1: max |sum_c (a-b)^2 - (2 - 2 cos)| over layer-3 positions = "
      f"{((a - b).pow(2).sum(1) - (2 - 2 * cos)).abs().max():.1e}")

loc = x.copy(); loc[:, :32, :32] = np.clip(loc[:, :32, :32] + 0.15 * rng.standard_normal((3, 32, 32)), 0, 1)
m = distance(net, to_t([x]), to_t([loc]), spatial=True)[0]
print(f"noise in the top-left quadrant only: scalar {distance(net, to_t([x]), to_t([loc]))[0]:.4f}, "
      f"mean of spatial map {m.mean():.4f}, share of map in that quadrant {m[:32, :32].sum() / m.sum():.2f}")

dists = {"noise s=0.05": np.clip(x + 0.05 * rng.standard_normal(x.shape), 0, 1),
         "blur s=1.0": blur(x, 1.0), "block DCT q=0.2": block_dct(x, 0.2), "1-px shift": shift1(x)}
print(f"\n{'distortion':16s} {'MSE':>8s} {'[-1,1]':>8s} {'[0,1]':>8s} {'128x128':>8s}")
for name, d in dists.items():
    r = distance(net, to_t([x]), to_t([d]))[0]
    r01 = distance(net, to_t([x]) * .5 + .5, to_t([d]) * .5 + .5)[0]   # forgot to rescale
    up = lambda t: F.interpolate(t, scale_factor=2, mode="bilinear", align_corners=False)
    r2 = distance(net, up(to_t([x])), up(to_t([d])))[0]                  # same pair, upsampled
    print(f"{name:16s} {mse(x, d):8.5f} {r:8.4f} {r01:8.4f} {r2:8.4f}")

# ---------- 2. Toy 2AFC with labels fixed by construction ----------
def triplet(rng):
    """Returns (ref, x0, x1, h, competitor); h = 1 means x1 is the closer one. By construction,
    a one-pixel shift is closer than visible noise or DCT artifacts whose MSE is 0.5 to 2 times
    the shift's MSE, so L2 picks the right patch only when the ratio exceeds 1."""
    x = make_patch(rng)
    near = shift1(x)
    target = mse(x, near) * rng.uniform(0.5, 2)
    if rng.random() < .5:
        kind, far = "noise", np.clip(x + np.sqrt(target) * rng.standard_normal(x.shape), 0, 1)
    else:
        q = min((q for q in np.linspace(.02, 1, 50) if mse(x, block_dct(x, q)) >= target), default=1.0)
        kind, far = "dct", block_dct(x, q)
    return (x, far, near, 1, kind) if rng.random() < .5 else (x, near, far, 0, kind)

def build(n, seed):
    rng = np.random.default_rng(seed)
    T = [triplet(rng) for _ in range(n)]
    ref, x0, x1 = (to_t([t[i] for t in T]) for i in range(3))
    return ref, x0, x1, torch.tensor([t[3] for t in T], dtype=torch.float32), np.array([t[4] for t in T])

def score_2afc(d0, d1, h):      # as in the official score_2afc_dataset; ties count 0.5
    return ((d0 < d1) * (1 - h) + (d1 < d0) * h + (d0 == d1) * .5).float()

train, test = build(600, 1), build(600, 2)
l2 = lambda r, a: (r - a).pow(2).mean((1, 2, 3))
s = score_2afc(l2(test[0], test[1]), l2(test[0], test[2]), test[3])
acc = lambda s: f"{s.mean():6.3f} {s[test[4] == 'noise'].mean():6.3f} {s[test[4] == 'dct'].mean():6.3f}"
print(f"\n2AFC on 600 test triplets  {'all':>6s} {'noise':>6s} {'DCT':>6s}")
print(f"{'L2':26s} {acc(s)}")

for seed in range(5):
    net = RandomNet(seed)
    D = {k: [torch.cat(z, 1) for z in (per_channel_diffs(net, S[0], S[1]), per_channel_diffs(net, S[0], S[2]))]
         for k, S in (("train", train), ("test", test))}
    # "lin": non-negative channel weights fitted to the training labels, logistic link on d0 - d1
    w = torch.ones(D["train"][0].shape[1], requires_grad=True)
    opt = torch.optim.Adam([w], lr=0.05)
    for it in range(300):
        d0, d1 = D["train"][0] @ w, D["train"][1] @ w
        loss = F.binary_cross_entropy_with_logits(d0 - d1, train[3])
        opt.zero_grad(); loss.backward(); opt.step()
        with torch.no_grad():
            w.clamp_(min=0)                     # projection onto w >= 0, as in the paper
    for name, wv in (("w = 1", torch.ones_like(w)), ("lin", w.detach())):
        s = score_2afc(D["test"][0] @ wv, D["test"][1] @ wv, test[3])
        print(f"{f'stand-in net {seed}, {name}':26s} {acc(s)}")

Output (PyTorch 2.14.1, NumPy 2.5.4; two runs gave identical output):

w = 1: max |sum_c (a-b)^2 - (2 - 2 cos)| over layer-3 positions = 4.0e-07
noise in the top-left quadrant only: scalar 0.2720, mean of spatial map 0.2720, share of map in that quadrant 0.83

distortion            MSE   [-1,1]    [0,1]  128x128
noise s=0.05      0.00256   0.2155   0.0383   0.1003
blur s=1.0        0.00081   0.0613   0.0119   0.0511
block DCT q=0.2   0.00097   0.0872   0.0159   0.0654
1-px shift        0.00473   0.3024   0.0453   0.3049

2AFC on 600 test triplets     all  noise    DCT
L2                          0.655  0.690  0.620
stand-in net 0, w = 1       0.762  0.793  0.730
stand-in net 0, lin         0.838  0.870  0.807
stand-in net 1, w = 1       0.692  0.703  0.680
stand-in net 1, lin         0.775  0.810  0.740
stand-in net 2, w = 1       0.620  0.657  0.583
stand-in net 2, lin         0.753  0.820  0.687
stand-in net 3, w = 1       0.675  0.687  0.663
stand-in net 3, lin         0.762  0.800  0.723
stand-in net 4, w = 1       0.662  0.663  0.660
stand-in net 4, lin         0.747  0.753  0.740

None of these numbers are LPIPS values or the paper’s results; they show the mechanics on random features.

Mechanics. The first line confirms the cosine identity behind the paper’s “Net” baselines to float32 rounding. The spatial map concentrates where the noise was added, which is what makes spatial=True useful for locating differences.

Conventions. Feeding [0,1][0, 1] images to a network that expects [−1,1][-1, 1] shrinks every distance by a different factor for each distortion, so values computed under the two conventions cannot be compared even when, as here, the ranking survives. Upsampling the same pair to 128×128128 \times 128 halves the noise distance and leaves the shift’s almost unchanged: bilinear interpolation averages neighbouring noise samples and weakens them, while the one-pixel shift becomes a two-pixel shift (our reasoning). Image size is part of the metric’s definition.

Toy 2AFC. ℓ2\ell_2 lands close to the two thirds expected by construction. The unweighted random features beat it for some networks and not for others; in our reading their partial tolerance to the shift comes from the strided first layer and max pooling. In the paper, too, a Gaussian-initialized network gained little over pixel metrics (64.3% against 63.2% for ℓ2\ell_2, Table 5). Fitting non-negative channel weights raises every network, the analogue of lin calibration. As with the greyed values in the paper’s table, the weights were fitted on the same kinds of distortion they are tested on, so the gain overstates what calibration would do on new distortions.

  • Mean squared error and PSNR compare pixels; LPIPS is usually reported with them, and it penalizes blur that they treat as a small error.
  • SSIM compares local means, contrasts and correlations in pixel space; averaged over the six 2AFC test sets it agreed with people no more often than ℓ2\ell_2.
  • DISTS, E-LPIPS and ST-LPIPS are learned successors aimed at texture tolerance, robustness and shift tolerance (see Variants).
  • NIQE scores an image without a reference.
  • Fréchet Inception Distance compares feature distributions of image sets rather than aligned pairs.

Related

  • AlexNet

    The eight-layer convolutional network of Krizhevsky, Sutskever and Hinton (2012), five convolutional and three fully connected layers with about 60 million parameters, almost all in the fully connected head; its two-GPU layout survives as grouped convolution, and a single-tower variant is the AlexNet that libraries ship today.

  • VGG

    The very deep convolutional networks of Simonyan and Zisserman (2015), plain stacks of 3×3 convolutions with 11 to 19 weight layers; VGG-16 and VGG-19 became standard backbones and feature extractors, at a cost of about 140 million parameters and 15 to 20 billion multiply-adds per image.

  • Feature Map

    One channel of a convolutional layer's output, a grid that records how strongly one learned filter responds at each position of the image, together with how its size is set by kernel, stride and padding and how maps change from edges to objects with depth.

  • Neural Rendering

    Image synthesis that combines learned components with the physics of rendering, from 2D networks that turn rasterized features into photographs to 3D scene representations fitted to photographs through a differentiable renderer; the main families, why gradients through rendering matter, and how results are measured.

  • Image-to-Image Translation

    The task of mapping an image from one domain to a corresponding image in another, such as label maps to photos, maps to aerial views or day to night; paired and unpaired training, pix2pix, CycleGAN and their successors, semantic image synthesis, diffusion-based translation, and why per-pixel losses and metrics favour blurry averages.

  • Loss Function

    The function that scores how far a network's output is from the desired output on one example; training minimizes its average over the training images, and the choice of loss encodes what counts as a good prediction for each vision task.

References

  1. Zhang, R., Isola, P., Efros, A. A., Shechtman, E. & Wang, O. (2018). The Unreasonable Effectiveness of Deep Features as a Perceptual Metric. IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR), 586–595.
  2. Zhang, R., Isola, P., Efros, A. A., Shechtman, E. & Wang, O. (2018). The Unreasonable Effectiveness of Deep Features as a Perceptual Metric. arXiv:1801.03924v2 (camera-ready text with Appendices A–D).
  3. Zhang, R., Isola, P., Efros, A. A., Shechtman, E. & Wang, O. PerceptualSimilarity repository (richzhang/PerceptualSimilarity): README.md, lpips/lpips.py, lpips/__init__.py, lpips/trainer.py and data/dataset/twoafc_dataset.py at commit 082bb24 (19 December 2023); the lpips package, version 0.1.4 on PyPI.
  4. Kettunen, M., Härkönen, E. & Lehtinen, J. (2019). E-LPIPS: Robust Perceptual Image Similarity via Random Transformation Ensembles. arXiv:1906.03973.
  5. Ghildyal, A. & Liu, F. (2022). Shift-tolerant Perceptual Similarity Metric. European Conference on Computer Vision (ECCV), Lecture Notes in Computer Science, 91–107.
  6. Ding, K., Ma, K., Wang, S. & Simoncelli, E. P. (2022). Image Quality Assessment: Unifying Structure and Texture Similarity. IEEE Transactions on Pattern Analysis and Machine Intelligence, 44(5).
  7. GRAPHDECO, Inria. Official 3D Gaussian Splatting code repository (graphdeco-inria/gaussian-splatting): metrics.py and lpipsPyTorch/ at commit 54c035f (30 October 2024).