t27.aiРусский

Mean squared error

You will learn

How MSE turns every miss into one number, why it chases large errors, and where its gradient points.

A loss turns all the misses of a model into one number to push down. Mean squared error averages the squared misses: predictions 1.5, 2 and 2 against targets 1, 2 and 3 cost (0.25 + 0 + 1) / 3 = 0.4167. Squaring makes a miss of 2 cost 4 times a miss of 1, so MSE chases large errors. Its gradient for each prediction is 2 (p - t) / n, here 0.333, 0 and -0.667: each prediction is pushed back toward its target. As a function of one prediction the loss is a parabola, a bowl with one bottom. The spec mse_loss.t27 computes the loss and the gradient.

Try it

Move prediction 2 onto its target and read the loss; then drag along the bowl and find its bottom.

Open the interactive lesson →

Mean squared error: how wrong a guess is, as one number
Mean squared error: how wrong a guess is, as one number ↗

Move three predictions against the targets 1, 2 and 3: the loss is the mean of the squared misses, its bowl is a parabola, and each gradient points its prediction back at the target.

specs/ml/loss/mse_loss.t27

// SPDX-License-Identifier: Apache-2.0
// t27/specs/
// Penalizes large errors more heavily | φ² + 1/φ² = 3 | TRINITY
// Restored 2026-10-05 from trinity specs/algo/mse_loss.tri: the port kept only
// the first parameter and returned void, so forward computed a value and
// threw it away, and every test asserted `result != undefined`.

module MseLoss;
    use base::types;
    use math::constants;

    // ═══════════════════════════════════════════════════════════
    // 2. Types
    // ═══════════════════════════════════════════════════════════

    // reduction: "mean", "sum" or "none" (from the .tri). forward below is the
    // .tri's mean; the field is carried, not yet read.
    pub const MSELossConfig = struct {
        reduction : []const u8,
    };

    // ═══════════════════════════════════════════════════════════
    // 3. Core Functions
    // ═══════════════════════════════════════════════════════════

    // forward(predictions, targets) -> mean((predictions - targets)^2); 0 for n = 0
    fn forward(predictions: []const f32, targets: []const f32) -> f32 {
        if (predictions.len == 0) {
            return 0.0;
        }
        var sum_loss : f32 = 0.0;
        var i : usize = 0;
        while (i < predictions.len) {
            const diff = predictions[i] - targets[i];
            sum_loss = sum_loss + diff * diff;
            i = i + 1;
        }
        return sum_loss / (predictions.len as f32);
    }

    // backward: grad_input[i] = 2 (predictions[i] - targets[i]) / n
    fn backward(predictions: []const f32, targets: []const f32, grad_input: []f32) -> void {
        if (predictions.len == 0) {
            return;
        }
        const scale = 2.0 / (predictions.len as f32);
        var i : usize = 0;
        while (i < predictions.len) {
            grad_input[i] = scale * (predictions[i] - targets[i]);
            i = i + 1;
        }
    }

    // ═══════════════════════════════════════════════════════════
    // TDD: Tests
    // ═══════════════════════════════════════════════════════════

    fn mse_at(p0: f32, p1: f32, p2: f32) -> f32 {
        const predictions : [3]f32 = [p0, p1, p2];
        const targets : [3]f32 = [1.0, 2.0, 3.0];
        return forward(&predictions, &targets);
    }

    fn mse_grad_at(p0: f32, p1: f32, p2: f32, index: usize) -> f32 {
        const predictions : [3]f32 = [p0, p1, p2];
        const targets : [3]f32 = [1.0, 2.0, 3.0];
        var grads : [3]f32 = [0.0] ** 3;
        backward(&predictions, &targets, &grads);
        return grads[index];
    }

    fn mse_empty() -> f32 {
        const none : [0]f32 = [];
        return forward(&none, &none);
    }

    test forward_zero_on_exact_match
        given result = mse_at(1.0, 2.0, 3.0)
        when also = mse_empty()
        then result == 0.0 and also == 0.0

    // errors (0.5, -1, 2): (0.25 + 1 + 4) / 3 = 1.75
    test forward_basic_case
        given result = mse_at(1.5, 1.0, 5.0)
        when flipped = mse_at(0.5, 3.0, 1.0)
        then @abs(result - 1.75) < 0.000001 and @abs(flipped - result) < 0.000001

    test backward_matches_finite_difference
        given slope = (mse_at(1.5, 1.001, 5.0) - mse_at(1.5, 0.999, 5.0)) / 0.002
        when result = mse_grad_at(1.5, 1.0, 5.0, 1)
        then @abs(result - slope) < 0.001 and @abs(result - (-2.0 / 3.0)) < 0.000001

    // ═══════════════════════════════════════════════════════════
    // TDD: Invariants
    // ═══════════════════════════════════════════════════════════

    invariant mse_zero_at_target
        assert mse_at(1.0, 2.0, 3.0) == 0.0

Open the lesson's spec in the player ↗

All lessons