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 1 | Input 2 | Target: 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.