/**
* @fileoverview Layer — Eine Schicht von Neuronen im Neuronalen Netz.
*
* Ein Layer verwaltet eine Gruppe von Neuronen, führt den kollektiven
* Forward-Pass durch und stellt aggregierte Snapshots bereit.
*
* Der Layer kennt seine Aktivierungsfunktion und wendet sie einheitlich
* auf alle Neuronen an. Für Softmax-Outputs wird die Aktivierung
* nach dem Forward-Pass auf Vektorebene berechnet.
*
* Architektur: Neuron → Layer → Network (hierarchisch)
*
* @author Alexander Wolf
* @see docs/architecture/NEURAL_NET_ARCHITECTURE.md
*/
/* global Neuron, ACTIVATION_FUNCTIONS, softmax, DebugConfig, DEBUG_DOMAINS */
/**
* @typedef {Object} LayerConfig
* @property {number} neuronCount - Anzahl der Neuronen in diesem Layer
* @property {number} inputCount - Anzahl der Eingänge pro Neuron (= Neuronen des vorherigen Layers)
* @property {string} [activation='relu'] - Name der Aktivierungsfunktion
* @property {string} [initMethod='xavier'] - Gewichts-Initialisierungsmethode
* @property {boolean} [useSoftmax=false] - Softmax als Vektor-Aktivierung (nur Output-Layer)
*/
/**
* @typedef {Object} LayerSnapshot
* @property {number} index - Index des Layers im Netzwerk
* @property {number} neuronCount - Anzahl der Neuronen
* @property {string} activation - Name der Aktivierungsfunktion
* @property {boolean} useSoftmax - Ob Softmax aktiv ist
* @property {Float64Array} outputs - Output-Werte aller Neuronen
* @property {NeuronSnapshot[]} neurons - Snapshots aller Neuronen
*/
class Layer {
/**
* Erstellt einen neuen Layer mit der gegebenen Konfiguration.
*
* @param {LayerConfig} config
* @param {number} layerIndex - Position des Layers im Netzwerk (für Snapshot/Debug)
*/
constructor(config, layerIndex) {
const {
neuronCount,
inputCount,
activation = 'relu',
initMethod = 'xavier',
useSoftmax = false
} = config;
/**
* Index des Layers im Netzwerk.
* @type {number}
*/
this.index = layerIndex;
/**
* Anzahl der Neuronen.
* @type {number}
*/
this.neuronCount = neuronCount;
/**
* Anzahl der Eingänge pro Neuron.
* @type {number}
*/
this.inputCount = inputCount;
/**
* Name der Aktivierungsfunktion.
* @type {string}
*/
this.activationName = activation;
/**
* Referenz auf die Aktivierungsfunktion (aus Registry).
* @type {ActivationFunction}
*/
this.activation = ACTIVATION_FUNCTIONS[activation] || ACTIVATION_FUNCTIONS.relu;
/**
* Ob Softmax als Vektor-Aktivierung genutzt wird (nur Output-Layer).
* @type {boolean}
*/
this.useSoftmax = useSoftmax;
/**
* Array aller Neuronen in diesem Layer.
* @type {Neuron[]}
*/
this.neurons = [];
/**
* Pre-allokierter Output-Buffer (Float64Array für V8-Performance).
* Wird bei jedem Forward-Pass überschrieben.
* @type {Float64Array}
*/
this.outputs = new Float64Array(neuronCount);
/**
* Pre-Activation-Werte (z-Werte) für den gesamten Layer.
* Benötigt für Softmax-Ableitung.
* @type {Float64Array}
*/
this.preActivations = new Float64Array(neuronCount);
// Neuronen erstellen
for (let i = 0; i < neuronCount; i++) {
this.neurons.push(new Neuron(inputCount, i, { initMethod }));
}
}
/**
* Forward-Pass: Berechnet den Output aller Neuronen.
*
* Ablauf:
* 1. Jedes Neuron berechnet z = Σ(w·x) + b
* 2a. Wenn useSoftmax: Per-Neuron Aktivierung ist linear, dann Softmax über den Vektor
* 2b. Sonst: Per-Neuron Aktivierung (ReLU, Sigmoid, etc.)
*
* @param {Float64Array|number[]} inputs - Eingabewerte (Output des vorherigen Layers)
* @returns {Float64Array} Ausgabewerte aller Neuronen
*/
forward(inputs) {
if (this.useSoftmax) {
// Softmax-Modus: Neuronen berechnen nur z (lineare Aktivierung)
for (let i = 0; i < this.neuronCount; i++) {
const neuron = this.neurons[i];
neuron.forward(inputs, ACTIVATION_FUNCTIONS.linear.fn);
this.preActivations[i] = neuron.preActivation;
}
// Softmax über den gesamten Vektor
const softmaxOutputs = softmax(this.preActivations);
for (let i = 0; i < this.neuronCount; i++) {
this.neurons[i].output = softmaxOutputs[i];
this.outputs[i] = softmaxOutputs[i];
}
} else {
// Standard-Modus: Jedes Neuron wendet seine Aktivierung individuell an
for (let i = 0; i < this.neuronCount; i++) {
this.outputs[i] = this.neurons[i].forward(inputs, this.activation.fn);
this.preActivations[i] = this.neurons[i].preActivation;
}
}
return this.outputs;
}
/**
* Setzt ein einzelnes Gewicht eines bestimmten Neurons.
*
* @param {number} neuronIndex - Index des Neurons
* @param {number} weightIndex - Index des Gewichts
* @param {number} value - Neuer Wert
*/
setWeight(neuronIndex, weightIndex, value) {
if (neuronIndex < 0 || neuronIndex >= this.neuronCount) {
if (typeof DebugConfig !== 'undefined') {
DebugConfig.log(DEBUG_DOMAINS.AI_NEURAL_NET, 'error',
'Invalid neuron index:', { neuronIndex, maxIndex: this.neuronCount - 1 });
}
return;
}
this.neurons[neuronIndex].setWeight(weightIndex, value);
}
/**
* Setzt den Bias eines bestimmten Neurons.
*
* @param {number} neuronIndex - Index des Neurons
* @param {number} value - Neuer Bias-Wert
*/
setBias(neuronIndex, value) {
if (neuronIndex < 0 || neuronIndex >= this.neuronCount) {
return;
}
this.neurons[neuronIndex].setBias(value);
}
/**
* Gibt die Gesamtzahl der Parameter (Gewichte + Biases) dieses Layers zurück.
*
* @returns {number}
*/
getParameterCount() {
return this.neuronCount * (this.inputCount + 1); // +1 für Bias
}
/**
* Erstellt einen Snapshot des gesamten Layers.
* Immutable: Alle Daten werden kopiert (Convention §5).
*
* @returns {LayerSnapshot}
*/
getSnapshot() {
return {
index: this.index,
neuronCount: this.neuronCount,
activation: this.activationName,
useSoftmax: this.useSoftmax,
outputs: new Float64Array(this.outputs),
neurons: this.neurons.map(n => n.getSnapshot())
};
}
}
// Export für globalen Zugriff (Window UND Worker-Kontext)
(function(root) {
root.Layer = Layer;
})(typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : this);