Функция Хьюбера
Вы узнаете
Как функция Хьюбера обходится с малой ошибкой как с квадратичной, а с большой линейно, чтобы выбросы тянули слабее.
Квадратичная ошибка позволяет одному выбросу всё решать: ошибка 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; затем найдите, где две кривые расходятся.

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
Все уроки
Модуль 1 · Один нейрон
Нейрон складывает входы и изгибает сумму: ReLU, сигмоида и tanh, и где каждая перестаёт пропускать градиент назад.
Модуль 2 · Новые изгибы
Гладкие кривые нынешних моделей: GELU с провалом, Swish с ручкой и ELU с полом вместо обрыва.
Модуль 3 · Слой
Что окружает нейроны: softmax превращает оценки в выбор, эмбеддинг превращает слово в числа, dropout выключает нейроны.
Модуль 4 · Картинки
Чтение картинки по участкам: окно, усреднённое по участку, max pooling и пакетная нормализация признаков разного масштаба.
Модуль 5 · Ошибка
Насколько неверна догадка, одним числом: среднеквадратичная ошибка, перекрёстная энтропия для вероятностей и функция Хьюбера для выбросов.
Модуль 6 · Под гору
Спуск по функции потерь: градиентный спуск с моментом, RMSProp без оглядки на величину градиента и Adam, который соединяет оба.
Модуль 7 · Обучение
Откуда берутся градиенты и чего они добиваются: обратное распространение через слой, сеть, которая учит XOR, и LAMB для больших пакетов.
Модуль 8 · Память и внимание
Сети, которые читают последовательность: рекуррентная ячейка и её затухающее эхо, вентили LSTM и внимание ко всем позициям сразу.
Модуль 9 · Трансформер и награда
Части трансформера и агента: позиции как часы, один блок энкодера и PPO, который учится на награде без рывков.