// Forward Propagation Animation document.addEventListener('DOMContentLoaded', () => { // Set initialization flag window.forwardPropInitialized = true; console.log('Forward propagation script initialized'); // Canvas initialization function function initializeCanvas() { console.log('Initializing forward propagation canvas'); const canvas = document.getElementById('forward-canvas'); if (!canvas) { console.error('Forward propagation canvas not found!'); return; } const ctx = canvas.getContext('2d'); if (!ctx) { console.error('Could not get 2D context for forward propagation canvas'); return; } // Set canvas dimensions const container = canvas.parentElement; if (container) { canvas.width = container.clientWidth || 800; canvas.height = container.clientHeight || 400; } else { canvas.width = 800; canvas.height = 400; } // Clear canvas ctx.clearRect(0, 0, canvas.width, canvas.height); // Reset animation state and redraw resetAnimation(); drawNetwork(); } // Register the canvas initialization function with tab manager if (typeof window !== 'undefined') { window.initForwardPropCanvas = initializeCanvas; } // Canvas and context const canvas = document.getElementById('forward-canvas'); const ctx = canvas.getContext('2d'); // Control elements const startButton = document.getElementById('start-forward-animation'); const pauseButton = document.getElementById('pause-forward-animation'); const resetButton = document.getElementById('reset-forward-animation'); const inputSelector = document.getElementById('input-selector'); // Display elements const currentLayerText = document.getElementById('current-layer'); const forwardDescription = document.getElementById('forward-description'); const computationValues = document.getElementById('computation-values'); // Animation state let animationState = { running: false, currentLayer: 0, // 0: input, 1: hidden, 2: output currentNeuron: -1, // -1 means all neurons in the layer are being processed network: null, animationFrameId: null, lastTimestamp: 0, speed: 3, // Speed of animation highlightedConnections: [] }; // Neuron states const INACTIVE = 0; const COMPUTING = 1; const ACTIVATED = 2; // Neural network class for visualization class ForwardNetwork { constructor() { // Architecture: 3 input neurons, 4 hidden neurons with ReLU, 2 output neurons with sigmoid this.layers = [ { neurons: 3, activation: 'none', name: 'Input' }, { neurons: 4, activation: 'relu', name: 'Hidden' }, { neurons: 2, activation: 'sigmoid', name: 'Output' } ]; // Generate random weights and biases this.weights = [ this.generateRandomWeights(3, 4), // Input to Hidden this.generateRandomWeights(4, 2) // Hidden to Output ]; this.biases = [ Array(4).fill(0).map(() => Math.random() * 0.4 - 0.2), // Hidden layer biases Array(2).fill(0).map(() => Math.random() * 0.4 - 0.2) // Output layer biases ]; // Neuron values - inputs, weighted sums (z), and activations (a) this.inputs = [ [0.8, 0.2, 0.5], // Default input values Array(4).fill(0), // Hidden layer Array(2).fill(0) // Output layer ]; this.weightedSums = [ Array(3).fill(0), // Input layer doesn't have weighted sums Array(4).fill(0), // Hidden layer weighted sums Array(2).fill(0) // Output layer weighted sums ]; this.activations = [ Array(3).fill(0), // Input layer activations are just the inputs Array(4).fill(0), // Hidden layer activations Array(2).fill(0) // Output layer activations ]; // Neuron states for animation this.neuronStates = [ Array(3).fill(INACTIVE), // Input layer neuron states Array(4).fill(INACTIVE), // Hidden layer neuron states Array(2).fill(INACTIVE) // Output layer neuron states ]; // Computation details for display this.currentComputation = { layer: 0, neuron: 0, inputs: [], weights: [], weightedSum: 0, bias: 0, activation: 0 }; } // Generate random weights generateRandomWeights(inputSize, outputSize) { const weights = []; for (let i = 0; i < inputSize * outputSize; i++) { weights.push(Math.random() * 0.4 - 0.2); // Random between -0.2 and 0.2 } return weights; } // ReLU activation function relu(x) { return Math.max(0, x); } // Sigmoid activation function sigmoid(x) { return 1 / (1 + Math.exp(-x)); } // Set input values setInputs(inputs) { this.inputs[0] = [...inputs]; this.activations[0] = [...inputs]; // For input layer, activations = inputs // Reset all neuron states and other layers' values for (let layer = 0; layer < this.layers.length; layer++) { this.neuronStates[layer] = Array(this.layers[layer].neurons).fill(INACTIVE); if (layer > 0) { this.inputs[layer] = Array(this.layers[layer].neurons).fill(0); this.weightedSums[layer] = Array(this.layers[layer].neurons).fill(0); this.activations[layer] = Array(this.layers[layer].neurons).fill(0); } } } // Compute a single neuron computeNeuron(layer, neuron) { if (layer === 0) { // Input layer neurons are already set directly this.neuronStates[layer][neuron] = ACTIVATED; return; } // Get inputs from previous layer const prevLayerActivations = this.activations[layer - 1]; // Compute weighted sum let weightedSum = this.biases[layer - 1][neuron]; const weights = []; const inputs = []; for (let i = 0; i < this.layers[layer - 1].neurons; i++) { const weightIdx = i * this.layers[layer].neurons + neuron; const weight = this.weights[layer - 1][weightIdx]; const input = prevLayerActivations[i]; weights.push(weight); inputs.push(input); weightedSum += weight * input; } // Store weighted sum this.weightedSums[layer][neuron] = weightedSum; // Apply activation function let activation; if (this.layers[layer].activation === 'relu') { activation = this.relu(weightedSum); } else if (this.layers[layer].activation === 'sigmoid') { activation = this.sigmoid(weightedSum); } else { activation = weightedSum; // Linear/no activation } // Store activation this.activations[layer][neuron] = activation; // Store computation details for display this.currentComputation = { layer, neuron, inputs, weights, weightedSum, bias: this.biases[layer - 1][neuron], activation }; // Update neuron state this.neuronStates[layer][neuron] = ACTIVATED; } // Reset the network reset() { // Reset all neuron states for (let layer = 0; layer < this.layers.length; layer++) { this.neuronStates[layer] = Array(this.layers[layer].neurons).fill(INACTIVE); if (layer > 0) { this.weightedSums[layer] = Array(this.layers[layer].neurons).fill(0); this.activations[layer] = Array(this.layers[layer].neurons).fill(0); } } // Set input layer activations to inputs this.activations[0] = [...this.inputs[0]]; } } // Canvas resize functionality function resizeCanvas() { const container = canvas.parentElement; canvas.width = container.clientWidth; canvas.height = container.clientHeight; // Redraw if already animating if (animationState.network) { drawNetwork(animationState.network); } } // Initialize the visualization function initVisualization() { if (!canvas) return; resizeCanvas(); window.addEventListener('resize', resizeCanvas); // Create neural network animationState.network = new ForwardNetwork(); // Set initial inputs if (inputSelector) { const selectedInput = inputSelector.value; switch(selectedInput) { case 'sample1': animationState.network.setInputs([0.8, 0.2, 0.5]); break; case 'sample2': animationState.network.setInputs([0.1, 0.9, 0.3]); break; case 'sample3': animationState.network.setInputs([0.5, 0.5, 0.5]); break; default: animationState.network.setInputs([0.8, 0.2, 0.5]); } } // Initialize neuron states for input layer animationState.network.neuronStates[0] = Array(animationState.network.layers[0].neurons).fill(ACTIVATED); // Draw initial state drawNetwork(animationState.network); // Update computation display updateComputationDisplay(animationState.network); // Set button states startButton.disabled = false; pauseButton.disabled = true; resetButton.disabled = true; } // Draw the network function drawNetwork(network) { if (!ctx) return; // Clear canvas ctx.clearRect(0, 0, canvas.width, canvas.height); const padding = 50; const width = canvas.width - padding * 2; const height = canvas.height - padding * 2; // Calculate neuron positions const layers = network.layers; const layerPositions = []; for (let i = 0; i < layers.length; i++) { const layerNeurons = []; const x = padding + (width / (layers.length - 1)) * i; for (let j = 0; j < layers[i].neurons; j++) { const y = padding + (height / (layers[i].neurons + 1)) * (j + 1); layerNeurons.push({ x, y }); } layerPositions.push(layerNeurons); } // Draw connections for (let layerIdx = 0; layerIdx < layers.length - 1; layerIdx++) { for (let i = 0; i < layers[layerIdx].neurons; i++) { for (let j = 0; j < layers[layerIdx + 1].neurons; j++) { const weightIdx = i * layers[layerIdx + 1].neurons + j; const weight = network.weights[layerIdx][weightIdx]; // Normalize weight for visualization const normalizedWeight = Math.min(Math.abs(weight) * 5, 1); // Check if this connection is highlighted const isHighlighted = animationState.highlightedConnections.some( conn => conn.layer === layerIdx && conn.from === i && conn.to === j ); // Set connection color based on state let connectionColor; if (isHighlighted) { connectionColor = `rgba(46, 204, 113, ${normalizedWeight + 0.2})`; ctx.lineWidth = 3; } else if (network.neuronStates[layerIdx][i] === ACTIVATED && network.neuronStates[layerIdx + 1][j] === ACTIVATED) { connectionColor = `rgba(52, 152, 219, ${normalizedWeight})`; ctx.lineWidth = 2; } else if (network.neuronStates[layerIdx][i] === ACTIVATED) { connectionColor = `rgba(52, 152, 219, ${normalizedWeight * 0.5})`; ctx.lineWidth = 1.5; } else { connectionColor = `rgba(200, 200, 200, ${normalizedWeight * 0.3})`; ctx.lineWidth = 1; } // Draw the connection ctx.beginPath(); ctx.moveTo(layerPositions[layerIdx][i].x, layerPositions[layerIdx][i].y); ctx.lineTo(layerPositions[layerIdx + 1][j].x, layerPositions[layerIdx + 1][j].y); ctx.strokeStyle = connectionColor; ctx.stroke(); } } } // Draw neurons for (let layerIdx = 0; layerIdx < layers.length; layerIdx++) { for (let i = 0; i < layers[layerIdx].neurons; i++) { const { x, y } = layerPositions[layerIdx][i]; // Get neuron activation and state const activation = network.activations[layerIdx][i]; const neuronState = network.neuronStates[layerIdx][i]; // Set neuron color based on state and activation let neuronColor; if (neuronState === COMPUTING) { neuronColor = 'rgba(241, 196, 15, 0.9)'; // Yellow for computing } else if (neuronState === ACTIVATED) { neuronColor = `rgba(52, 152, 219, ${Math.min(Math.max(activation, 0.3), 0.9)})`; } else { neuronColor = 'rgba(200, 200, 200, 0.5)'; // Grey for inactive } // Draw neuron ctx.beginPath(); ctx.arc(x, y, 20, 0, Math.PI * 2); ctx.fillStyle = neuronColor; ctx.fill(); ctx.strokeStyle = '#2980b9'; ctx.lineWidth = 2; ctx.stroke(); // Draw neuron value ctx.fillStyle = '#fff'; ctx.font = '12px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; if (layerIdx === 0 || neuronState === ACTIVATED) { // Show activation for activated neurons ctx.fillText(activation.toFixed(2), x, y); } else { // Show ? for inactive neurons ctx.fillText('?', x, y); } // Draw layer labels if (i === 0) { ctx.fillStyle = '#333'; ctx.font = '14px Arial'; ctx.textAlign = 'center'; ctx.fillText(layers[layerIdx].name, x, y - 40); // Highlight current layer being processed if (layerIdx === animationState.currentLayer) { ctx.beginPath(); ctx.arc(x, y - 40, 5, 0, Math.PI * 2); ctx.fillStyle = '#e74c3c'; ctx.fill(); } } } } } // Update computation display function updateComputationDisplay(network) { if (!computationValues) return; const currentLayer = animationState.currentLayer; const currentNeuron = animationState.currentNeuron; // Update current layer text if (currentLayerText) { currentLayerText.textContent = network.layers[currentLayer].name; } // Update description if (forwardDescription) { if (currentLayer === 0) { forwardDescription.textContent = "Input values are passed directly to the first layer."; } else if (currentNeuron === -1) { forwardDescription.textContent = `All neurons in the ${network.layers[currentLayer].name} layer compute their activations.`; } else { const activationType = network.layers[currentLayer].activation; forwardDescription.textContent = `Computing neuron ${currentNeuron + 1} in the ${network.layers[currentLayer].name} layer using ${activationType.toUpperCase()} activation.`; } } // Update computation values if (currentLayer === 0 || currentNeuron === -1) { // Show layer summary let html = ''; if (currentLayer === 0) { html += '