diff --git a/playground/css/style.css b/playground/css/style.css index fbfca12..c88bc42 100644 --- a/playground/css/style.css +++ b/playground/css/style.css @@ -285,6 +285,38 @@ html, body { font-size: 11px; color: var(--text-dim); font-family: monospace; + margin-bottom: 8px; +} + +.metrics-grid { + display: grid; + grid-template-columns: auto 1fr; + gap: 3px 10px; + font-size: 10px; + margin-bottom: 8px; + font-family: monospace; +} + +.metric-label { + color: #707070; +} + +.metric-value { + color: #b5b5b5; + text-align: right; +} + +.loss-plot-wrap { + width: 100%; + border: 1px solid #2a2a2a; + border-radius: 8px; + overflow: hidden; +} + +#loss-plot { + width: 100%; + height: 86px; + display: block; } .hidden { display: none !important; } diff --git a/playground/js/nisps/iml.js b/playground/js/nisps/iml.js index 470e278..d03509e 100644 --- a/playground/js/nisps/iml.js +++ b/playground/js/nisps/iml.js @@ -48,6 +48,9 @@ export class IML { this.storedWeights = null; this.weightsRandomised = false; this.lastLoss = null; + this.bestLoss = null; + this.lossHistory = []; + this.totalTrainingIterations = 0; this.logFn = null; } @@ -162,7 +165,7 @@ export class IML { this.process(); } - train() { + train(options = {}) { // Restore weights if randomised if (this.weightsRandomised && this.storedWeights) { this.mlp.setWeights(this.storedWeights); @@ -183,9 +186,21 @@ export class IML { labels, this.learningRate, this.maxIterations, - this.convergenceThreshold + this.convergenceThreshold, + options ); + const latestHistory = this.mlp.lastTrainingHistory || []; + if (latestHistory.length > 0) { + this.lossHistory.push(...latestHistory); + this.totalTrainingIterations += latestHistory.length; + if (this.lossHistory.length > 1200) { + this.lossHistory = this.lossHistory.slice(this.lossHistory.length - 1200); + } + const runBest = Math.min(...latestHistory); + this.bestLoss = this.bestLoss === null ? runBest : Math.min(this.bestLoss, runBest); + } + // Run inference after training const inputWithBias = [...this.inputState, 1.0]; const { output } = this.mlp.getOutput(inputWithBias); diff --git a/playground/js/nisps/mlp.js b/playground/js/nisps/mlp.js index 361bbeb..f5bc2b7 100644 --- a/playground/js/nisps/mlp.js +++ b/playground/js/nisps/mlp.js @@ -67,9 +67,11 @@ export class MLP { } // Per-sample SGD training (Train method from C++) - train(features, labels, learningRate, maxIterations = 1000, minError = 0.00001) { + train(features, labels, learningRate, maxIterations = 1000, minError = 0.00001, options = {}) { const sampleSizeRecip = 1 / features.length; let loss = 0; + const history = []; + const onIteration = typeof options.onIteration === 'function' ? options.onIteration : null; for (let iter = 0; iter < maxIterations; iter++) { loss = 0; @@ -89,6 +91,9 @@ export class MLP { } loss *= sampleSizeRecip; + history.push(loss); + + if (onIteration) onIteration(iter, loss); if (this.progressCallback && (iter & 0x1F) === 0) { this.progressCallback(iter, loss); @@ -97,14 +102,17 @@ export class MLP { if (loss < minError) break; } + this.lastTrainingHistory = history; return loss; } // Batch training with RMSProp (TrainBatch from C++) - trainBatch(features, labels, learningRate, maxIterations = 1000, batchSize = 8, minError = 0.00001) { + trainBatch(features, labels, learningRate, maxIterations = 1000, batchSize = 8, minError = 0.00001, options = {}) { const nSamples = features.length; const nBatches = Math.ceil(nSamples / batchSize); let epochLoss = 0; + const history = []; + const onIteration = typeof options.onIteration === 'function' ? options.onIteration : null; for (let iter = 0; iter < maxIterations; iter++) { epochLoss = 0; @@ -164,6 +172,8 @@ export class MLP { } epochLoss /= nBatches; + history.push(epochLoss); + if (onIteration) onIteration(iter, epochLoss); if (this.progressCallback) { this.progressCallback(iter, epochLoss); @@ -172,6 +182,7 @@ export class MLP { if (epochLoss < minError) break; } + this.lastTrainingHistory = history; return epochLoss; } diff --git a/playground/js/ui/controls.js b/playground/js/ui/controls.js index 56dcae1..fe698d2 100644 --- a/playground/js/ui/controls.js +++ b/playground/js/ui/controls.js @@ -35,6 +35,27 @@ export class Controls { Noise: 0.05 +