Download NetworkToDisplay.js from richardhcli/simple_neural_network_visualizer: direct link, hf CLI and curl.
- Browser
- Download file 7.85 kB
-
https://huggingface.co/spaces/richardhcli/simple_neural_network_visualizer/resolve/main/NetworkToDisplay.js
- Command line
-
hf download hf://spaces/richardhcli/simple_neural_network_visualizer/NetworkToDisplay.js
-
curl -L -o NetworkToDisplay.js https://huggingface.co/spaces/richardhcli/simple_neural_network_visualizer/resolve/main/NetworkToDisplay.js
7.85 kB
| /** | |
| * NetworkToDisplay - Interface between neural network and 3D visualization | |
| * Creates spheres and manages connections directly in the SceneManager. | |
| */ | |
| class NetworkToDisplay { | |
| constructor(network, sceneManager, options = {}) { | |
| this.network = network; | |
| this.sceneManager = sceneManager; | |
| const { | |
| layerSpacing = 10, | |
| neuronSpacing = 2, | |
| neuronRadius = 0.5, | |
| inputColor = '#ff6b6b', | |
| hiddenColor = '#ffd93d', | |
| outputColor = '#4ecdc4', | |
| weightThreshold = 0.1, | |
| lineWidth = 0.1, | |
| opacity = 0.1 | |
| } = options; | |
| this.layerSpacing = layerSpacing; | |
| this.neuronSpacing = neuronSpacing; | |
| this.neuronRadius = neuronRadius; | |
| this.inputColor = inputColor; | |
| this.hiddenColor = hiddenColor; | |
| this.outputColor = outputColor; | |
| this.weightThreshold = weightThreshold; | |
| this.lineWidth = lineWidth; | |
| this.opacity = opacity; | |
| } | |
| /** | |
| * Create all neuron spheres and initial connections in the scene. | |
| */ | |
| initialGeneration() { | |
| const layers = this.network.neuronCounts; | |
| const numLayers = layers.length; | |
| for (let layerIdx = 0; layerIdx < numLayers; layerIdx++) { | |
| const neuronCount = layers[layerIdx]; | |
| const x = layerIdx * this.layerSpacing - (numLayers - 1) * this.layerSpacing / 2; | |
| let color = this.hiddenColor; | |
| if (layerIdx === 0) color = this.inputColor; | |
| else if (layerIdx === numLayers - 1) color = this.outputColor; | |
| if (layerIdx === 0) { | |
| const gridSize = Math.sqrt(neuronCount); | |
| console.assert( | |
| Math.floor(gridSize) * Math.floor(gridSize) === neuronCount, | |
| `Input layer neuron count ${neuronCount} is not a perfect square` | |
| ); | |
| const intGridSize = Math.floor(gridSize); | |
| const halfSize = (intGridSize - 1) * this.neuronSpacing / 2; | |
| let sphereCount = 0; | |
| for (let row = 0; row < intGridSize; row++) { | |
| for (let col = 0; col < intGridSize; col++) { | |
| const y = row * this.neuronSpacing - halfSize; | |
| const z = col * this.neuronSpacing - halfSize; | |
| this.sceneManager.sphereManager.addSphere(`L${layerIdx}_N${sphereCount}`, { | |
| radius: this.neuronRadius, | |
| color: color, | |
| position: new THREE.Vector3(x, y, z), | |
| opacity: 1 | |
| }); | |
| sphereCount++; | |
| } | |
| } | |
| console.assert( | |
| sphereCount === neuronCount, | |
| `Generated ${sphereCount} spheres but expected ${neuronCount} neurons in input layer` | |
| ); | |
| } else { | |
| const verticalOffset = -(neuronCount - 1) * this.neuronSpacing / 2; | |
| for (let neuronIdx = 0; neuronIdx < neuronCount; neuronIdx++) { | |
| const y = verticalOffset + neuronIdx * this.neuronSpacing; | |
| this.sceneManager.sphereManager.addSphere(`L${layerIdx}_N${neuronIdx}`, { | |
| radius: this.neuronRadius, | |
| color: color, | |
| position: new THREE.Vector3(x, y, 0), | |
| opacity: 1 | |
| }); | |
| } | |
| } | |
| } | |
| this.updateConnections(); | |
| console.log('✓ Initial generation complete'); | |
| } | |
| /** | |
| * Update/create/delete connections in the scene based on current weights. | |
| */ | |
| updateConnections() { | |
| const weightThreshold = this.weightThreshold; | |
| for (let layerIdx = 0; layerIdx < this.network.weights.length; layerIdx++) { | |
| const weightMatrix = this.network.weights[layerIdx]; | |
| const fromLayer = layerIdx; | |
| const toLayer = layerIdx + 1; | |
| const toNeuronCount = weightMatrix.length; | |
| const fromNeuronCount = weightMatrix[0].length; | |
| for (let toIdx = 0; toIdx < toNeuronCount; toIdx++) { | |
| for (let fromIdx = 0; fromIdx < fromNeuronCount; fromIdx++) { | |
| const weight = weightMatrix[toIdx][fromIdx]; | |
| const weightMagnitude = Math.abs(weight); | |
| const connectionId = `W_L${layerIdx}_F${fromIdx}_T${toIdx}`; | |
| const fromName = `L${fromLayer}_N${fromIdx}`; | |
| const toName = `L${toLayer}_N${toIdx}`; | |
| const existing = this.sceneManager.connectionManager.getConnection(connectionId); | |
| if (weightMagnitude < weightThreshold) { | |
| if (existing) { | |
| this.sceneManager.connectionManager.removeConnection(connectionId); | |
| } | |
| continue; | |
| } | |
| const connectionColorThis = NetworkToDisplay.weightToColor(weight); | |
| if (existing) { | |
| this.sceneManager.connectionManager.updateConnectionColor(connectionId, connectionColorThis); | |
| } else { | |
| const fromSphere = this.sceneManager.sphereManager.getSphere(fromName); | |
| const toSphere = this.sceneManager.sphereManager.getSphere(toName); | |
| if (fromSphere && toSphere) { | |
| this.sceneManager.connectionManager.addConnection( | |
| connectionId, | |
| fromSphere.position, | |
| toSphere.position, | |
| { | |
| color: connectionColorThis, | |
| lineWidth: this.lineWidth, | |
| opacity: this.opacity, | |
| fromSphere: fromName, | |
| toSphere: toName, | |
| weight: weight | |
| } | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Highlight a specific neuron activation | |
| * @param {number} layerIdx - Layer index | |
| * @param {number} neuronIdx - Neuron index | |
| * @param {number} activation - Activation value (0-1) | |
| */ | |
| highlightNeuron(layerIdx, neuronIdx, activation) { | |
| const sphereName = `L${layerIdx}_N${neuronIdx}`; | |
| const sphere = this.sceneManager.sphereManager.getSphere(sphereName); | |
| if (sphere) { | |
| const scale = 1 + activation * 0.5; | |
| sphere.scale.set(scale, scale, scale); | |
| } | |
| } | |
| static weightToColor(weight) { | |
| const positiveColor = { r: 0, g: 255, b: 0 }; // Green for positive weights | |
| const neutralColor = { r: 255, g: 255, b: 255 }; // White for neutral weights | |
| const negativeColor = { r: 255, g: 0, b: 0 }; // Red for negative weights | |
| const magnitude = Math.min(1, Math.abs(weight) / 3); // Normalize weight magnitude | |
| // Interpolate between neutral and sign-specific color | |
| const target = weight >= 0 ? positiveColor : negativeColor; | |
| const r = Math.round(neutralColor.r * (1 - magnitude) + target.r * magnitude); | |
| const g = Math.round(neutralColor.g * (1 - magnitude) + target.g * magnitude); | |
| const b = Math.round(neutralColor.b * (1 - magnitude) + target.b * magnitude); | |
| return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`; | |
| } | |
| } | |
| // Expose globally for script-tag usage | |
| window.NetworkToDisplay = NetworkToDisplay; |