Try first
Get your notes from lesson 3062, where you guessed the four weights. You are about to find out how close you were.
Before running anything, also write down what you think the model’s typical error will be, in thousands. Commit to a number.
The whole pipeline
const scaler = minMaxScaler(houses);
const scaled = houses.map(r => ({ x: scaler(r.x), y: r.y }));
const X = scaled.map(r => r.x);
const y = scaled.map(r => r.y);
const [w, b] = trainAll(X, y, 0.1, 50000);
log("weights", w.map(v => v.toFixed(1)).join(" "));
log("bias ", b.toFixed(1));
log("rmse ", Math.sqrt(lossAll(X, y, w, b)).toFixed(2), "thousand");
Five steps and you have seen all of them before: scale, arrange as matrices, train, report. This is the shape of every supervised learning script ever written, including the ones that take a week on a cluster.
Compare against doing nothing
const mean = y.reduce((s, v) => s + v, 0) / y.length;
const baseline = y.reduce((s, v) => s + (v - mean) * (v - mean), 0) / y.length;
log("always guess the average, rmse", Math.sqrt(baseline).toFixed(2));
Run this before you celebrate anything. If the model is not well clear of the baseline, it has learned nothing worth having, whatever its loss looks like in isolation.
Reading the weights
The weights describe scaled features, so they are all measured across the full range of their column. That makes them comparable to each other, which is genuinely useful, and it makes none of them a price per unit.
To recover a real world rate, undo the scaling. Min-max divided each column by its width, so
realWeight[j] = w[j] / (hi[j] - lo[j])
const cols = [0, 1, 2, 3].map(j => column(houses, j));
const widths = cols.map(c => Math.max(...c) - Math.min(...c));
const names = ["per sq m", "per bedroom", "per year of age", "per km to station"];
w.forEach((v, j) => log(names[j].padEnd(18), (v / widths[j]).toFixed(2)));
Now compare those to your guesses from lesson 3062. Area should be a few thousand per square meter. Age should be negative. Distance should be negative.
Three warnings about reading weights
Correlated features split the credit arbitrarily. Area and bedrooms move together in this data. The model can put the weight on either one, or share it, and every arrangement fits about equally well. So a small weight on bedrooms does not mean bedrooms do not matter. It may mean area already said it.
A weight is not a cause. The model found that price and distance move in opposite directions in ten rows. It has no idea why, and it would report the same thing if both were driven by something you never measured.
Ten rows and four features is very little data. With enough parameters relative to rows, a model can fit closely and mean nothing. That is the subject of section 9, and it is the most important thing left in this course.
What to watch
You measured the error on the same ten houses you trained on. The model has already seen every answer, so that number is the best case and not an estimate of anything. Try it on a house that was not in the data:
log(predict(w, b, scaler([80, 3, 25, 1.0])).toFixed(1));
Note that the new house went through scaler, the one fitted on the training data, exactly as lesson 3042 insisted. Refitting a scaler here would be meaningless.
How much do you trust that number? You have no way to answer yet. Section 9 gives you one.
Exercises
- Train on the first eight houses and predict the last two. Compare that error to the error on the eight. Which is larger, and by how much?
- Drop the bedrooms column entirely and retrain. How much worse is the fit? What does that say about exercise 1 of lesson 3062?
- Add a fifth column of pure random numbers and retrain. What weight does it get, and why is it not zero?
- Swap
minMaxScalerforstandardScaler. Does the fit change? Do the weights change? Explain the difference between those two answers.