Backprop Through Depth

Send price error backward through two ReLU layers and take one gradient-descent step.

Backprop through depth

Backpropagation sends the price error backward through every layer so each weight knows its share of the blame — then takes one gradient-descent step.

You already ran a forward pass on Forward Pass Through Depth. Same DeepHouseNet (3 → 4 ReLU → 4 ReLU → 1 linear). Now fix the dials for one house.

Error
target − prediction — how far the dollar guess missed the sale price.
Backpropagation
Walk that error output → h2 → h1, updating weights on the way.
ReLU′
Derivative is 1 if the pre-activation was positive, else 0. Off neurons get no update.

Setup

One house, one step

Same sold listing as the machine-learning trail. Features [1500, 3, 10], target $295,000. Fresh net with seed 37; learning rate 1e-9. Square footage is thousands; we scale inputs so training does not explode.

InputValue
sqft, beds, age[1500, 3, 10]
Scaled[1.5, 3, 1]
Target price$295,000
Architecture3 → 4 ReLU → 4 ReLU → 1 linear
Learning rate1e-9

Backward

Error walks output → h2 → h1

Forward already gave a prediction. Compare to the target, then reverse the stack. Each ReLU layer multiplies by ReLU′ so neurons that were off stay quiet.

Blame flows backward through DeepHouseNet
Output errupdate W_outh2 err× ReLU′h1 err× ReLU′Inputsfixedchainchainstop
  • updating
StatusMeasure the miss

error = target − prediction. With seed 37 the fresh net guesses ≈ 0.006 for this house — error is about 295000 dollars.

Step 1 of 5

Solution in TypeScript

trainOne scales features, keeps pre-activations from the forward pass, updates the linear output, then chains h2Error and h1Error through reluDeriv. Same GD beat as Neural Networks — one more hidden layer.

deep-backprop.tsTypeScript
type Vector = number[];
type Matrix = number[][]; // rows = neurons, cols = inputs

function relu(x: number): number {
  return Math.max(0, x);
}

function reluDeriv(x: number): number {
  return x > 0 ? 1 : 0;
}

/** Square footage is thousands; we scale inputs so training does not explode. */
function scale(features: Vector): Vector {
  return [features[0] / 1000, features[1], features[2] / 10];
}

/** Mulberry32 — fixed seed so this lesson’s numbers are reproducible. */
function mulberry32(seed: number): () => number {
  return () => {
    seed |= 0;
    seed = (seed + 0x6d2b79f5) | 0;
    let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}

class Layer {
  weights: Matrix;
  biases: Vector;
  activation: "relu" | "linear";

  constructor(
    inputSize: number,
    outputSize: number,
    activation: "relu" | "linear",
    rnd: () => number,
  ) {
    this.activation = activation;
    this.weights = Array.from({ length: outputSize }, () =>
      Array.from({ length: inputSize }, () => rnd() * 0.5 - 0.25),
    );
    this.biases = Array(outputSize).fill(0);
  }

  forward(inputs: Vector): { outputs: Vector; preActivations: Vector } {
    const preActivations: Vector = [];
    const outputs: Vector = [];
    for (let j = 0; j < this.weights.length; j++) {
      let sum = this.biases[j];
      for (let i = 0; i < inputs.length; i++) {
        sum += inputs[i] * this.weights[j][i];
      }
      preActivations.push(sum);
      outputs.push(this.activation === "relu" ? relu(sum) : sum);
    }
    return { outputs, preActivations };
  }
}

/** DeepHouseNet: 3 → 4 ReLU → 4 ReLU → 1 linear. */
class DeepHouseNet {
  h1: Layer;
  h2: Layer;
  out: Layer;

  constructor(seed = 37) {
    const rnd = mulberry32(seed);
    this.h1 = new Layer(3, 4, "relu", rnd);
    this.h2 = new Layer(4, 4, "relu", rnd);
    this.out = new Layer(4, 1, "linear", rnd);
  }

  forward(features: Vector): number {
    const x = scale(features);
    const a = this.h1.forward(x);
    const b = this.h2.forward(a.outputs);
    const o = this.out.forward(b.outputs);
    return o.outputs[0];
  }
}

/** One house, one downhill step through every layer. */
function trainOne(
  net: DeepHouseNet,
  features: Vector,
  target: number,
  lr: number,
): void {
  const x = scale(features); // Square footage is thousands; we scale inputs so training does not explode.
  // Forward — keep pre-activations for ReLU′
  const h1 = net.h1.forward(x);
  const h2 = net.h2.forward(h1.outputs);
  const o = net.out.forward(h2.outputs);
  const pred = o.outputs[0];
  const error = target - pred;

  // 1. Update output (linear)
  for (let j = 0; j < net.out.weights.length; j++) {
    for (let i = 0; i < h2.outputs.length; i++) {
      net.out.weights[j][i] += error * h2.outputs[i] * lr;
    }
    net.out.biases[j] += error * lr;
  }

  // 2. Chain blame into h2 × ReLU′
  const h2Error: Vector = Array(h2.outputs.length).fill(0);
  for (let i = 0; i < h2.outputs.length; i++) {
    let sum = 0;
    for (let j = 0; j < net.out.weights.length; j++) {
      sum += net.out.weights[j][i] * error;
    }
    h2Error[i] = sum * reluDeriv(h2.preActivations[i]);
  }

  for (let j = 0; j < net.h2.weights.length; j++) {
    for (let i = 0; i < h1.outputs.length; i++) {
      net.h2.weights[j][i] += h2Error[j] * h1.outputs[i] * lr;
    }
    net.h2.biases[j] += h2Error[j] * lr;
  }

  // 3. Chain blame into h1 × ReLU′
  const h1Error: Vector = Array(h1.outputs.length).fill(0);
  for (let i = 0; i < h1.outputs.length; i++) {
    let sum = 0;
    for (let j = 0; j < net.h2.weights.length; j++) {
      sum += net.h2.weights[j][i] * h2Error[j];
    }
    h1Error[i] = sum * reluDeriv(h1.preActivations[i]);
  }

  for (let j = 0; j < net.h1.weights.length; j++) {
    for (let i = 0; i < x.length; i++) {
      net.h1.weights[j][i] += h1Error[j] * x[i] * lr;
    }
    net.h1.biases[j] += h1Error[j] * lr;
  }
}

const net = new DeepHouseNet();
const features: Vector = [1500, 3, 10];
const target = 295_000;
const lr = 1e-9;

console.log("before", net.forward(features).toFixed(4)); // ≈ 0.0060
trainOne(net, features, target, lr);
console.log("after ", net.forward(features).toFixed(4)); // ≈ 0.0064
// One step: tiny nudge — expected. Off ReLU neurons get ReLU′ = 0 — no blame.

Check

Before and after one step

Same house, seed 37, lr = 1e-9. Forward once, train once, forward again:

Predictionvs $295,000
Before trainOne≈ 0.006miss ≈ $295,000
After one step≈ 0.0064still essentially $0
Tiny learning rate, scaled inputs. Square footage is thousands; we scale inputs so training does not explode. With 1e-9 one step is a microscopic nudge — you will need many epochs (next lesson). Off ReLU neurons get ReLU′ = 0 and take no blame; that is the gate doing its job.

Next

Repeat for every house

One step on one listing is the unit of learning. Training means: loop trainOne over all five HOUSES, for thousands of epochs, and watch loss crawl down.

Trail. Forward Pass Through Depth → Backprop Through Depth → Train the Deep House Model.

Keep reading

TopicDescription
Deep LearningMachine learning with stacked neurons and ReLU layers: how a deep network builds a price guess from house features in TypeScript.
Deep House ArchitectureBuild a 3→4→4→1 ReLU network and run one forward pass on a house — no training yet.
Forward Pass Through DepthTrace [1500, 3, 10] through two ReLU layers to a price; see which neurons switch off.
Train the Deep House ModelEpochs over all five HOUSES: forward, backprop, update — watch loss fall.
Inference and Hold-OutFreeze the net, price a new listing, and check a held-out house so train loss is not the whole story.