The World Vol 5 · Bodies and Brains
ch 51 / 105
Chapter 51

Multiply, Add, Decide

The first neuron

Twenty-four scaled readings reach the top of a creature's tick, and the deciding rule is still text someone typed. The next driver needs a decision made only from numbers.

A neuron is three pieces of arithmetic: multiply every input by a weight of its own, add the products with one more number that shifts the total, and compare the result with zero. The unit is small enough to work by hand before Go gets it.

Hand-written rules are readable, and they move the first creatures. They also change only when a person edits them. A rule made of conditions cannot be nudged, copied or compared as a row of numbers can.

The worked example uses two readings: a ray distance and a store level. The arithmetic is dull on purpose, because the rest of the volume repeats these same operations thousands of times.

The two-input decision

Here is a creature standing in the valley with one thing to settle. The ray pointing straight ahead has found a plant three cells away. Its store is holding 96 energy units of the 400 it holds when full. The question is whether to head for the plant, and the only two readings that bear on it are already in the sensor row.

Neither arrives in the units it was measured in. A ray comes back as a share of how far the eye reaches, which is twelve cells, so a plant at three cells is 3 ÷ 12 = 0.2500, and a ray that ran the whole way and met nothing reports 1.0000. The store comes back as a share of full, so 96 units is 96 ÷ 400 = 0.2400. Two numbers between 0 and 1: 0.2500 and 0.2400.

Now give each of them a weight, which is the number saying how much that reading counts and which way it pushes. Both readings here are reasons against going. The further the plant, the less a walk to it pays; the fuller the store, the less any of it matters. So both weights come out negative: −1.5 on the distance and −2.0 on the store. Those two numbers were typed. Nothing in this chapter works out what they ought to be, and the arithmetic does not care where they came from.

The signs are doing more work than the sizes here, and it is the signs to keep an eye on. A negative weight on the distance says a creature likes what is close. Turn that weight positive and you have described a creature that prefers the far plant to the near one, which sounds absurd until you notice it is exactly what an animal avoiding a crowded patch of ground would want. Nothing in the arithmetic prefers one sign to the other, and nothing anywhere validates the choice. The sizes then say how the two readings trade against each other: at −1.5 and −2.0, one whole extra cell of distance is cancelled by having 25 fewer energy units in the store, and that exchange rate holds everywhere on the page.

Multiply, then add. −1.5 × 0.2500 = −0.3750. −2.0 × 0.2400 = −0.4800. Added together that is −0.8550, and there is a problem sitting in plain sight: both weights are negative and both readings are positive or zero, so the total can never climb above zero, and a creature that decides on "above zero" would never go anywhere. It needs one more number, added to every total whatever the readings say. Call it the bias and set it to 1.2. Now −0.8550 + 1.2 = 0.3450, which is above zero, and the creature goes.

Change one thing at a time and the arithmetic answers on its own. Fill the store: the store term becomes −2.0 × 1.0000 = −2.0000, the total is −0.3750 − 2.0000 + 1.2 = −1.1750, and the creature stays where it is. Move the plant out to six cells with the store back at 96: −0.7500 − 0.4800 + 1.2 = −0.0300, and it stays, barely. Leave the plant at six cells and drop the store to 80: −0.7500 − 0.4000 + 1.2 = 0.0500, and it goes. Sixteen units of energy is the difference between walking six cells for a meal and ignoring it.

That pair invites a sharper question. Exactly what store, at six cells, turns the answer over? The total is zero when 1.5 × 0.5 plus 2.0 times the store's share equals 1.2. That is 0.75 + 2.0s = 1.2, so 2.0s = 0.45 and s = 0.225, which is 0.225 × 400 = 90 energy units. At exactly 90 the total is 0.0000, and 0.0000 is not above zero, so 90 says stay and 89 says go. The same division done with the distance left as a letter gives the whole boundary at once: the turnover store is 240 − 25 × cells. It reaches zero at 9.6 cells, and past 9.6 cells this creature will not cross the ground for food however empty it is. Nobody typed a 9.6 anywhere. It is 1.2 divided by 1.5, times twelve.

∑ Math Interlude — a neuron in six letters

Every number above was worked in full. Here is the shorthand for it, which earns its place only because a creature in this volume runs three hundred and seventy-eight weights and writing them out is not an option. Call the inputs x1 and x2, in the order they arrive in the row. Call their weights w1 and w2. Call the bias b and call the total z. A dot between two letters means multiply, so w1·x1 is −1.5 × 0.2500.

z = w1·x1 + w2·x2 + b

Check it against the first situation: −0.3750 + (−0.4800) + 1.2 = 0.3450, the same total as before. With twenty-four inputs that line would run off the page, so there is a mark for "add up one of these for every input", written as a capital sigma with the counter under it. Read the line below as: let i be 1, then 2, and so on up to n, work out wi·xi each time, add all of those together, then add b.

z = ∑i=1..n wi·xi + b

The decision is a second, separate step, and this chapter uses the plainest one there is. step(z) is 1 when z is above zero and 0 when it is not.

y = step(z)

The boundary is the set of readings where z is exactly 0, and setting the first line to zero and solving for x2 is where the turnover store came from.

x2 = −(w1·x1 + b) ÷ w2

At six cells x1 is 0.5, so that is −(−1.5 × 0.5 + 1.2) ÷ (−2.0) = −0.45 ÷ (−2.0) = 0.225, and 0.225 of a 400-unit store is 90 units. Two multiplications, one addition and one division, and the answer was known before any code ran.

xithe i-th input, already scaled; 0.2500 and 0.2400 here
withe weight on that input: how much it counts and which way it pushes
bthe bias, added to the total whatever the inputs are; 1.2 here
zthe total: every input times its weight, plus the bias
a·ba multiplied by b
i=1..nwork out what follows once for each i from 1 to n and add the results
step(z)1 when z is above zero, 0 when it is not: the decision
ywhat the neuron answers, 1 or 0
◆ Note — how much of a nerve cell this borrows

The name came from biology and so did the picture: a nerve cell gathers signals from many others, some pushing it toward firing and some away from it, and it fires once enough of them arrive together. Weights standing for connections of different strengths and a threshold standing for "enough" is a fair sketch of that much. Nearly everything else about a real cell is absent here, and it helps to know which parts.

A nerve cell fires in spikes spread through time, so when a signal arrives matters as much as how strong it is, and a cell that has just fired will not fire again for a while. The unit above has no time in it whatever. Hand it the same row on two different ticks and it answers identically, with no memory that the first one happened. A real cell's connections change strength over hours as a consequence of its own firing; nothing on this page changes anything. Brains run dozens of kinds of cell doing different jobs, with chemistry that speeds some up and slows others down; there is one kind here and it has three numbers. What the borrowed name buys is a useful unit of arithmetic and a mental picture of many small contributions summing to one answer. Every claim past that belongs to biology, and this book will not be making any.

The Neuron type

These land in a new package, and the first thing to decide about it is what it is allowed to import, which turns out to be nothing from the valley at all. A package that cannot reach a plant, a tick or a coordinate cannot quietly consult one, so whatever it answers is a function of the row it was handed and the numbers it holds. That is what makes a bench with no world in it able to print totals a running valley will reproduce, and it is the reason the tests in this volume can be arithmetic instead of ecology.

▣ Build · stage 1 — one neuron, one total, one decision
// internal/mind/neuron.go
// Package mind is the arithmetic a creature decides with. Nothing in
// here knows what a plant is, what a tick is, or which end of the
// valley it is standing in: everything takes a row of numbers and hands
// back a number, so the same weights fed the same senses answer the
// same way on every machine that runs them.
package mind

// Neuron is one decision written down as numbers: a weight for every
// input it reads, and a bias added to the total before anything looks
// at it. The weights say how much each input counts and which way it
// pushes. The bias says what the total is when every input is zero,
// which is the only thing the weights cannot say.
type Neuron struct {
	W []float64 // one weight per input, in the order the inputs arrive
	B float64   // added to the total, whatever the inputs are
}

// Sum multiplies each input by its own weight, adds the products
// together, and adds the bias. It reads exactly as many inputs as it
// has weights: a longer row is read up to len(n.W) and no further, and
// a shorter one is a wiring mistake that panics on the index instead of
// quietly deciding on numbers that are not there.
func (n Neuron) Sum(in []float64) float64 {
	z := n.B
	for i, w := range n.W {
		z += w * in[i]
	}
	return z
}

// Step is the oldest decision there is: 1 when the total is above zero,
// 0 when it is not. Zero itself counts as not above, so a neuron with
// nothing to go on answers no.
func Step(z float64) float64 {
	if z > 0 {
		return 1
	}
	return 0
}

Three small decisions in that file are what keep it standing when the inputs number twenty-four. The weights are a slice and not two named fields, so the same type serves a neuron reading two numbers and one reading twenty-four without a line changing. The bias sits outside that slice because it multiplies nothing: giving it a weight would mean inventing an input that is always 1, which works and reads like a trick. And Sum returns the total instead of the answer, with Step standing apart as its own function. The total is the arithmetic; the decision is a policy about the arithmetic, and they will not stay married for long.

▣ Build · stage 2 — the six situations, every product printed
// cmd/neuron/main.go — the bench: no world, no tick, one neuron

// The two numbers from the valley's opening creature row this bench
// needs. They are here to turn cells and energy units into the 0-to-1
// numbers a sense is scaled to before any weight touches it.
const (
	sight = 12.0  // cells a vision ray runs before it reports the cap
	full  = 400.0 // energy units the store holds when it is full
)

// moment is one situation in the valley written in the units it is
// measured in: how many cells off the nearest plant the eye ray found
// is, and how much energy is in the store. A ray that met nothing
// reports the cap, so "nothing seen" is the moment whose cells are
// sight.
type moment struct {
	name  string
	cells float64
	store float64
}

var six = []moment{
	{"3 cells, store 96", 3, 96},
	{"3 cells, store 400", 3, 400},
	{"11 cells, store 96", 11, 96},
	{"nothing, store 40", sight, 40},
	{"6 cells, store 96", 6, 96},
	{"6 cells, store 80", 6, 80},
}

// senses scales a moment into the two numbers the neuron reads: the
// distance as a share of how far the eye reaches, and the store as a
// share of full.
func senses(m moment) []float64 { return []float64{m.cells / sight, m.store / full} }

// answer reads the step decision back out as a word.
func answer(z float64) string {
	if mind.Step(z) == 1 {
		return "yes"
	}
	return "no"
}

func table(n mind.Neuron, ms []moment) {
	fmt.Printf("\n  %-19s %8s %8s %10s %10s %10s %7s\n",
		"the situation", "dist", "share", "w*dist", "w*share", "total", "answer")
	for _, m := range ms {
		in := senses(m)
		z := n.Sum(in)
		fmt.Printf("  %-19s %8.4f %8.4f %10.4f %10.4f %10.4f %7s\n",
			m.name, in[0], in[1], n.W[0]*in[0], n.W[1]*in[1], z, answer(z))
	}
}
$ go run ./cmd/neuron
one neuron: -1.5000 on the distance, -2.0000 on the store, bias 1.2000
  a ray runs 12 cells before it reports the cap, and a full store holds 400 units

  the situation           dist    share     w*dist    w*share      total  answer
  3 cells, store 96     0.2500   0.2400    -0.3750    -0.4800     0.3450     yes
  3 cells, store 400    0.2500   1.0000    -0.3750    -2.0000    -1.1750      no
  11 cells, store 96    0.9167   0.2400    -1.3750    -0.4800    -0.6550      no
  nothing, store 40     1.0000   0.1000    -1.5000    -0.2000    -0.5000      no
  6 cells, store 96     0.5000   0.2400    -0.7500    -0.4800    -0.0300      no
  6 cells, store 80     0.5000   0.2000    -0.7500    -0.4000     0.0500     yes

Every figure in the first row is one you already worked: 0.2500, 0.2400, −0.3750, −0.4800, 0.3450. The fourth row is the one to sit with. A ray that met nothing reports the cap, which is the same 1.0000 a plant sitting exactly twelve cells out would report, so "nothing met" and "something hopelessly far away" are the same reading and get the same answer. That is the eye admitting that beyond twelve cells it has nothing to say, and a decision built on it inherits the admission.

▣ Build · stage 3 — the turnover predicted, then walked to
// cmd/neuron/main.go

// scan walks the store one whole energy unit at a time and reports the
// last store this neuron still says yes to and the first it says no to,
// without consulting the arithmetic that predicts either of them.
func scan(n mind.Neuron, d float64) (string, string) {
	yes, no := "none", "none"
	for s := 0; s <= int(full); s++ {
		if mind.Step(n.Sum([]float64{d, float64(s) / full})) == 1 {
			yes = fmt.Sprintf("%d", s)
			continue
		}
		if no == "none" {
			no = fmt.Sprintf("%d", s)
		}
	}
	return yes, no
}

// line is the boundary: for each distance, the store at which the total
// crosses zero, worked out from the weights and then found by walking.
func line(n mind.Neuron) {
	fmt.Printf("\n  %6s %8s %11s %10s %10s\n",
		"cells", "dist", "predicted", "last yes", "first no")
	for c := 0.0; c <= sight; c += 2 {
		d := c / sight
		yes, no := scan(n, d)
		fmt.Printf("  %6.2f %8.4f %11.4f %10s %10s\n",
			c, d, -full*(n.W[0]*d+n.B)/n.W[1], yes, no)
	}
	fmt.Printf("  the boundary reaches an empty store at %.4f cells\n", -n.B/n.W[0]*sight)
}
$ go run ./cmd/neuron -mode line
one neuron: -1.5000 on the distance, -2.0000 on the store, bias 1.2000
  a ray runs 12 cells before it reports the cap, and a full store holds 400 units

   cells     dist   predicted   last yes   first no
    0.00   0.0000    240.0000        239        240
    2.00   0.1667    190.0000        189        190
    4.00   0.3333    140.0000        139        140
    6.00   0.5000     90.0000         89         90
    8.00   0.6667     40.0000         39         40
   10.00   0.8333    -10.0000       none          0
   12.00   1.0000    -60.0000       none          0
  the boundary reaches an empty store at 9.6000 cells

The predicted column is the interlude's division, done once per row from the three weights. The two columns beside it never see that division: they walk the store from 0 to 400 one unit at a time and report where the answer actually changed. They agree on all five rows where the boundary lands inside a store a creature can have, and they agree in the exact way the arithmetic said they would, with the last yes one unit under the prediction and the first no sitting on it, because the boundary itself totals 0.0000 and 0.0000 is not above zero. The rows drop by exactly 50 units of store for every 2 cells of distance, which is the 240 − 25 × cells worked out on paper.

The last two rows are the interesting failure of the prediction, and it is not a failure of the arithmetic. At ten cells the predicted turnover is a store of −10.0000, and no creature can hold −10 energy units, so the boundary has left the range of situations that can happen: last yes reads none and the first store to say no is 0. Everything past 9.6 cells is outside this neuron's world.

The boundary a neuron's three numbers draw across two senses A graph whose across axis is how many cells away the plant the ray found is, from 0 to 12, and whose up axis is the energy in the store, from 0 to 400. A straight gold line runs from a store of 240 at 0 cells down to a store of 0 at 9.6 cells. Six marked situations sit on the plane. Everything below and left of the line totals above zero and the creature goes; everything above and right of it totals zero or below and the creature stays. Two of the six sit at 6 cells with stores of 96 and 80, one just above the line and one just below it, where the boundary is a store of 90. WHERE THE ANSWER TURNS OVER 0 100 200 300 400 0 2 4 6 8 10 12 240 with the plant at the eye 9.6 cells total above zero: go total zero or below: stay 1 2 3 4 5 6 boundary here: store 90 across: cells to the plant the ray found up: energy units in the store 1 3 cells, store 96: go 2 3 cells, store 400: stay 3 11 cells, store 96: stay 4 nothing seen, store 40: stay 5 6 cells, store 96: stay 6 6 cells, store 80: go
Figure 51.1 — the six situations and the straight line that sorts them. Weights 1 and 2 tilt the line; the bias slides it up and down without changing the tilt. Points 5 and 6 sit six units above the boundary and ten units below it, and that gap of sixteen is the whole difference between walking and staying put.

The twenty-four-weight neuron

A neuron reading two numbers can look like a small custom thing built for one question. The type has no idea how many inputs it is supposed to have. Hand the same struct twenty-four weights and it reads the whole sensor row, and where twenty-two of those weights are 0, the twenty-two readings under them contribute 0 × whatever and the total does not move. There is no wiring anywhere, no list of senses this neuron subscribes to, no lookup of which entries it cares about. A weight of zero is the entire mechanism by which a neuron declines to listen, and it is the reason a controller can be one flat run of numbers with no structure recorded anywhere beside it.

▣ Build · stage 4 — the same decision spread over the whole row
// cmd/neuron/main.go

// The two entries of the sensor row this neuron listens to. The row is
// nine rays as a distance and a code each, then four gradient numbers,
// then the store's share of full and the speed's share of top, so the
// ray straight ahead is the fifth pair and its distance is entry 8.
const (
	rayDist = 8
	storeAt = 22
)

// wide spreads a two-weight neuron across the whole sensor row: the two
// weights land on the entries they were written for and the other
// twenty-two are zero, which is how a neuron says it is not listening.
func wide(n mind.Neuron) mind.Neuron {
	w := make([]float64, 24)
	w[rayDist] = n.W[0]
	w[storeAt] = n.W[1]
	return mind.Neuron{W: w, B: n.B}
}

// spread writes one moment into a full sensor row. Every entry the
// neuron is not listening to gets the same filler, so running it again
// with a different filler is a direct test of whether it listened.
func spread(m moment, fill float64) []float64 {
	in := make([]float64, 24)
	for i := range in {
		in[i] = fill
	}
	in[rayDist] = m.cells / sight
	in[storeAt] = m.store / full
	return in
}
$ go run ./cmd/neuron -mode row
one neuron: -1.5000 on the distance, -2.0000 on the store, bias 1.2000
  a ray runs 12 cells before it reports the cap, and a full store holds 400 units

  one sensor row, every entry this neuron ignores set to 0.50
    entry      0     1     2     3     4     5     6     7     8     9    10    11
    value   0.50  0.50  0.50  0.50  0.50  0.50  0.50  0.50  0.25  0.50  0.50  0.50
    entry     12    13    14    15    16    17    18    19    20    21    22    23
    value   0.50  0.50  0.50  0.50  0.50  0.50  0.50  0.50  0.50  0.50  0.24  0.50
  twenty-four weights, 22 of them zero
    entry      0     1     2     3     4     5     6     7     8     9    10    11
    weight  0.00  0.00  0.00  0.00  0.00  0.00  0.00  0.00 -1.50  0.00  0.00  0.00
    entry     12    13    14    15    16    17    18    19    20    21    22    23
    weight  0.00  0.00  0.00  0.00  0.00  0.00  0.00  0.00  0.00  0.00 -2.00  0.00

  the situation         two wide   row wide  answer
  3 cells, store 96       0.3450     0.3450     yes
  3 cells, store 400     -1.1750    -1.1750      no
  11 cells, store 96     -0.6550    -0.6550      no
  nothing, store 40      -0.5000    -0.5000      no
  6 cells, store 96      -0.0300    -0.0300      no
  6 cells, store 80       0.0500     0.0500     yes

Six totals in the row wide column, six identical totals in the two wide column beside it, and the twenty-four-weight neuron did twenty-two more multiplications to arrive at the same place. Run it again with -fill -0.9 and every ignored entry in the printed row flips to −0.90 while all six totals stay at 0.3450, −1.1750, −0.6550, −0.5000, −0.0300 and 0.0500. That is the test that catches the break: if any of those numbers had moved, one of the twenty-two zeros would not have been a zero.

◆ Note — the cost of listening to nothing

Twenty-two multiplications by zero is twenty-two multiplications the machine performs in full and then throws away, and a valley intends to run thousands of these every tick. The cheap-looking fix is to skip the zeros, keeping a list of which entries matter and looping over that instead. It is almost always the wrong trade. A flat loop over a contiguous slice is the pattern processors are best at; a loop that consults an index list first reads two pieces of memory to do one multiplication, and the branch it adds is unpredictable. Measure before believing anyone about this, including the paragraph you are reading.

The bias boundary

Two of the three numbers in this neuron have an obvious meaning. The weights say how much each reading counts. The bias says nothing about any reading, has no sense attached to it, and was introduced above with the frank admission that the arithmetic did not work without it. That reads like a patch, and the natural instinct on meeting a patch is to take it out and let the honest part of the model carry the load.

⚠ Worked failure — the creature that starved in a field of food
$ go run ./cmd/neuron -mode nobias
one neuron: -1.5000 on the distance, -2.0000 on the store, bias 0.0000
  a ray runs 12 cells before it reports the cap, and a full store holds 400 units

  the situation           dist    share     w*dist    w*share      total  answer
  3 cells, store 96     0.2500   0.2400    -0.3750    -0.4800    -0.8550      no
  3 cells, store 400    0.2500   1.0000    -0.3750    -2.0000    -2.3750      no
  11 cells, store 96    0.9167   0.2400    -1.3750    -0.4800    -1.8550      no
  nothing, store 40     1.0000   0.1000    -1.5000    -0.2000    -1.7000      no
  6 cells, store 96     0.5000   0.2400    -0.7500    -0.4800    -1.2300      no
  6 cells, store 80     0.5000   0.2000    -0.7500    -0.4000    -1.1500      no
  0 cells, store 0      0.0000   0.0000    -0.0000    -0.0000     0.0000      no

Seven situations, seven refusals. The creature with a plant three cells off and a store a quarter full says no. The creature with a fifth of its store left and a plant six cells ahead of it says no. Drop this controller into the valley and it stands still until its store empties, in a valley that grew four species of plant for it.

The last row was added to find out how bad it gets, and it is the diagnosis. Put the plant at zero cells, right at the eye, and empty the store completely: the best case available, both readings at their most encouraging. Both products are zero, the total is 0.0000, and Step answers no, because zero is not above zero. With the bias gone, the largest total this neuron can produce over every situation the valley can present is exactly zero. It does not lean toward refusing. It cannot do anything else.

The reasoning from the run to the cause is short once the last row is on the page. Both weights are negative and both readings are at least zero, so every product is at most zero, so their sum is at most zero. Nothing that varies has any chance of lifting it. The bias was the only term in the whole expression that does not get multiplied by a reading, and it was carrying the entire question of what this creature does by default.

Two details, in case the fix looks like luck. The first is the pair of -0.0000 entries in the last row, which is what a negative weight times a zero reading prints in binary floating point. Negative zero adds like any other zero and changes no total; it is the printer being honest, not the arithmetic wobbling.

The second is the objection that the real mistake was upstream, in encoding the eye's reading as a distance where small is good. Encode it as nearness instead, 1 at the eye and 0 at the cap, and the weight on it turns positive and can lift the total on its own. Try it: one function builds the re-encoded neuron out of the original, and a second runs the two of them over the same six situations and reports whether they ever disagree.

// cmd/neuron/main.go

// near is the same neuron with the distance sense re-encoded as
// nearness, 1 at the eye and 0 at the cap. Multiplying out 1 minus the
// distance flips that weight's sign and drops the old weight into the
// bias. The decision is not touched.
func near(n mind.Neuron) mind.Neuron {
	return mind.Neuron{W: []float64{-n.W[0], n.W[1]}, B: n.B + n.W[0]}
}

func nearMode(n mind.Neuron) {
	e := near(n)
	fmt.Printf("  nearness form: %.4f on the nearness, %.4f on the store, bias %.4f\n",
		e.W[0], e.W[1], e.B)
	fmt.Printf("\n  %-19s %10s %10s %7s %7s\n",
		"the situation", "distance", "nearness", "agree", "answer")
	for _, m := range six {
		in := senses(m)
		zd, zn := n.Sum(in), e.Sum([]float64{1 - in[0], in[1]})
		agree := "yes"
		if answer(zd) != answer(zn) {
			agree = "no"
		}
		fmt.Printf("  %-19s %10.4f %10.4f %7s %7s\n", m.name, zd, zn, agree, answer(zd))
	}
}
$ go run ./cmd/neuron -mode near
one neuron: -1.5000 on the distance, -2.0000 on the store, bias 1.2000
  a ray runs 12 cells before it reports the cap, and a full store holds 400 units
  nearness form: 1.5000 on the nearness, -2.0000 on the store, bias -0.3000

  the situation         distance   nearness   agree  answer
  3 cells, store 96       0.3450     0.3450     yes     yes
  3 cells, store 400     -1.1750    -1.1750     yes      no
  11 cells, store 96     -0.6550    -0.6550     yes      no
  nothing, store 40      -0.5000    -0.5000     yes      no
  6 cells, store 96      -0.0300    -0.0300     yes      no
  6 cells, store 80       0.0500     0.0500     yes     yes

The weight on the eye's reading did turn positive, 1.5000 instead of −1.5000, and the bias did not go away. It moved, from 1.2000 to −0.3000. Nearness is 1 minus the distance, so multiplying out −1.5 × (1 − nearness) gives 1.5 × nearness − 1.5, and the −1.5 lands on the bias, taking 1.2 down to −0.3000, which is the number the bench printed. All six totals agree to the last digit in both forms, so nothing about the creature changed. Re-encoding an input moves the bias around. It never removes it.

Why three numbers draw a line

Figure 51.1 is the general statement of this chapter, and read it with the creature taken out of it. Two inputs make a flat plane of possible situations. The condition z = 0 is one straight line across that plane, and the neuron's answer is nothing more than which side of the line a situation fell on. The two weights set the line's tilt: making the distance weight more negative steepens it, so distance starts mattering more per cell. The bias slides the whole line without tipping it, which is why halving it from 1.2 to 0.6 pulls the intercept from a store of 240 down to 120 and the reach from 9.6 cells in to 4.8, with the tilt untouched.

Add a third input and the line becomes a flat sheet cutting through a three-dimensional space of situations. Add twenty-four and there is no picture left, but every word of the description survives: one flat cut, the weights setting its tilt, the bias setting where it sits, the answer being which side you landed on. The arithmetic does not get harder as the senses multiply. It gets longer.

Two consequences fall straight out of that, and both matter more than the neuron does. The first is about scale. What a neuron compares is products, so an input that ranges from 0 to 12 contributes twelve times as much as one ranging from 0 to 1 at the same weight, and it would take a weight a twelfth the size to have an equal say. Feed a controller raw cells beside raw shares and its weights spend their range compensating for your units instead of expressing a preference. That is the reason every entry in the sensor row is scaled before it is handed over.

The second is about limits, and it is best said now rather than discovered later. A straight cut is all one neuron has. Any preference that needs two separate regions of the plane to answer yes, say a creature that chases a plant at close range and also bolts for a distant one when its store is nearly gone, cannot be drawn with one line however the three numbers are chosen. You can check that claim with a ruler on Figure 51.1: put a dot in the bottom left and a dot in the bottom right, insist that everything between them answers no, and no single straight line will do it. That limitation is exact, it is a property of the arithmetic and not of this example, and it is the reason a controller is never one neuron.

One last property of Step belongs on this page, because the loss in it is deliberate. A total carries a size as well as a sign. The most these three numbers can ever produce is 1.2000, at a plant sitting on the eye with an empty store, and the least that still counts as going is a hair over zero. Step flattens that whole range to the digit 1. For one yes-or-no question the flattening is exactly right, and it is how 0.3450 and 0.0500 leave the table as the same word. For a creature picking one of six things to do it is a problem you can see coming: six neurons all answering 1 have said nothing whatever about which of the six. The totals knew. The step threw it away.

✓ Checkpoint — one neuron, worked and written
  • Given two scaled readings, two weights and a bias, produce the total with a calculator and say which way Step answers, including the case where the total lands exactly on 0.0000.
  • Turn 3 cells into 0.2500 and 96 energy units into 0.2400, and say what the eye reports when its ray runs the full twelve cells and meets nothing.
  • Handed a distance, solve for the store at which the answer turns over before running anything, and get 90 units at six cells the same way the bench did.
  • Explain why a boundary predicted at a store of −10 makes the last yes column read none.
  • Say what a weight of 0 does to the reading under it, and why the twenty-four-weight neuron and the two-weight one print the same six totals.
  • Shown a controller that refuses every situation it is offered, check whether anything in it can lift the total when every reading is zero.
⚡ Exercises — try first, then reveal
Exercise 1 — halve the bias. Predict the whole boundary table for a bias of 0.6 before you run go run ./cmd/neuron -mode line -bias 0.6, including the intercept, the step per two cells, and the distance at which the boundary reaches an empty store.

The bias slides the line without tilting it, so the step per two cells is unchanged: still 50 units of store. The intercept halves with the bias, from 240 to 120, and the run prints 120.0000, 70.0000, 20.0000 and then negatives from six cells on. The reach is 0.6 ÷ 1.5 × 12 = 4.8 cells, and the last line says exactly that. Half the bias buys half the appetite: this creature will not walk five cells for a meal at any store it can hold.

Exercise 2 — make it greedier and watch the boundary leave. Run go run ./cmd/neuron -mode line -ws -1, which halves how much a full store discourages it. One row of that table does something no row has done yet. Find it and account for it.

The zero-cell row predicts a turnover at a store of 480.0000, and the columns beside it read 400 and none: there is no store this creature can hold that makes it refuse a plant at the eye, because the store only goes to 400 and the boundary now sits above it. The prediction is right about arithmetic the valley has no way to stage. Every other row behaves, stepping down 100 units of store per 2 cells instead of 50, since a weaker store weight needs more store to cancel the same distance.

Exercise 3 — find the weight that ignores the store. Without running anything, work out what the six situations answer if the store weight is 0 and the other two numbers are left alone. Then check with go run ./cmd/neuron -ws 0.

With no weight on the store the total is 1.2 − 1.5 × dist, so the answer depends on distance alone and turns over at 0.8, which is 9.6 cells. The four situations at three and six cells all say yes, including the one with a completely full store, and the eleven-cell and nothing-seen situations say no. The run prints 0.8250 twice for the two three-cell rows and 0.4500 twice for the two six-cell rows: identical totals for situations that differ only in a reading this neuron has stopped hearing. A creature wired this way eats until it bursts, which is a sentence about one number.

One neuron settles one question, and the six outputs a creature needs are six questions asked of the same twenty-four readings. Nothing above stops you from writing six of these and running each in turn, and that is very nearly the right answer: a row of neurons over one row of inputs, their weights laid end to end in a single slice, indexed the way the terrain grid is indexed. What that arrangement makes possible, and what it quietly cannot do however many rows are stacked, is the next thing to work out.