99 lines
3.7 KiB
JavaScript
99 lines
3.7 KiB
JavaScript
'use strict';
|
|
// Vluchtige Node-test: laadt app.js met een minimale DOM-stub en
|
|
// controleert of het neuraal netwerk echt leert (XOR / cirkel / spiraal).
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const src = fs.readFileSync(path.join(__dirname, 'app.js'), 'utf8');
|
|
|
|
function makeCtx() {
|
|
const data = new Uint8ClampedArray(200 * 150 * 4);
|
|
return {
|
|
createImageData: () => ({ width: 200, height: 150, data }),
|
|
putImageData: () => {}, clearRect: () => {}, drawImage: () => {},
|
|
fillRect: () => {}, fillText: () => {}, beginPath: () => {}, arc: () => {},
|
|
fill: () => {}, stroke: () => {}, moveTo: () => {}, lineTo: () => {}, strokeRect: () => {}
|
|
};
|
|
}
|
|
function makeEl() {
|
|
return {
|
|
width: 460, height: 320, style: {}, textContent: '', innerHTML: '',
|
|
scrollTop: 0, scrollHeight: 0,
|
|
classList: { add() {}, remove() {}, toggle() { return false; } },
|
|
appendChild() {}, addEventListener() {}, setAttribute() {}, click() {},
|
|
getContext: makeCtx, parentElement: { clientWidth: 800 }, dataset: {}, value: '6',
|
|
toDataURL: () => 'data:image/png;base64,xx'
|
|
};
|
|
}
|
|
const documentStub = {
|
|
getElementById: () => makeEl(),
|
|
querySelectorAll: () => [],
|
|
createElement: () => makeEl()
|
|
};
|
|
const rafNoop = () => {};
|
|
|
|
const code = src + '\n;globalThis.__NN = NeuralNet; globalThis.__makeDataset = makeDataset;';
|
|
new Function('document', 'requestAnimationFrame', 'window', 'setTimeout', code)(
|
|
documentStub, rafNoop, { addEventListener() {} }, setTimeout
|
|
);
|
|
|
|
const NeuralNet = globalThis.__NN;
|
|
const makeDataset = globalThis.__makeDataset;
|
|
|
|
function trainOn(name, epochs, lr, hidden) {
|
|
const net = new NeuralNet([2, hidden, 1]);
|
|
const samples = makeDataset(name);
|
|
const idx = samples.map((_, i) => i);
|
|
let last = null;
|
|
for (let e = 0; e < epochs; e++) {
|
|
for (let i = idx.length - 1; i > 0; i--) {
|
|
const j = Math.floor(Math.random() * (i + 1));
|
|
const t = idx[i]; idx[i] = idx[j]; idx[j] = t;
|
|
}
|
|
let sum = 0;
|
|
for (const i of idx) sum += net.trainSample(samples[i].x, samples[i].y, lr);
|
|
last = sum / idx.length;
|
|
}
|
|
const test = makeDataset(name);
|
|
const n = Math.min(200, test.length);
|
|
let correct = 0;
|
|
for (let i = 0; i < n; i++) {
|
|
if ((net.predict(test[i].x) > 0.5 ? 1 : 0) === test[i].y) correct++;
|
|
}
|
|
return { acc: (correct / n) * 100, loss: last };
|
|
}
|
|
|
|
// Finale controle — zelfde instelling als app.js (Adam, lr 0.05)
|
|
function trial(name, hidden, epochs) {
|
|
const res = [];
|
|
for (let t = 0; t < 6; t++) res.push(trainOn(name, epochs, 0.05, hidden));
|
|
const worst = Math.min(...res.map((a) => a.acc));
|
|
return { worst, raw: res.map((a) => a.acc.toFixed(1) + '%').join(' ') };
|
|
}
|
|
|
|
const xor0 = trainOn('XOR', 0, 0.05, 10);
|
|
const xor = trial('XOR', 10, 250);
|
|
const cir = trial('CIRKEL', 14, 400);
|
|
const spi = trial('SPIRAAL', 30, 1400);
|
|
|
|
console.log('XOR 0ep :', xor0.acc.toFixed(1) + '%');
|
|
console.log('XOR 250ep :', xor.raw, ' (slechtste ' + xor.worst.toFixed(1) + '%)');
|
|
console.log('CIRKEL 400ep :', cir.raw, ' (slechtste ' + cir.worst.toFixed(1) + '%)');
|
|
console.log('SPIRAAL 1400ep :', spi.raw, ' (slechtste ' + spi.worst.toFixed(1) + '%)');
|
|
|
|
let fail = 0;
|
|
if (xor0.acc < 35 || xor0.acc > 70) { console.error('FAIL: XOR ongetraind moet ~50% zijn'); fail = 1; }
|
|
if (xor.worst < 85) { console.error('FAIL: XOR convergerde niet'); fail = 1; }
|
|
if (cir.worst < 90) { console.error('FAIL: Cirkel convergerde niet'); fail = 1; }
|
|
if (spi.worst < 85) { console.error('FAIL: Spiraal convergerde niet'); fail = 1; }
|
|
|
|
if (fail) {
|
|
console.error('VALIDATIE MISLUKT');
|
|
process.exit(1);
|
|
}
|
|
console.log('PASS: alle drie datasets leren betrouwbaar met Adam (lr 0.05).');
|
|
process.exit(0);
|
|
|
|
if (fail) process.exit(1);
|
|
console.log('PASS: app.js laadt schoon én het netwerk leert alle drie datasets.');
|
|
process.exit(0);
|