carbono / index.html
appvoid's picture
Update index.html
c6ec9c9 verified
<!DOCTYPE html>
<html>
<head>
<title>Carbono UI</title>
<style>
a {
color: white;
}
body {
background: #000;
color: #fff;
font-family: monospace;
margin: 0;
padding-top: 16px;
padding: 5%;
display: flex;
flex-direction: column;
gap: 15px;
overflow-x: hidden;
}
h3 {
margin: 1.5rem;
margin-bottom: 0;
}
p {
margin: 1.5rem;
margin-top: 0rem;
color: #777;
}
.grid {
display: grid;
grid-template-columns: minmax(400px, 1fr) minmax(300px, 2fr);
gap: 15px;
opacity: 0;
transform: translateY(20px);
animation: fadeInUp 0.5s ease-out forwards;
}
.widget {
background: #000;
border-radius: 10px;
padding: 15px;
box-sizing: border-box;
width: 100%;
opacity: 0;
transform: translateY(20px);
animation: fadeInUp 0.5s ease-out forwards;
animation-delay: 0.2s;
}
.widget-title {
font-size: 1.1em;
margin-bottom: 12px;
border-bottom: 1px solid #333;
padding-bottom: 8px;
opacity: 0;
transform: translateY(10px);
animation: fadeInUp 0.5s ease-out forwards;
animation-delay: 0.3s;
}
.input-group {
margin-bottom: 12px;
opacity: 0;
transform: translateY(10px);
animation: fadeInUp 0.5s ease-out forwards;
animation-delay: 0.4s;
}
.settings-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 10px;
margin-bottom: 12px;
opacity: 0;
transform: translateY(10px);
animation: fadeInUp 0.5s ease-out forwards;
animation-delay: 0.5s;
}
input[type="text"],
input[type="number"],
select,
textarea {
outline: none;
width: 100%;
padding: 6px;
background: #222;
border: 1px solid #444;
color: #fff;
border-radius: 8px;
margin-top: 4px;
box-sizing: border-box;
transition: background 0.3s, border 0.3s;
}
span {
background-color: white;
color: black;
font-weight: 600;
font-size: 12px;
padding: 1px;
border-radius: 3px;
cursor: pointer;
}
input[type="text"]:focus,
input[type="number"]:focus,
select:focus,
textarea:focus {
background: #333;
border: 1px solid #666;
}
button {
background: #fff;
color: #000;
border: none;
padding: 6px 12px;
border-radius: 6px;
cursor: pointer;
transition: all 0.1s ease;
border: 1px solid white;
opacity: 0;
height: 28px;
transform: translateY(10px);
animation: fadeInUp 0.5s ease-out forwards;
animation-delay: 0.6s;
}
button:hover {
border: 1px solid white;
color: white;
background: #000;
}
.progress-container {
height: 180px;
position: relative;
border: 1px solid #333;
border-radius: 8px;
margin-bottom: 10px;
opacity: 0;
transform: translateY(10px);
animation: fadeInUp 0.5s ease-out forwards;
animation-delay: 0.7s;
}
.loss-graph {
position: absolute;
bottom: 0;
width: 100%;
height: 100%;
}
.network-graph {
position: absolute;
bottom: 0;
width: 100%;
height: 100%;
}
.flex-container {
display: flex;
gap: 20px;
opacity: 0;
transform: translateY(10px);
animation: fadeInUp 0.5s ease-out forwards;
animation-delay: 0.8s;
}
.prediction-section,
.model-section {
flex: 1;
}
.button-group {
display: flex;
gap: 10px;
opacity: 0;
transform: translateY(10px);
animation: fadeInUp 0.5s ease-out forwards;
animation-delay: 0.9s;
}
.visualization-container {
margin-top: 15px;
opacity: 0;
transform: translateY(10px);
animation: fadeInUp 0.5s ease-out forwards;
animation-delay: 1s;
}
.epoch-progress {
height: 5px;
background: #222;
border-radius: 8px;
overflow: hidden;
}
.epoch-bar {
height: 100%;
width: 0;
background: #fff;
transition: width 0.3s ease;
}
@keyframes fadeInUp {
to {
opacity: 1;
transform: translateY(0);
}
}
/* Responsive Design */
@media (max-width: 768px) {
.grid {
grid-template-columns: 1fr;
}
.flex-container {
flex-direction: column;
}
}
</style>
</head>
<body>
<h3>playground</h3>
<p>this is a web app for showcasing carbono, a self-contained micro-library that makes it super easy to play, create and share small neural networks; it's the easiest, hackable machine learning js library; it's also convenient to quickly prototype on embedded devices. to download it and know more you can go to the <a href="https://github.com/appvoid/carbono" target="_blank">github repo</a>; you can see additional training details by opening the console; to load a dummy dataset, <span id="loadDataBtn">click here</span> and then click "train" button.</p>
<div class="grid">
<!-- Group 1: Data & Training -->
<div class="widget">
<div class="widget-title">model settings</div>
<div class="input-group">
<label>training set:</label>
<textarea id="trainingData" rows="3" placeholder="1,1,1,0
1,0,1,0
0,1,0,1"></textarea>
</div>
<p>last number represents actual desired output</p>
<div class="input-group">
<label>validation set:</label>
<textarea id="testData" rows="3" placeholder="0,0,0,1"></textarea>
</div>
<div class="settings-grid">
<div class="input-group">
<label>epochs:</label>
<input type="number" id="epochs" value="50">
</div>
<div class="input-group">
<label>learning rate:</label>
<input type="number" id="learningRate" value="0.1" step="0.001">
</div>
<div class="input-group">
<label>batch size:</label>
<input type="number" id="batchSize" value="8">
</div>
<div class="input-group">
<label>hidden layers:</label>
<input type="number" id="numHiddenLayers" value="1">
</div>
</div>
<!-- New UI Elements for Layer Configuration -->
<div id="hiddenLayersConfig"></div>
</div>
<!-- Group 2: Progress & Visualization -->
<div class="widget">
<div class="widget-title">training progress</div>
<div id="progress">
<div class="progress-container">
<canvas id="lossGraph" class="loss-graph"></canvas>
</div>
<p>training loss is white, validation loss is gray</p>
<div class="epoch-progress">
<div id="epochBar" class="epoch-bar"></div>
</div>
<div id="stats" style="margin-top: 10px;"></div>
</div>
<div class="model-section">
<br>
<div class="widget-title">model management</div>
<p>save the weights to load them on your app or share them on huggingface!</p>
<div class="button-group">
<button id="trainButton">train</button>
<button id="saveButton">save</button>
<button id="loadButton">load</button>
<div class="prediction-section">
<div class="widget-title">prediction</div>
<p>predict output</p>
<div class="input-group">
<label>input:</label>
<input type="text" id="predictionInput" placeholder="0.4, 0.2, 0.6">
</div>
<button id="predictButton">predict</button>
<div id="predictionResult" style="margin-top: 10px;"></div>
</div>
<div class="visualization-container">
<div class="widget-title">visualization</div>
<div class="progress-container">
<canvas id="networkGraph" class="network-graph"></canvas>
</div>
<p>internal model's representation</p>
</div>
</div>
</div>
</div>
</div>
<script>
class carbono {
constructor(debug = true) {
this.layers = [];
this.weights = [];
this.biases = [];
this.activations = [];
this.details = {};
this.debug = debug;
}
// Add a new layer to the neural network
layer(inputSize, outputSize, activation = 'tanh') {
this.layers.push({
inputSize,
outputSize,
activation
});
if (this.weights.length > 0) {
const lastLayerOutputSize = this.layers[this.layers.length - 2].outputSize;
if (inputSize !== lastLayerOutputSize) {
throw new Error('Oops! The input size of the new layer must match the output size of the previous layer.');
}
}
const weights = [];
for (let i = 0; i < outputSize; i++) {
const row = [];
for (let j = 0; j < inputSize; j++) {
row.push((Math.random() - 0.5) * 2 * Math.sqrt(6 / (inputSize + outputSize)));
}
weights.push(row);
}
this.weights.push(weights);
const biases = Array(outputSize).fill(0.01);
this.biases.push(biases);
this.activations.push(activation);
}
// Apply the activation function
activationFunction(x, activation) {
switch (activation) {
case 'tanh':
return Math.tanh(x);
case 'sigmoid':
return 1 / (1 + Math.exp(-x));
case 'relu':
return Math.max(0, x);
case 'selu':
const alpha = 1.67326;
const scale = 1.0507;
return x > 0 ? scale * x : scale * alpha * (Math.exp(x) - 1);
default:
throw new Error('Whoops! We don\'t know that activation function.');
}
}
// Calculate the derivative of the activation function
activationDerivative(x, activation) {
switch (activation) {
case 'tanh':
return 1 - Math.pow(Math.tanh(x), 2);
case 'sigmoid':
const sigmoid = 1 / (1 + Math.exp(-x));
return sigmoid * (1 - sigmoid);
case 'relu':
return x > 0 ? 1 : 0;
case 'selu':
const alpha = 1.67326;
const scale = 1.0507;
return x > 0 ? scale : scale * alpha * Math.exp(x);
default:
throw new Error('Oops! We don\'t know the derivative of that activation function.');
}
}
// Positional Encoding
positionalEncoding(input, maxLen) {
const pe = new Array(maxLen).fill(0).map((_, pos) => {
return new Array(input[0].length).fill(0).map((_, i) => {
const angle = pos / Math.pow(10000, 2 * i / input[0].length);
return pos % 2 === 0 ? Math.sin(angle) : Math.cos(angle);
});
});
return input.map((seq, idx) => seq.map((val, i) => val + pe[idx][i]));
}
// Simplified Multi-Head Self-Attention
multiHeadSelfAttention(input, numHeads = 2) {
const headSize = input[0].length / numHeads;
const heads = new Array(numHeads).fill(0).map(() => new Array(input.length).fill(0).map(() => new Array(headSize).fill(0)));
for (let h = 0; h < numHeads; h++) {
for (let i = 0; i < input.length; i++) {
for (let j = 0; j < headSize; j++) {
heads[h][i][j] = input[i][h * headSize + j];
}
}
}
const attentionScores = new Array(numHeads).fill(0).map(() => new Array(input.length).fill(0).map(() => new Array(input.length).fill(0)));
for (let h = 0; h < numHeads; h++) {
for (let i = 0; i < input.length; i++) {
for (let j = 0; j < input.length; j++) {
let score = 0;
for (let k = 0; k < headSize; k++) {
score += heads[h][i][k] * heads[h][j][k];
}
attentionScores[h][i][j] = score;
}
}
}
const attentionWeights = attentionScores.map(head => head.map(row => row.map(score => Math.exp(score) / row.reduce((sum, s) => sum + Math.exp(s), 0))));
const output = new Array(input.length).fill(0).map(() => new Array(input[0].length).fill(0));
for (let h = 0; h < numHeads; h++) {
for (let i = 0; i < input.length; i++) {
for (let j = 0; j < headSize; j++) {
for (let k = 0; k < input.length; k++) {
output[i][h * headSize + j] += attentionWeights[h][i][k] * heads[h][k][j];
}
}
}
}
return output;
}
// Layer Normalization
layerNormalization(input) {
const mean = input.reduce((sum, val) => sum + val, 0) / input.length;
const variance = input.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / input.length;
return input.map(val => (val - mean) / Math.sqrt(variance + 1e-5));
}
// Train the neural network
async train(trainSet, options = {}) {
const {
epochs = 200,
learningRate = 0.212,
batchSize = 16,
printEveryEpochs = 100,
earlyStopThreshold = 1e-6,
testSet = null,
callback = null
} = options;
const start = Date.now();
if (batchSize < 1) batchSize = 2;
if (this.layers.length === 0) {
const numInputs = trainSet[0].input.length;
this.layer(numInputs, numInputs, 'tanh');
this.layer(numInputs, 1, 'tanh');
}
let lastTrainLoss = 0;
let lastTestLoss = null;
for (let epoch = 0; epoch < epochs; epoch++) {
let trainError = 0;
for (let b = 0; b < trainSet.length; b += batchSize) {
const batch = trainSet.slice(b, b + batchSize);
let batchError = 0;
for (const data of batch) {
const layerInputs = [data.input];
for (let i = 0; i < this.weights.length; i++) {
const inputs = layerInputs[i];
const weights = this.weights[i];
const biases = this.biases[i];
const activation = this.activations[i];
const outputs = [];
for (let j = 0; j < weights.length; j++) {
const weight = weights[j];
let sum = biases[j];
for (let k = 0; k < inputs.length; k++) {
sum += inputs[k] * weight[k];
}
outputs.push(this.activationFunction(sum, activation));
}
layerInputs.push(outputs);
}
const outputLayerIndex = this.weights.length - 1;
const outputLayerInputs = layerInputs[layerInputs.length - 1];
const outputErrors = [];
for (let i = 0; i < outputLayerInputs.length; i++) {
const error = data.output[i] - outputLayerInputs[i];
outputErrors.push(error);
}
let layerErrors = [outputErrors];
for (let i = this.weights.length - 2; i >= 0; i--) {
const nextLayerWeights = this.weights[i + 1];
const nextLayerErrors = layerErrors[0];
const currentLayerInputs = layerInputs[i + 1];
const currentActivation = this.activations[i];
const errors = [];
for (let j = 0; j < this.layers[i].outputSize; j++) {
let error = 0;
for (let k = 0; k < this.layers[i + 1].outputSize; k++) {
error += nextLayerErrors[k] * nextLayerWeights[k][j];
}
errors.push(error * this.activationDerivative(currentLayerInputs[j], currentActivation));
}
layerErrors.unshift(errors);
}
for (let i = 0; i < this.weights.length; i++) {
const inputs = layerInputs[i];
const errors = layerErrors[i];
const weights = this.weights[i];
const biases = this.biases[i];
for (let j = 0; j < weights.length; j++) {
const weight = weights[j];
for (let k = 0; k < inputs.length; k++) {
weight[k] += learningRate * errors[j] * inputs[k];
}
biases[j] += learningRate * errors[j];
}
}
batchError += Math.abs(outputErrors[0]);
}
trainError += batchError;
}
lastTrainLoss = trainError / trainSet.length;
if (testSet) {
let testError = 0;
for (const data of testSet) {
const prediction = this.predict(data.input);
testError += Math.abs(data.output[0] - prediction[0]);
}
lastTestLoss = testError / testSet.length;
}
if ((epoch + 1) % printEveryEpochs === 0 && this.debug === true) {
console.log(`Epoch ${epoch + 1}, Train Loss: ${lastTrainLoss.toFixed(6)}${testSet ? `, Test Loss: ${lastTestLoss.toFixed(6)}` : ''}`);
}
if (callback) {
await callback(epoch + 1, lastTrainLoss, lastTestLoss);
}
await new Promise(resolve => setTimeout(resolve, 0));
if (lastTrainLoss < earlyStopThreshold) {
console.log(`We stopped at epoch ${epoch + 1} with train loss: ${lastTrainLoss.toFixed(6)}${testSet ? ` and test loss: ${lastTestLoss.toFixed(6)}` : ''}`);
break;
}
}
const end = Date.now();
let totalParams = 0;
for (let i = 0; i < this.weights.length; i++) {
const weightLayer = this.weights[i];
const biasLayer = this.biases[i];
totalParams += weightLayer.flat().length + biasLayer.length;
}
const trainingSummary = {
trainLoss: lastTrainLoss,
testLoss: lastTestLoss,
parameters: totalParams,
training: {
time: end - start,
epochs,
learningRate,
batchSize
},
layers: this.layers.map(layer => ({
inputSize: layer.inputSize,
outputSize: layer.outputSize,
activation: layer.activation
}))
};
this.details = trainingSummary;
return trainingSummary;
}
// Use the trained network to make predictions
predict(input) {
let layerInput = input;
const allActivations = [input];
const allRawValues = [];
for (let i = 0; i < this.weights.length; i++) {
const weights = this.weights[i];
const biases = this.biases[i];
const activation = this.activations[i];
const layerOutput = [];
const rawValues = [];
for (let j = 0; j < weights.length; j++) {
const weight = weights[j];
let sum = biases[j];
for (let k = 0; k < layerInput.length; k++) {
sum += layerInput[k] * weight[k];
}
rawValues.push(sum);
layerOutput.push(this.activationFunction(sum, activation));
}
allRawValues.push(rawValues);
allActivations.push(layerOutput);
layerInput = layerOutput;
}
this.lastActivations = allActivations;
this.lastRawValues = allRawValues;
return layerInput;
}
// Save the model to a file
save(name = 'model') {
const data = {
weights: this.weights,
biases: this.biases,
activations: this.activations,
layers: this.layers,
details: this.details
};
const blob = new Blob([JSON.stringify(data)], {
type: 'application/json'
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${name}.json`;
a.click();
URL.revokeObjectURL(url);
}
// Load a saved model from a file
load(callback) {
const handleListener = (event) => {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
const text = event.target.result;
try {
const data = JSON.parse(text);
this.weights = data.weights;
this.biases = data.biases;
this.activations = data.activations;
this.layers = data.layers;
this.details = data.details;
callback();
if (this.debug === true) console.log('Model loaded successfully!');
input.removeEventListener('change', handleListener);
input.remove();
} catch (e) {
input.removeEventListener('change', handleListener);
input.remove();
if (this.debug === true) console.error('Failed to load model:', e);
}
};
reader.readAsText(file);
};
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
input.style.opacity = '0';
document.body.append(input);
input.addEventListener('change', handleListener.bind(this));
input.click();
}
}
document.getElementById("loadDataBtn").onclick = () => {
document.getElementById('trainingData').value = `1.0, 0.0, 0.0, 0.0
0.7, 0.7, 0.8, 1
0.0, 1.0, 0.0, 0.5`
document.getElementById('testData').value = `0.4, 0.2, 0.6, 1.0
0.2, 0.82, 0.83, 1.0`
}
// Interface code
const nn = new carbono();
let lossHistory = [];
const ctx = document.getElementById('lossGraph').getContext('2d');
function parseCSV(csv) {
return csv.trim().split('\n').map(row => {
const values = row.split(',').map(Number);
return {
input: values.slice(0, -1),
output: [values[values.length - 1]]
};
});
}
function drawLossGraph() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
const width = ctx.canvas.width;
const height = ctx.canvas.height;
// Combine train and test losses to find overall max for scaling
const maxLoss = Math.max(
...lossHistory.map(loss => Math.max(loss.train, loss.test || 0))
);
// Draw training loss (white line)
ctx.strokeStyle = '#fff';
ctx.beginPath();
lossHistory.forEach((loss, i) => {
const x = (i / (lossHistory.length - 1)) * width;
const y = height - (loss.train / maxLoss) * height;
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
});
ctx.stroke();
// Draw test loss (gray line)
ctx.strokeStyle = '#777';
ctx.beginPath();
lossHistory.forEach((loss, i) => {
if (loss.test !== undefined) {
const x = (i / (lossHistory.length - 1)) * width;
const y = height - (loss.test / maxLoss) * height;
if (i === 0 || lossHistory[i - 1].test === undefined) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
});
ctx.stroke();
}
function createLayerConfigUI(numLayers) {
const container = document.getElementById('hiddenLayersConfig');
container.innerHTML = ''; // Clear previous UI
for (let i = 0; i < numLayers; i++) {
const group = document.createElement('div');
group.className = 'input-group';
const label = document.createElement('label');
label.textContent = `layer ${i + 1} nodes:`;
const input = document.createElement('input');
input.type = 'number';
input.value = 5;
input.dataset.layerIndex = i;
const activationLabel = document.createElement('label');
activationLabel.innerHTML = `<br>activation:`;
const activationSelect = document.createElement('select');
const activations = ['tanh', 'sigmoid', 'relu', 'selu'];
activations.forEach(act => {
const option = document.createElement('option');
option.value = act;
option.textContent = act;
activationSelect.appendChild(option);
});
activationSelect.dataset.layerIndex = i;
group.appendChild(label);
group.appendChild(input);
group.appendChild(activationLabel);
group.appendChild(activationSelect);
container.appendChild(group);
}
}
document.getElementById('numHiddenLayers').addEventListener('change', (event) => {
const numLayers = parseInt(event.target.value);
createLayerConfigUI(numLayers);
});
createLayerConfigUI(document.getElementById('numHiddenLayers').value);
document.getElementById('trainButton').addEventListener('click', async () => {
lossHistory = []; // Initialize as empty array
const trainingData = parseCSV(document.getElementById('trainingData').value);
const testData = parseCSV(document.getElementById('testData').value);
lossHistory = [];
document.getElementById('stats').innerHTML = '';
const numHiddenLayers = parseInt(document.getElementById('numHiddenLayers').value);
const layerConfigs = [];
for (let i = 0; i < numHiddenLayers; i++) {
const sizeInput = document.querySelector(`input[data-layer-index="${i}"]`);
const activationSelect = document.querySelector(`select[data-layer-index="${i}"]`);
layerConfigs.push({
size: parseInt(sizeInput.value),
activation: activationSelect.value
});
}
nn.layers = []; // Reset layers
nn.weights = [];
nn.biases = [];
nn.activations = [];
const numInputs = trainingData[0].input.length;
nn.layer(numInputs, layerConfigs[0].size, layerConfigs[0].activation);
for (let i = 1; i < layerConfigs.length; i++) {
nn.layer(layerConfigs[i - 1].size, layerConfigs[i].size, layerConfigs[i].activation);
}
nn.layer(layerConfigs[layerConfigs.length - 1].size, 1, 'tanh'); // Output layer
const options = {
epochs: parseInt(document.getElementById('epochs').value),
learningRate: parseFloat(document.getElementById('learningRate').value),
batchSize: parseInt(document.getElementById('batchSize').value),
printEveryEpochs: 1,
testSet: testData.length > 0 ? testData : null,
callback: async (epoch, trainLoss, testLoss) => {
lossHistory.push({
train: trainLoss,
test: testLoss
});
drawLossGraph();
document.getElementById('epochBar').style.width =
`${(epoch / options.epochs) * 100}%`;
document.getElementById('stats').innerHTML =
`<p> - current epoch: ${epoch}/${options.epochs}` +
`<br> - train/val loss: ${trainLoss.toFixed(6)}` +
(testLoss ? ` | ${testLoss.toFixed(6)}</p>` : '');
}
}
try {
const trainButton = document.getElementById('trainButton');
trainButton.disabled = true;
trainButton.textContent = 'training...';
// nn.play()
const summary = await nn.train(trainingData, options);
trainButton.disabled = false;
trainButton.textContent = 'train';
// Display final summary
document.getElementById('stats').innerHTML += '<strong>Model trained</strong>';
} catch (error) {
console.error('Training error:', error);
document.getElementById('trainButton').disabled = false;
document.getElementById('trainButton').textContent = 'train';
}
});
function drawNetwork() {
const canvas = document.getElementById('networkGraph');
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (!nn.lastActivations) return; // Don't draw if no predictions made yet
const padding = 40;
const width = canvas.width - padding * 2;
const height = canvas.height - padding * 2;
// Calculate node positions
const layerPositions = [];
// Add input layer explicitly
const inputLayer = [];
const inputX = padding;
const inputSize = nn.layers[0].inputSize;
for (let i = 0; i < inputSize; i++) {
const inputY = padding + (height * i) / (inputSize - 1);
inputLayer.push({
x: inputX,
y: inputY,
value: nn.lastActivations[0][i]
});
}
layerPositions.push(inputLayer);
// Add hidden layers
for (let i = 1; i < nn.lastActivations.length - 1; i++) {
const layer = nn.lastActivations[i];
const layerNodes = [];
const layerX = padding + (width * i) / (nn.lastActivations.length - 1);
for (let j = 0; j < layer.length; j++) {
const nodeY = padding + (height * j) / (layer.length - 1);
layerNodes.push({
x: layerX,
y: nodeY,
value: layer[j]
});
}
layerPositions.push(layerNodes);
}
// Add output layer explicitly
const outputLayer = [];
const outputX = canvas.width - padding;
const outputY = padding + height / 2; // Center the output node
outputLayer.push({
x: outputX,
y: outputY,
value: nn.lastActivations[nn.lastActivations.length - 1][0]
});
layerPositions.push(outputLayer);
// Draw connections
ctx.lineWidth = 1;
for (let i = 0; i < layerPositions.length - 1; i++) {
const currentLayer = layerPositions[i];
const nextLayer = layerPositions[i + 1];
const weights = nn.weights[i];
for (let j = 0; j < currentLayer.length; j++) {
const nextLayerSize = nextLayer.length;
for (let k = 0; k < nextLayerSize; k++) {
const weight = weights[k][j];
const signal = Math.abs(currentLayer[j].value * weight);
const opacity = Math.min(Math.max(signal, 0.01), 1);
ctx.strokeStyle = `rgba(255, 255, 255, ${opacity})`;
ctx.beginPath();
ctx.moveTo(currentLayer[j].x, currentLayer[j].y);
ctx.lineTo(nextLayer[k].x, nextLayer[k].y);
ctx.stroke();
}
}
}
// Draw nodes
for (const layer of layerPositions) {
for (const node of layer) {
const value = Math.abs(node.value);
const radius = 4;
// Node fill
ctx.fillStyle = `rgba(255, 255, 255, ${Math.min(Math.max(value, 0.2), 1)})`;
ctx.beginPath();
ctx.arc(node.x, node.y, radius, 0, Math.PI * 2);
ctx.fill();
// Node border
ctx.strokeStyle = 'rgba(255, 255, 255, 1.0)';
ctx.lineWidth = 1;
ctx.stroke();
}
}
}
// Modify the predict button event listener
document.getElementById('predictButton').addEventListener('click', () => {
const input = document.getElementById('predictionInput').value
.split(',').map(Number);
const prediction = nn.predict(input);
document.getElementById('predictionResult').innerHTML =
`Prediction: ${prediction[0].toFixed(6)}`;
drawNetwork(); // Draw the network visualization
});
// Add network canvas resize handling
function resizeCanvases() {
const lossCanvas = document.getElementById('lossGraph');
const networkCanvas = document.getElementById('networkGraph');
lossCanvas.width = lossCanvas.parentElement.clientWidth;
lossCanvas.height = lossCanvas.parentElement.clientHeight;
networkCanvas.width = networkCanvas.parentElement.clientWidth;
networkCanvas.height = networkCanvas.parentElement.clientHeight;
drawNetwork(); // Redraw network when canvas is resized
}
window.addEventListener('resize', resizeCanvases);
resizeCanvases();
// Save button functionality
document.getElementById('saveButton').addEventListener('click', () => {
nn.save('model');
});
// Load button functionality
document.getElementById('loadButton').addEventListener('click', () => {
nn.load(() => {
console.log('Model loaded successfully!');
// Optionally, you can add a message to the UI indicating that the model has been loaded
document.getElementById('stats').innerHTML += '<p><strong>Model loaded successfully!</strong></p>';
});
});
</script>
</body>
</html>