/**
* @fileoverview Aktivierungsfunktionen für das Neuronale Netz.
*
* Jede Funktion ist als Objekt mit `fn` (Vorwärts) und `derivative` (Ableitung)
* implementiert, um im Backpropagation-Schritt direkt die Ableitung berechnen zu können.
*
* Unterstützte Funktionen: Sigmoid, ReLU, Tanh, Linear, Softmax.
* Softmax wird separat behandelt, da sie über den gesamten Vektor operiert.
*
* @author Alexander Wolf
* @see docs/architecture/NEURAL_NET_ARCHITECTURE.md
*/
/* global DebugConfig, DEBUG_DOMAINS */
/**
* @typedef {Object} ActivationFunction
* @property {string} name - Name der Funktion
* @property {function(number): number} fn - Vorwärtsberechnung (Skalar)
* @property {function(number, number=): number} derivative - Ableitung; erwartet Output und optional preActivation (z.B. ReLU)
*/
/**
* Registry aller verfügbaren Aktivierungsfunktionen.
* Zugriff über `ACTIVATION_FUNCTIONS[name]`.
*
* @type {Object<string, ActivationFunction>}
*/
const ACTIVATION_FUNCTIONS = {
/**
* Sigmoid: σ(x) = 1 / (1 + e^(-x))
* Ausgabe: (0, 1) — ideal für binäre Klassifikation.
* Achtung: Vanishing Gradient bei großen |x|.
*/
sigmoid: {
name: 'sigmoid',
/**
* @param {number} x - Eingabewert
* @returns {number} σ(x) ∈ (0, 1)
*/
fn(x) {
// Numerisch stabil: Vermeide Overflow bei großem negativem x
if (x >= 0) {
const ex = Math.exp(-x);
return 1 / (1 + ex);
}
const ex = Math.exp(x);
return ex / (1 + ex);
},
/**
* Ableitung: σ'(x) = σ(x) · (1 - σ(x))
* Hinweis: Erwartet den BEREITS BERECHNETEN Output σ(x), nicht x selbst.
* @param {number} output - Bereits berechneter σ(x)
* @returns {number}
*/
derivative(output) {
return output * (1 - output);
}
},
/**
* ReLU: f(x) = max(0, x)
* Standard für Hidden-Layers. Kein Vanishing Gradient für x > 0.
*/
relu: {
name: 'relu',
/**
* @param {number} x
* @returns {number} max(0, x)
*/
fn(x) {
return x > 0 ? x : 0;
},
/**
* Ableitung: 1 wenn x > 0, sonst 0 (bei x=0 definieren wir 0)
* Hinweis: Benötigt den PRE-ACTIVATION Wert (z), nicht den Output.
* @param {number} _output - Nicht verwendet bei ReLU
* @param {number} preActivation - Der z-Wert vor Aktivierung
* @returns {number}
*/
derivative(_output, preActivation) {
return preActivation > 0 ? 1 : 0;
}
},
/**
* Tanh: f(x) = (e^x - e^(-x)) / (e^x + e^(-x))
* Ausgabe: (-1, 1) — zentriert um 0, besser als Sigmoid für Hidden-Layers.
*/
tanh: {
name: 'tanh',
/**
* @param {number} x
* @returns {number} tanh(x) ∈ (-1, 1)
*/
fn(x) {
return Math.tanh(x);
},
/**
* Ableitung: 1 - tanh²(x)
* @param {number} output - Bereits berechneter tanh(x)
* @returns {number}
*/
derivative(output) {
return 1 - output * output;
}
},
/**
* Linear (Identity): f(x) = x
* Genutzt als Output-Aktivierung für Regression.
*/
linear: {
name: 'linear',
/**
* @param {number} x
* @returns {number} x
*/
fn(x) {
return x;
},
/**
* Ableitung: immer 1
* @returns {number}
*/
derivative() {
return 1;
}
}
};
/**
* Softmax-Funktion für Vektoren (gesamter Layer).
* Wird separat behandelt, da sie über alle Neuronen eines Layers operiert
* und nicht pro Neuron angewendet werden kann.
*
* @param {Float64Array|number[]} values - Rohwerte (logits) des Output-Layers
* @returns {Float64Array} Wahrscheinlichkeitsverteilung (Summe = 1)
*/
function softmax(values) {
const result = new Float64Array(values.length);
// Numerische Stabilität: Subtrahiere Maximum
let maxVal = -Infinity;
for (let i = 0; i < values.length; i++) {
if (values[i] > maxVal) maxVal = values[i];
}
let sumExp = 0;
for (let i = 0; i < values.length; i++) {
result[i] = Math.exp(values[i] - maxVal);
sumExp += result[i];
}
for (let i = 0; i < values.length; i++) {
result[i] /= sumExp;
}
return result;
}
/**
* Maskiert illegale Züge und wendet Softmax an.
* Besetzte Felder erhalten -∞ vor dem Softmax, damit ihre Wahrscheinlichkeit ≈ 0 wird.
*
* @param {Float64Array|number[]} rawOutput - Rohe Netzwerk-Ausgabe
* @param {number[]} mask - Maske: 1 = erlaubt, 0 = blockiert
* @returns {Float64Array} Wahrscheinlichkeitsverteilung über legale Aktionen
*/
function maskedSoftmax(rawOutput, mask) {
const masked = new Float64Array(rawOutput.length);
for (let i = 0; i < rawOutput.length; i++) {
masked[i] = mask[i] ? rawOutput[i] : -1e9;
}
return softmax(masked);
}
/**
* Heaviside Step Function: f(x) = 1 wenn x ≥ 0, sonst 0.
* Klassische Perzeptron-Aktivierung (nicht-differenzierbar bei x=0).
* Für das Perzeptron-Training wird stattdessen die Delta-Regel verwendet.
*/
const STEP_ACTIVATION = {
name: 'step',
/**
* @param {number} x
* @returns {number} 0 oder 1
*/
fn(x) {
return x >= 0 ? 1 : 0;
},
/**
* Pseudo-Ableitung: 1 (überall), da Step nicht differenzierbar ist.
* Wird bei der Delta-Regel nicht benötigt, aber für Konsistenz bereitgestellt.
* @returns {number}
*/
derivative() {
return 1;
}
};
ACTIVATION_FUNCTIONS.step = STEP_ACTIVATION;
// Export für globalen Zugriff (Window UND Worker-Kontext)
(function(root) {
root.ACTIVATION_FUNCTIONS = ACTIVATION_FUNCTIONS;
root.softmax = softmax;
root.maskedSoftmax = maskedSoftmax;
})(typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : this);