Train the Deep House Model

Epochs over all five HOUSES: forward, backprop, update — watch loss fall.

Train the deep house model

Training means: for many epochs, call trainOne on every sold house — forward, backprop, update — until mean squared error falls.

Same five HOUSES as the machine-learning lessons. Same DeepHouseNet (3 → 4 ReLU → 4 ReLU → 1 linear). Learning rate 1e-9; seed 37. Square footage is thousands; we scale inputs so training does not explode.

Epoch
One full pass over all five houses — five trainOne calls.
MSE
Mean of squared (prediction − target) over the training set. Lower is better.
Loss history
train returns one MSE per epoch so you can watch the curve.

Data

The five sold houses

Every epoch visits each row once. Features are [sqft, beds, age]; the label is the sale price.

sqftbedsageSale price
1200215$245,000
180038$310,000
220043$420,000
900240$180,000
1500310$295,000

Loop

Epochs while loss crawls down

With lr = 1e-9 and scaled features, each epoch is a small downhill shuffle — the same dial from Gradient Descent. Watch the shape of the loop as MSE falls from ~9.04×10¹⁰ toward ~1.5×10⁸.

Train loop: houses → trainOne → MSE
HOUSES5 rowstrainOneforward + backMSEhistory.pushpredict[1500, 3, 10]each rowafter epochlater
  • in play
StatusBefore training

Seed 37, fresh weights. Predictions are near zero. MSE on HOUSES is huge — about 9.04 × 10¹⁰.

Step 1 of 4
Scaled inputs + tiny learning rate. Square footage is thousands; we scale inputs so training does not explode. 1e-9 keeps updates stable through the two ReLU layers. Five thousand epochs is enough for this toy set to reach usable dollar guesses.

Solution in TypeScript

mse averages squared error. train runs epochs, calls trainOne on every house, and returns the loss history. Then freeze the net and forward once for a price guess.

deep-house-train.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];
  }
}

const HOUSES: [Vector, number][] = [
  [[1200, 2, 15], 245_000],
  [[1800, 3,  8], 310_000],
  [[2200, 4,  3], 420_000],
  [[ 900, 2, 40], 180_000],
  [[1500, 3, 10], 295_000],
];

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.
  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;

  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;
  }

  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;
  }

  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;
  }
}

function mse(net: DeepHouseNet, data: [Vector, number][]): number {
  return (
    data.reduce((sum, [x, y]) => sum + (net.forward(x) - y) ** 2, 0) /
    data.length
  );
}

/** Epochs over all HOUSES; returns loss history (one MSE per epoch). */
function train(
  net: DeepHouseNet,
  data: [Vector, number][],
  epochs: number,
  lr: number,
): number[] {
  const history: number[] = [];
  for (let epoch = 0; epoch < epochs; epoch++) {
    for (const [x, y] of data) {
      trainOne(net, x, y, lr);
    }
    history.push(mse(net, data));
  }
  return history;
}

const net = new DeepHouseNet();
const history = train(net, HOUSES, 5000, 1e-9);

console.log("epoch 0   MSE", history[0].toExponential(3));
console.log("epoch 999 MSE", history[999].toExponential(3));
console.log("epoch 4999 MSE", history[4999].toExponential(3));
for (const [x, y] of HOUSES) {
  console.log(x, "→", net.forward(x).toFixed(0), "(actual", y + ")");
}
// Seed 37 + scale + 1e-9: loss ~9.04e10 → ~1.5e8; predictions land near sale prices.

Results

What the run looks like (seed 37)

Verified numbers for seed 37, scale, and lr = 1e-9 after 5000 epochs:

CheckpointMSE (approx)forward([1500, 3, 10])
After epoch 0~9.04 × 10¹⁰~0.006
After epoch 1000~9.04 × 10¹⁰ (slightly lower)still tiny
After epoch 5000~1.5 × 10⁸≈ 303754
Target for that house$295,000
FeaturesPredictionActual
[1200, 2, 15]≈ 227611245000
[1800, 3, 8]≈ 325667310000
[2200, 4, 3]≈ 408375420000
[900, 2, 40]≈ 180165180000
[1500, 3, 10]≈ 303754295000
Five houses is a toy. A deep net has many dials. With only five training rows you are learning the loop — ballpark prices, not a production appraiser. Generalization still applies: train loss alone is not success.

Next

Freeze the weights

Training moves dials. Inference freezes them and only calls forward — price a new listing, then check a held-out house so train loss is not the whole story.

Trail. Backprop Through Depth → Train the Deep House Model → Inference and Hold-Out.

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.
Backprop Through DepthSend price error backward through two ReLU layers and take one gradient-descent step.
Inference and Hold-OutFreeze the net, price a new listing, and check a held-out house so train loss is not the whole story.