/**
* @fileoverview Neuron — Einzelne Recheneinheit des Neuronalen Netzes.
*
* Ein Neuron hält seine Gewichte, seinen Bias und den letzten Aktivierungswert.
* Alle numerischen Daten werden in Float64Array gespeichert (V8-optimiert).
*
* Das Neuron bietet Hook-Callbacks, über die Gewichte und Aktivierungen
* von außen ausgelesen werden können (für IframeBridge-Visualisierung).
*
* Architektur: Neuron → Layer → Network (hierarchisch)
*
* @author Alexander Wolf
* @see docs/architecture/NEURAL_NET_ARCHITECTURE.md
*/
/* global DebugConfig, DEBUG_DOMAINS */
/**
* @typedef {Object} NeuronSnapshot
* @property {number} index - Index des Neurons im Layer
* @property {Float64Array} weights - Aktuelle Gewichte
* @property {number} bias - Aktueller Bias
* @property {number} output - Letzter Aktivierungswert
* @property {number} preActivation - Letzter z-Wert (vor Aktivierung)
* @property {Float64Array|null} gradients - Letzte Gewichts-Gradienten (nach Backprop)
*/
class Neuron {
/**
* Erstellt ein neues Neuron mit zufällig initialisierten Gewichten.
*
* @param {number} inputCount - Anzahl der Eingänge (= Gewichte)
* @param {number} index - Position des Neurons im Layer (für Snapshot/Debug)
* @param {Object} [options={}]
* @param {string} [options.initMethod='xavier'] - Initialisierungsmethode: 'xavier', 'he', 'random'
* @param {number} [options.initScale=1] - Skalierungsfaktor für Gewichte
*/
constructor(inputCount, index, options = {}) {
const { initMethod = 'xavier', initScale = 1 } = options;
/**
* Index des Neurons im Layer.
* @type {number}
*/
this.index = index;
/**
* Gewichte zum vorherigen Layer.
* Pre-allokiert als Float64Array für V8-Optimierung.
* @type {Float64Array}
*/
this.weights = new Float64Array(inputCount);
/**
* Bias-Wert.
* @type {number}
*/
this.bias = 0;
/**
* Letzter berechneter Aktivierungswert (Output nach Aktivierungsfunktion).
* @type {number}
*/
this.output = 0;
/**
* Letzter Pre-Activation-Wert (z = Σ(w·x) + b, VOR der Aktivierungsfunktion).
* Benötigt für ReLU-Ableitung im Backpropagation-Schritt.
* @type {number}
*/
this.preActivation = 0;
/**
* Delta-Wert aus dem Backpropagation-Schritt.
* Wird temporär während des Backward-Pass gespeichert.
* @type {number}
*/
this.delta = 0;
/**
* Letzte Gewichts-Gradienten (optional, für Visualisierung).
* @type {Float64Array|null}
*/
this.gradients = null;
// Gewichte initialisieren
this._initializeWeights(inputCount, initMethod, initScale);
}
/**
* Initialisiert Gewichte nach der gewählten Methode.
*
* - Xavier: σ = √(2 / (fan_in + fan_out)) — gut für Sigmoid/Tanh
* - He: σ = √(2 / fan_in) — gut für ReLU
* - Random: Gleichverteilung in [-scale, +scale]
*
* @param {number} inputCount - Anzahl der Eingänge (fan_in)
* @param {string} method - 'xavier', 'he', 'random'
* @param {number} scale - Skalierungsfaktor
* @private
*/
_initializeWeights(inputCount, method, scale) {
let sigma;
switch (method) {
case 'he':
sigma = Math.sqrt(2 / inputCount) * scale;
break;
case 'random':
sigma = scale;
break;
case 'xavier':
default:
// Xavier: fan_out ist hier unbekannt, approximieren mit fan_in
sigma = Math.sqrt(2 / (inputCount + 1)) * scale;
break;
}
for (let i = 0; i < this.weights.length; i++) {
this.weights[i] = this._gaussianRandom() * sigma;
}
this.bias = 0; // Bias startet bei 0 (Standard)
}
/**
* Berechnet den Forward-Pass für dieses Neuron.
* z = Σ(w_i · x_i) + b
* output = activation(z)
*
* @param {Float64Array|number[]} inputs - Eingabewerte vom vorherigen Layer
* @param {function(number): number} activationFn - Aktivierungsfunktion
* @returns {number} Der Aktivierungswert
*/
forward(inputs, activationFn) {
let z = this.bias;
for (let i = 0; i < this.weights.length; i++) {
z += this.weights[i] * inputs[i];
}
this.preActivation = z;
this.output = activationFn(z);
return this.output;
}
/**
* Setzt ein einzelnes Gewicht manuell (für Nutzer-Interaktion).
*
* @param {number} weightIndex - Index des Gewichts
* @param {number} value - Neuer Wert
* @throws {Error} Wenn der Index außerhalb des gültigen Bereichs liegt
*/
setWeight(weightIndex, value) {
if (weightIndex < 0 || weightIndex >= this.weights.length) {
if (typeof DebugConfig !== 'undefined') {
DebugConfig.log(DEBUG_DOMAINS.AI_NEURAL_NET, 'error',
'Invalid weight index:', { weightIndex, maxIndex: this.weights.length - 1 });
}
return;
}
this.weights[weightIndex] = value;
}
/**
* Setzt den Bias manuell (für Nutzer-Interaktion).
*
* @param {number} value - Neuer Bias-Wert
*/
setBias(value) {
this.bias = value;
}
/**
* Erstellt einen Snapshot des aktuellen Zustands.
* Nutzt Kopien, um Immutabilität zu gewährleisten (Convention §5).
*
* @returns {NeuronSnapshot}
*/
getSnapshot() {
return {
index: this.index,
weights: new Float64Array(this.weights),
bias: this.bias,
output: this.output,
preActivation: this.preActivation,
gradients: this.gradients ? new Float64Array(this.gradients) : null
};
}
/**
* Gausssche Zufallszahl via Box-Muller-Transformation.
* Erzeugt normalverteilte Werte mit μ=0, σ=1.
*
* @returns {number} Normalverteilter Zufallswert
* @private
*/
_gaussianRandom() {
let u1, u2;
do {
u1 = Math.random();
} while (u1 === 0); // Verhindere log(0)
u2 = Math.random();
return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
}
}
// Export für globalen Zugriff (Window UND Worker-Kontext)
(function(root) {
root.Neuron = Neuron;
})(typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : this);