🔍

artificial neuronal basic concept

This interactive visualization offers a clear and engaging way to understand the fundamentals of neural networks. Each node in the canvas represents a neuron, and the connections between nodes illustrate the synaptic links found in artificial neural networks. The varying thickness of the connections shows the strength of these links, reflecting how different connections contribute to the network’s overall function.

As you add, remove, or adjust connections, you can see how the network’s structure evolves, mimicking the training process of a neural network where weights are updated based on new data. The circular arrangement of nodes provides an organized view of how neurons are interconnected and how their interactions influence the network’s behavior.

This visualization serves as a practical demonstration of machine learning concepts, making the abstract workings of neural networks more tangible and easier to grasp. It highlights key aspects such as node representation, connection weights, and dynamic updates, offering a hands-on approach to learning about neural networks.

code of the basic version

This setup uses you local storage of your browser to save necessary data to provide this basic neuronal training demo. Also play with it yourself here: ONLINE P5.JS EDITOR CODE


// This sketch visualizes a network of nodes with connections, where nodes are arranged in a circular layout. Connections between nodes are represented with bezier curves, and nodes are dynamically sized based on their connection count. Data is stored in and retrieved from local storage.

let connections = []; // Array to hold connections between nodes
let nodeRadius = 16; // Base radius for nodes (currently not used in the code)
let nodeDistance = 220; // Distance between nodes in the circular layout
let nodePositions = {}; // Object to hold the positions of nodes
const maxNodes = 20; // Maximum number of nodes to visualize
const baseSize = 20; // Minimum size for nodes
const maxSize = 50; // Maximum size for nodes

function preload() {
  // Load connection data from local storage or initialize to an empty array if not present
  let storedConnections = localStorage.getItem('connections');
  connections = storedConnections ? JSON.parse(storedConnections) : [];
}

function setup() {
  createCanvas(660, 660); // Create a canvas with a size of 660x660 pixels
  
  // Create buttons for user interaction
  createButton('WIPE').mousePressed(eraseAllEntries); // Button to erase all connections
  createButton('ADD').mousePressed(addRandomEntry); // Button to add a random connection
  createButton('UPDATE').mousePressed(visualizeConnectionsAndDraw); // Button to update the visualization
  
  // Initial visualization of connections
  visualizeConnectionsAndDraw();
}

function draw() {
  // The draw function is empty because visualization is controlled by button presses
}

function addConnection(subjectId, objectId, weight) {
  // Add or update a connection between nodes with a specified weight
  let existingConnection = connections.find(conn => conn.subjectId === subjectId && conn.objectId === objectId);
  
  if (existingConnection) {
    // If the connection already exists, increase its weight
    existingConnection.weight += 1.0;
  } else {
    // If the connection does not exist, add a new connection with an initial weight of 1.0
    connections.push({subjectId, objectId, weight: 1.0});
  }
  
  // Decrease the weight of all connections slightly and remove those with negative weights
  connections = connections.map(conn => {
    conn.weight -= 0.06;
    return conn.weight < 0 ? null : conn; // Remove connection if weight is below 0
  }).filter(conn => conn !== null);
  
  saveConnections(); // Save the updated connections to local storage
  visualizeConnectionsAndDraw(); // Update the visualization
}

function removeConnection(subjectId, objectId) {
  // Remove a connection between nodes
  if (Array.isArray(connections)) {
    connections = connections.filter(conn => conn.subjectId !== subjectId || conn.objectId !== objectId);
    saveConnections(); // Save the updated connections to local storage
    visualizeConnectionsAndDraw(); // Update the visualization
  } else {
    console.error('Connections is not an array');
  }
}

function editConnection(subjectId, objectId, newWeight) {
  // Update the weight of an existing connection
  if (Array.isArray(connections)) {
    for (let conn of connections) {
      if (conn.subjectId === subjectId && conn.objectId === objectId) {
        conn.weight = newWeight;
        saveConnections(); // Save the updated connections to local storage
        visualizeConnectionsAndDraw(); // Update the visualization
        break;
      }
    }
  } else {
    console.error('Connections is not an array');
  }
}

function saveConnections() {
  // Save the connections array to local storage
  localStorage.setItem('connections', JSON.stringify(connections));
}

function createRandomConnection() {
  // Generate a random connection with random subjectId, objectId, and weight
  return {
    subjectId: Math.floor(Math.random() * maxNodes), // Random subject node ID
    objectId: Math.floor(Math.random() * maxNodes), // Random object node ID
    weight: Math.random() // Random weight between 0 and 1
  };
}

function addRandomEntry() {
  // Create a new random connection and add it to the connections array
  let newConnection = createRandomConnection();
  addConnection(newConnection.subjectId, newConnection.objectId, newConnection.weight);
}

function eraseAllEntries() {
  // Remove all connections and update the visualization
  connections = [];
  saveConnections(); // Save the empty connections array to local storage
  visualizeConnectionsAndDraw(); // Update the visualization
}

function getNodeSize(id) {
  // Calculate the size of a node based on the number of connections it has
  let count = connections.reduce((acc, conn) => {
    if (conn.subjectId === id || conn.objectId === id) {
      acc++;
    }
    return acc;
  }, 0);

  // Scale the node size based on the connection count
  return constrain(map(count, 0, 10, baseSize, maxSize), baseSize, maxSize);
}

function drawConnections() {
  // Draw connections between nodes
  noFill();
  connections.forEach(conn => {
    let subject = nodePositions[conn.subjectId];
    let object = nodePositions[conn.objectId];
    if (subject && object) {
      // Generate a random offset for the bezier curve
      let offset = random(-10, 10); // Random offset for curve control points

      strokeWeight(map(conn.weight, 0, 10, 1, 100)); // Map connection weight to stroke width
      stroke(0,188); // Set stroke color to a shade of blue
      
      if (subject === object) {
        // Handle self-loops (connections from a node to itself)
        let r = getNodeSize(subject.id) * 1.5; // Radius for self-loop
        let ctrlX1 = subject.x + cos(random(TWO_PI)) * r;
        let ctrlY1 = subject.y + sin(random(TWO_PI)) * r;
        let ctrlX2 = subject.x + cos(random(TWO_PI)) * r;
        let ctrlY2 = subject.y + sin(random(TWO_PI)) * r;
        bezier(
          subject.x, subject.y, 
          ctrlX1, ctrlY1, 
          ctrlX2, ctrlY2, 
          subject.x, subject.y
        );
      } else {
        // Draw normal connections
        bezier(
          subject.x, subject.y, 
          (subject.x + object.x) / 2 + offset, (subject.y + object.y) / 2 - 50, 
          (subject.x + object.x) / 2 + offset, (subject.y + object.y) / 2 + 50, 
          object.x, object.y
        );
      }
    }
  });
}

function drawNodes() {
  // Draw nodes at their specified positions
  fill(100, 150, 255); // Node fill color
  
  textAlign(CENTER, CENTER); // Center align text
  textSize(16); // Set text size for node IDs
  textStyle(BOLD); // Make text bold
  noStroke(); // Disable stroke for nodes
  
  Object.keys(nodePositions).forEach(id => {
    let pos = nodePositions[id];
    let size = getNodeSize(id); // Get the size of the node based on connections
    fill(22); // Node fill color
    ellipse(pos.x, pos.y, size * 2); // Draw the node as an ellipse with dynamic size
    fill(255); // Text color
    text(id, pos.x, pos.y); // Draw the node ID at the center of the node
  });
}

function visualizeConnectionsAndDraw() {
  // Visualize connections and draw nodes and connections
  visualizeConnections(); // Generate node positions based on connections
  background(255); // Clear the canvas with a white background
  drawConnections(); // Draw connections between nodes
  drawNodes(); // Draw nodes at their positions
}

function visualizeConnections() {
  // Extract unique node IDs and arrange them in a circular layout
  let nodeIds = new Set();
  connections.forEach(conn => {
    nodeIds.add(conn.subjectId);
    nodeIds.add(conn.objectId);
  });

  // Limit to maxNodes unique node IDs
  if (nodeIds.size > maxNodes) {
    nodeIds = new Set(Array.from(nodeIds).slice(0, maxNodes));
  }

  // Calculate the positions for nodes in a circular layout
  let angleStep = TWO_PI / maxNodes; // Angle step between nodes
  let centerX = width / 2; // X coordinate of the center of the canvas
  let centerY = height / 2; // Y coordinate of the center of the canvas
  nodePositions = {}; // Reset node positions
  
  let i = 0;
  for (let id = 0; id < maxNodes; id++) {
    let angle = i * angleStep; // Calculate the angle for the node
    let x = centerX + cos(angle) * nodeDistance; // Calculate x position
    let y = centerY + sin(angle) * nodeDistance; // Calculate y position
    nodePositions[id] = { x, y }; // Assign position to node ID
    i++;
  }
}