From the Sigmoid to the First Neuron

From the Sigmoid to the First Neuron#

In the previous lesson, our watering model learned to calculate a score from temperature and humidity.

We had:

temperature
      +
humidity
  parameters
     score
   decision

But something bothered us.

Our model could produce:

-4.93
0.01
3.05

Then we simply asked it:

if score > 0 → water
otherwise    → do not water

That works for making a decision.

But we immediately lose information.

A score of 0.01 and a score of 8 both give the same answer:

water

Yet those two scores clearly do not tell the same story.

So we ended the previous lesson with a question:

How can we transform our score into an output between 0 and 1?

Let’s try.

A function between 0 and 1#

We are looking for a function that can receive any score and bring it back between 0 and 1.

One function is particularly well suited to this job: the sigmoid.

Its formula is:

sigmoid(x) = 1 / (1 + exp(-x))

It may look a little less welcoming than our usual score.

Rather than dissecting it immediately, let’s look at what it does.

During our previous experiment, we calculated a few values:

Score Sigmoid
-4 0.018
-2 0.119
-1 0.269
0 0.500
1 0.731
2 0.881
4 0.982

The first thing we notice is that negative scores give values below 0.5.

Positive scores give values above 0.5.

And exactly in the middle:

sigmoid(0) = 0.5

Let’s go a little further.

For a score of 8, we obtained:

sigmoid(8) = 0.9996646498695336

So we are very close to 1.

Very close.

But still not 1.

Our sigmoid seems decidedly reluctant to make absolute decisions.

In the other direction, it behaves the same way: it gets closer and closer to 0 without reaching it.

A curious S-shaped curve#

The numbers already give us a clue.

The graph makes the behavior much easier to see.

S-shaped sigmoid curve crossing 0.5 when the score is 0
y = 1 / (1 + exp(-x))

We can find our values again on this curve.

On the left, it gradually approaches 0.

In the center:

x = 0
y = 0.5

On the right, it gradually approaches 1.

And most importantly, the curve is much more sensitive around its center.

That is exactly the property we were looking for.

Our score is no longer abruptly turned into 0 or 1.

We keep an intermediate value.

Back to watering#

We already know the parameters learned earlier:

temperature weight = 0.38
humidity weight    = -0.19
bias               = 0.01

Let’s try:

temperature = 28 °C
humidity    = 40 %

Our usual calculation gives:

score = 28 × 0.38
      + 40 × (-0.19)
      + 0.01

score = 3.05

Now pass 3.05 through the sigmoid.

We obtain:

0.9547825265167125

About 0.955.

We are clearly on the side of 1.

Now keep 28 °C, but raise humidity to 80 %.

This time:

output ≈ 0.010

We are almost at the other end.

That fits our watering problem fairly well:

output close to 0 → do not water
output close to 1 → water

But what happens between the two?

Let’s find the boundary#

Keep the temperature at 28 °C.

Then vary humidity.

Around 56 %, we obtained:

28 °C / 55 % → 0.5498
28 °C / 56 % → 0.5025
28 °C / 57 % → 0.4551

Now this is much more interesting.

At 55 %, our output is slightly above 0.5.

At 57 %, it is slightly below.

And at 56 %:

0.5025

Our model is practically sitting on the fence.

Yet at some point we still have to decide which side to fall on.

We can choose a rule:

output >= 0.5 → water
output <  0.5 → do not water

But that decision rule comes after the sigmoid.

The sigmoid itself keeps the nuance.

It lets us distinguish an output of 0.5025 from an output of 0.9548.

So we have solved our first problem.

Our score has become a continuous output between 0 and 1.

But now let’s look at the program we have been building.

What have we built?#

We already had this:

temperature ──× weight ──┐
humidity ─────× weight ──┼──> score
bias ────────────────────┘

We have just added:

score ──> sigmoid ──> output

Put the two together:

temperature ──× temperature_weight ──┐
humidity ─────× humidity_weight ───────┼──> score ──> sigmoid ──> output
bias ──────────────────────────────────┘

Look at what we have.

We have inputs.

Each input has a weight.

We add a bias.

We calculate a score.

Then we pass that score through an activation function.

This is starting to look suspiciously like something.

We have just built our first artificial neuron.

And we did it almost without noticing.

Our neuron in Python#

The corresponding program is actually very short:

import math


temperature_weight = 0.38
humidity_weight = -0.19
bias = 0.01


def sigmoid(x):
    return 1 / (1 + math.exp(-x))


def neuron(temperature, humidity):
    score = (
        temperature * temperature_weight
        + humidity * humidity_weight
        + bias
    )

    return sigmoid(score)

Our neuron() function receives two inputs and returns an output.

We can then apply our decision rule:

output = neuron(28, 40)

if output >= 0.5:
    print("water")
else:
    print("do not water")

So now we know what our first neuron is.

But there is still one small problem.

And it is a rather important one.

We wrote:

temperature_weight = 0.38
humidity_weight = -0.19
bias = 0.01

Where did those values come from?

Who sets the weights?#

We already know part of the answer.

Our neuron has five examples:

Temperature Humidity Target
22 70 0
24 65 0
27 42 1
29 35 1
25 55 0

For each one, it produces an output.

We also know the target.

So we need to measure how far the two are from each other.

That should sound familiar.

In Lesson 03, we used a loss function.

Let’s use the same idea again:

error = target - output
loss = error²

We do not need to invent a new mechanism.

We have our neuron.

We have a target.

And now we know how to measure its error.

A familiar question returns:

In which direction should we change the parameters to make this loss smaller?

An old tool returns#

Take only the temperature weight.

Its current value is:

0.38

During our experiment, we moved it very slightly around that value:

0.379 → loss ≈ 0.0866
0.380 → loss ≈ 0.0890
0.381 → loss ≈ 0.0916

When we increase the weight slightly, the loss increases.

So we know something about the slope at that point.

And that slope has a name we already know:

the gradient.

We have found the same idea as gradient descent again.

This time, it will help us adjust the parameters of our neuron.

We use the same reasoning for the humidity weight and the bias.

Then we can correct them.

Let’s try.

A first step… slightly too enthusiastic#

Start with:

learning_rate = 0.01

Our loss before the correction is about:

0.089041

After correcting all three parameters:

0.505459

We wanted to go down.

We went up.

And not by a little.

The local direction gave us useful information, but our step was clearly too large.

Let’s try ten times smaller:

learning_rate = 0.001

This time:

0.089041 → 0.060073

That looks much more like a descent.

So we have rediscovered another idea from the previous lessons: knowing the direction is not enough.

We also need a suitable step size.

Let’s continue#

Now that our first step goes in the right direction, repeat it.

After ten small steps, we observed:

start          0.089041
iteration 1    0.060073
iteration 2    0.052935
iteration 3    0.050631
...
iteration 10   0.048669

The loss does not magically disappear.

But it goes down.

The parameters have changed slightly too:

temperature weight ≈  0.3769
humidity weight    ≈ -0.2035
bias               ≈  0.0098

Good.

But after all those calculations, one question is much more interesting than the sixth decimal place of our loss:

What can our neuron do now?

Let’s look at its outputs instead#

After those ten iterations:

Temperature Humidity Target Output
22 70 0 0.003
24 65 0 0.015
27 42 1 0.838
29 35 1 0.979
25 55 0 0.147

All five observations are now on the expected side of 0.5.

So the three numerical values we initially placed in our program were not fixed forever.

The neuron can correct them from examples.

What is actually learned is:

the weights
the bias

The loss itself is not learned.

It is a measurement we use to see how the model behaves and to guide the corrections.

One last caution is necessary.

An output of:

0.838

is indeed between 0 and 1.

But we have not demonstrated that it means:

83.8 % probability that we should water

For now, it mainly tells us which side of our chosen boundary the observation lies on, and how far it is from 0.5.

What we have just built#

We started with a very simple problem:

our model produces a score

We wanted to keep more information before making a decision.

We discovered the sigmoid.

Then, almost without looking for it, we ended up with:

inputs
weights + bias
score
sigmoid
output

That was our first neuron.

One question remained:

who sets its weights?

And to answer it, we did not need to start over.

We found the tools from the previous lessons again:

output
target
loss
gradient
correction of weights and bias
new output

So we have not entered a completely different world.

We have simply assembled, one by one, pieces we already knew.

And this time, their combination has a name:

an artificial neuron that learns.

We still have only one.

So a new question appears quite naturally:

What might happen if several neurons worked together?