Femos AI
Neural Network · browser runtime Sign in to save
Primitive building tools · batch, connect, reuse

Batch neurons

Connect selections

Shift-select sources, save them, then Shift-select destinations. Every connection is a real weight.

Selection & history

Reusable modules

Save selected neurons or a group as an authoring recipe. Placed instances expand into editable neurons and weighted edges; they do not become black boxes.

medium · 100% · 5 real neurons · 6 real edges
Run & train

Try a forward pass

Train the connections

epochs

4 examples · edit examples below

Training examples
Input 1Input 2Target: Output 1
Generated browser JavaScript
// Generated by FEMOS Neural Network Studio
const network = {
  "version": 1,
  "neurons": [
    {
      "id": "input-0-0-mgx98",
      "label": "Input 1",
      "kind": "input",
      "x": 120,
      "y": 243.33333333333334,
      "bias": 0,
      "activation": "linear",
      "groupId": "layer-0-dqjmz"
    },
    {
      "id": "input-0-1-kmxmn",
      "label": "Input 2",
      "kind": "input",
      "x": 120,
      "y": 386.6666666666667,
      "bias": 0,
      "activation": "linear",
      "groupId": "layer-0-dqjmz"
    },
    {
      "id": "hidden-1-0-b98ur",
      "label": "Hidden 1.1",
      "kind": "hidden",
      "x": 480,
      "y": 243.33333333333334,
      "bias": -0.023276,
      "activation": "sigmoid",
      "groupId": "layer-1-7jy6r"
    },
    {
      "id": "hidden-1-1-e56rc",
      "label": "Hidden 1.2",
      "kind": "hidden",
      "x": 480,
      "y": 386.6666666666667,
      "bias": -0.096125,
      "activation": "sigmoid",
      "groupId": "layer-1-7jy6r"
    },
    {
      "id": "output-2-0-kbcxp",
      "label": "Output 1",
      "kind": "output",
      "x": 840,
      "y": 315,
      "bias": 0.114185,
      "activation": "sigmoid",
      "groupId": "layer-2-noj53"
    }
  ],
  "edges": [
    {
      "id": "edge-input-0-0-mgx98-hidden-1-0-b98ur",
      "source": "input-0-0-mgx98",
      "target": "hidden-1-0-b98ur",
      "weight": 0.530445
    },
    {
      "id": "edge-input-0-0-mgx98-hidden-1-1-e56rc",
      "source": "input-0-0-mgx98",
      "target": "hidden-1-1-e56rc",
      "weight": -0.350194
    },
    {
      "id": "edge-input-0-1-kmxmn-hidden-1-0-b98ur",
      "source": "input-0-1-kmxmn",
      "target": "hidden-1-0-b98ur",
      "weight": -0.468132
    },
    {
      "id": "edge-input-0-1-kmxmn-hidden-1-1-e56rc",
      "source": "input-0-1-kmxmn",
      "target": "hidden-1-1-e56rc",
      "weight": 0.082861
    },
    {
      "id": "edge-hidden-1-0-b98ur-output-2-0-kbcxp",
      "source": "hidden-1-0-b98ur",
      "target": "output-2-0-kbcxp",
      "weight": 0.460284
    },
    {
      "id": "edge-hidden-1-1-e56rc-output-2-0-kbcxp",
      "source": "hidden-1-1-e56rc",
      "target": "output-2-0-kbcxp",
      "weight": -0.196851
    }
  ],
  "groups": [
    {
      "id": "layer-0-dqjmz",
      "label": "Input Layer",
      "kind": "layer",
      "parentGroupId": null,
      "x": 35,
      "y": 45,
      "width": 170,
      "height": 505,
      "color": "#0ea5e9"
    },
    {
      "id": "layer-1-7jy6r",
      "label": "Hidden Layer 1",
      "kind": "layer",
      "parentGroupId": null,
      "x": 395,
      "y": 45,
      "width": 170,
      "height": 505,
      "color": "#8b5cf6"
    },
    {
      "id": "layer-2-noj53",
      "label": "Output Layer",
      "kind": "layer",
      "parentGroupId": null,
      "x": 755,
      "y": 45,
      "width": 170,
      "height": 505,
      "color": "#f97316"
    }
  ],
  "samples": [
    {
      "inputs": {
        "input-0-0-mgx98": 0,
        "input-0-1-kmxmn": 0
      },
      "targets": {
        "output-2-0-kbcxp": 0
      }
    },
    {
      "inputs": {
        "input-0-0-mgx98": 0,
        "input-0-1-kmxmn": 1
      },
      "targets": {
        "output-2-0-kbcxp": 1
      }
    },
    {
      "inputs": {
        "input-0-0-mgx98": 1,
        "input-0-1-kmxmn": 0
      },
      "targets": {
        "output-2-0-kbcxp": 1
      }
    },
    {
      "inputs": {
        "input-0-0-mgx98": 1,
        "input-0-1-kmxmn": 1
      },
      "targets": {
        "output-2-0-kbcxp": 0
      }
    }
  ],
  "learningRate": 0.35
};

function activate(name, x) {
  if (name === "relu") return Math.max(0, x);
  if (name === "sigmoid") return 1 / (1 + Math.exp(-x));
  if (name === "tanh") return Math.tanh(x);
  return x;
}

export function predict(inputs) {
  const values = {};
  const pending = new Set(network.neurons.map((neuron) => neuron.id));
  while (pending.size) {
    let progressed = false;
    for (const neuron of network.neurons) {
      if (!pending.has(neuron.id)) continue;
      if (neuron.kind === "input") {
        values[neuron.id] = Number(inputs[neuron.id] ?? 0);
        if (!Number.isFinite(values[neuron.id])) throw new Error(neuron.label + ": input must be finite.");
        pending.delete(neuron.id); progressed = true; continue;
      }
      const incoming = network.edges.filter((edge) => edge.target === neuron.id);
      if (!incoming.every((edge) => edge.source in values)) continue;
      const terms = incoming.map((edge) => values[edge.source] * edge.weight);
      const operation = neuron.operation ?? "weighted-sum";
      if ((operation === "multiply" || operation === "softmax" || operation === "layer-norm") && terms.length < 2) throw new Error(neuron.label + ": at least two inputs required.");
      if (operation === "multiply") values[neuron.id] = terms.reduce((product, term) => product * term, 1);
      else if (operation === "sum") values[neuron.id] = terms.reduce((total, term) => total + term, 0);
      else if (operation === "softmax") {
        const selected = incoming.findIndex((edge) => edge.source === neuron.softmaxSourceId);
        if (selected < 0) throw new Error(neuron.label + ": choose an incoming softmax score.");
        const peak = Math.max(...terms);
        const exponentials = terms.map((term) => Math.exp(term - peak));
        values[neuron.id] = exponentials[selected] / exponentials.reduce((total, term) => total + term, 0);
      } else if (operation === "layer-norm") {
        const selected = incoming.findIndex((edge) => edge.source === neuron.normalizedSourceId);
        if (selected < 0) throw new Error(neuron.label + ": choose a normalized feature.");
        const mean = terms.reduce((total, term) => total + term, 0) / terms.length;
        const variance = terms.reduce((total, term) => total + (term - mean) ** 2, 0) / terms.length;
        values[neuron.id] = (terms[selected] - mean) / Math.sqrt(variance + 1e-5);
      } else values[neuron.id] = activate(neuron.activation, terms.reduce((total, term) => total + term, neuron.bias));
      if (!Number.isFinite(values[neuron.id])) throw new Error(neuron.label + ": non-finite result.");
      pending.delete(neuron.id); progressed = true;
    }
    if (!progressed) throw new Error("Network contains a cycle or missing input.");
  }
  return Object.fromEntries(network.neurons.filter((neuron) => neuron.kind === "output").map((neuron) => [neuron.label, values[neuron.id]]));
}

Everything runs locally in this browser.