appvoid commited on
Commit
d5a9bbf
·
verified ·
1 Parent(s): 667ba68

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +345 -446
index.html CHANGED
@@ -2,85 +2,81 @@
2
  <html>
3
 
4
  <head>
5
- <title>Carbono UI</title>
6
  <style>
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  a {
8
- color: white;
9
  }
10
 
11
  body {
12
- background: #000;
13
- color: #fff;
14
- font-family: monospace;
15
  margin: 0;
16
- padding-top: 16px;
17
- padding: 5%;
18
  display: flex;
19
  flex-direction: column;
20
  gap: 15px;
21
- overflow-x: hidden;
22
  }
23
 
24
  h3 {
25
- margin: 1.5rem;
26
- margin-bottom: 0;
27
  }
28
 
29
  p {
30
- margin: 1.5rem;
31
- margin-top: 0rem;
32
- color: #777;
33
  }
34
 
35
  .grid {
36
  display: grid;
37
- grid-template-columns: minmax(400px, 1fr) minmax(300px, 2fr);
38
  gap: 15px;
39
- opacity: 0;
40
- transform: translateY(20px);
41
- animation: fadeInUp 0.5s ease-out forwards;
 
 
 
42
  }
43
 
44
  .widget {
45
- background: #000;
46
  border-radius: 10px;
47
- padding: 15px;
48
  box-sizing: border-box;
49
- width: 100%;
50
- opacity: 0;
51
- transform: translateY(20px);
52
- animation: fadeInUp 0.5s ease-out forwards;
53
- animation-delay: 0.2s;
54
  }
55
 
56
  .widget-title {
57
- font-size: 1.1em;
58
- margin-bottom: 12px;
59
- border-bottom: 1px solid #333;
60
- padding-bottom: 8px;
61
- opacity: 0;
62
- transform: translateY(10px);
63
- animation: fadeInUp 0.5s ease-out forwards;
64
- animation-delay: 0.3s;
65
  }
66
 
67
  .input-group {
68
- margin-bottom: 12px;
69
- opacity: 0;
70
- transform: translateY(10px);
71
- animation: fadeInUp 0.5s ease-out forwards;
72
- animation-delay: 0.4s;
73
  }
74
 
75
  .settings-grid {
76
  display: grid;
77
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
78
- gap: 10px;
79
- margin-bottom: 12px;
80
- opacity: 0;
81
- transform: translateY(10px);
82
- animation: fadeInUp 0.5s ease-out forwards;
83
- animation-delay: 0.5s;
84
  }
85
 
86
  input[type="text"],
@@ -89,22 +85,22 @@
89
  textarea {
90
  outline: none;
91
  width: 100%;
92
- padding: 6px;
93
- background: #222;
94
- border: 1px solid #444;
95
- color: #fff;
96
  border-radius: 8px;
97
- margin-top: 4px;
98
  box-sizing: border-box;
99
  transition: background 0.3s, border 0.3s;
100
  }
101
 
102
- span {
103
- background-color: white;
104
- color: black;
105
  font-weight: 600;
106
  font-size: 12px;
107
- padding: 1px;
108
  border-radius: 3px;
109
  cursor: pointer;
110
  }
@@ -113,87 +109,50 @@
113
  input[type="number"]:focus,
114
  select:focus,
115
  textarea:focus {
116
- background: #333;
117
- border: 1px solid #666;
118
  }
119
 
120
  button {
121
- background: #fff;
122
- color: #000;
123
- border: none;
124
- padding: 6px 12px;
125
  border-radius: 6px;
126
  cursor: pointer;
127
- transition: all 0.1s ease;
128
- border: 1px solid white;
129
- opacity: 0;
130
- height: 28px;
131
- transform: translateY(10px);
132
- animation: fadeInUp 0.5s ease-out forwards;
133
- animation-delay: 0.6s;
134
  }
135
 
136
- button:hover {
137
- border: 1px solid white;
138
- color: white;
139
- background: #000;
140
  }
141
 
142
  .progress-container {
143
  height: 180px;
144
  position: relative;
145
- border: 1px solid #333;
146
  border-radius: 8px;
147
  margin-bottom: 10px;
148
- opacity: 0;
149
- transform: translateY(10px);
150
- animation: fadeInUp 0.5s ease-out forwards;
151
- animation-delay: 0.7s;
152
  }
153
 
154
- .loss-graph {
155
  position: absolute;
156
  bottom: 0;
157
  width: 100%;
158
  height: 100%;
159
  }
160
 
161
- .network-graph {
162
- position: absolute;
163
- bottom: 0;
164
- width: 100%;
165
- height: 100%;
166
- }
167
-
168
- .flex-container {
169
- display: flex;
170
- gap: 20px;
171
- opacity: 0;
172
- transform: translateY(10px);
173
- animation: fadeInUp 0.5s ease-out forwards;
174
- animation-delay: 0.8s;
175
- }
176
-
177
- .prediction-section,
178
- .model-section {
179
- flex: 1;
180
- }
181
-
182
  .button-group {
183
  display: flex;
184
  gap: 10px;
185
- opacity: 0;
186
- transform: translateY(10px);
187
- animation: fadeInUp 0.5s ease-out forwards;
188
- animation-delay: 0.9s;
189
- }
190
-
191
- .visualization-container {
192
- margin-top: 15px;
193
- opacity: 0;
194
- transform: translateY(10px);
195
- animation: fadeInUp 0.5s ease-out forwards;
196
- animation-delay: 1s;
197
  }
198
 
199
  .epoch-progress {
@@ -201,123 +160,93 @@
201
  background: #222;
202
  border-radius: 8px;
203
  overflow: hidden;
 
204
  }
205
 
206
  .epoch-bar {
207
  height: 100%;
208
  width: 0;
209
- background: #fff;
210
  transition: width 0.3s ease;
211
  }
212
-
213
- @keyframes fadeInUp {
214
- to {
215
- opacity: 1;
216
- transform: translateY(0);
217
- }
218
- }
219
-
220
- /* Responsive Design */
221
- @media (max-width: 768px) {
222
- .grid {
223
- grid-template-columns: 1fr;
224
- }
225
-
226
- .flex-container {
227
- flex-direction: column;
228
- }
229
- }
230
  </style>
231
  </head>
232
 
233
  <body>
234
- <h3>playground</h3>
235
- <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>
 
236
  <div class="grid">
237
- <!-- Group 1: Data & Training -->
238
  <div class="widget">
239
- <div class="widget-title">model settings</div>
240
-
241
  <div class="input-group">
242
- <label>training set:</label>
243
- <textarea id="trainingData" rows="3" placeholder="1,1,1,0
244
- 1,0,1,0
245
- 0,1,0,1"></textarea>
246
  </div>
247
- <p>last number represents actual desired output</p>
248
  <div class="input-group">
249
- <label>validation set:</label>
250
  <textarea id="testData" rows="3" placeholder="0,0,0,1"></textarea>
251
  </div>
252
-
253
  <div class="settings-grid">
254
  <div class="input-group">
255
- <label>epochs:</label>
256
  <input type="number" id="epochs" value="50">
257
  </div>
258
  <div class="input-group">
259
- <label>learning rate:</label>
260
  <input type="number" id="learningRate" value="0.1" step="0.001">
261
  </div>
262
  <div class="input-group">
263
- <label>batch size:</label>
264
  <input type="number" id="batchSize" value="8">
265
  </div>
266
  <div class="input-group">
267
- <label>hidden layers:</label>
268
  <input type="number" id="numHiddenLayers" value="1">
269
  </div>
270
  </div>
271
-
272
- <!-- New UI Elements for Layer Configuration -->
273
-
274
  <div id="hiddenLayersConfig"></div>
275
  </div>
276
 
277
- <!-- Group 2: Progress & Visualization -->
278
  <div class="widget">
279
- <div class="widget-title">training progress</div>
280
- <div id="progress">
281
- <div class="progress-container">
282
- <canvas id="lossGraph" class="loss-graph"></canvas>
283
- </div>
284
- <p>training loss is white, validation loss is gray</p>
285
- <div class="epoch-progress">
286
- <div id="epochBar" class="epoch-bar"></div>
287
- </div>
288
- <div id="stats" style="margin-top: 10px;"></div>
289
  </div>
290
- <div class="model-section">
291
- <br>
292
- <div class="widget-title">model management</div>
293
- <p>save the weights to load them on your app or share them on huggingface!</p>
294
- <div class="button-group">
295
- <button id="trainButton">train</button>
296
- <button id="saveButton">save</button>
297
- <button id="loadButton">load</button>
298
- <div class="prediction-section">
299
- <div class="widget-title">prediction</div>
300
- <p>predict output</p>
301
- <div class="input-group">
302
- <label>input:</label>
303
- <input type="text" id="predictionInput" placeholder="0.4, 0.2, 0.6">
304
- </div>
305
- <button id="predictButton">predict</button>
306
- <div id="predictionResult" style="margin-top: 10px;"></div>
307
- </div>
308
- <div class="visualization-container">
309
- <div class="widget-title">visualization</div>
310
- <div class="progress-container">
311
- <canvas id="networkGraph" class="network-graph"></canvas>
312
- </div>
313
- <p>internal model's representation</p>
314
- </div>
315
- </div>
316
  </div>
 
317
  </div>
318
  </div>
319
 
320
  <script>
 
321
  class carbono {
322
  constructor(debug = true) {
323
  this.layers = [];
@@ -327,7 +256,6 @@
327
  this.details = {};
328
  this.debug = debug;
329
  }
330
-
331
  // Add a new layer to the neural network
332
  layer(inputSize, outputSize, activation = 'tanh') {
333
  this.layers.push({
@@ -354,7 +282,6 @@
354
  this.biases.push(biases);
355
  this.activations.push(activation);
356
  }
357
-
358
  // Apply the activation function
359
  activationFunction(x, activation) {
360
  switch (activation) {
@@ -372,7 +299,6 @@
372
  throw new Error('Whoops! We don\'t know that activation function.');
373
  }
374
  }
375
-
376
  // Calculate the derivative of the activation function
377
  activationDerivative(x, activation) {
378
  switch (activation) {
@@ -391,7 +317,6 @@
391
  throw new Error('Oops! We don\'t know the derivative of that activation function.');
392
  }
393
  }
394
-
395
  // Positional Encoding
396
  positionalEncoding(input, maxLen) {
397
  const pe = new Array(maxLen).fill(0).map((_, pos) => {
@@ -402,7 +327,6 @@
402
  });
403
  return input.map((seq, idx) => seq.map((val, i) => val + pe[idx][i]));
404
  }
405
-
406
  // Simplified Multi-Head Self-Attention
407
  multiHeadSelfAttention(input, numHeads = 2) {
408
  const headSize = input[0].length / numHeads;
@@ -439,14 +363,12 @@
439
  }
440
  return output;
441
  }
442
-
443
  // Layer Normalization
444
  layerNormalization(input) {
445
  const mean = input.reduce((sum, val) => sum + val, 0) / input.length;
446
  const variance = input.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / input.length;
447
  return input.map(val => (val - mean) / Math.sqrt(variance + 1e-5));
448
  }
449
-
450
  // Train the neural network
451
  async train(trainSet, options = {}) {
452
  const {
@@ -467,7 +389,6 @@
467
  }
468
  let lastTrainLoss = 0;
469
  let lastTestLoss = null;
470
-
471
  for (let epoch = 0; epoch < epochs; epoch++) {
472
  let trainError = 0;
473
  for (let b = 0; b < trainSet.length; b += batchSize) {
@@ -540,7 +461,6 @@
540
  }
541
  lastTestLoss = testError / testSet.length;
542
  }
543
-
544
  if ((epoch + 1) % printEveryEpochs === 0 && this.debug === true) {
545
  console.log(`Epoch ${epoch + 1}, Train Loss: ${lastTrainLoss.toFixed(6)}${testSet ? `, Test Loss: ${lastTestLoss.toFixed(6)}` : ''}`);
546
  }
@@ -579,7 +499,6 @@
579
  this.details = trainingSummary;
580
  return trainingSummary;
581
  }
582
-
583
  // Use the trained network to make predictions
584
  predict(input) {
585
  let layerInput = input;
@@ -608,7 +527,6 @@
608
  this.lastRawValues = allRawValues;
609
  return layerInput;
610
  }
611
-
612
  // Save the model to a file
613
  save(name = 'model') {
614
  const data = {
@@ -628,7 +546,6 @@
628
  a.click();
629
  URL.revokeObjectURL(url);
630
  }
631
-
632
  // Load a saved model from a file
633
  load(callback) {
634
  const handleListener = (event) => {
@@ -665,275 +582,257 @@
665
  input.click();
666
  }
667
  }
668
- document.getElementById("loadDataBtn").onclick = () => {
669
- document.getElementById('trainingData').value = `1.0, 0.0, 0.0, 0.0
670
- 0.7, 0.7, 0.8, 1
671
- 0.0, 1.0, 0.0, 0.5`
672
- document.getElementById('testData').value = `0.4, 0.2, 0.6, 1.0
673
- 0.2, 0.82, 0.83, 1.0`
674
- }
675
- // Interface code
676
- const nn = new carbono();
677
- let lossHistory = [];
678
- const ctx = document.getElementById('lossGraph').getContext('2d');
679
-
680
- function parseCSV(csv) {
681
- return csv.trim().split('\n').map(row => {
682
- const values = row.split(',').map(Number);
683
- return {
684
- input: values.slice(0, -1),
685
- output: [values[values.length - 1]]
686
- };
687
- });
688
- }
689
 
690
- function drawLossGraph() {
691
- ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
692
- const width = ctx.canvas.width;
693
- const height = ctx.canvas.height;
694
- // Combine train and test losses to find overall max for scaling
695
- const maxLoss = Math.max(
696
- ...lossHistory.map(loss => Math.max(loss.train, loss.test || 0))
697
- );
698
- // Draw training loss (white line)
699
- ctx.strokeStyle = '#fff';
700
- ctx.beginPath();
701
- lossHistory.forEach((loss, i) => {
702
- const x = (i / (lossHistory.length - 1)) * width;
703
- const y = height - (loss.train / maxLoss) * height;
704
- if (i === 0) ctx.moveTo(x, y);
705
- else ctx.lineTo(x, y);
706
- });
707
- ctx.stroke();
708
- // Draw test loss (gray line)
709
- ctx.strokeStyle = '#777';
710
- ctx.beginPath();
711
- lossHistory.forEach((loss, i) => {
712
- if (loss.test !== undefined) {
713
- const x = (i / (lossHistory.length - 1)) * width;
714
- const y = height - (loss.test / maxLoss) * height;
715
- if (i === 0 || lossHistory[i - 1].test === undefined) ctx.moveTo(x, y);
716
- else ctx.lineTo(x, y);
717
- }
718
- });
719
- ctx.stroke();
720
- }
721
-
722
- function createLayerConfigUI(numLayers) {
723
- const container = document.getElementById('hiddenLayersConfig');
724
- container.innerHTML = ''; // Clear previous UI
725
- for (let i = 0; i < numLayers; i++) {
726
- const group = document.createElement('div');
727
- group.className = 'input-group';
728
- const label = document.createElement('label');
729
- label.textContent = `layer ${i + 1} nodes:`;
730
- const input = document.createElement('input');
731
- input.type = 'number';
732
- input.value = 5;
733
- input.dataset.layerIndex = i;
734
- const activationLabel = document.createElement('label');
735
- activationLabel.innerHTML = `<br>activation:`;
736
- const activationSelect = document.createElement('select');
737
- const activations = ['tanh', 'sigmoid', 'relu', 'selu'];
738
- activations.forEach(act => {
739
- const option = document.createElement('option');
740
- option.value = act;
741
- option.textContent = act;
742
- activationSelect.appendChild(option);
743
- });
744
- activationSelect.dataset.layerIndex = i;
745
- group.appendChild(label);
746
- group.appendChild(input);
747
- group.appendChild(activationLabel);
748
- group.appendChild(activationSelect);
749
- container.appendChild(group);
750
- }
751
- }
752
- document.getElementById('numHiddenLayers').addEventListener('change', (event) => {
753
- const numLayers = parseInt(event.target.value);
754
- createLayerConfigUI(numLayers);
755
- });
756
- createLayerConfigUI(document.getElementById('numHiddenLayers').value);
757
- document.getElementById('trainButton').addEventListener('click', async () => {
758
- lossHistory = []; // Initialize as empty array
759
- const trainingData = parseCSV(document.getElementById('trainingData').value);
760
- const testData = parseCSV(document.getElementById('testData').value);
761
- lossHistory = [];
762
- document.getElementById('stats').innerHTML = '';
763
- const numHiddenLayers = parseInt(document.getElementById('numHiddenLayers').value);
764
- const layerConfigs = [];
765
- for (let i = 0; i < numHiddenLayers; i++) {
766
- const sizeInput = document.querySelector(`input[data-layer-index="${i}"]`);
767
- const activationSelect = document.querySelector(`select[data-layer-index="${i}"]`);
768
- layerConfigs.push({
769
- size: parseInt(sizeInput.value),
770
- activation: activationSelect.value
771
  });
772
  }
773
- nn.layers = []; // Reset layers
774
- nn.weights = [];
775
- nn.biases = [];
776
- nn.activations = [];
777
- const numInputs = trainingData[0].input.length;
778
- nn.layer(numInputs, layerConfigs[0].size, layerConfigs[0].activation);
779
- for (let i = 1; i < layerConfigs.length; i++) {
780
- nn.layer(layerConfigs[i - 1].size, layerConfigs[i].size, layerConfigs[i].activation);
781
- }
782
- nn.layer(layerConfigs[layerConfigs.length - 1].size, 1, 'tanh'); // Output layer
783
- const options = {
784
- epochs: parseInt(document.getElementById('epochs').value),
785
- learningRate: parseFloat(document.getElementById('learningRate').value),
786
- batchSize: parseInt(document.getElementById('batchSize').value),
787
- printEveryEpochs: 1,
788
- testSet: testData.length > 0 ? testData : null,
789
- callback: async (epoch, trainLoss, testLoss) => {
790
- lossHistory.push({
791
- train: trainLoss,
792
- test: testLoss
793
  });
794
- drawLossGraph();
795
- document.getElementById('epochBar').style.width =
796
- `${(epoch / options.epochs) * 100}%`;
797
- document.getElementById('stats').innerHTML =
798
- `<p> - current epoch: ${epoch}/${options.epochs}` +
799
- `<br> - train/val loss: ${trainLoss.toFixed(6)}` +
800
- (testLoss ? ` | ${testLoss.toFixed(6)}</p>` : '');
801
  }
802
  }
803
- try {
804
- const trainButton = document.getElementById('trainButton');
805
- trainButton.disabled = true;
806
- trainButton.textContent = 'training...';
807
- // nn.play()
808
- const summary = await nn.train(trainingData, options);
809
- trainButton.disabled = false;
810
- trainButton.textContent = 'train';
811
- // Display final summary
812
- document.getElementById('stats').innerHTML += '<strong>Model trained</strong>';
813
- } catch (error) {
814
- console.error('Training error:', error);
815
- document.getElementById('trainButton').disabled = false;
816
- document.getElementById('trainButton').textContent = 'train';
817
- }
818
- });
819
 
820
- function drawNetwork() {
821
- const canvas = document.getElementById('networkGraph');
822
- const ctx = canvas.getContext('2d');
823
- ctx.clearRect(0, 0, canvas.width, canvas.height);
824
- if (!nn.lastActivations) return; // Don't draw if no predictions made yet
825
- const padding = 40;
826
- const width = canvas.width - padding * 2;
827
- const height = canvas.height - padding * 2;
828
- // Calculate node positions
829
- const layerPositions = [];
830
- // Add input layer explicitly
831
- const inputLayer = [];
832
- const inputX = padding;
833
- const inputSize = nn.layers[0].inputSize;
834
- for (let i = 0; i < inputSize; i++) {
835
- const inputY = padding + (height * i) / (inputSize - 1);
836
- inputLayer.push({
837
- x: inputX,
838
- y: inputY,
839
- value: nn.lastActivations[0][i]
840
- });
 
841
  }
842
- layerPositions.push(inputLayer);
843
- // Add hidden layers
844
- for (let i = 1; i < nn.lastActivations.length - 1; i++) {
845
- const layer = nn.lastActivations[i];
846
- const layerNodes = [];
847
- const layerX = padding + (width * i) / (nn.lastActivations.length - 1);
848
- for (let j = 0; j < layer.length; j++) {
849
- const nodeY = padding + (height * j) / (layer.length - 1);
850
- layerNodes.push({
851
- x: layerX,
852
- y: nodeY,
853
- value: layer[j]
 
 
 
 
854
  });
855
  }
856
- layerPositions.push(layerNodes);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
857
  }
858
- // Add output layer explicitly
859
- const outputLayer = [];
860
- const outputX = canvas.width - padding;
861
- const outputY = padding + height / 2; // Center the output node
862
- outputLayer.push({
863
- x: outputX,
864
- y: outputY,
865
- value: nn.lastActivations[nn.lastActivations.length - 1][0]
866
- });
867
- layerPositions.push(outputLayer);
868
- // Draw connections
869
- ctx.lineWidth = 1;
870
- for (let i = 0; i < layerPositions.length - 1; i++) {
871
- const currentLayer = layerPositions[i];
872
- const nextLayer = layerPositions[i + 1];
873
- const weights = nn.weights[i];
874
- for (let j = 0; j < currentLayer.length; j++) {
875
- const nextLayerSize = nextLayer.length;
876
- for (let k = 0; k < nextLayerSize; k++) {
877
- const weight = weights[k][j];
878
- const signal = Math.abs(currentLayer[j].value * weight);
879
- const opacity = Math.min(Math.max(signal, 0.01), 1);
880
- ctx.strokeStyle = `rgba(255, 255, 255, ${opacity})`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
881
  ctx.beginPath();
882
- ctx.moveTo(currentLayer[j].x, currentLayer[j].y);
883
- ctx.lineTo(nextLayer[k].x, nextLayer[k].y);
 
 
884
  ctx.stroke();
885
  }
886
  }
887
  }
888
- // Draw nodes
889
- for (const layer of layerPositions) {
890
- for (const node of layer) {
891
- const value = Math.abs(node.value);
892
- const radius = 4;
893
- // Node fill
894
- ctx.fillStyle = `rgba(255, 255, 255, ${Math.min(Math.max(value, 0.2), 1)})`;
895
- ctx.beginPath();
896
- ctx.arc(node.x, node.y, radius, 0, Math.PI * 2);
897
- ctx.fill();
898
- // Node border
899
- ctx.strokeStyle = 'rgba(255, 255, 255, 1.0)';
900
- ctx.lineWidth = 1;
901
- ctx.stroke();
902
- }
903
  }
904
- }
905
- // Modify the predict button event listener
906
- document.getElementById('predictButton').addEventListener('click', () => {
907
- const input = document.getElementById('predictionInput').value
908
- .split(',').map(Number);
909
- const prediction = nn.predict(input);
910
- document.getElementById('predictionResult').innerHTML =
911
- `Prediction: ${prediction[0].toFixed(6)}`;
912
- drawNetwork(); // Draw the network visualization
913
- });
914
- // Add network canvas resize handling
915
- function resizeCanvases() {
916
- const lossCanvas = document.getElementById('lossGraph');
917
- const networkCanvas = document.getElementById('networkGraph');
918
- lossCanvas.width = lossCanvas.parentElement.clientWidth;
919
- lossCanvas.height = lossCanvas.parentElement.clientHeight;
920
- networkCanvas.width = networkCanvas.parentElement.clientWidth;
921
- networkCanvas.height = networkCanvas.parentElement.clientHeight;
922
- drawNetwork(); // Redraw network when canvas is resized
923
- }
924
- window.addEventListener('resize', resizeCanvases);
925
- resizeCanvases();
926
- // Save button functionality
927
- document.getElementById('saveButton').addEventListener('click', () => {
928
- nn.save('model');
929
- });
930
- // Load button functionality
931
- document.getElementById('loadButton').addEventListener('click', () => {
932
- nn.load(() => {
933
- console.log('Model loaded successfully!');
934
- // Optionally, you can add a message to the UI indicating that the model has been loaded
935
- document.getElementById('stats').innerHTML += '<p><strong>Model loaded successfully!</strong></p>';
936
  });
 
 
 
 
 
 
 
 
 
 
 
937
  });
938
  </script>
939
  </body>
 
2
  <html>
3
 
4
  <head>
5
+ <title>Carbono UI - Enhanced</title>
6
  <style>
7
+ :root {
8
+ --primary-color: #fff;
9
+ --secondary-color: #000;
10
+ --tertiary-color: #777;
11
+ --background-color: #000;
12
+ --widget-background: #111;
13
+ --border-color: #333;
14
+ --input-background: #222;
15
+ --input-focus-background: #333;
16
+ --button-hover-background: #000;
17
+ --font-family: monospace;
18
+ }
19
+
20
  a {
21
+ color: var(--primary-color);
22
  }
23
 
24
  body {
25
+ background: var(--background-color);
26
+ color: var(--primary-color);
27
+ font-family: var(--font-family);
28
  margin: 0;
29
+ padding: 2%;
 
30
  display: flex;
31
  flex-direction: column;
32
  gap: 15px;
 
33
  }
34
 
35
  h3 {
36
+ margin: 1rem 0;
 
37
  }
38
 
39
  p {
40
+ margin: 0 0 1rem 0;
41
+ color: var(--tertiary-color);
42
+ line-height: 1.5;
43
  }
44
 
45
  .grid {
46
  display: grid;
47
+ grid-template-columns: 1fr;
48
  gap: 15px;
49
+ }
50
+
51
+ @media (min-width: 768px) {
52
+ .grid {
53
+ grid-template-columns: repeat(2, 1fr);
54
+ }
55
  }
56
 
57
  .widget {
58
+ background: var(--widget-background);
59
  border-radius: 10px;
60
+ padding: 20px;
61
  box-sizing: border-box;
 
 
 
 
 
62
  }
63
 
64
  .widget-title {
65
+ font-size: 1.2em;
66
+ margin-bottom: 15px;
67
+ border-bottom: 1px solid var(--border-color);
68
+ padding-bottom: 10px;
 
 
 
 
69
  }
70
 
71
  .input-group {
72
+ margin-bottom: 15px;
 
 
 
 
73
  }
74
 
75
  .settings-grid {
76
  display: grid;
77
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
78
+ gap: 15px;
79
+ margin-bottom: 15px;
 
 
 
 
80
  }
81
 
82
  input[type="text"],
 
85
  textarea {
86
  outline: none;
87
  width: 100%;
88
+ padding: 8px;
89
+ background: var(--input-background);
90
+ border: 1px solid var(--border-color);
91
+ color: var(--primary-color);
92
  border-radius: 8px;
93
+ margin-top: 5px;
94
  box-sizing: border-box;
95
  transition: background 0.3s, border 0.3s;
96
  }
97
 
98
+ span#loadDataBtn {
99
+ background-color: var(--primary-color);
100
+ color: var(--secondary-color);
101
  font-weight: 600;
102
  font-size: 12px;
103
+ padding: 2px 4px;
104
  border-radius: 3px;
105
  cursor: pointer;
106
  }
 
109
  input[type="number"]:focus,
110
  select:focus,
111
  textarea:focus {
112
+ background: var(--input-focus-background);
113
+ border-color: var(--primary-color);
114
  }
115
 
116
  button {
117
+ background: var(--primary-color);
118
+ color: var(--secondary-color);
119
+ border: 1px solid var(--primary-color);
120
+ padding: 8px 15px;
121
  border-radius: 6px;
122
  cursor: pointer;
123
+ transition: all 0.2s ease;
124
+ }
125
+
126
+ button:hover,
127
+ button:disabled {
128
+ background: var(--button-hover-background);
129
+ color: var(--primary-color);
130
  }
131
 
132
+ button:disabled {
133
+ cursor: not-allowed;
134
+ opacity: 0.7;
 
135
  }
136
 
137
  .progress-container {
138
  height: 180px;
139
  position: relative;
140
+ border: 1px solid var(--border-color);
141
  border-radius: 8px;
142
  margin-bottom: 10px;
 
 
 
 
143
  }
144
 
145
+ .graph {
146
  position: absolute;
147
  bottom: 0;
148
  width: 100%;
149
  height: 100%;
150
  }
151
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  .button-group {
153
  display: flex;
154
  gap: 10px;
155
+ flex-wrap: wrap;
 
 
 
 
 
 
 
 
 
 
 
156
  }
157
 
158
  .epoch-progress {
 
160
  background: #222;
161
  border-radius: 8px;
162
  overflow: hidden;
163
+ margin-top: 10px;
164
  }
165
 
166
  .epoch-bar {
167
  height: 100%;
168
  width: 0;
169
+ background: var(--primary-color);
170
  transition: width 0.3s ease;
171
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  </style>
173
  </head>
174
 
175
  <body>
176
+ <h3>Playground</h3>
177
+ <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. 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 the "Train" button.</p>
178
+
179
  <div class="grid">
 
180
  <div class="widget">
181
+ <div class="widget-title">Model Settings</div>
 
182
  <div class="input-group">
183
+ <label>Training Set:</label>
184
+ <textarea id="trainingData" rows="4" placeholder="1,1,1,0\n1,0,1,0\n0,1,0,1"></textarea>
185
+ <p>Last number represents the desired output.</p>
 
186
  </div>
 
187
  <div class="input-group">
188
+ <label>Validation Set:</label>
189
  <textarea id="testData" rows="3" placeholder="0,0,0,1"></textarea>
190
  </div>
 
191
  <div class="settings-grid">
192
  <div class="input-group">
193
+ <label>Epochs:</label>
194
  <input type="number" id="epochs" value="50">
195
  </div>
196
  <div class="input-group">
197
+ <label>Learning Rate:</label>
198
  <input type="number" id="learningRate" value="0.1" step="0.001">
199
  </div>
200
  <div class="input-group">
201
+ <label>Batch Size:</label>
202
  <input type="number" id="batchSize" value="8">
203
  </div>
204
  <div class="input-group">
205
+ <label>Hidden Layers:</label>
206
  <input type="number" id="numHiddenLayers" value="1">
207
  </div>
208
  </div>
 
 
 
209
  <div id="hiddenLayersConfig"></div>
210
  </div>
211
 
 
212
  <div class="widget">
213
+ <div class="widget-title">Training Progress</div>
214
+ <div class="progress-container">
215
+ <canvas id="lossGraph" class="graph"></canvas>
 
 
 
 
 
 
 
216
  </div>
217
+ <p>Training loss is white, validation loss is gray.</p>
218
+ <div class="epoch-progress">
219
+ <div id="epochBar" class="epoch-bar"></div>
220
+ </div>
221
+ <div id="stats" style="margin-top: 10px;"></div>
222
+
223
+ <div class="widget-title" style="margin-top: 20px;">Model Management</div>
224
+ <p>Save the weights to load them in your app or share them on Hugging Face!</p>
225
+ <div class="button-group">
226
+ <button id="trainButton">Train</button>
227
+ <button id="saveButton">Save</button>
228
+ <button id="loadButton">Load</button>
229
+ </div>
230
+
231
+ <div class="widget-title" style="margin-top: 20px;">Prediction</div>
232
+ <p>Predict output.</p>
233
+ <div class="input-group">
234
+ <label>Input:</label>
235
+ <input type="text" id="predictionInput" placeholder="0.4, 0.2, 0.6">
236
+ </div>
237
+ <button id="predictButton">Predict</button>
238
+ <div id="predictionResult" style="margin-top: 10px;"></div>
239
+
240
+ <div class="widget-title" style="margin-top: 20px;">Visualization</div>
241
+ <div class="progress-container">
242
+ <canvas id="networkGraph" class="graph"></canvas>
243
  </div>
244
+ <p>Internal model's representation.</p>
245
  </div>
246
  </div>
247
 
248
  <script>
249
+ // Carbono library code remains the same...
250
  class carbono {
251
  constructor(debug = true) {
252
  this.layers = [];
 
256
  this.details = {};
257
  this.debug = debug;
258
  }
 
259
  // Add a new layer to the neural network
260
  layer(inputSize, outputSize, activation = 'tanh') {
261
  this.layers.push({
 
282
  this.biases.push(biases);
283
  this.activations.push(activation);
284
  }
 
285
  // Apply the activation function
286
  activationFunction(x, activation) {
287
  switch (activation) {
 
299
  throw new Error('Whoops! We don\'t know that activation function.');
300
  }
301
  }
 
302
  // Calculate the derivative of the activation function
303
  activationDerivative(x, activation) {
304
  switch (activation) {
 
317
  throw new Error('Oops! We don\'t know the derivative of that activation function.');
318
  }
319
  }
 
320
  // Positional Encoding
321
  positionalEncoding(input, maxLen) {
322
  const pe = new Array(maxLen).fill(0).map((_, pos) => {
 
327
  });
328
  return input.map((seq, idx) => seq.map((val, i) => val + pe[idx][i]));
329
  }
 
330
  // Simplified Multi-Head Self-Attention
331
  multiHeadSelfAttention(input, numHeads = 2) {
332
  const headSize = input[0].length / numHeads;
 
363
  }
364
  return output;
365
  }
 
366
  // Layer Normalization
367
  layerNormalization(input) {
368
  const mean = input.reduce((sum, val) => sum + val, 0) / input.length;
369
  const variance = input.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / input.length;
370
  return input.map(val => (val - mean) / Math.sqrt(variance + 1e-5));
371
  }
 
372
  // Train the neural network
373
  async train(trainSet, options = {}) {
374
  const {
 
389
  }
390
  let lastTrainLoss = 0;
391
  let lastTestLoss = null;
 
392
  for (let epoch = 0; epoch < epochs; epoch++) {
393
  let trainError = 0;
394
  for (let b = 0; b < trainSet.length; b += batchSize) {
 
461
  }
462
  lastTestLoss = testError / testSet.length;
463
  }
 
464
  if ((epoch + 1) % printEveryEpochs === 0 && this.debug === true) {
465
  console.log(`Epoch ${epoch + 1}, Train Loss: ${lastTrainLoss.toFixed(6)}${testSet ? `, Test Loss: ${lastTestLoss.toFixed(6)}` : ''}`);
466
  }
 
499
  this.details = trainingSummary;
500
  return trainingSummary;
501
  }
 
502
  // Use the trained network to make predictions
503
  predict(input) {
504
  let layerInput = input;
 
527
  this.lastRawValues = allRawValues;
528
  return layerInput;
529
  }
 
530
  // Save the model to a file
531
  save(name = 'model') {
532
  const data = {
 
546
  a.click();
547
  URL.revokeObjectURL(url);
548
  }
 
549
  // Load a saved model from a file
550
  load(callback) {
551
  const handleListener = (event) => {
 
582
  input.click();
583
  }
584
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
585
 
586
+ document.addEventListener('DOMContentLoaded', () => {
587
+ const nn = new carbono();
588
+ let lossHistory = [];
589
+
590
+ const lossCanvas = document.getElementById('lossGraph');
591
+ const networkCanvas = document.getElementById('networkGraph');
592
+ const lossCtx = lossCanvas.getContext('2d');
593
+
594
+ const elements = {
595
+ loadDataBtn: document.getElementById('loadDataBtn'),
596
+ trainingData: document.getElementById('trainingData'),
597
+ testData: document.getElementById('testData'),
598
+ numHiddenLayers: document.getElementById('numHiddenLayers'),
599
+ hiddenLayersConfig: document.getElementById('hiddenLayersConfig'),
600
+ trainButton: document.getElementById('trainButton'),
601
+ stats: document.getElementById('stats'),
602
+ epochBar: document.getElementById('epochBar'),
603
+ epochs: document.getElementById('epochs'),
604
+ learningRate: document.getElementById('learningRate'),
605
+ batchSize: document.getElementById('batchSize'),
606
+ predictButton: document.getElementById('predictButton'),
607
+ predictionInput: document.getElementById('predictionInput'),
608
+ predictionResult: document.getElementById('predictionResult'),
609
+ saveButton: document.getElementById('saveButton'),
610
+ loadButton: document.getElementById('loadButton')
611
+ };
612
+
613
+ const parseCSV = (csv) => {
614
+ return csv.trim().split('\n').map(row => {
615
+ const values = row.split(',').map(Number);
616
+ return {
617
+ input: values.slice(0, -1),
618
+ output: [values[values.length - 1]]
619
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
620
  });
621
  }
622
+
623
+ const drawLossGraph = () => {
624
+ const {
625
+ width,
626
+ height
627
+ } = lossCanvas;
628
+ lossCtx.clearRect(0, 0, width, height);
629
+
630
+ const maxLoss = Math.max(...lossHistory.map(loss => Math.max(loss.train, loss.test || 0)));
631
+
632
+ const drawLine = (data, color) => {
633
+ lossCtx.strokeStyle = color;
634
+ lossCtx.beginPath();
635
+ data.forEach((val, i) => {
636
+ const x = (i / (data.length - 1)) * width;
637
+ const y = height - (val / maxLoss) * height;
638
+ if (i === 0) lossCtx.moveTo(x, y);
639
+ else lossCtx.lineTo(x, y);
 
 
640
  });
641
+ lossCtx.stroke();
642
+ };
643
+
644
+ drawLine(lossHistory.map(l => l.train), 'white');
645
+ if (lossHistory.some(l => l.test !== undefined)) {
646
+ drawLine(lossHistory.map(l => l.test), '#777');
 
647
  }
648
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
649
 
650
+ const createLayerConfigUI = (numLayers) => {
651
+ elements.hiddenLayersConfig.innerHTML = '';
652
+ for (let i = 0; i < numLayers; i++) {
653
+ const group = document.createElement('div');
654
+ group.className = 'input-group settings-grid';
655
+ group.innerHTML = `
656
+ <div>
657
+ <label>Layer ${i + 1} Nodes:</label>
658
+ <input type="number" value="5" data-layer-index="${i}">
659
+ </div>
660
+ <div>
661
+ <label>Activation:</label>
662
+ <select data-layer-index="${i}">
663
+ <option>tanh</option>
664
+ <option>sigmoid</option>
665
+ <option>relu</option>
666
+ <option>selu</option>
667
+ </select>
668
+ </div>
669
+ `;
670
+ elements.hiddenLayersConfig.appendChild(group);
671
+ }
672
  }
673
+
674
+ const trainModel = async () => {
675
+ lossHistory = [];
676
+ const trainingData = parseCSV(elements.trainingData.value);
677
+ const testData = parseCSV(elements.testData.value);
678
+
679
+ elements.stats.innerHTML = '';
680
+ const numHiddenLayers = parseInt(elements.numHiddenLayers.value);
681
+ const layerConfigs = [];
682
+
683
+ for (let i = 0; i < numHiddenLayers; i++) {
684
+ const sizeInput = document.querySelector(`input[data-layer-index="${i}"]`);
685
+ const activationSelect = document.querySelector(`select[data-layer-index="${i}"]`);
686
+ layerConfigs.push({
687
+ size: parseInt(sizeInput.value),
688
+ activation: activationSelect.value
689
  });
690
  }
691
+
692
+ nn.layers = [];
693
+ nn.weights = [];
694
+ nn.biases = [];
695
+ nn.activations = [];
696
+
697
+ const numInputs = trainingData[0].input.length;
698
+ nn.layer(numInputs, layerConfigs[0].size, layerConfigs[0].activation);
699
+ for (let i = 1; i < layerConfigs.length; i++) {
700
+ nn.layer(layerConfigs[i - 1].size, layerConfigs[i].size, layerConfigs[i].activation);
701
+ }
702
+ nn.layer(layerConfigs[layerConfigs.length - 1].size, 1, 'tanh');
703
+
704
+ const options = {
705
+ epochs: parseInt(elements.epochs.value),
706
+ learningRate: parseFloat(elements.learningRate.value),
707
+ batchSize: parseInt(elements.batchSize.value),
708
+ printEveryEpochs: 1,
709
+ testSet: testData.length > 0 ? testData : null,
710
+ callback: async (epoch, trainLoss, testLoss) => {
711
+ lossHistory.push({
712
+ train: trainLoss,
713
+ test: testLoss
714
+ });
715
+ drawLossGraph();
716
+ elements.epochBar.style.width = `${(epoch / options.epochs) * 100}%`;
717
+ elements.stats.innerHTML = `<p>Epoch: ${epoch}/${options.epochs}<br>Train/Val Loss: ${trainLoss.toFixed(6)}${testLoss ? ` | ${testLoss.toFixed(6)}` : ''}</p>`;
718
+ }
719
+ }
720
+
721
+ try {
722
+ elements.trainButton.disabled = true;
723
+ elements.trainButton.textContent = 'Training...';
724
+ await nn.train(trainingData, options);
725
+ elements.stats.innerHTML += '<strong>Model trained</strong>';
726
+ } catch (error) {
727
+ console.error('Training error:', error);
728
+ } finally {
729
+ elements.trainButton.disabled = false;
730
+ elements.trainButton.textContent = 'Train';
731
+ }
732
  }
733
+
734
+ function drawNetwork() {
735
+ const ctx = networkCanvas.getContext('2d');
736
+ ctx.clearRect(0, 0, networkCanvas.width, networkCanvas.height);
737
+ if (!nn.lastActivations) return;
738
+
739
+ const padding = 40;
740
+ const width = networkCanvas.width - padding * 2;
741
+ const height = networkCanvas.height - padding * 2;
742
+
743
+ const layerPositions = [];
744
+ const inputLayer = [];
745
+ const inputX = padding;
746
+ const inputSize = nn.layers[0].inputSize;
747
+ for (let i = 0; i < inputSize; i++) {
748
+ const inputY = padding + (inputSize > 1 ? (height * i) / (inputSize - 1) : height / 2);
749
+ inputLayer.push({ x: inputX, y: inputY, value: nn.lastActivations[0][i] });
750
+ }
751
+ layerPositions.push(inputLayer);
752
+
753
+ for (let i = 1; i < nn.lastActivations.length - 1; i++) {
754
+ const layer = nn.lastActivations[i];
755
+ const layerNodes = [];
756
+ const layerX = padding + (width * i) / (nn.lastActivations.length - 1);
757
+ for (let j = 0; j < layer.length; j++) {
758
+ const nodeY = padding + (layer.length > 1 ? (height * j) / (layer.length - 1) : height / 2);
759
+ layerNodes.push({ x: layerX, y: nodeY, value: layer[j] });
760
+ }
761
+ layerPositions.push(layerNodes);
762
+ }
763
+
764
+ const outputLayer = [];
765
+ const outputX = networkCanvas.width - padding;
766
+ const outputY = padding + height / 2;
767
+ outputLayer.push({ x: outputX, y: outputY, value: nn.lastActivations[nn.lastActivations.length - 1][0] });
768
+ layerPositions.push(outputLayer);
769
+
770
+ ctx.lineWidth = 1;
771
+ for (let i = 0; i < layerPositions.length - 1; i++) {
772
+ const currentLayer = layerPositions[i];
773
+ const nextLayer = layerPositions[i + 1];
774
+ const weights = nn.weights[i];
775
+ for (let j = 0; j < currentLayer.length; j++) {
776
+ for (let k = 0; k < nextLayer.length; k++) {
777
+ const weight = weights[k][j];
778
+ const signal = Math.abs(currentLayer[j].value * weight);
779
+ const opacity = Math.min(Math.max(signal, 0.01), 1);
780
+ ctx.strokeStyle = `rgba(255, 255, 255, ${opacity})`;
781
+ ctx.beginPath();
782
+ ctx.moveTo(currentLayer[j].x, currentLayer[j].y);
783
+ ctx.lineTo(nextLayer[k].x, nextLayer[k].y);
784
+ ctx.stroke();
785
+ }
786
+ }
787
+ }
788
+
789
+ for (const layer of layerPositions) {
790
+ for (const node of layer) {
791
+ const value = Math.abs(node.value);
792
+ const radius = 4;
793
+ ctx.fillStyle = `rgba(255, 255, 255, ${Math.min(Math.max(value, 0.2), 1)})`;
794
  ctx.beginPath();
795
+ ctx.arc(node.x, node.y, radius, 0, Math.PI * 2);
796
+ ctx.fill();
797
+ ctx.strokeStyle = 'rgba(255, 255, 255, 1.0)';
798
+ ctx.lineWidth = 1;
799
  ctx.stroke();
800
  }
801
  }
802
  }
803
+
804
+ function resizeCanvases() {
805
+ [lossCanvas, networkCanvas].forEach(canvas => {
806
+ canvas.width = canvas.parentElement.clientWidth;
807
+ canvas.height = canvas.parentElement.clientHeight;
808
+ });
809
+ drawNetwork();
 
 
 
 
 
 
 
 
810
  }
811
+
812
+ elements.loadDataBtn.onclick = () => {
813
+ elements.trainingData.value = `1.0, 0.0, 0.0, 0.0\n0.7, 0.7, 0.8, 1\n0.0, 1.0, 0.0, 0.5`;
814
+ elements.testData.value = `0.4, 0.2, 0.6, 1.0\n0.2, 0.82, 0.83, 1.0`;
815
+ };
816
+
817
+ elements.numHiddenLayers.addEventListener('change', (e) => createLayerConfigUI(parseInt(e.target.value)));
818
+ elements.trainButton.addEventListener('click', trainModel);
819
+ elements.predictButton.addEventListener('click', () => {
820
+ const input = elements.predictionInput.value.split(',').map(Number);
821
+ const prediction = nn.predict(input);
822
+ elements.predictionResult.innerHTML = `Prediction: ${prediction[0].toFixed(6)}`;
823
+ drawNetwork();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
824
  });
825
+ elements.saveButton.addEventListener('click', () => nn.save('model'));
826
+ elements.loadButton.addEventListener('click', () => {
827
+ nn.load(() => {
828
+ elements.stats.innerHTML += '<p><strong>Model loaded successfully!</strong></p>';
829
+ });
830
+ });
831
+
832
+ window.addEventListener('resize', resizeCanvases);
833
+
834
+ createLayerConfigUI(parseInt(elements.numHiddenLayers.value));
835
+ resizeCanvases();
836
  });
837
  </script>
838
  </body>