Have you ever ever tried to really construct a neural community? No, neither have I…till right this moment!
On this article we’ll cowl a number of issues I realized and a couple of demos of some quite simple neural networks, written in vanilla JS.
Introduction
I used to be studying the @supabase_io “AI Content Storm” articles earlier right this moment.
And one factor dawned on me. I get neural networks…besides I do not truly get them in any respect!
Like, I get the idea of a neuron. However how does the maths work?
Particularly how do you employ “again propagation” to coach a neural community? How do bias and weightings work? What or who’s a sigmoid? and so on.
Now, the smart factor to do would have been to learn a load of articles, seize a library and play with that.
However I’m not smart.
So as an alternative I learn a load of articles…after which determined to construct my first neural community.
However that wasn’t onerous sufficient, so I made a decision to do it in JavaScript (as everybody appears to make use of Python…). Oh and I made a decision I might do it with none libraries. Oh and I needed to construct a visualiser in it too.
There’s something mistaken with me…I appear to thrive on ache.
Anyway, I did it, and here’s what I realized.
Be aware: this isn’t a tutorial
Look, I need to be clear, this isn’t a tutorial!
That is simply me sharing a number of issues that I discovered fascinating whereas studying and my first neural community.
Be aware that there’s emphasis on first, so please do not take this as something aside from one thing fascinating to take a look at and play with.
I additionally do my finest to elucidate every part and what it does, however as with all the things, you get higher at explaining it the more adept you’re with one thing…so a few of my explanations could also be just a little “off”!
Anyway, with all that out of the best way, let’s get on with it!
If you wish to skip straight to the final demo then go proper forward!
First steps
Okay very first thing is first, what’s the most simple neural community I can construct?
Effectively after a little bit of studying I came upon {that a} neural community could be so simple as some enter neurons and a few output neurons.
Every enter neuron is related to an output neuron after which we are able to add weightings to every of those connections.
With that in thoughts I needed to give you an issue to unravel that was easy to observe, but complicated sufficient to make sure my community was working.
I made a decision upon a neural community that takes the X and Y coordinates of a degree on a graph after which relying on whether or not they’re optimistic or adverse assigns a “crew” (color) to them.
So that provides us 2 inputs (X and Y place) after which 4 outputs:
 X > 0 and Y > 0
 X < 0 and Y > 0
 X > 0 and Y < 0
 X < 0 and Y < 0
As a consequence of how easy the necessities are right here, we are able to get away with out some “hidden” neurons (that’s one thing I’ll cowl later) and maintain issues tremendous easy!
So basically we now have to construct a neural community that appears one thing like this:
The circles on the left are our inputs (X and Y positions) and the circles on the correct are our outputs we mentioned earlier.
Our first neurons
OK so now we are able to truly get began.
Now I did not truly construct a neuron first. In truth I truly constructed a visualiser first, because it was the best strategy to see if issues have been working, however I’ll cowl that later.
So let’s construct a neuron (or extra particularly, a number of neurons and their connections).
Fortunately, neurons are literally fairly easy! (or ought to I say, they can be fairly easy…they get extra complicated in Massive Language Fashions (LLMs) and so on.)
Easy neurons have a bias (consider that like an inside weighting, a quantity we’ll add to our ultimate calculation to weight every neuron) and are related to different neurons with weightings between every connection.
Now, looking back, including the connections to every neuron individually might have been a greater concept, however as an alternative I made a decision so as to add every layer of neurons and every layer of connections as separate objects because it made it simpler to know.
So the code to construct my first neural community regarded like this:
class NeuralNetwork {
constructor(inputLen, outputLen) {
this.inputLen = inputLen;
this.outputLen = outputLen;
this.weights = Array.from({ size: this.outputLen }, () =>
Array.from({ size: this.inputLen }, () => Math.random())
);
this.bias = Array(this.outputLen).fill(0);
}
}
const neuralNetwork = new NeuralNetwork(2, 4);
Okay, I skipped a number of steps, so let’s briefly cowl every half.
this.inputLen = inputLen;
and this.outputLen = outputLen;
are simply so we are able to reference the variety of inputs and outputs.
this.weights = [...]
is the connections. Now it might look just a little intimidating, however here’s what we’re doing:
 create an array of output neurons (
outputLen
)  add an array of size
inputLen
to every of the array entries and populate it with some random values between 0 and 1 to start out us off.
An instance of the output of that code would appears to be like like this:
this.weights = [
[0.7583747881712366,0.4306037998314902],
[0.40553698492617807,0.4419651593960727],
[0.852978801662627,0.9762509253699836],
[0.8701610553353811,0.5583309725764114]
]
They usually important signify the next:
[input 1 to output 1, input 2 to output 1],
[input 1 to output 2, input 2 to output 2],
[input 1 to output 3, input 2 to output 3],
[input 1 to output 4, input 2 to output 4],
Then we even have this.bias
.
That is for every of the neurons within the output layer. It’s what we use so as to add onto the output worth later to make some neurons stronger and a few weaker.
It’s simply an array of 4 zeros to start out us off as we do not need an preliminary biases!
Now, though it is a neural community, it’s utterly ineffective.
We’ve no approach of truly utilizing it…and if we did use it the outcomes it produces can be utterly random!
So we have to remedy these issues!
Utilizing our community!
The very first thing that we have to do is to really take some inputs, run them via our community and collect the outputs.
Here’s what I got here up with:
propagate(inputs) {
const output = new Array(this.outputLen);
for (let i = 0; i < this.outputLen; i++) {
output[i] = 0;
for (let j = 0; j < this.inputLen; j++) {
output[i] += this.weights[i][j] * inputs[j];
}
output[i] += this.bias[i];
output[i] = this.sigmoid(output[i]);
}
return output;
}
sigmoid(x) {
return 1 / (1 + Math.exp(x));
}
Now there are two fascinating issues right here.
Sigmoid
To begin with one fascinating factor is our sigmoid
perform. All that this does is remodel a worth we enter (say 12) into a worth between 0 and 1 alongside an “sshaped” curve.
That is our approach of normalising values from extremes to one thing extra uniform and all the time optimistic.
After additional studying there are different choices right here on how we modify a worth to between 0 and 1, however I’ve not explored them totally but (for instance ReLU.
I’m certain there are some excellent explanations why that is wanted, however in my monkey mind that is simply the best way of retaining values between 0 and 1 in order that the multiplication stays inside a sure bounds and the values are “flattened”.
That approach you aren’t getting “runaway” paths between neurons which are overly sturdy.
For instance, think about you had a reference to a weight of 16 and one with a weight of 1, utilizing our sigmoid perform we are able to scale back that from a distinction of 16 occasions to a distinction of about 35% (sigmoid(1)
is 0.73 and sigmoid(16)
is 0.99 after working via our perform).
It additionally implies that adverse values are turned optimistic.
So working values although our sigmoid perform implies that adverse numbers get remodeled to a worth between 0 and 0.5, a worth of 0 turns into precisely 0.5 and a worth larger than 0 turns into a worth between 0.5 and 1.
If you concentrate on it, that is smart because the second we begin multiplying adverse and optimistic numbers collectively we are able to massively change our output.
For instance, if we now have a single adverse neuron in a path of 100 and the remaining are optimistic, this modifications a robust worth to a weak one and is prone to trigger points.
Anyway, as I learn extra and experiment extra I’m certain I’ll perceive this half higher!
Did I want biases?
The second fascinating factor is the output[i] += this.bias[i];
.
Effectively, on this neural community, all 4 outputs are equally necessary and we now have no hidden neurons, so I later eliminated this to simplify the code!
Sarcastically although, on our extra complicated neural community, I wanted to readd the biases on the output neurons, as a consequence of how the community again propagation was working. In any other case one output neuron activated on a regular basis.
What I couldn’t work out is whether or not this was a obligatory step, or whether or not I made a mistake with my neural community and this was compensating for it.
But once more, I need to remind you, I’m nonetheless studying and solely simply grasp the fundamentals, so I don’t know which it’s! π€£
We’re practically there
The remainder of the code above within reason straight ahead. We’re simply multiplying every enter by a weighting related to every output (and including our pointless bias!).
In truth, we are able to run this now, however out outcomes might be atrocious! Let’s repair that!
Time to coach!
Okay final necessary a part of a neural community, coaching it!
Now as this text is getting lengthy, I’ll simply cowl the details of the next coaching code (which took me practically an hour to write down by the best way…I instructed you I’m a noob at this!)
prepare(inputs, goal) {
const output = this.propagate(inputs);
const errors = new Array(this.outputLen);
for (let i = 0; i < this.outputLen; i++) {
errors[i] = goal[i]  output[i];
for (let j = 0; j < this.inputLen; j++) {
this.weights[i][j] +=
this.learningRate *
errors[i] *
output[i] *
(1  output[i]) *
inputs[j];
}
this.bias[i] += this.learningRate * errors[i];
}
}
“Why did it take so lengthy?” I hear you ask! Effectively, it was getting my head round all of the bits that wanted multiplying collectively to be able to replace every of the weights.
Additionally this.learningRate
took just a little getting used to. It’s merely a discount within the price that we modify the weightings in order that we do not “overshoot” our goal values for every weighting, however tuning it to an inexpensive worth takes expertise…I did not have expertise and set it approach too low, so my code appeared damaged!
After a little bit of fiddling, I settled on a worth of 0.1 (as an alternative of 0.01 π€¦πΌββοΈ) and hastily issues began working higher!
Proper, so we now have a coaching perform. However keep in mind that this coaching perform solely does one go of coaching.
We have to prepare our community a load of occasions, with every time it trains hopefully making it extra correct.
We’ll cowl that in a second, however I need to share a fast aspect level / factor I realized.
Coaching knowledge adjustment
I do know we have not even coated the ultimate coaching knowledge, however this was an fascinating level I realized that matches right here (because it explains why it took me so lengthy to write down this coaching perform).
Initially I used to be producing tons of of various coaching X and Y coordinates, all randomised.
However then I acquired a lot better outcomes by producing simply 4 static coaching factors after some additional studying:
const trainingData = [
{ x: 0.5, y: 0.5, label: "blue" },
{ x: 0.5, y: 0.5, label: "red" },
{ x: 0.5, y: 0.5, label: "green" },
{ x: 0.5, y: 0.5, label: "purple" }
];
It is smart, when you get it!
We need to “pull” values nearer to a goal, the above values are the precise “centre level” of every of our areas.
So our error charges will all the time be constant for a given distance.
This implies our neural community learns extra rapidly as our error charges are bigger relying on whether or not they’re farther from X or farther from Y.
I might clarify that higher, however that’s past the scope of this text. Hopefully if in case you have a give it some thought, then it’s going to additionally “click on” for you prefer it did for me!
Sarcastically I went again to the extra randomised knowledge set for the larger mannequin as I needed to essentially check my understanding of studying charges, overtraining and so on.
We’ve a functioning and helpful neural community!
Now that’s, in impact, our complete neural community.
There may be one factor we have to do although.
Our coaching perform must run it a load of occasions!
So we want one final perform to do this, which takes our coaching knowledge and runs our coaching perform a number of hundred occasions:
perform prepare() {
for (let i = 0; i < 10000; i++) {
const knowledge =
trainingData[Math.floor(Math.random() * trainingData.length)];
neuralNetwork.prepare([data.x, data.y], encode(knowledge.label));
}
console.log("Coaching full");
}
Goldilocks iterations
Discover that we prepare our community 10,000 occasions within the for
loop.
10,000 iterations was lots to coach this specific neural community. However for the extra complicated one we’ll cowl in a bit, I wanted extra iterations (and to show down the educational price).
This is among the fascinating elements of machine studying, you must prepare a neural community sufficient (which is tough to get proper), however for those who prepare it an excessive amount of you get “over becoming” taking place and really begin getting worse outcomes. So it must be completely balanced for the very best outcomes!
Anyway, that was quite a bit, we’re lastly at our first demo!
Easy vanilla JS neural community demo
It’s a little messy, however our neural community and all the coaching half is within the first 67 strains of the CodePen under.
The remaining strains of code truly run our community (neuralNetwork.propagate([x, y]);
roughly line 85) after which output the factors and their predicted colors onto a <canvas>
.
encode
and decode
are purely to take our output neurons, discover which one has the best activation after which map that to a color for our visualisation.
And right here is the very last thing to know. Our output neurons will all have a worth. A neural community would not simply output 1, 0, 0, 0.
As an alternative it’s going to output a “certainty” or guess for every of the output neurons. So we’ll get one thing like 0.92,0.76, 0.55, 0.87
as our output.
So that’s the reason we now have our decode
perform, which finds the best outputting neuron and takes that as our ultimate guess!
// this line finds the max worth of all of our output neurons after which returns its index so we are able to use that to categorise our X and Y coordinates.
const maxIndex = output.indexOf(Math.max(...output));
Utilization and precise demo
To make use of the instance you have got 3 buttons:
 Prepare – to coach our neural community because it begins untrained and randomised.
 Classify Factors – that is to run our neural community. It’s going to plot the factors on the graph and assign them a color. I counsel working this earlier than and after coaching.
 reset – this may create a brand new neural community that’s untrained. Nice for testing out the classification of factors earlier than and after coaching.
Additionally observe that every of the areas is colored based on what color ought to present there. It actually let’s you see how removed from profitable a randomised and untrained neural community is (reset after which classify factors to check)!
Have a play!
Finish of our most simple neural community
So there we now have our most simple neural community!
It features properly for our wants and we managed to be taught just a little bit about again propagation (our prepare
perform in the principle class) and weightings and biases.
However it is extremely restricted. If we need to do something extra superior sooner or later, we have to add some hidden neurons!
Model 2 – hidden neurons
OK, so why hidden neurons? What objective do they serve?
In additional complicated examples they function a strategy to take the inputs and add extra dimensions to the best way they’re categorised.
We’re nonetheless utilizing 2 enter neurons and 4 output neurons, however this time we now have added a further layer within the center (which we are able to change and alter the variety of neurons in).
So our neural community appears to be like one thing like this:
As neural networks must deal with extra inputs and do extra complicated calculations, further neurons in hidden layers permit them to raised categorise inputs and supply higher outcomes.
Hidden layers may also be totally different “depths”.
So for example we now have 2 enter neurons. We might join them to six “hidden” neurons, after which join them to out 4 output neurons.
However we might additionally join out 6 neurons in our first layer to a second layer of hidden neurons. This second layer might have 8 neurons, which then hook up with our 4 output neurons.
However that could be a lot to observe, and this was for me to be taught the fundamentals, so I selected so as to add a single hidden layer. This additionally meant I might maintain every connection layer as a separate array, which is simply simpler to know at this stage!
So what’s new?
Not a lot has modified, simply we now have extra connections and some extra neurons!
You possibly can consider it as including 2 of our unique neural networks in collection, simply that the output of the primary one now acts because the enter for the second.
Whereas the code could also be much more convoluted, our neural community follows the identical rules.
Right here is the code:
class NeuralNetwork {
constructor(inputSize, hiddenSize, outputSize) {
this.inputSize = inputSize;
this.hiddenSize = hiddenSize;
this.outputSize = outputSize;
this.weightsInputToHidden = Array.from({ size: hiddenSize }, () =>
Array.from({ size: inputSize }, () => Math.random() * 2  1)
);
this.biasHidden = Array(hiddenSize).fill(0);
this.weightsHiddenToOutput = Array.from({ size: outputSize }, () =>
Array.from({ size: hiddenSize }, () => Math.random() * 2  1)
);
this.biasOutput = Array(outputSize).fill(0);
this.learningRate = doc.querySelector('#learningRate').worth; // Adjusted studying price
this.hiddenLayer = new Array(this.hiddenSize);
}
feedForward(inputs) {
for (let i = 0; i < this.hiddenSize; i++) {
this.hiddenLayer[i] = 0;
for (let j = 0; j < this.inputSize; j++) {
this.hiddenLayer[i] +=
this.weightsInputToHidden[i][j] * inputs[j];
}
this.hiddenLayer[i] += this.biasHidden[i];
this.hiddenLayer[i] = sigmoid(this.hiddenLayer[i]);
}
const output = new Array(this.outputSize);
for (let i = 0; i < this.outputSize; i++) {
output[i] = 0;
for (let j = 0; j < this.hiddenSize; j++) {
output[i] +=
this.weightsHiddenToOutput[i][j] * this.hiddenLayer[j];
}
output[i] += this.biasOutput[i];
output[i] = sigmoid(output[i]);
}
return output;
}
prepare(inputs, goal) {
for (let i = 0; i < this.hiddenSize; i++) {
this.hiddenLayer[i] = 0;
for (let j = 0; j < this.inputSize; j++) {
this.hiddenLayer[i] +=
this.weightsInputToHidden[i][j] * inputs[j];
}
this.hiddenLayer[i] += this.biasHidden[i];
this.hiddenLayer[i] = sigmoid(this.hiddenLayer[i]);
}
const output = new Array(this.outputSize);
for (let i = 0; i < this.outputSize; i++) {
output[i] = 0;
for (let j = 0; j < this.hiddenSize; j++) {
output[i] += this.weightsHiddenToOutput[i][j] * this.hiddenLayer[j];
}
output[i] += this.biasOutput[i];
output[i] = sigmoid(output[i]);
}
const errorsOutput = new Array(this.outputSize);
const errorsHidden = new Array(this.hiddenSize);
for (let i = 0; i < this.outputSize; i++) {
errorsOutput[i] = goal[i]  output[i];
for (let j = 0; j < this.hiddenSize; j++) {
this.weightsHiddenToOutput[i][j] +=
this.learningRate *
errorsOutput[i] *
output[i] *
(1  output[i]) *
this.hiddenLayer[j];
}
this.biasOutput[i] += this.learningRate * errorsOutput[i];
}
for (let i = 0; i < this.hiddenSize; i++) {
errorsHidden[i] = 0;
for (let j = 0; j < this.outputSize; j++) {
errorsHidden[i] += this.weightsHiddenToOutput[j][i] * errorsOutput[j];
}
this.biasHidden[i] += this.learningRate * errorsHidden[i];
for (let j = 0; j < this.inputSize; j++) {
this.weightsInputToHidden[i][j] +=
this.learningRate *
errorsHidden[i] *
this.hiddenLayer[i] *
(1  this.hiddenLayer[i]) *
inputs[j];
}
}
}
}
Now, do not be intimidated, I’ve simply copied a number of loops with barely totally different goal units of information to control.
We’ve added an additional set of biases (for our hidden layer) and an additional set of connections: our enter layer to our hidden layer after which our hidden layer now connects to our output layer.
Lastly our prepare
perform has a number of further loops simply to again propagate via every of the steps.
And the one different change value mentioning is that we now have a 3rd enter parameter (within the center), for the variety of hidden neurons.
Ugly, however it appears to work
Look, I need to say it yet one more time, this was me studying as I’m going and so the code displays that.
There may be a number of repetition right here and it’s not very extensible.
Nonetheless, so far as I can inform, it really works.
With that being mentioned, though it really works, it seems to carry out worse than our unique, a lot less complicated neural community.
It both means I made a mistake (probably), or it’s that I have not “dialled in” the right coaching settings.
Talking of which…
Including some variables to play with
As this was extra complicated I’ve “bodged” in some fast settings.
Now we are able to replace:
 Coaching Information Dimension – the variety of totally different random factors we generate

Coaching iterations – what number of occasions we choose a random knowledge level from the coaching set and feed that into our
prepare
perform on the neural community.  Studying Charge – our multiplier for a way rapidly we must always alter based mostly on errors.
 hidden nodes (greater than 2!) – adjusting what number of hidden nodes there are within the second layer (requires you to initialise the community once more in any other case it’s going to break!)
 factors to categorise – the variety of factors to go to our educated neural community and plot on the graph.
This implies we are able to play with values way more rapidly to see what impact they’ve on our neural community and it is accuracy!
One final thing
Oh and I added a button for a visualisation of what the neural community appears to be like like.
By all means press “Visualize Neurons and Weights”, however it is not completed. I additionally don’t have any rapid intention to complete it as I need to utterly redesign my method to constructing a neural community so it’s extra extensible.
Nonetheless, the button is there, be happy to press it. Higher but, be happy to repair it for me! π€£π
The Demo
Controls are the identical as earlier than, plus the inputs talked about above within the earlier 2 subsections.
Have a play with it and see for those who can high quality tune the educational price, variety of neurons and coaching settings to get a very correct end result!
Remember to replace some values, reinitialise the neural community, strive it with a unique variety of hidden neurons and so on.
Hopefully you’ll begin understanding a number of issues if you’re a newbie like me!
Conclusion
It was actually enjoyable constructing a neural community in vanilla JS.
I’ve no seen many individuals doing it like this, so I hope it’s helpful to you or no less than anyone!
I realized quite a bit about biases, again propagation (the important thing to neural networks) and so on.
Clearly this instance and the issues realized listed here are only one% of machine studying. However the core rules are the identical for a tiny, unoptimized neural community like mine and a huge multibillion parameter mannequin.
This instance was just like the “hiya world” of Machine Studying (ML) and Neural Networks.
Subsequent I actually need to try to construct a a lot bigger neural community that’s higher structured and simpler to increase, to see if we are able to do some Optical Character Recognition (OCR). You possibly can consider that because the “ToDo Listing” of ML and Neural Networks!
Depart a remark.
Are you a professional with neural networks? Inform me the place I went mistaken!
Are you an entire newbie like me? Then inform me if this helped you perceive, no less than just a little bit! Or, if this truly made it extra complicated! π±
Above all, if this text has impressed you to both grimace at my terrible coding, or to need to construct your individual neural community…then I’m glad that it had some impact on you and would love to listen to about it both approach! π