Below is a miniature evolutionary NAS. Each architecture is a chain of three operations — ReLU, Sigmoid, or Identity — applied to a 4-input toy dataset. The fitness is accuracy on that dataset (0–100 %).
<p class="hint">Each <b>architecture</b> is three operations applied in sequence. <b>Fitness</b> = accuracy on a 4-input toy dataset. Press <b>Evolve</b> to run one generation of selection + mutation.</p>
<div class="pop-wrap">
<div id="population" class="population"></div>
</div>
<div class="stats" id="stats">Generation: 0 | Best accuracy: —</div>
<div class="btns">
<button id="evolveBtn" type="button">Evolve</button>
<button id="evolve10Btn" type="button">Evolve ×10</button>
<button id="resetBtn" type="button" class="ghost">Reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
.pop-wrap { overflow-x: auto; }
.population { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: .5rem; min-height: 80px; }
.arch {
display: flex; flex-direction: column; align-items: center;
background: #eef2f6; border: 1.5px solid #c8d3de; border-radius: 10px;
padding: 6px 8px; min-width: 90px; transition: border-color .2s, background .2s;
}
.arch.best { background: #d4edda; border-color: #28a745; }
.arch.survivor { background: #e8f4fd; border-color: #2196f3; }
.arch.mutant { background: #fff3cd; border-color: #ffc107; }
.arch-ops { display: flex; gap: 3px; margin-bottom: 4px; }
.op {
font-size: .7rem; font-weight: 700; padding: 2px 5px;
border-radius: 4px; letter-spacing: .02em;
}
.op-relu { background: #1d3557; color: #fff; }
.op-sigmoid { background: #e63946; color: #fff; }
.op-identity { background: #a8dadc; color: #1d3557; }
.arch-acc { font-size: .78rem; font-weight: 600; color: #333; }
.arch-label { font-size: .65rem; color: #666; margin-top: 1px; }
.stats { font-size: .95rem; font-weight: 600; margin: .4rem 0; color: #1d3557; min-height: 1.4em; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Miniature evolutionary NAS
// Architecture = sequence of 3 ops (relu / sigmoid / identity)
// Fitness = accuracy on a 4-point centered dataset
// Evolution: keep top half, replace bottom half with mutants
const OPS = ['relu', 'sigmoid', 'identity'];
const POP_SIZE = 10;
// Toy dataset: inputs centered at 0, label = sign(x)
// ReLU clips negatives -> bad signal for negative class (only 7/27 chains reach 100%)
const DATASET = [
{ x: -0.7, y: 0 },
{ x: -0.3, y: 0 },
{ x: 0.3, y: 1 },
{ x: 0.7, y: 1 },
];
function applyOp(op, v) {
if (op === 'relu') return Math.max(0, v);
if (op === 'sigmoid') return 1 / (1 + Math.exp(-v * 4));
return v; // identity
}
function evaluate(ops) {
// Apply the 3 ops in sequence, starting from x, with a small linear bias
// so 'identity' alone isn't perfect; subtract 0.5 to center then re-add.
let correct = 0;
for (const { x, y } of DATASET) {
let v = x;
for (const op of ops) v = applyOp(op, v);
const pred = v >= 0.5 ? 1 : 0;
if (pred === y) correct++;
}
return Math.round((correct / DATASET.length) * 100);
}
function randomArch() {
return [0, 1, 2].map(() => OPS[Math.floor(Math.random() * OPS.length)]);
}
function mutate(arch) {
const copy = [...arch];
const idx = Math.floor(Math.random() * copy.length);
let newOp;
do { newOp = OPS[Math.floor(Math.random() * OPS.length)]; } while (newOp === copy[idx]);
copy[idx] = newOp;
return copy;
}
let population = [];
let generation = 0;
function initPop() {
population = Array.from({ length: POP_SIZE }, () => {
const ops = randomArch();
return { ops, acc: evaluate(ops), kind: 'init' };
});
generation = 0;
}
function evolveOnce() {
// Sort descending by accuracy
population.sort((a, b) => b.acc - a.acc);
const survivors = population.slice(0, Math.ceil(POP_SIZE / 2)).map(a => ({ ...a, kind: 'survivor' }));
survivors[0] = { ...survivors[0], kind: 'best' };
const mutants = survivors.map(s => {
const ops = mutate(s.ops);
return { ops, acc: evaluate(ops), kind: 'mutant' };
});
population = [...survivors, ...mutants].slice(0, POP_SIZE);
generation++;
}
function render() {
const el = document.getElementById('population');
el.innerHTML = '';
const sorted = [...population].sort((a, b) => b.acc - a.acc);
for (const arch of sorted) {
const div = document.createElement('div');
div.className = 'arch' + (arch.kind === 'best' ? ' best' : arch.kind === 'survivor' ? ' survivor' : arch.kind === 'mutant' ? ' mutant' : '');
const opsDiv = document.createElement('div');
opsDiv.className = 'arch-ops';
for (const op of arch.ops) {
const span = document.createElement('span');
span.className = 'op op-' + op;
span.textContent = op === 'relu' ? 'R' : op === 'sigmoid' ? 'S' : 'I';
span.title = op;
opsDiv.appendChild(span);
}
div.appendChild(opsDiv);
const accDiv = document.createElement('div');
accDiv.className = 'arch-acc';
accDiv.textContent = arch.acc + '%';
div.appendChild(accDiv);
const lbl = document.createElement('div');
lbl.className = 'arch-label';
lbl.textContent = arch.kind === 'best' ? 'best' : arch.kind === 'survivor' ? 'survivor' : arch.kind === 'mutant' ? 'mutant' : '';
div.appendChild(lbl);
el.appendChild(div);
}
const best = sorted[0].acc;
document.getElementById('stats').textContent =
fmt('Generation: %generation% | Best accuracy: %best%%', { generation: generation, best: best });
}
document.getElementById('evolveBtn').addEventListener('click', () => { evolveOnce(); render(); });
document.getElementById('evolve10Btn').addEventListener('click', () => { for (let i = 0; i < 10; i++) evolveOnce(); render(); });
document.getElementById('resetBtn').addEventListener('click', () => { initPop(); render(); });
initPop();
render();
<p class="hint">Each <b>architecture</b> is three operations applied in sequence. <b>Fitness</b> = accuracy on a 4-input toy dataset. Press <b>Evolve</b> to run one generation of selection + mutation.</p>
<div class="pop-wrap">
<div id="population" class="population"></div>
</div>
<div class="stats" id="stats">Generation: 0 | Best accuracy: —</div>
<div class="btns">
<button id="evolveBtn" type="button">Evolve</button>
<button id="evolve10Btn" type="button">Evolve ×10</button>
<button id="resetBtn" type="button" class="ghost">Reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
.pop-wrap { overflow-x: auto; }
.population { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: .5rem; min-height: 80px; }
.arch {
display: flex; flex-direction: column; align-items: center;
background: #eef2f6; border: 1.5px solid #c8d3de; border-radius: 10px;
padding: 6px 8px; min-width: 90px; transition: border-color .2s, background .2s;
}
.arch.best { background: #d4edda; border-color: #28a745; }
.arch.survivor { background: #e8f4fd; border-color: #2196f3; }
.arch.mutant { background: #fff3cd; border-color: #ffc107; }
.arch-ops { display: flex; gap: 3px; margin-bottom: 4px; }
.op {
font-size: .7rem; font-weight: 700; padding: 2px 5px;
border-radius: 4px; letter-spacing: .02em;
}
.op-relu { background: #1d3557; color: #fff; }
.op-sigmoid { background: #e63946; color: #fff; }
.op-identity { background: #a8dadc; color: #1d3557; }
.arch-acc { font-size: .78rem; font-weight: 600; color: #333; }
.arch-label { font-size: .65rem; color: #666; margin-top: 1px; }
.stats { font-size: .95rem; font-weight: 600; margin: .4rem 0; color: #1d3557; min-height: 1.4em; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Miniature evolutionary NAS
// Architecture = sequence of 3 ops (relu / sigmoid / identity)
// Fitness = accuracy on a 4-point centered dataset
// Evolution: keep top half, replace bottom half with mutants
const OPS = ['relu', 'sigmoid', 'identity'];
const POP_SIZE = 10;
// Toy dataset: inputs centered at 0, label = sign(x)
// ReLU clips negatives -> bad signal for negative class (only 7/27 chains reach 100%)
const DATASET = [
{ x: -0.7, y: 0 },
{ x: -0.3, y: 0 },
{ x: 0.3, y: 1 },
{ x: 0.7, y: 1 },
];
function applyOp(op, v) {
if (op === 'relu') return Math.max(0, v);
if (op === 'sigmoid') return 1 / (1 + Math.exp(-v * 4));
return v; // identity
}
function evaluate(ops) {
// Apply the 3 ops in sequence, starting from x, with a small linear bias
// so 'identity' alone isn't perfect; subtract 0.5 to center then re-add.
let correct = 0;
for (const { x, y } of DATASET) {
let v = x;
for (const op of ops) v = applyOp(op, v);
const pred = v >= 0.5 ? 1 : 0;
if (pred === y) correct++;
}
return Math.round((correct / DATASET.length) * 100);
}
function randomArch() {
return [0, 1, 2].map(() => OPS[Math.floor(Math.random() * OPS.length)]);
}
function mutate(arch) {
const copy = [...arch];
const idx = Math.floor(Math.random() * copy.length);
let newOp;
do { newOp = OPS[Math.floor(Math.random() * OPS.length)]; } while (newOp === copy[idx]);
copy[idx] = newOp;
return copy;
}
let population = [];
let generation = 0;
function initPop() {
population = Array.from({ length: POP_SIZE }, () => {
const ops = randomArch();
return { ops, acc: evaluate(ops), kind: 'init' };
});
generation = 0;
}
function evolveOnce() {
// Sort descending by accuracy
population.sort((a, b) => b.acc - a.acc);
const survivors = population.slice(0, Math.ceil(POP_SIZE / 2)).map(a => ({ ...a, kind: 'survivor' }));
survivors[0] = { ...survivors[0], kind: 'best' };
const mutants = survivors.map(s => {
const ops = mutate(s.ops);
return { ops, acc: evaluate(ops), kind: 'mutant' };
});
population = [...survivors, ...mutants].slice(0, POP_SIZE);
generation++;
}
function render() {
const el = document.getElementById('population');
el.innerHTML = '';
const sorted = [...population].sort((a, b) => b.acc - a.acc);
for (const arch of sorted) {
const div = document.createElement('div');
div.className = 'arch' + (arch.kind === 'best' ? ' best' : arch.kind === 'survivor' ? ' survivor' : arch.kind === 'mutant' ? ' mutant' : '');
const opsDiv = document.createElement('div');
opsDiv.className = 'arch-ops';
for (const op of arch.ops) {
const span = document.createElement('span');
span.className = 'op op-' + op;
span.textContent = op === 'relu' ? 'R' : op === 'sigmoid' ? 'S' : 'I';
span.title = op;
opsDiv.appendChild(span);
}
div.appendChild(opsDiv);
const accDiv = document.createElement('div');
accDiv.className = 'arch-acc';
accDiv.textContent = arch.acc + '%';
div.appendChild(accDiv);
const lbl = document.createElement('div');
lbl.className = 'arch-label';
lbl.textContent = arch.kind === 'best' ? 'best' : arch.kind === 'survivor' ? 'survivor' : arch.kind === 'mutant' ? 'mutant' : '';
div.appendChild(lbl);
el.appendChild(div);
}
const best = sorted[0].acc;
document.getElementById('stats').textContent =
fmt('Generation: %generation% | Best accuracy: %best%%', { generation: generation, best: best });
}
document.getElementById('evolveBtn').addEventListener('click', () => { evolveOnce(); render(); });
document.getElementById('evolve10Btn').addEventListener('click', () => { for (let i = 0; i < 10; i++) evolveOnce(); render(); });
document.getElementById('resetBtn').addEventListener('click', () => { initPop(); render(); });
initPop();
render();
Press Evolve to run one generation: the top survivors are kept, the rest are replaced by mutants. Watch the best accuracy climb as evolution drives the population toward better architectures. Press Reset to start fresh with a random population.
The key insight: we never need to know in advance which architecture is best. The search process finds it by trying, scoring, and breeding — the same loop that powers real NAS systems like DARTS and EfficientNet.
Comments
Loading comments...