t27.aiEnglish

Функция Хьюбера

Вы узнаете

Как функция Хьюбера обходится с малой ошибкой как с квадратичной, а с большой линейно, чтобы выбросы тянули слабее.

Квадратичная ошибка позволяет одному выбросу всё решать: ошибка 3 стоит 4.5 как половина своего квадрата, столько же, сколько 36 ошибок по 0.5. Функция Хьюбера равна половине квадрата, пока |e| не больше delta, а дальше растёт только линейно, delta |e| - delta^2 / 2, так что при delta 1 та же ошибка стоит 2.5. Два куска сходятся с одинаковым значением и наклоном, и градиент никогда не превышает delta: далёкая точка тянет модель с ограниченной силой. Для пары ошибок спеки 0.5 и 3 среднее равно 1.3125 против 2.3125 для половины квадрата. Спека huber_loss.t27 вычисляет обе.

Попробовать

Поставьте delta на 0.5 и тяните ошибку к 3; затем найдите, где две кривые расходятся.

Открыть интерактивный урок →

Huber loss: a loss that does not panic at an outlier
Huber loss: a loss that does not panic at an outlier ↗

Drag the error along: Huber is half its square inside delta and only grows linearly outside, so one wild miss pulls on the model with a bounded force. The dashed curve is the pure square.

specs/ml/loss/huber_loss.t27

// SPDX-License-Identifier: Apache-2.0
// t27/specs/
// Threshold where loss changes from quadratic to linear | φ² + 1/φ² = 3 | TRINITY
// Restored 2026-10-05 from trinity specs/algo/huber_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 HuberLoss;
    use base::types;
    use math::constants;

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

    pub const HuberLossConfig = struct {
        delta : f32,
    };

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

    // per element e = prediction - target:
    //   |e| <= delta: 0.5 e^2      else: delta |e| - 0.5 delta^2
    fn element_loss(e: f32, delta: f32) -> f32 {
        const a = @abs(e);
        if (a <= delta) {
            return 0.5 * e * e;
        }
        return delta * a - 0.5 * delta * delta;
    }

    // forward(predictions, targets, delta) -> f32. The .tri gives the
    // per-element formula only; this returns the MEAN over elements
    // (PyTorch HuberLoss default reduction). 0 for n = 0.
    fn forward(predictions: []const f32, targets: []const f32, delta: f32) -> f32 {
        if (predictions.len == 0) {
            return 0.0;
        }
        var sum_loss : f32 = 0.0;
        var i : usize = 0;
        while (i < predictions.len) {
            sum_loss = sum_loss + element_loss(predictions[i] - targets[i], delta);
            i = i + 1;
        }
        return sum_loss / (predictions.len as f32);
    }

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

    fn huber_one(e: f32, delta: f32) -> f32 {
        const predictions : [1]f32 = [e];
        const targets : [1]f32 = [0.0];
        return forward(&predictions, &targets, delta);
    }

    test quadratic_inside_delta
        given result = huber_one(0.5, 1.0)
        when negative = huber_one(-0.5, 1.0)
        then @abs(result - 0.125) < 0.000001 and result == negative

    // delta |e| - delta^2 / 2 = 3 - 0.5
    test linear_outside_delta
        given result = huber_one(3.0, 1.0)
        when wide = huber_one(3.0, 2.0)
        then @abs(result - 2.5) < 0.000001 and @abs(wide - 4.0) < 0.000001

    // the two pieces meet with equal value and slope at |e| = delta
    test continuous_at_delta
        given below = huber_one(0.999, 1.0)
        when above = huber_one(1.001, 1.0)
        then @abs(above - below - 0.002) < 0.0001

    // mean of (0.125, 2.5) over two elements
    fn huber_two(e0: f32, e1: f32, delta: f32) -> f32 {
        const predictions : [2]f32 = [e0 + 1.0, e1 + 1.0];
        const targets : [2]f32 = [1.0, 1.0];
        return forward(&predictions, &targets, delta);
    }

    test forward_mean
        given result = huber_two(0.5, 3.0, 1.0)
        when expected = 1.3125
        then @abs(result - expected) < 0.000001

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

    invariant huber_zero_at_target
        assert huber_one(0.0, 1.0) == 0.0

Открыть спеку урока в плеере ↗

Все уроки