t27.aiEnglish

ELU: пол, а не обрыв

Вы узнаете

Как ELU сохраняет мягкую отрицательную сторону с полом на -alpha и почему её наклон никогда не умирает до конца.

ELU сохраняет положительную сторону ReLU и заменяет обрыв на отрицательной стороне кривой alpha (e^x - 1), которая выполаживается к -alpha. При alpha 1 вход x = -3 даёт -0.9502, уже близко к полу -1, а наклон там 0.0498: мал, но не 0, так что нейрон ещё может сдвинуться. Отрицательные выходы не выбрасываются, поэтому среднее выходов слоя ближе к 0. Спека elu_activation.t27 вычисляет кривую, наклон и пол, а также свой пакет при -1, 0 и 2: -0.632, 0 и 2.

Попробовать

Поставьте alpha на 0.5 и прочитайте пол; затем найдите, как далеко влево должен уйти x, чтобы наклон стал меньше 0.01.

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

ELU: a negative side with a floor
ELU: a negative side with a floor ↗

Drag along ELU: x above zero, alpha (e^x - 1) below. The negative side never falls under -alpha, so outputs average closer to zero, and its slope never quite dies.

specs/ml/activation/elu_activation.t27

// SPDX-License-Identifier: Apache-2.0
// t27/specs/
// Derivative | φ² + 1/φ² = 3 | TRINITY
// #8980: `math::exp` named a module this spec never imports, forward_batch grew
// an array with `+`, and every test called a two-argument function with one
// argument and asserted `result != undefined`. Now each test checks a value.

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

    // ═══════════════════════════════════════════════════════════
    // 1. Constants
    // ═══════════════════════════════════════════════════════════

    const DEFAULT_ALPHA : f32 = 1.0;

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

    pub const ELUConfig = struct {
        alpha : f32,
    };

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

    // forward(x, alpha) = x for x > 0, alpha * (exp(x) - 1) otherwise
    fn forward(x: f32, alpha: f32) -> f32 {
        if (x > 0.0) {
            return x;
        }
        return alpha * (exp(x) - 1.0);
    }

    // forward_batch: output[i] = forward(input[i], alpha)
    fn forward_batch(input: []const f32, alpha: f32, output: []f32) -> void {
        var i : usize = 0;
        while (i < input.len) {
            output[i] = forward(input[i], alpha);
            i = i + 1;
        }
    }

    // derivative(x, alpha) = 1 for x > 0, alpha * exp(x) otherwise
    fn derivative(x: f32, alpha: f32) -> f32 {
        if (x > 0.0) {
            return 1.0;
        }
        return alpha * exp(x);
    }

    // The inputs the tests and invariants share: one negative, zero, one positive.
    fn negative_input() -> f32 {
        return -1.0;
    }

    fn valid_input() -> f32 {
        return 2.0;
    }

    fn batch_at(index: usize) -> f32 {
        const input : [3]f32 = [-1.0, 0.0, 2.0];
        var output : [3]f32 = [0.0] ** 3;
        forward_batch(&input, DEFAULT_ALPHA, &output);
        return output[index];
    }

    // ═══════════════════════════════════════════════════════════
    // TDD: Tests (from .tri behaviors)
    // ═══════════════════════════════════════════════════════════

    // ELU(-1) = exp(-1) - 1 = -0.6321206; ELU(2) = 2
    test "forward_basic_case"
        given input = negative_input()
        when result = forward(input, DEFAULT_ALPHA)
        then @abs(result - (-0.6321206)) < 0.000001 and forward(valid_input(), DEFAULT_ALPHA) == 2.0

    // alpha scales only the negative side, and ELU(x) never goes below -alpha (in f32
    // ELU(-20) rounds to exactly -alpha)
    test "forward_alpha_scales_negative_side"
        given input = negative_input()
        when result = forward(input, 0.5)
        then @abs(result - 0.5 * forward(input, 1.0)) < 0.000001 and forward(-20.0, 0.5) >= -0.5

    test "forward_batch_basic_case"
        given input = negative_input()
        when result = batch_at(0)
        then result == forward(input, DEFAULT_ALPHA) and batch_at(1) == 0.0 and batch_at(2) == 2.0

    // ELU'(-1) = exp(-1) = 0.3678794; ELU'(2) = 1
    test "derivative_basic_case"
        given input = negative_input()
        when result = derivative(input, DEFAULT_ALPHA)
        then @abs(result - 0.3678794) < 0.000001 and derivative(valid_input(), DEFAULT_ALPHA) == 1.0

    test "derivative_matches_finite_difference"
        given slope = (forward(-1.0 + 0.001, DEFAULT_ALPHA) - forward(-1.0 - 0.001, DEFAULT_ALPHA)) / 0.002
        when result = derivative(-1.0, DEFAULT_ALPHA)
        then @abs(result - slope) < 0.001

    // ═══════════════════════════════════════════════════════════
    // TDD: Invariants (from .tri constraints)
    // ═══════════════════════════════════════════════════════════

    invariant elu_constraint_0
        assert DEFAULT_ALPHA > 0.0

    // name: elu_formula -- identity on the positive side
    invariant elu_constraint_1
        assert forward(valid_input(), DEFAULT_ALPHA) == valid_input()

    // name: derivative -- 1 on the positive side
    invariant elu_constraint_2
        assert derivative(valid_input(), DEFAULT_ALPHA) == 1.0

    // "Clevert et al. (2016) - Fast and Accurate Deep Network Learning by Exponential Linear Units (ELUs)":
    // ELU is continuous at 0
    invariant elu_constraint_3
        assert forward(0.0, DEFAULT_ALPHA) == 0.0

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

Все уроки