Chapter 26 · Artificial Neural Networks
Part VIII opens with the model that named an industry. A bank has 10,000 customer records and one anxious question: who is about to leave? The artificial neural network answers it — and this chapter builds it from first principles. The neuron is Chapter 11's logistic unit; the network is logistic units stacked in layers; the training is calculus run backwards. Every step is executed by hand on a tiny network — forward pass, chain rule, one learning step with exact arithmetic — before Keras trains the real 12-6-6-1 churn network and every number on the page comes from that actual run. And because the next question is always "can it predict a price?", the chapter closes the loop: three changes turn the yes/no network into a regression network, which then faces tomorrow's AAPL — and the honest baseline that beats it.
🎯 Learning Objectives
- Map the biological neuron onto the artificial one — dendrites, axon, synapse — and define the neuron as a weighted sum plus activation
- State what the inputs and output of a neuron are: one row of features in, one value out; scaled, and shaped by the output type
- Read the four classic activation curves — threshold, sigmoid, rectifier, tanh — and explain why ReLU lives in hidden layers and sigmoid at the output
- Explain what hidden neurons learn — specialist detectors that combine inputs into new features — and why training follows the slope of the cost (gradient descent: batch, stochastic, mini-batch) instead of brute force
- Count a network's parameters by hand (12-6-6-1 → 127) and write the forward pass in matrix form
- Run backpropagation by hand on a 2-2-1 network: forward pass, error signal δ, every gradient, one SGD step — exact numbers
- Build the network in Keras and defend each choice — Sequential vs Functional, units, per-layer activation, initializer, optimizer and loss pairing — plus the fit() guards (validation split, early stopping, class weights)
- Train the real churn network and read the genuine epoch curve, confusion matrix, and threshold trade-offs
- State honestly when an ANN beats the earlier models — and when it is overkill
- Convert the classifier into a regressor — linear output head, MSE loss — and run it on next-day returns with the time-split and naive-baseline discipline the task demands
1. The Neuron: From Biology to Arithmetic
1.1 Why copy the brain?
The human brain is the most powerful learning machine we know. Deep learning copies its structure in the hope that a machine built the same way can learn too. The copy starts small: with a single neuron.
Real neurons were first drawn in detail by the Spanish neuroscientist Santiago Ramón y Cajal in 1899. He stained brain tissue, looked at it under a microscope, and drew what he saw. Each neuron has three parts: a cell body, short branches called dendrites, and one long tail called the axon. One neuron alone can do very little — like one ant. Millions of neurons, connected, learn to see, hear and think — like an ant colony. That is the bet of deep learning: connect enough simple units and something powerful emerges.
Two roles carry over. Dendrites receive incoming signals. The axon transmits the neuron's own signal out. A signal travels down one neuron's axon and arrives at the next neuron's dendrites — and the two never touch. The connection where the signal crosses is the synapse.
Artificial neurons borrow the word. Every connection between two artificial neurons is called a synapse. This avoids an awkward question: is a given connection an axon or a dendrite — and whose? A synapse is simply where a signal passes. That is the only meaning the term carries in this book.
1.2 The artificial neuron
An artificial neuron (also called a node) receives input signals through its synapses and produces one output signal. The inputs come either from the input layer or from other hidden neurons — the mechanism is the same. In the brain, the input layer plays the role of the senses: everything you see, hear and touch arrives as electrical signals. In a network, the input layer is simply the row of feature values.
Two facts about the inputs decide whether the network works at all:
- Inputs describe one single observation — one row of your dataset. Age, salary, balance: all descriptors of the same customer. The output the network returns is for that same row. Rows are never mixed; think of one row fed to a linear regression, in and out.
- Inputs must be scaled. The neuron adds its inputs together, so all of them should sit in a similar range — otherwise a salary of 100,000 drowns a 0/1 flag in the sum. Two standard fixes:
normalization: xᵢ² = (xᵢ − min) / (max − min) → all values in [0, 1]
1.3 What the output can be
The neuron's output value can take three forms:
| Output type | Example | Output layer |
|---|---|---|
| Continuous (a number) | a price | one output neuron |
| Binary (yes/no) | customer exits or stays | one output neuron — sigmoid or threshold |
| Categorical (k classes) | France / Germany / Spain | k output neurons — dummy variables, one per class |
A categorical output needs several output values, not one: the categories become dummy variables. This chapter's task is binary (churn or not), so the output layer will hold a single sigmoid neuron.
1.4 Weights: where learning lives
Every synapse carries a number called a weight. The weight decides how much of each input signal passes through: important signals get large weights, unimportant ones get weights near zero. Training a network means adjusting every weight in every synapse until the predictions fit the data. That process — gradient descent driven by backpropagation — is Section 3's subject. Whenever you hear "the network learned", read: its weights changed.
1.5 Inside the neuron: three steps
Signals arrive at the neuron. Then, in order:
- Compute the weighted sum of the inputs — each input times its weight, plus a bias.
- Apply the activation function to that sum.
- Pass the result on as the neuron's output signal.
Look closely: with g = sigmoid, this is exactly the logistic regression of Chapter 11 — a weighted sum squashed to (0, 1). One neuron is one logistic unit. Stack such units in layers, each feeding the next, and the network can bend and recombine its inputs into representations no single linear boundary could reach. Why the non-linear g is essential is Section 2's warning; the calculus of adjusting the weights is Section 3's work.
1.6 The activation menu: four classic functions
The activation function decides whether — and how strongly — a neuron passes its signal on. Four classics cover most of the field.
| Activation | Formula | Range | Used for |
|---|---|---|---|
| Threshold (step) | 1 if z ≥ 0, else 0 | {0, 1} | Yes/no output — the original perceptron |
| ReLU (rectifier) | max(0, z) | [0, ∞) | Hidden layers — the modern default |
| Sigmoid | 1 / (1 + e−z) | (0, 1) | Binary output — a probability |
| Tanh | (eᶿ − eḝ) / (eᶿ + eḝ) | (−1, 1) | Hidden layers (older nets, recurrent nets) |
| Softmax | eᶿᵣ / Σ eᶿᵢ | (0, 1), sums to 1 | Multi-class output (Ch 13's multiclass cousin) |
In one sentence each. Threshold is a rigid switch: below zero it passes 0, at zero or above it passes 1 — yes or no, nothing in between, and (Section 3) no useful gradient to learn from. Sigmoid is its smooth cousin: the same 0-to-1 range with soft slopes, which is why logistic regression uses it and why gradient descent can train it. Rectifier passes 0 for negative sums and the sum itself above zero — cheap to compute and the hidden-layer default. Tanh is sigmoid-shaped but centred at zero, from −1 to +1, which keeps hidden signals balanced around 0.
Suppose the dependent variable is binary — exit or stay. Which functions fit? Two. The threshold function answers directly with 0 or 1. The sigmoid answers with the probability of getting 1 — round it for a hard answer, or keep it and apply Chapter 18's threshold logic. The standard recipe for binary classification combines two of the four: ReLU in the hidden layers, sigmoid at the output — exactly the network Section 2 builds.
2. The Network and Its Algebra
2.1 What do hidden neurons actually learn?
A property-price example makes the hidden layer concrete. Four inputs: area (sq ft), bedrooms, distance to the city and age. One output: the price. The network is already trained — the weights are fixed. What did its hidden neurons each decide to look for?
Each neuron keeps weights only on the inputs it cares about; the rest get weights of zero and are simply ignored. One neuron watches area + distance: it fires for properties close to the city that still have unusually large space — the "unfair square footage" deals. Another watches area + bedrooms + age: large, many-bedroom, new builds — exactly what young families buy. A third watches age alone: under 100 years it stays silent, but past 100 the home becomes historic and the neuron's contribution rises — the rectifier function in action: zero until a threshold, then a growing signal.
That is the trick hidden layers play: each neuron combines raw inputs into a new attribute nobody hand-designed — "family-home-ness", "historic-ness" — and fires only when its pattern is present. One neuron cannot predict a price. Many specialists, combined in the output layer, can — the ant colony again.
2.2 The churn network
The churn network, exactly as the source builds it: 12 inputs → 6 ReLU neurons → 6 ReLU neurons → 1 sigmoid output. Twelve inputs because the raw 10 features become 12 after encoding (Section 4). The output is a single number — the probability the customer leaves. Its 12 hidden detectors are trained, not narrated: some may watch Germany + inactive, others balance + products — the same specialisation, discovered by gradient descent rather than told.
2.3 Counting the parameters, by hand
A layer with n inputs and m neurons owns a weight per edge plus a bias per neuron: n × m + m parameters. The real network:
| Layer | Weights | Bias | Subtotal |
|---|---|---|---|
| input → hidden 1 | 12 × 6 = 72 | 6 | 78 |
| hidden 1 → hidden 2 | 6 × 6 = 36 | 6 | 42 |
| hidden 2 → output | 6 × 1 = 6 | 1 | 7 |
| Total | 127 — verified: ann.count_params() = 127 |
In matrix form, one layer is one line: a = g(Wᵗx + b), where Wᵗ is that layer's weight matrix. The whole forward pass is three such lines chained — a¹ = ReLU(W₁ᵗx + b₁), then a² = ReLU(W₂ᵗa¹ + b₂), then ŷ = σ(W₃ᵗa² + b₃). GPUs earn their keep by doing these matrix products at enormous speed.
3. Learning: Gradient Descent & Backprop, by Hand
There are two ways to get a program to do what you want. The first: write every rule yourself — pointy ears means cat, floppy ears means dog — and chase every exception by hand. The second: build a network, show it inputs and the correct answers, and let it find the rules on its own. Deep learning is the second road. The name of the game from here is learning: how do the weights get set without anyone telling them?
The simplest network — input layer straight into one output neuron — is the perceptron, built by Frank Rosenblatt in 1957 as the first model meant to learn and adjust itself. Everything in this section happens in the perceptron first, then scales up. One convention before starting: y is the actual value from the data; ŷ ("y-hat") is the network's prediction. Learning means making ŷ agree with y.
3.1 The learning loop and the cost function
Feed one row in. The weights scale it, the neuron activates, out comes ŷ. Compare ŷ to the actual y. The difference is the error, and the cost function is the number that measures it. The classic choice — half the squared error:
3.2 Why not just try every weight? (brute force fails)
One honest idea first: why not try thousands of candidate weights, compute the cost for each, and keep the best? For one weight, it works. For a real network it dies, and the property network of Section 2.1 shows why. Fully connected it has 25 weights (4×5 + 5×1). Test 1,000 candidate values per weight:
10⁷ⁱ ÷ (93 × 10¹⁵ tests/second) ≈ 10⁵⁸ seconds ≈ 10⁵⁰ years
3.3 Gradient descent: follow the slope
The working idea is cheaper: stand on the cost curve and look which way is downhill. Pick a starting weight, work out the slope of the cost at that point (the gradient — one derivative), and step the weight the downhill way. At the new point, check the slope again. Repeat. Like a ball rolling down a valley: not a straight ride, a tightening zigzag that settles at the bottom. The bottom is the minimum of the cost — the best weights we can reach from where we started.
In symbols, one step of the walk is the update rule — and it is the nudge rule this chapter's hand example will execute:
3.4 Batch, stochastic, mini-batch — when do the weights update?
Plain gradient descent quietly assumes the cost curve is convex: one bowl, one minimum. Real cost surfaces are often non-convex (Figure 26.7, right) — a different cost function, or a squared error stretched over many dimensions, can grow extra valleys. Descent that updates carefully can then settle in a local minimum and miss the best weights. The fix is when you update:
| Method | Weights updated… | Character |
|---|---|---|
| Batch gradient descent | once per full pass — all rows first, then one update | deterministic (same start → same result); needs all data in memory; can settle in a local minimum |
| Stochastic gradient descent (SGD) | after every single row | faster and lighter (one row at a time); noisy steps jump out of local minima; results vary run to run — it is random by design |
| Mini-batch gradient descent | after each small batch of rows | the practical middle — steadier than SGD, cheaper than batch. The churn network trains this way: batch_size = 32 |
So the full learning story over a dataset: feed every row (all through the same network — the weights are shared, not per-row), accumulate the cost, update the weights by following the slope, and start the next pass. The whole cycle — forward pass, cost, backward pass, weight update — goes by the name backpropagation.
3.5 Backpropagation by hand: the 2-2-1 network
The network starts with random weights and must learn the 127 numbers of Section 2 — but the hard part is knowing ∂L/∂w for weights buried deep in the stack. That is backpropagation proper: the chain rule applied systematically from the output backwards. Watch it work on a network small enough to hold in your head: 2 inputs → 2 ReLU neurons → 1 sigmoid output, exact numbers throughout.
| Symbol | Value | Meaning |
|---|---|---|
| x = (x₁, x₂) | (1, 2) | one training example |
| y | 1 | true label (this customer churned) |
| w₁₁, w₂₁; b₁ | 0.5, 0.2; 0.1 | into hidden neuron 1 |
| w₁₂, w₂₂; b₂ | −0.3, 0.8; −0.1 | into hidden neuron 2 |
| W₀ = (0.7, −0.4); b₀ | hidden → output |
3.6 Forward pass
z₂ = −0.3(1) + 0.8(2) − 0.1 = 1.2 → a₂ = ReLU(1.2) = 1.2
z₀ = 0.7(1.0) − 0.4(1.2) + 0.05 = 0.27 → ŷ = σ(0.27) = 0.5671
L = −ln(0.5671) = 0.5672
3.7 Backward pass — the chain rule, one arrow at a time
Backpropagation's engine is the error signal δ at each neuron: how much the loss would change if that neuron's pre-activation z moved. Signals flow backwards, each layer's δ built from the next layer's:
With δ₀ in hand, the output layer's gradients are one multiplication each (∂L/∂w = δ × the input arriving on that edge):
| Gradient | Computation | Value | Meaning |
|---|---|---|---|
| ∂L/∂w₀₁ | δ₀ × a₁ = −0.4329 × 1.0 | −0.4329 | increase this weight |
| ∂L/∂w₀₂ | δ₀ × a₂ = −0.4329 × 1.2 | −0.5195 | increase (more: busier path) |
| ∂L/∂b₀ | δ₀ | −0.4329 | raise the bias |
Now push δ₀ back into the hidden layer. Each hidden neuron's error is the δ₀ flowing in through its output weight, multiplied by its own activation's slope — ReLU's derivative is 1 where z > 0 (both neurons here, z₁ = 1.0, z₂ = 1.2) and 0 where z < 0:
δ₂ = (w₀₂ · δ₀) · ReLU′(z₂) = (−0.4) × (−0.4329) × 1 = +0.1732
Hidden-layer gradients, same one-multiplication rule:
| Gradient | Computation | Value |
|---|---|---|
| ∂L/∂w₁₁ | x₁ × δ₁ = 1 × (−0.3030) | −0.3030 |
| ∂L/∂w₂₁ | x₂ × δ₁ = 2 × (−0.3030) | −0.6061 |
| ∂L/∂w₁₂ | x₁ × δ₂ = 1 × 0.1732 | +0.1732 |
| ∂L/∂w₂₂ | x₂ × δ₂ = 2 × 0.1732 | +0.3463 |
| ∂L/∂b₁, ∂L/∂b₂ | δ₁, δ₂ | −0.3030, +0.1732 |
3.8 One learning step
Apply the update with learning rate η = 0.1 — subtract 0.1 × each gradient. For example w₀₁: 0.7 − 0.1(−0.4329) = 0.7433 (up, as promised), and w₁₂: −0.3 − 0.1(0.1732) = −0.3173 (down). Re-running the forward pass with all nine updated numbers gives the verdict:
3.9 The full training recipe — seven steps
Everything from this section, as one checklist. These are the
steps the Keras fit() call of Section 4 runs for
you — worth knowing what it is actually doing:
- Initialize the weights to small random numbers near zero — never exactly zero (Keras does this automatically).
- Feed in one row: each feature goes to its own input node.
- Forward-propagate, left to right: weights scale how strongly each neuron activates, layer after layer, until ŷ comes out.
- Compare ŷ with the actual y and measure the error — the cost.
- Back-propagate the error, right to left, and update every weight in the same pass, each in proportion to its share of the error. The learning rate η sets the size of the nudge.
- Repeat steps 2–5 after each row (stochastic) or after each batch (batch / mini-batch).
- Epoch and repeat: one full pass over the training set is one epoch; run epoch after epoch until the cost stops falling.
4. Training the Churn Network
4.1 The data, prepared
Churn_Modelling.csv: 10,000 customers, 14
columns, label Exited (1 = left the bank).
Chapter 3's full toolkit applies before a single neuron fires:
- Drop identifiers — RowNumber, CustomerId, Surname: unique per row, zero generalising power (Ch 3's first rule).
- Label-encode Gender — Female = 0, Male = 1 (alphabetical, sklearn's LabelEncoder convention).
- One-hot Geography — France, Germany, Spain → 3 binary columns placed first in X; 10 features become 12.
- Split 8,000 train / 2,000 test (seed 0) — test holds 1,595 stayers, 405 churners (20.25%).
- Scale — StandardScaler fit on train only. Non-negotiable for networks: salary (≈10⁵) and HasCrCard (0/1) must share a scale or the gradients drown (Ch 12's lesson, doubled).
4.2 The compile step — three choices
| Choice | Here | Why |
|---|---|---|
| Optimizer | Adam | SGD with per-parameter adaptive learning rates and momentum — the sensible default; converges fast without hand-tuning η |
| Loss | binary_crossentropy | Sigmoid output's natural partner (Ch 11/25) — and the source of the clean δ₀ = ŷ − y |
| Batch size / epochs | 32 / 100 | mini-batch gradient descent (Section 3.4): 8,000/32 = 250 weight updates per epoch; 100 epochs = 25,000 updates total |
4.3 The real learning curve
Training loss and accuracy at eight checkpoints of the actual run (seed 0, the numbers this chapter's model produced):
| Epoch | 1 | 5 | 10 | 20 | 30 | 50 | 75 | 100 |
|---|---|---|---|---|---|---|---|---|
| Loss | 0.589 | 0.426 | 0.380 | 0.347 | 0.343 | 0.338 | 0.335 | 0.332 |
| Train acc | 0.712 | 0.803 | 0.831 | 0.859 | 0.860 | 0.862 | 0.864 | 0.865 |
Two honest readings. First, the plateau is real: by epoch 20 the network has captured nearly everything 127 parameters can hold; the remaining 80 epochs shave a sliver — diminishing returns, exactly like Chapter 6's bias-variance story told in epochs instead of degrees. Second, no memorisation: final training accuracy 0.8645 versus test accuracy 0.8620 (next section) — a gap of 0.3 points. With only 127 parameters against 8,000 examples, this network is far too small to memorise; deep learning's overfitting problem arrives with millions of parameters (Section 7).
5. Implementation in Python (Keras)
The complete pipeline first — then the build step goes under the microscope. Every line of it has options, and knowing those options is the difference between using Keras and understanding it.
import numpy as np
import pandas as pd
import tensorflow as tf
# ----- Part 1: data preparation (Chapter 3's full toolkit)
dataset = pd.read_csv('Churn_Modelling.csv')
X = dataset.iloc[:, 3:-1].values # drop RowNumber, CustomerId, Surname
y = dataset.iloc[:, -1].values # Exited
from sklearn.preprocessing import LabelEncoder, OneHotEncoder, StandardScaler
from sklearn.compose import ColumnTransformer
from sklearn.model_selection import train_test_split
le = LabelEncoder()
X[:, 2] = le.fit_transform(X[:, 2]) # Gender: Female=0, Male=1
ct = ColumnTransformer(
transformers=[('encoder', OneHotEncoder(), [1])], # Geography
remainder='passthrough')
X = np.array(ct.fit_transform(X)) # France/Germany/Spain first 3 cols → 12 features
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=0)
sc = StandardScaler()
X_train = sc.fit_transform(X_train) # fit on train ONLY (Ch 3 rule)
X_test = sc.transform(X_test)
# ----- Part 2: build the ANN (12 → 6 → 6 → 1, 127 parameters)
ann = tf.keras.models.Sequential([
tf.keras.layers.Input(shape=(X_train.shape[1],)),
tf.keras.layers.Dense(units=6, activation='relu'),
tf.keras.layers.Dense(units=6, activation='relu'),
tf.keras.layers.Dense(units=1, activation='sigmoid'),
])
# ----- Part 3: compile and train
ann.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy'])
ann.fit(X_train, y_train, batch_size=32, epochs=100, verbose=0)
# ----- Part 4: predict and evaluate
y_pred = (ann.predict(X_test, verbose=0) > 0.5).astype(int)
from sklearn.metrics import confusion_matrix, accuracy_score
print(confusion_matrix(y_test, y_pred)) # [[1511 84]
# [ 192 213]]
print(accuracy_score(y_test, y_pred)) # 0.862
# The homework customer: France, credit 600, Male, 40, tenure 3,
# balance 60000, 2 products, card, active, salary 50000
p = ann.predict(sc.transform(
[[1, 0, 0, 600, 1, 40, 3, 60000, 2, 1, 1, 50000]]), verbose=0)
print(p) # [[0.0287]] → stays (2.9% churn risk)
Listing 26.1 — The complete churn ANN (source: artificial_neural_network.py, modernised: Input layer, verbosity).
5.1 Building the ANN, line by line — and the options at each
ann = tf.keras.models.Sequential([
tf.keras.layers.Input(shape=(X_train.shape[1],)),
tf.keras.layers.Dense(units=6, activation='relu'),
tf.keras.layers.Dense(units=6, activation='relu'),
tf.keras.layers.Dense(units=1, activation='sigmoid'),
])
ann.summary() # sanity check: must print "Total params: 127"
Listing 26.2 — Part 2 alone. Five lines, five decisions — model type, input shape, layer type, width, activation.
Sequential or Functional?
Sequential builds the network as a
sequence of layers — input layer, then the fully
connected layers one after another, then the output layer —
which is exactly the shape every network in this book has.
It is not the only shape networks come in: the other family
is the computational graph, where neurons connect
outside any layer order (restricted Boltzmann machines are
the classic example — advanced territory, not in this
book). And within layer-shaped networks, the Functional API
covers graphs that branch: two inputs
(image + metadata), two outputs (price + class), shared
layers, or the skip connections of Chapter 29. Same
building blocks — only the wiring differs:
# style A — the source course's step-by-step style: .add() one layer at a time
ann = tf.keras.models.Sequential()
ann.add(tf.keras.layers.Input(shape=(12,)))
ann.add(tf.keras.layers.Dense(units=6, activation='relu'))
ann.add(tf.keras.layers.Dense(units=6, activation='relu'))
ann.add(tf.keras.layers.Dense(units=1, activation='sigmoid'))
# style B — the same network, Functional API: layers called like functions
inputs = tf.keras.Input(shape=(12,))
x = tf.keras.layers.Dense(6, activation='relu')(inputs)
x = tf.keras.layers.Dense(6, activation='relu')(x)
outputs = tf.keras.layers.Dense(1, activation='sigmoid')(x)
ann = tf.keras.Model(inputs=inputs, outputs=outputs)
Listing 26.3 — Two more spellings of the same 12 → 6 → 6 → 1 network. Listing 26.2's list-of-layers form and style A's .add() form are interchangeable; the Functional form (B) is for branching graphs.
Worth reading the import path once, slowly:
tf.keras — the Keras library, part of
TensorFlow since version 2.0 (before that they were
separate installs); tf.keras.models — the
module holding model-level classes like
Sequential; tf.keras.layers — the
module holding every layer you can add: Dense, Dropout,
Conv2D, and the rest. The same layer zoo exists in PyTorch
under other names — the concepts transfer one-to-one.
The Input layer declares what one row looks
like: 12 numbers, no batch dimension. In the
.add() style you can even omit it — the first
Dense creates the input layer automatically
from its own input specification. The older style passed
input_dim=12 to the first Dense layer for the
same reason; the explicit Input object is the
modern recommended form — cleaner summary(),
safer model rebuilds.
Dense means fully connected —
every input reaches every neuron in the layer, which is why
hidden layer 1 owns 12×6 + 6 = 78 parameters (Section 2.3's
count). The .add() method accepts any layer
from the layers module: a Dense for the fully
connected stacks of this chapter, a Dropout
layer to fight overfitting, a Conv2D for images
(Ch 27), recurrent layers for sequences (Ch 29) — all
created the same way, all trained by the same Section 3
machinery. For tabular data, Dense is the tool.
units=6 — how many neurons per hidden
layer? This is the most asked question in deep
learning, and the honest answer is: no rule of thumb —
experimentation. Units is a
hyperparameter: a number
you choose before training, which training never
adjusts — unlike the weights, which training exists to
adjust. Common starting points:
- Between the input and output sizes — the classic heuristic. Here (12 + 1) / 2 ≈ 6, which is exactly the 6 the source picked (after trying several values and landing on roughly the same accuracy each time).
- Powers of two (32, 64, 128) — no magic in the numbers, but memory alignment makes them efficient on GPUs.
- Too few neurons underfit (not enough detectors for Section 2.1's specialisations); too many overfit and train slowly — 127 parameters against 8,000 rows is comfortable; 127,000 would not be.
Start small, grow only when validation results say so.
activation — chosen per layer,
from Section 1.6's menu. Hidden layers take
'relu' (the default; 'tanh' the
older alternative). The output layer's activation is decided
by the problem, and it drags the loss function along with it:
| Problem | Output layer | Loss |
|---|---|---|
| Binary (this chapter) | Dense(1, activation='sigmoid') | binary_crossentropy |
| Multi-class, one-hot labels | Dense(k, activation='softmax') | categorical_crossentropy |
| Multi-class, integer labels | Dense(k, activation='softmax') | sparse_categorical_crossentropy |
| Regression (a price) | Dense(1) — linear, no activation | mse (or mae) |
How many hidden layers? Also no formula — a ladder of ambition:
- 0 hidden layers — the perceptron of Section 3: only linear (or logistic) boundaries can be drawn.
- 1 hidden layer — called a shallow network; already a universal approximator, enough for many tabular problems.
- 2 — this chapter. Adding the second hidden layer is literally what makes the model deep: the "deep" in deep learning counts hidden layers. Two levels of feature composition — detectors built from detectors.
- Many — earns its keep on images, audio and text (Ch 27 onward), where features nest deeply. On small tabular data, depth usually just overfits.
Where do the weights start?
kernel_initializer. Step 1 of the
seven-step recipe (Section 3.9): small random numbers near
zero, never zero. Keras does this automatically —
glorot_uniform (Xavier) is the default
all-rounder; 'he_normal' is the recommended
partner for ReLU. Initializing with 'zeros'
would not be an option but a bug: identical neurons
receiving identical gradients would stay identical forever —
symmetry never breaks, nothing learns:
# the explicit form of what Keras already does for us
tf.keras.layers.Dense(6, activation='relu', kernel_initializer='he_normal')
Listing 26.4 — Initializers: Glorot (default) or He (ReLU's partner); randomness is load-bearing.
5.2 Compile: the three dials, and their alternatives
Section 4.2 explains why this run picks
adam + binary_crossentropy. The
menu behind each dial:
optimizer:'sgd'— plain gradient descent, needs learning-rate tuning, plus momentum/nesterov variants;'rmsprop'— adaptive, the recurrent-network favourite;'adam'— adaptive per-parameter rates plus momentum, the sensible default most teams start with.loss: matched to the output layer by the table above — sigmoid pairs with binary cross-entropy, softmax with (sparse) categorical, linear with mse.metrics: what to display during training (['accuracy']); purely informative — it never affects the weight updates.
# a legitimate alternative compile — RMSprop would train this net fine
ann.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['accuracy'])
Listing 26.5 — Swapping optimizers is a one-word change; the loss must match the output layer, the optimizer may be anything sensible.
5.3 fit() beyond the defaults
The training method is fit() — the very same
method that has trained every model in this book since
Chapter 3; a Keras network is just another estimator. It
takes the same X_train, y_train, plus two
arguments networks additionally care about. The canonical
run trains plain — 32-row batches, 100 epochs, nothing
else. Real projects add guards. The same fit call with the
production touches:
from tensorflow.keras.callbacks import EarlyStopping
h = ann.fit(
X_train, y_train,
batch_size=32, epochs=100,
validation_split=0.1, # hold out 10% — watch overfitting live
callbacks=[EarlyStopping(
patience=10, # tolerate 10 flat epochs, then stop
restore_best_weights=True)], # keep the BEST epoch, not the last
class_weight={0: 1., 1: 3.9}, # churners are 20.4% — weigh their errors
verbose=0)
Listing 26.6 — fit() with guards: validation, early stopping, class weighting. Options, not obligations.
batch_size— how many predictions are compared to their real results per weight update (Section 3.4's mini-batching). 32 is the classic default; it is a hyperparameter, but not one worth over-tuning — leave it at 32 until you have a reason not to.epochs— must not be too small: the network needs enough passes to learn the correlations. 100 gives comfortable headroom here — Figure 26.8 shows convergence by epoch 20, so the extra 80 cost little and insured against a slow start.validation_split— Keras holds out the last 10% of the array and reports its loss every epoch; the widening train-vs-validation gap is the overfit signal, measured live. One caveat: the split takes the last rows before any shuffling — with our France-first one-hot ordering that slice is geography-biased, so shuffle first or passvalidation_datafrom your own split.EarlyStopping— stop when validation stops improving. On this network it would fire around epoch 25–30 (Figure 26.8's plateau) and save 70 epochs of compute for nothing.class_weight— Chapter 18's cost logic as a training knob: with 79.8% stayers, each churner's error gets ≈3.9× weight (0.798/0.204). It trades a little accuracy for recall during training — the same trade Section 6.1 makes by moving the threshold afterwards.shuffle=True(the default) — reshuffles rows each epoch; quietly important here, because the one-hot encoding left the rows ordered by geography.
predict wants a 2D array —
one row per customer. And the one-hot columns come first
(France = 1, 0, 0), matching where the ColumnTransformer placed
them. Scale the row with the trained scaler — never raw
values, never a refit scaler.
# TensorFlow is not part of scikit-learn — install it first
pip install tensorflow # full (GPU-capable)
pip install tensorflow-cpu # smaller, CPU-only — fine for this chapter's networks
# reproducibility (the numbers in this chapter used both seeds)
import numpy as np, tensorflow as tf
np.random.seed(0); tf.random.set_seed(0)
Listing 26.7 — Environment and seeding. Exact numbers still vary slightly across machines/BLAS builds; the story does not.
6. Reading the Results
The test set: 2,000 customers, 405 of them churners. At the default threshold 0.5, the real confusion matrix:
| Predicted | ||
|---|---|---|
| Actual | Stays | Churns |
| Stays (1,595) | 1,511 TN | 84 FP — retention spend wasted |
| Churns (405) | 192 FN — customers lost silently | 213 TP — caught |
Chapter 18's metrics, computed: accuracy = 1,724/2,000 = 0.862; precision = 213/297 = 0.717; recall = 213/405 = 0.526; F1 = 0.607. Context makes these numbers meaningful:
| Model (same split, same scaling) | Accuracy | Recall | F1 | Verdict |
|---|---|---|---|---|
| Predict "stays" always (majority) | 0.798 | 0.000 | — | the floor to beat |
| Logistic regression (Ch 11) | 0.811 | 0.237 | 0.337 | +1.3 acc, blind to 3 of 4 churners |
| ANN 12-6-6-1 | 0.862 | 0.526 | 0.607 | +5.1 acc over logistic; recall more than doubled |
The network's advantage is not just accuracy — it found interactions the linear model could not: logistic regression catches only 96 of 405 churners; the ANN catches 213. The predicted probabilities are genuinely graded: min 0.002, mean 0.222, max 1.000 on the test set.
6.1 The threshold is a business decision
A retention campaign costs money per contact; a lost customer costs far more. Chapter 18's threshold logic, run on the real probabilities:
| Threshold | Accuracy | Precision | Recall | FP | FN | Reading |
|---|---|---|---|---|---|---|
| 0.3 | 0.823 | 0.550 | 0.682 | 226 | 129 | catch 2 of 3 churners; chase 226 false leads |
| 0.5 | 0.862 | 0.717 | 0.526 | 84 | 192 | the default balance |
| 0.7 | 0.866 | 0.870 | 0.395 | 24 | 245 | aim precisely, miss 6 of 10 churners |
Note the trap: threshold 0.7 has the highest accuracy (0.866) and is the worst decision for a retention team — accuracy is dominated by the 80% majority class (Ch 18's lesson, live). If a retention offer costs little and a churner lost costs much, 0.3 is the operating point.
6.2 The homework customer, answered
The source's own test: France, credit score 600, Male, 40, tenure 3, balance $60,000, 2 products, has card, active, salary $50,000 — the real model's answer:
6.3 What did it learn? Ask the data
A neural network will not hand you a coefficient table — but the dataset's own churn rates show exactly which signals those 127 parameters absorbed:
| Signal | Churn rate | Contrast |
|---|---|---|
| Geography | France 16.2% · Spain 16.7% · Germany 32.4% | Germany doubles the risk |
| Activity | inactive 26.9% · active 14.3% | nearly 2× |
| Age (mean) | churners 44.8 · stayers 37.4 | 7 years apart |
| Products | 1: 27.7% · 2: 7.6% · 3: 82.7% · 4: 100% | nonlinear — a tree-shaped pattern |
| Balance (mean) | churners $91,109 · stayers $72,745 | richer customers leave more |
7. ANN vs the Models So Far
After 25 chapters, the honest ledger. The ANN beat logistic regression by five accuracy points and doubled recall — but Chapter 16's decision trees and Chapter 17's forests were not run on this dataset here, and on tabular data of this size they are formidably hard to beat. What each family is actually for:
| Model | Shines at | Cost | Interpretability |
|---|---|---|---|
| Logistic regression (Ch 11) | baselines, odds ratios, small data | minutes | excellent — signed coefficients |
| Decision tree (Ch 16) | rules, nonlinear patterns like products 3–4 | seconds | excellent — readable rules |
| Random forest / boosting (Ch 17, 32) | tabular data of this size — usually the winner | minutes | moderate — importances |
| ANN (this chapter) | scale, interactions, unstructured data (text Ch 25, images Ch 27) | hours + tuning | poor — 127 numbers, no story |
Three honest caveats for the road. Scale hunger: ANNs want thousands of examples per feature — at 10,000 rows this network is near the sensible minimum. Tuning burden: layer counts, learning rates, batch sizes, epochs — the source's 6-6 architecture is a sensible hand choice, not a derived one. Opacity: when a regulator asks "why was this customer flagged?", the 127 weights offer no sentence-level answer. The modern playbook: start with the simple model, reach for the network when the simple model demonstrably leaves value behind — or when the data is images, audio or text, where nothing else comes close.
MLPClassifier(hidden_layer_sizes=(6, 6),
activation='relu', max_iter=100) — same idea, no TensorFlow
install, integrates with pipelines and cross-validation. For
chapter-scale problems it is often all you need.
8. From Yes/No to How Much: Regression with the ANN
The churn network answers a yes/no question: will this customer leave? Chapter 25 met three-answer questions. There is a third shape, and finance asks it constantly: how much? What will the price be tomorrow? What will demand be next week? The same network answers it — with a new head and a new loss. Nothing else changes.
8.1 Three changes
| This chapter so far (yes/no) | How much (regression) | |
|---|---|---|
| Label y | 0 or 1 | a number — the return |
| Output layer | Dense(1, activation='sigmoid') | Dense(1) — no activation, linear |
| Loss | binary_crossentropy | mean_squared_error |
| Metric | accuracy | MAE / RMSE (Chapter 10) |
Why remove the activation? The sigmoid squeezes its output
into 0–1 — a probability. A price can be any number, so the
last neuron must be free to output any number: just weights,
sum, done. And one practical habit: scale y too
(subtract the train mean, divide by the train standard
deviation), train on the scaled target, then un-scale the
predictions.
8.2 The finance rules — more important than the code
- Predict returns, not prices. A network trained to output "tomorrow's price" learns one cheap trick: copy today's price. It looks accurate and predicts nothing. The honest target is the return — tomorrow's price divided by today's.
- Split by time, never randomly. Train on the past, test on the future. A random shuffle lets the network peek at tomorrow while training — Chapter 3's leakage rule, fatal in markets.
- Always race the naive baseline: "tomorrow equals today" (return = 0). If a model cannot beat doing nothing, it is not a model.
- Next week instead of next day? Same machinery — the target becomes the 5-day forward return.
8.3 The real run: can the ANN read tomorrow's AAPL?
We ran it, for real, on the course's own equity snapshot (AAPL, January 2020 – September 2026). Features: the last five daily returns. Target: tomorrow's return. Time split: train on the first 80% of days (through 2025-05-27), test on the last 338 days. Same 8-4-1 network as the mini-project — with the linear head and MSE loss:
# ---- Listing 26.8: the regression head, end to end ----
r = np.diff(np.log(close)) # daily log returns
X = np.array([r[t-5:t] for t in range(5, len(r))]) # 5 lagged returns
y = r[5:] # tomorrow's return
cut = int(0.8 * len(y)) # TIME split — no shuffle
sc = StandardScaler()
X_train, X_test = sc.fit_transform(X[:cut]), sc.transform(X[cut:])
y_train, y_test = y[:cut], y[cut:]
ym, ys = y_train.mean(), y_train.std() # scale y on train statistics
model = tf.keras.Sequential([
tf.keras.layers.Dense(8, activation='relu', input_shape=(5,)),
tf.keras.layers.Dense(4, activation='relu'),
tf.keras.layers.Dense(1)]) # linear head — any number
model.compile(optimizer='adam', loss='mean_squared_error')
model.fit(X_train, (y_train - ym) / ys, epochs=100, batch_size=32, verbose=0)
p = model.predict(X_test, verbose=0).ravel() * ys + ym # un-scale
print(np.abs(p - y_test).mean()) # 0.01112 MAE
print(np.abs(y_test).mean()) # 0.01088 naive baseline's MAE
print(((p > 0) == (y_test > 0)).mean()) # 0.518 direction accuracy
Listing 26.8 — The regression ANN on AAPL's next-day return. Deterministic run (seed 0, op-determinism on): every number reproduces on the same TensorFlow build.
| MAE | Direction | |
|---|---|---|
| ANN (8-4-1 linear) | 0.01112 | 51.8% |
| Naive: "price unchanged" | 0.01088 | coin = 50.0% |
Read it honestly. The correlation between predicted and actual returns is −0.01; R² is −0.04. The network works — it outputs a number, and the last close of $333.02 got a tomorrow prediction of $334.05 — and it is beaten by doing nothing. Five lagged returns carry almost no signal. Markets price obvious information in seconds; Mini-Project 1 found the same wall with headlines, and Applied Project 1 with technical indicators. This is that lesson arriving from the ANN's direction.
What would make it a real experiment rather than a parable? Features with genuine information (order flow, fundamentals, cross-asset signals), walk-forward retraining instead of one split, and transaction costs in every number. Applied Project 3 (SVR) will meet this exact task again with a different learner — and should expect the same honest wall.
9. The Deep Learning Ecosystem & the Road Ahead
The framework landscape this chapter's code lives in — a fast-moving neighbourhood, hence this section's quarterly review cycle:
- Keras 3 is now multi-backend: the same model code can
run on TensorFlow, PyTorch or JAX. The
tf.kerasimport used in this chapter remains fully supported, but new projects often import Keras directly. - PyTorch dominates current research and a large share of production deep learning; TensorFlow/Keras remains entrenched in deployed industrial systems. The concepts of this chapter — layers, activations, backprop, optimizers — transfer one-to-one.
- The scaling era: transformers (Ch 25's closing note) are stacks of exactly these building blocks plus attention, trained with exactly this loop — the same δ = ŷ − y arithmetic of Section 3, industrialised.
Next stop, Chapter 27: Convolutional Neural Networks — the architecture that taught networks to see. Instead of feeding 12 tidy columns, we feed pixels; instead of Dense layers connecting everything to everything, convolutional layers slide small weight patches across the image — and the same gradient-descent machinery of this chapter does the learning.
🛠 Hands-On: The Churn Early-Warning SystemIntermediate
The retention team has budget for 400 outreach calls per quarter — roughly the number of churners in the test set. Build the network, verify the hand arithmetic, then choose the operating point that spends those calls best, and defend it.
Steps
- Hand-verify Section 2's parameter count: 12-6-6-1 → 127. Then confirm with
ann.count_params(). - Hand-run the 2-2-1 network of Section 3 for the second training example x = (2, 1), y = 0 (same weights): forward pass, δ₀, all nine gradients, one η = 0.1 step, and the new loss.
- Train the real churn network (Listing 26.1); verify the epoch-100 loss lands near 0.33 and the confusion matrix near [[1511, 84], [192, 213]].
- Compute accuracy/precision/recall/F1 by hand from the matrix; verify against sklearn.
- Threshold study 0.1–0.9: for each, how many of the 400-call budget is spent on true churners? Recommend an operating point.
- Score the homework customer and the Germany/65/inactive variant; verify the stay/leave flip.
- Bonus: rerun with sklearn's MLPClassifier(hidden_layer_sizes=(6, 6)) and compare.
View Solution / Walkthrough
Step 2 (hand, x = (2, 1), y = 0): forward: z₁ = 0.5(2) + 0.2(1) + 0.1 = 1.3, a₁ = 1.3; z₂ = −0.3(2) + 0.8(1) − 0.1 = 0.1, a₂ = 0.1; z₀ = 0.7(1.3) − 0.4(0.1) + 0.05 = 0.92, ŷ = σ(0.92) = 0.7150. Loss = −ln(1 − 0.7150) = 1.2554 — the network is confidently wrong. Backward: δ₀ = ŷ − y = +0.7150; ∂L/∂w₀₁ = 0.7150 × 1.3 = 0.9296, ∂L/∂w₀₂ = 0.7150 × 0.1 = 0.0715; δ₁ = 0.7(0.7150)(1) = 0.5005, δ₂ = (−0.4)(0.7150)(1) = −0.2860; ∂L/∂w₁₁ = 2(0.5005) = 1.0011, ∂L/∂w₂₁ = 1(0.5005) = 0.5005, ∂L/∂w₁₂ = 2(−0.2860) = −0.5720, ∂L/∂w₂₂ = 1(−0.2860) = −0.2860. One η = 0.1 step drops ŷ 0.7150 → 0.6165 and the loss 1.2554 → 0.9584. Positive δ₀ now means: push ŷ down — the sign of learning depends entirely on the label.
# Steps 3-7
import numpy as np, pandas as pd, tensorflow as tf
np.random.seed(0); tf.random.set_seed(0)
ds = pd.read_csv('Churn_Modelling.csv')
X = ds.iloc[:, 3:-1].values; y = ds.iloc[:, -1].values
from sklearn.preprocessing import LabelEncoder, OneHotEncoder, StandardScaler
from sklearn.compose import ColumnTransformer
from sklearn.model_selection import train_test_split
le = LabelEncoder(); X[:, 2] = le.fit_transform(X[:, 2])
X = np.array(ColumnTransformer([('e', OneHotEncoder(), [1])],
remainder='passthrough').fit_transform(X))
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=.2, random_state=0)
sc = StandardScaler(); Xtr = sc.fit_transform(Xtr); Xte = sc.transform(Xte)
ann = tf.keras.models.Sequential([
tf.keras.layers.Input(shape=(12,)),
tf.keras.layers.Dense(6, activation='relu'),
tf.keras.layers.Dense(6, activation='relu'),
tf.keras.layers.Dense(1, activation='sigmoid')])
ann.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
h = ann.fit(Xtr, ytr, batch_size=32, epochs=100, verbose=0)
print('final loss %.4f, params %d' % (h.history['loss'][-1], ann.count_params()))
probs = ann.predict(Xte, verbose=0).ravel()
from sklearn.metrics import confusion_matrix, accuracy_score
cm = confusion_matrix(yte, (probs >= .5).astype(int))
print(cm, accuracy_score(yte, (probs >= .5).astype(int)))
# [[1511 84] [192 213]] 0.862
for t in [.1, .2, .3, .4, .5, .6, .7, .8, .9]:
yp = (probs >= t).astype(int)
tp, fp = int(((yp==1)&(yte==1)).sum()), int(((yp==1)&(yte==0)).sum())
print('t=%.1f calls spent %3d on true churners %3d hit-rate %.2f'
% (t, tp+fp, tp, tp/max(tp+fp, 1)))
# homework + risk variant
for row, name in [([[1,0,0,600,1,40,3,60000,2,1,1,50000]], 'homework'),
([[0,1,0,600,1,65,3,60000,2,1,0,50000]], 'risk ')]:
print(name, '%.4f' % ann.predict(sc.transform(row), verbose=0)[0][0])
Expected results (real): final loss ≈ 0.33; cm [[1511, 84], [192, 213]], acc 0.862, P 0.717, R 0.526, F1 0.607; homework 0.0287 (stays), risk variant 0.9360 (leaves). Threshold anchors from the canonical run: t = 0.3 spends 502 calls for 276 true churners (hit rate 0.550); t = 0.5 spends 297 calls for 213 (hit rate 0.718); t = 0.7 spends 184 calls for 160 (hit rate 0.870). Between the 502-call and 297-call anchors, call volume falls monotonically as t rises — so a threshold in the 0.35–0.45 range is where your sweep will land the 400-call budget, with a hit rate between 0.55 and 0.72. Recommendation: with a hard 400-call cap, take the threshold whose call count just fits the budget, not the accuracy-maximising 0.7 — Chapter 18's cost logic deciding a Chapter 26 model.
🎯 Key Takeaways
The artificial neuron copies the biological one's flow: signals in through synapses, weighted sum, activation, signal out. Dendrites receive, axons transmit — synapses carry the weights, and training is nothing but adjusting them.
A neuron takes the features of one single observation in and returns one value out. Inputs must be scaled — standardized or normalized — and the output layer's shape follows the output type: one neuron for continuous/binary, one dummy neuron per class for categorical.
Four classic activations: threshold (rigid yes/no), sigmoid (smooth probability), rectifier (the hidden-layer default), tanh (zero-centred S). Binary classification's standard recipe: ReLU in the hidden layers, sigmoid at the output.
A neuron with sigmoid is Chapter 11's logistic unit; a network is layers of them, and the nonlinearity between layers is the entire point of depth.
Brute force cannot set the weights — 25 weights with 1,000 candidates each is already 10⁷⁵ combinations. Gradient descent follows the slope of the cost instead; the churn net trains mini-batch style (batch_size = 32: one update per batch, 250 per epoch).
The churn network is 12 → 6 → 6 → 1 with ReLU hidden layers, sigmoid output: 127 parameters, counted by hand as (12·6+6) + (6·6+6) + (6·1+1).
No formula picks the architecture: start at units ≈ (inputs + outputs)/2 (here 6), grow on validation evidence. Match output layer to loss — sigmoid+BCE binary, softmax+categorical multi-class, linear+MSE regression — and always initialize small-random (Glorot/He), never zeros.
Backpropagation is the chain rule with a to-do list: δ flows backwards (δ₀ = ŷ − y for sigmoid + cross-entropy), every gradient is one multiplication, and every weight is updated at once. One hand step moved ŷ from 0.5671 to 0.6434 and cut the loss 22%.
Real results: accuracy 0.862 vs majority 0.798 and logistic 0.811 — recall doubled to 0.526. The homework customer stays (P = 0.029); the Germany/65/inactive variant leaves (0.936). Networks learn factor combinations, not isolated coefficients.
Test Your Understanding
42 questions across the chapter — the neuron and its biological roots, the activation menu, what hidden neurons learn, the learning loop and gradient descent, the algebra, backpropagation by hand, the Keras build choices, the real training run, honest model choice, and the regression extension. Choose one answer per question, then check your answers.
Q1.The dataset and task:
Q2.An artificial neuron computes:
Q3.A single neuron with sigmoid activation is precisely:
Q4.Why ReLU in hidden layers rather than sigmoid?
Q5.Why sigmoid at the output of this network?
Q6.In a biological neuron, signals are received by the dendrites and sent out along the axon. The synapse is:
Q7.The threshold (step) activation function:
Q8.If the dependent variable is categorical with k classes, the output layer needs:
Q9.In the property-price example, a hidden neuron that fires only for homes over 100 years old shows that:
Q10.Why can't we find the best weights by brute force — trying every combination?
Q11.The churn network trains with batch_size = 32. That makes it:
Q12.(Hand) The parameter count of 12 → 6 → 6 → 1:
Q13.How do you choose units — the number of neurons in a hidden layer?
Q14.Why must the weights be initialized to small random values rather than all zeros?
Q15.Adding the second hidden layer is literally what makes this a deep-learning model, because:
Q16.units and batch_size are called hyperparameters because:
Q17.Why must RowNumber, CustomerId and Surname be dropped?
Q18.Stacking layers without activation functions would:
Q19.(Hand, Section 3) The forward pass with x = (1, 2) gives z₁ = 1.0 because:
Q20.For a sigmoid output with binary cross-entropy, the output error signal is:
Q21.ReLU's derivative in the backward pass is:
Q22.(Hand) With δ₀ = −0.4329 and a₂ = 1.2, the gradient ∂L/∂w₀₂ is:
Q23.One SGD step at η = 0.1 on the hand network achieved:
Q24.The sign of the gradient on w₀₁ was −0.4329, so the update rule does what?
Q25.An "epoch" is:
Q26.Why batch_size = 32 rather than all 8,000 at once?
Q27.Adam, the chosen optimizer, is best described as:
Q28.(Real) The training curve showed:
Q29.Train accuracy 0.8645 vs test accuracy 0.8620 means:
Q30.(Real) The confusion matrix [[1511, 84], [192, 213]] gives recall:
Q31.The majority baseline (predict "stays" always) scores:
Q32.(Real) ANN vs logistic regression on the same split:
Q33.(Real) Threshold 0.7 maximises accuracy (0.866) but:
Q34.(Real) The homework customer (France, 40, active) and the variant (Germany, 65, inactive) got:
Q35.Why must features be scaled for an ANN (but a decision tree shrugs)?
Q36.The gender and geography encodings used here:
Q37.(Real data insight) Which pattern did Section 6.3 surface?
Q38.Honest model choice for THIS dataset's size and shape:
Q39.The scikit-learn route to the same model, no TensorFlow install:
Q40.Chapter 27 (CNNs) extends this chapter by:
Q41.To turn this chapter's yes/no network into one that predicts a number (a return, a price), the essential change is:
Q42.(Real) In the AAPL next-day experiment of Section 8, the honest headline result was: