ai/neural/core/layer.js

/**
 * @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);