What we are building
Vectors and weights gave you the prediction shape. This lesson trains it: a linear regression model that learns one weight per feature, plus a bias, from sold houses.
Linear regression calculates a “line of best fit” through historical data points to minimize total prediction error. Same Learning overview loop — build, train, infer — with the full model.
The idea
One feature, one straight line
Start with size alone. The model is the equation of a line: slope times square footage, plus a baseline.
Price = (m × Square Footage) + b
m (Weight/Slope): how much the price increases for every additional square foot. b (Bias/Intercept): baseline value of a house when square footage is 0.
Sold houses are dots. The model is the line. Training nudges that line until the total squared gap to the dots is as small as it gets.
Build model
Multiple features, same shape
Real housing models use more than size. Give each feature its own weight. The shape stays a weighted sum plus bias — just more terms.
Price = (w₁ × Sq Ft) + (w₂ × Bedrooms) + (w₃ × Age) + b
w₁, w₂, w₃: one weight per feature — same role as m, just one dial each.
b (Bias): still shifts every prediction by the same baseline amount.
This lesson trains price ≈ w₁·sqft + w₂·beds + w₃·age + bias on five sold houses. Prediction is still one number; training now moves four knobs instead of one.
- in play
You already know the fan-in: each feature times its weight, then add. That is what the vectors page built.
Training
How the model trains
Training is three moves on a loop: start with a guess, score how wrong the line is, then nudge the knobs downhill. An epoch is one full pass through every training house — five thousand epochs means each of the five houses is seen five thousand times.
1. Initial guess
The model starts with initial weights (w) and bias (b) — often random; in this example they begin at zero. Early predictions are nonsense on purpose.
2. Calculate loss (mean squared error)
Predict a price for every house, then measure how far the line sits from the actual prices. Square each gap so overs and unders cannot cancel, then average:
MSE = average of (y_actual − y_predicted)²
One score for “how wrong overall.” Training tries to shrink it.
That score is mean squared error (MSE) — the loss this lesson watches on the chart.
3. Gradient descent
Adjust every weight (and bias) a little to shrink that overall error, then repeat until the line of best fit settles. Gradient Descent is the name for that downhill walk.
wᵢ += error × xᵢ × lr
b += error × lr
Larger features take a larger share of each correction. Bias has no feature multiplier.
Walk the first correction on house H5 by hand, then zoom out across epochs. The chart tracks MSE — it should fall fast, then flatten.
Weights and bias begin at zero. MSE on the five houses is enormous — every prediction is $0.
What happens in this step
weights = [0, 0, 0] bias = 0 MSE ≈ 90.4 billion
Inference
Freeze the model, then predict
Training had sale prices and kept moving weights and bias. Inference does not. A new listing arrives as a feature vector; predict returns one number — no label, no update.
- current phase
LinearModel with three weights and a bias. Predict is defined; nothing has learned yet.
Solution in TypeScript
Everything above in one file. Comments name the term and the formula before each piece — predict, train, mse, and the epoch loop.
type Vector = number[];
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],
];
class LinearModel {
weights: Vector;
bias = 0;
/** Initial guess — start every weight at 0 (bias already 0). */
constructor(featureCount: number) {
this.weights = Array(featureCount).fill(0);
}
/** Prediction — Price = Σ(xᵢ · wᵢ) + b */
predict(inputs: Vector): number {
const sum = inputs.reduce((acc, x, i) => acc + x * this.weights[i], 0);
return sum + this.bias;
}
/** Gradient step — wᵢ += error × xᵢ × lr ; b += error × lr */
train(inputs: Vector, actual: number, learningRate = 0.00000001): void {
const prediction = this.predict(inputs);
const error = actual - prediction;
for (let i = 0; i < this.weights.length; i++) {
this.weights[i] += error * inputs[i] * learningRate;
}
this.bias += error * learningRate;
}
}
/** Mean squared error (MSE) — average of (y_actual − y_predicted)² */
function mse(model: LinearModel, data: [Vector, number][]): number {
const total = data.reduce((sum, [x, y]) => {
const err = y - model.predict(x);
return sum + err * err;
}, 0);
return total / data.length;
}
const model = new LinearModel(3);
/** Epoch — one full pass over every training house. */
for (let epoch = 0; epoch < 5000; epoch++) {
for (const [features, price] of HOUSES) {
model.train(features, price);
}
}
console.log(model.weights.map((w) => w.toFixed(2))); // ~[189, 0.93, 27]
console.log(model.bias.toFixed(2)); // ~0.69
console.log(model.predict([2000, 3, 5]).toFixed(0)); // ~378457That × inputs[i] is the upgrade from
Your First Learning Model. If a feature is zero on this house, its weight does not move. If it is large, it takes more of the blame — and more of the correction.