classMatrix{
constructor(rows, cols){this.rows = rows;this.cols = cols;this.data =newArray(this.rows).fill().map(()=>newArray(this.cols).fill(0));}
copy(){
let matrix =newMatrix(this.rows,this.cols);
matrix.data =this.data;return matrix;}static fromArray(arr){returnnewMatrix(arr.length,1).map((e, i)=> arr[i]);}static subtract(a, b){if(a.rows !== b.rows || a.cols !== b.cols){Doodle.showMsg("Columns and Rows of A must match Columns and Rows of B.");return;}// Return a new Matrix a-breturnnewMatrix(a.rows, a.cols).map((_, i, j)=> a.data[i][j]- b.data[i][j]);}
toArray(){
let arr =[];for(let i =0; i <this.rows; i++){for(let j =0; j <this.cols; j++){
arr.push(this.data[i][j]);}}return arr;}
randomize(){returnthis.map((e)=>Math.random()*2-1);}
add(n){if(this.rows !== n.rows ||this.cols !== n.cols){Doodle.showMsg("Columns and Rows of A must match Columns and Rows of B.");return;}returnthis.map((e, i, j)=> e + n.data[i][j]);}static add(n){if(n instanceof Matrix){if(this.rows !== n.rows ||this.cols !== n.cols){Doodle.showMsg("Columns and Rows of A must match Columns and Rows of B.");return;}returnthis.map((e, i, j)=> e + n.data[i][j]);}else{returnthis.map((e)=> e + n);}}static transpose(matrix){returnnewMatrix(matrix.cols, matrix.rows).map((_, i, j)=> matrix.data[j][i]);}static multiply(a, b){// Matrix productif(a.cols !== b.rows){Doodle.showMsg("Columns of A must match rows of B.");return;}returnnewMatrix(a.rows, b.cols).map((e, i, j)=>{// Dot product of values in col
let sum =0;for(let k =0; k < a.cols; k++){
sum += a.data[i][k]* b.data[k][j];}return sum;});}
multiply(n){if(n instanceof Matrix){if(this.rows !== n.rows ||this.cols !== n.cols){Doodle.showMsg("Columns and Rows of A must match Columns and Rows of B.");return;}// hadamard productreturnthis.map((e, i, j)=> e * n.data[i][j]);}else{// Scalar productreturnthis.map((e)=> e * n);}}
map(fn){// Apply a function to every element of matrixfor(let idx =0; idx <this.rows; idx++){for(let jdx =0; jdx <this.cols; jdx++){
let val =this.data[idx][jdx];this.data[idx][jdx]= fn(val, idx, jdx);}}returnthis;}static map(matrix, fn){// Apply a function to every element of matrixreturnnewMatrix(matrix.rows, matrix.cols).map((e, i, j)=>
fn(matrix.data[i][j], i, j));}
print(){
console.table(this.data);returnthis;}
serialize(){return JSON.stringify(this);}static deserialize(data){if(typeof data =="string"){
data = JSON.parse(data);}
let matrix =newMatrix(data.rows, data.cols);
matrix.data = data.data;return matrix;}}classNeuralLayer{// input_nodes, nodes
constructor(parent, inputNeurons, neurons, type){this.parent = parent;this.type = type;this.neurons = neurons;this.inputNeurons = inputNeurons;this.addWeight();this.addBias();}
addWeight(){this.weights =newMatrix(this.neurons,this.inputNeurons);this.weights.randomize();}
addBias(){this.bias =newMatrix(this.neurons,1);this.bias.randomize();}
predict(input_matrix){
input_matrix =Matrix.multiply(this.weights, input_matrix);
input_matrix.add(this.bias);
input_matrix.map(this.parent.activationFunction.fn);return input_matrix;}
applyError(prediction, previousPrediction, current_errors){// Calculate the gradients for the layer
let gradients =Matrix.map(
prediction,this.parent.activationFunction.errorFn
);
gradients.multiply(current_errors);
gradients.multiply(this.parent.learningRate);// Calculate deltas
let previousPredictionTranspose =Matrix.transpose(previousPrediction);
let weight_deltas =Matrix.multiply(
gradients,
previousPredictionTranspose
);//Apply Errors to the weights and the biasthis.weights.add(weight_deltas);this.bias.add(gradients);// Calculate the next layer errors
let weightsTranspose =Matrix.transpose(this.weights);
current_errors =Matrix.multiply(weightsTranspose, current_errors);return current_errors;}}classNeuralNetwork{
constructor(input_nodes, hidden_nodes, output_nodes){this.input_nodes = input_nodes;this.layers =[];this.output_nodes = output_nodes;// Checking if hidden nodes is an integer// If so convert it to an array with one entrythis.hidden_nodes =Number.isInteger(hidden_nodes)?[hidden_nodes]: hidden_nodes;this.setupLayers();this.setLearningRate();this.addActivationFunctions();this.setActivationFunction();}
setupLayers(){// Creating the Layers of the Neural Network// Loop hidden layers except for the first and last, set manuelly// input_nodes for NeuralLayer is always the previous Layerthis.layers.push(newNeuralLayer(this,this.input_nodes,this.hidden_nodes[0],"Input Layer"));// Loop hidden layers except for the first and last, set manuellyfor(let idx =1; idx <this.hidden_nodes.length; idx++){// input_nodes for NeuralNetworkLayer is always the previous Layerthis.layers.push(newNeuralLayer(this,this.hidden_nodes[idx -1],this.hidden_nodes[idx],"Hidden Layer"));}// hidden_nodes.length is the last entry at that timethis.layers.push(newNeuralLayer(this,this.hidden_nodes[this.hidden_nodes.length -1],this.output_nodes,"Output Layer"));}
predict(input_array){
input_array =Matrix.fromArray(input_array);// Loop layer over the inputsthis.layers.forEach((layer)=>{
input_array = layer.predict(input_array);});// Sending prediction to the caller!return input_array.toArray();}
setLearningRate(learningRate){this.learningRate = learningRate ||0.1;}
addActivationFunctions(){this.sigmoid =newActivationFunction("Sigmoid",(x)=>1/(1+Math.exp(-x)),(y)=> y *(1- y));this.tanh =newActivationFunction("Tanh",(x)=>Math.tanh(x),(y)=>1- y * y
);}
setActivationFunction(activationFunction){this.activationFunction = activationFunction
?this[activationFunction]:this.sigmoid;}
train(input_array, target_array){// Convert input arrays to matrix objects
let inputs =Matrix.fromArray(input_array);
let targets =Matrix.fromArray(target_array);
let predictions =[];
let prediction = inputs;// Loop layer over the inputsthis.layers.forEach((layer)=>{
prediction = layer.predict(prediction);
predictions.push(prediction);});// Last layer == output layer
let outputs = predictions[predictions.length -1];// Calculate the error// ERROR = TARGETS - OUTPUTS
let current_errors =Matrix.subtract(targets, outputs);for(let idx =this.layers.length -1; idx >=0; idx--){// Calculate deltas
current_errors =this.layers[idx].applyError(
predictions[idx],
idx ===0? inputs : predictions[idx -1],
current_errors
);}}
serialize(){
let cache =[];
let result = JSON.stringify(this,(key, value)=>{if(typeof value ==="object"&& value !==null){if(cache.indexOf(value)!==-1){// Circular reference found, discard keyreturn;}// Store value in our collection
cache.push(value);}return value;});
cache =null;return result;}static deserialize(data){if(typeof data ==="string"){
data = JSON.parse(data);}
let _NeuralNetwork=newNeuralNetwork(
data.input_nodes,
data.hidden_nodes,
data.output_nodes
);
let _NeuralNetworkLayers=[];
data.layers.map((layer)=>{
let _NeuralLayer=newNeuralLayer(_NeuralNetwork,
layer.weights.cols,
layer.weights.rows
);_NeuralLayer.weights =Matrix.deserialize(layer.weights);_NeuralLayer.bias =Matrix.deserialize(layer.bias);_NeuralNetworkLayers.push(_NeuralLayer);});_NeuralNetwork.layers =_NeuralNetworkLayers;return_NeuralNetwork;}}classActivationFunction{
constructor(name, fn, errorFn){this.name = name;this.fn = fn;this.errorFn = errorFn;}}classDoodle{
constructor(){this.figures =["banana","airplane","car","cat","bat","mountain","train","line",];this.imageLength =784;this.totalData =5000;this.training =[];this.trainingLength =500;this.testing =[];this.testingLength =500;this.accuracies =[];this.epochCounter =0;this.NeuralNetwork=null;this.activationFunction =null;this.learningRate =0.1;this.totalHiddenLayers =1;this.totalHiddenNeurons =64;this.setupLoadBytes();}
preload(){this.figures.forEach((fig)=>{
let _fig =`${fig}s`;
let _figData =`${fig}sData`;this[_fig]={};this[_figData]= loadBytes(`/uploads/hrwx/${fig}Compressed.bin`);});}
setup(){this.setupCanvas();this.loadData();this.createNeuralNetwork();this.refresh();}
setupCanvas(){
createCanvas(840,840);
background(80);}
refresh(){this.setupHTML();this.setupTrain();this.setupTest();this.setupGuess();this.setupClear();this.setupSave();this.setupLoad();this.setupLearningRate();this.setupHiddenLayers();this.setupHiddenNeurons();this.setupActivationFunction();}
resizeCanvas(){
createCanvas(840,840);
background(80);}
loadData(){this.figures.forEach((fig, idx)=>{
let _fig =`${fig}s`;
let _figData =`${fig}sData`;this.prepareData(this[_fig],this[_figData], idx);});this.figures.forEach((fig, idx)=>{
let _fig =`${fig}s`;this.training =this.training.concat(this[_fig].training);this.testing =this.testing.concat(this[_fig].testing);});}
setupTrain(){
let me =this;
let trainLength = document.getElementById("training_length");
let trainButton = document.getElementById("train");
$(trainButton).click(function(e){
me.showMsg(`TrainingforEpoch ${me.epochCounter}.`);
e.stopPropagation();
e.preventDefault();
me.trainingLength = trainLength.value;
me.trainEpoch();
me.epochCounter++;
me.refresh();
me.showMsg(`Epoch ${me.epochCounter}.`);});}
setupTest(){
let me =this;
let testLength = document.getElementById("testing_length");
let testButton = document.getElementById("test");
$(testButton).click(function(event){
event.stopPropagation();
event.preventDefault();
me.testingLength = testLength.value;
let percent = me.test();
me.showGraph();
me.showMsg(`Accuracy: ${nf(percent,2,2)}%`);});}
setupGuess(){
let me =this;
let guessButton = document.getElementById("guess");
$(guessButton).click(function(event){
event.stopPropagation();
event.preventDefault();
let inputs =[];
let doodleGuesses =[];
let img =get();
img.resize(28,28);
img.loadPixels();for(let idx =0; idx < me.imageLength; idx++){
let bright = img.pixels[idx *4];
inputs[idx]=(255- bright)/255.0;}
let guess = me.NeuralNetwork.predict(inputs);
let _guess =[...guess].sort().reverse();for(let idx =0; idx < min([_guess.length,3]); idx++){if(idx >3){break;}
doodleGuesses.push(me.figures[guess.indexOf(_guess[idx])]);}
me.showMsg(`Is it a ${doodleGuesses[0]}😅 or ${doodleGuesses[1]}🤯 or ${doodleGuesses[2]}😑.`);});}
setupClear(){
let me =this;
let clearButton = document.getElementById("clear");
$(clearButton).click(function(event){
event.stopPropagation();
event.preventDefault();
background(80);});}
setupSave(){
let me =this;
let saveButton = document.getElementById("save");
$(saveButton).click(function(event){
event.stopPropagation();
event.preventDefault();
me.showMsg(`SavingNeuralNetwork.`);
me.saveNeuralNetwork();
me.showMsg(`NeuralNetwork saved.`);});}
setupLoad(){
let me =this;
let loadButton = document.getElementById("load");
$(loadButton).click(function(event){
event.stopPropagation();
event.preventDefault();
me.loadNeuralNetwork();});}
setupLearningRate(){
let me =this;
let learningRate = document.getElementById("learning_rate");
$(learningRate).blur(function(event){
event.stopPropagation();
event.preventDefault();
me.showMsg(`Settings learning rate ${learningRate.value}.`);
me.NeuralNetwork.setLearningRate(learningRate.value);});}
setupHiddenLayers(){
let me =this;
let hiddenLayer = document.getElementById("hidden_layer");
$(hiddenLayer).change(function(event){
event.stopPropagation();
event.preventDefault();
me.showMsg(`${
me.totalHiddenLayers > hiddenLayer.value
?`Decreasing`:`Increasing`} hidden layers to ${hiddenLayer.value}.`);
me.totalHiddenLayers = parseInt(hiddenLayer.value);
me.createNeuralNetwork();
me.showMsg(`Hidden layers changed to ${hiddenLayer.value}.`);});}
setupHiddenNeurons(){
let me =this;
let hiddenNeurons = document.getElementById("hidden_neurons");
$(hiddenNeurons).change(function(event){
event.stopPropagation();
event.preventDefault();
me.showMsg(`${
me.totalHiddenNeurons > hiddenNeurons.value
?`Decreasing`:`Increasing`} hidden neurons to ${hiddenNeurons.value}.`);
me.totalHiddenNeurons = parseInt(hiddenNeurons.value);
me.createNeuralNetwork();
me.showMsg(`Hidden neurons changed to ${hiddenNeurons.value}.`);});}
setupActivationFunction(){
let me =this;
let activationFunction = document.getElementById("activation_function");
$(activationFunction).change(function(event){
event.stopPropagation();
event.preventDefault();
let labels ={
sigmoid:"Sigmoid",
tanh:"Tanh",
relu:"ReLU",};
me.showMsg(`ChangingActivationFunction to ${
labels[activationFunction.value]}.`);
me.NeuralNetwork.setActivationFunction(activationFunction.value);
me.showMsg(`ActivationFunction changed to ${
labels[activationFunction.value]}.`);});}
createNeuralNetwork(){
let hiddenLayers =[];
let hiddenLayerCount =this.totalHiddenLayers +1;for(let idx =0; idx < hiddenLayerCount; idx++){
hiddenLayers.push(this.totalHiddenNeurons);}this.NeuralNetwork=newNeuralNetwork(this.imageLength,
hiddenLayers,this.figures.length
);}
showMsg(_msg){
let msg = document.getElementById("msg");
$(msg).text(_msg);}static showMsg(_msg){
let msg = document.getElementById("msg");
$(msg).text(_msg);}
setupHTML(){
AB.msg(`<style>*{
box-sizing: border-box;}/* Create two equal columns that floats next to each other */.column {float: left;
width:50%;
padding:10px;}/* Clear floats after the columns */.row:after {
content:"";
display: table;
clear: both;}</style><fieldset><legend>Message</legend><div id="msg"></div></fieldset><br><fieldset><legend>NeuralNetworkTestAccuracy</legend><div class="chart"></div></fieldset><br><fieldset><legend>NeuralNetwork</legend><div class="row"><div class="column"><button id="save">Save</button></div><div class="column"><button id="load">LoadExisting</button></div></div></fieldset><br><fieldset><legend>NeuralNetworkTraining and Testing</legend><div class="row"><p>${this.epochCounter ===0?`NeuralNetwork is not trained`:``}</p></div><div class="row"><div class="column"><label for="training_length">TrainingSize:</label><br><input type="number" id="training_length" name="training_length" min="1" max="${this.training.length
}" value="${this.trainingLength}"><button id="train">Train</button></div><div class="column"><label for="testing_length">TestingSize:</label><br><input type="number" id="testing_length" name="testing_length" min="1" max="${this.testing.length
}" value="${this.testingLength}"><button id="test">Test</button></div></div></fieldset><br><fieldset><legend>NeuralNetworkOperations</legend><div class="row"><div class="column"><button id="guess">Guess</button></div><div class="column"><button id="clear">Clear</button></div></div></fieldset><br><fieldset><legend>NeuralNetworkTweaks</legend><div class="row"><div class="column"><label for="learning_rate">LearningRate:</label><br><input type="number" id="learning_rate" name="learning_rate" min="0.1" max="1" value="${this.learningRate
}"><br><br><label for="activation_function">ActivationFunction:</label><br><select type="select" id="activation_function" name="activation_function" value=${this.NeuralNetwork.activationFunction
}><option value="sigmoid">Sigmoid</option><option value="tanh">Tanh</option></select></div><div class="column"><label for="hidden_layer">TotalHiddenLayer:</label><br><input type="number" id="hidden_layer" name="hidden_layer" min="1" max="200" value="${this.totalHiddenLayers
}"><br><br><label for="hidden_neurons">TotalNeurons in HiddenLayer:</label><br><input type="number" id="hidden_neurons" name="hidden_neurons" min="1" max="200" value="${this.totalHiddenNeurons
}"></div></div></fieldset>`);}
updateHTML(){this.setupHTML();}
draw(){
strokeWeight(8);
stroke(0);if(mouseIsPressed){
line(pmouseX, pmouseY, mouseX, mouseY);}}
trainEpoch(){
shuffle(this.training,true);// Train for one epochfor(let idx =0; idx <this.trainingLength; idx++){
console.log(`Training image: ${idx+1} of ${this.trainingLength}`);
let trainData =this.training[idx];
let inputs =Array.from(trainData).map((x)=> x /255);
let label = trainData.label;
let targets =Array(this.figures.length).fill(0);
targets[label]=1;this.NeuralNetwork.train(inputs, targets);}}
test(){
shuffle(this.testing,true);
let correct =0;this.accuracies =[];// Train for one epochfor(let idx =0; idx <this.testingLength; idx++){
let testData =this.testing[idx];
console.log(`Testing image: ${idx+1} of ${this.trainingLength}`);if(!testData)continue;
let inputs =Array.from(testData).map((x)=> x /255);
let label = testData.label;
let guess =this.NeuralNetwork.predict(inputs);
let classification = guess.indexOf(max(guess));this.accuracies.push(guess[label]);if(classification === label){
correct++;}}return(100* correct)/this.testingLength;}
setupLoadBytes(){
p5.prototype.registerPreloadMethod("loadBytes");
p5.prototype.loadBytes =function(file, callback){
let self =this;
let data ={};
let xhr =newXMLHttpRequest();
xhr.open("GET", file,true);
xhr.responseType ="arraybuffer";
xhr.onload =function(oEvent){var arrayBuffer = xhr.response;if(arrayBuffer){
data.bytes =newUint8Array(arrayBuffer);if(callback){
callback(data);}
self._decrementPreload();}};
xhr.send(null);return data;};}
prepareData(category, data, label){
category.training =[];
category.testing =[];
category.label = label;for(let idx =0; idx <this.totalData; idx++){
let offset = idx *this.imageLength;
let threshold = floor(0.8*this.totalData);if(idx < threshold){
category.training[idx]= data.bytes.subarray(
offset,
offset +this.imageLength
);
category.training[idx].label = label;}else{
category.testing[idx - threshold]= data.bytes.subarray(
offset,
offset +this.imageLength
);
category.testing[idx - threshold].label = label;}}}
showGraph(){if(!this.accuracies)return;
let _data ={
data:{
labels:Array.from({ length:this.testingLength },(e, i)=> i),
datasets:[{
values:this.accuracies
}]},
title:"Test Accuracy",
type:"line",// or 'bar', 'line', 'pie', 'percentage'
height:300,
colors:['red']};new frappe.Chart(".chart", _data);}
saveNeuralNetwork(){if(AB.myuser ==="none"){
alert("User not logged in to save Neural Network.");}
let data ={NeuralNetwork:this.NeuralNetwork.serialize(),
learningRate:this.learningRate,
totalHiddenLayers:this.totalHiddenLayers,
totalHiddenNeurons:this.totalHiddenNeurons,
epochCounter:this.epochCounter
};
AB.saveData(JSON.stringify(data));}
loadNeuralNetwork(){
let me =this;if(AB.myuser ==="none"){
alert("User not logged in to load Neural Network.");}
AB.restoreData((data)=>{
data = JSON.parse(data);
me.showMsg(`Loading existing NeuralNetwork.`);
me.NeuralNetwork=NeuralNetwork.deserialize(data.NeuralNetwork);
me.learningRate = data.learningRate;
me.totalHiddenLayers = data.totalHiddenLayers;
me.totalHiddenNeurons = data.totalHiddenNeurons;
me.epochCounter =Number.isInteger(data.epochCounter)? data.epochCounter :0;
me.refresh();
me.showMsg(`NeuralNetwork loaded.`);});}}
$.getScript("https://unpkg.com/frappe-charts@latest");
window.doodle =newDoodle();function preload(){
doodle.preload();}function setup(){
doodle.setup();}function draw(){
doodle.draw();}function windowResized(){
doodle.resizeCanvas();}