Skip to content
Reading as a guest: progress is saved in this browser. Create a free account and we’ll carry your progress over.

Foundations: Tensors & Data

Data Normalization

Mission Map
2 / 6 · Concept
◎ Concept

Feature scaling changes numeric units using fitted statistics or known bounds. Comparable scales can make neural-network training easier.

You will be able to

  • Explain how feature scaling affects neural-network training
  • Apply z-score normalization to nonconstant training features
  • Apply min-max normalization and explain why later values can exceed 0-1
  • Recognize when to use each normalization strategy
  • Feed normalized readings through a real dense neuron and read its verdict

Why Scale Features

Min-max is the arithmetic under a scroll progress bar. scrollY / (docHeight - viewportHeight) takes a value in pixels and maps it to 0-1 so the bar can use it. Subtract the minimum, divide by the range: that's the entire formula, and it does the same job on a sensor channel that it does on a scroll position.

For this neural network, scaling can help training. The temperature channel runs 19 to 35. Signal strength runs 0.1 to 0.9. A typical temperature value is near 25 and a typical signal strength near 0.5, so temperature's numbers arrive about fifty times larger. With comparable , temperature contributes more to the and can produce larger . Scaling can make optimization easier; it does not decide which is informative. A trained model can still assign different weights to the features.

Two names in the code below. The is the typical distance between a value and its column's mean, so dividing by it measures a feature in its own units of spread: a of 1 sits one typical distance above the mean, whatever the raw units were. tf.moments hands back the instead, that distance squared and averaged, so the standard deviation is variance.sqrt(). The averaging divides by the number of rows, n, which makes both statistics the population version. The usual unbiased sample variance divides by n minus 1. Its square root is larger than the population standard deviation when n > 1 and variance is nonzero; that square root is not itself an unbiased estimator of standard deviation.

Challenge

Normalized data exists to be fed to a model, so this challenge ends at one. Farah hands you one frozen off her scratch pad, weights fixed: two features in, one verdict out. That is enough to show why the saved statistics matter. Feed it a reading normalized with different stats and it answers a different question.

Normalize the Archimedes sensor data using z-score normalization, then run every reading through the preview neuron.

Commander Mirza

Before the deep scan runs, normalize every sensor feed. One scale, no exceptions.

Ensign Demir

The thermal channel reads in the thirties, signal strength lives under one. If we feed both in raw, the model's going to think temperature is the whole story.

Dr. Farah

With comparable starting weights, temperature has the larger numerical contribution. Scaling helps us fit the model; it does not tell us which sensor matters.

Why Scale Features

Min-max is the arithmetic under a scroll progress bar. scrollY / (docHeight - viewportHeight) takes a value in pixels and maps it to 0-1 so the bar can use it. Subtract the minimum, divide by the range: that's the entire formula, and it does the same job on a sensor channel that it does on a scroll position.

JavaScript

Scroll progress calculation
const progress = scrollY / (docHeight - viewportHeight)

Machine Learning

Min-Max Normalization
const normalized = data.sub(min).div(max.sub(min))
Structural Bridge
Where the analogy ends
Scroll progress uses page dimensions; sensor scaling uses chosen physical bounds or statistics fitted on training data. New observations can exceed fitted min/max values. Z-score uses a mean and standard deviation instead, but it is not automatically better or immune to drift and outliers.

For this neural network, scaling can help training. The temperature channel runs 19 to 35. Signal strength runs 0.1 to 0.9. A typical temperature value is near 25 and a typical signal strength near 0.5, so temperature's numbers arrive about fifty times larger. With comparable , temperature contributes more to the and can produce larger . Scaling can make optimization easier; it does not decide which is informative. A trained model can still assign different weights to the features.

Two names in the code below. The is the typical distance between a value and its column's mean, so dividing by it measures a feature in its own units of spread: a of 1 sits one typical distance above the mean, whatever the raw units were. tf.moments hands back the instead, that distance squared and averaged, so the standard deviation is variance.sqrt(). The averaging divides by the number of rows, n, which makes both statistics the population version. The usual unbiased sample variance divides by n minus 1. Its square root is larger than the population standard deviation when n > 1 and variance is nonzero; that square root is not itself an unbiased estimator of standard deviation.

Try the calculation

Keep the model and scaler together

These four synthetic training rows fit the scaler. The neuron keeps weights [0.6, -0.8] and bias 0.1 throughout. Change preprocessing or one reading and watch the score change. The weights are supplied, not trained here, and the sigmoid score is not a calibrated probability.

Keep the model and scaler together controls
Experiment inputs

Training rows [temperature, signal]: [20, 0], [20, 2], [24, 0], [24, 2]. Population mean [22, 1], standard deviation [2, 1]. A saved zero standard deviation uses divisor 1; the saved statistic stays zero.

artifact: demo-neuron-v1 · scaler: training-v1 · features: temperature, signal

Enter numbers separated by commas when there is more than one. Rounding to three decimal places is enough.

0/20 checks in this case. No prediction recorded yet. Viewing feedback also counts as help for later attempts. Reset keeps attempts, help and any frozen policy. Draft is in memory only. This practice does not complete the lesson.

This tab keeps the practice record. Sign in to save a summary across devices. Answers stay in this browser.

Reference: TensorFlow.js scalers and the exercise neuron
normalization.tstypescript
import * as tf from '@tensorflow/tfjs';

// Sensor data: temperature in °C, signal strength as a 0-1 fraction
const temperature = tf.tensor([20, 22, 19, 35, 28]);  // range: 19-35
const signal = tf.tensor([0.1, 0.8, 0.3, 0.9, 0.5]);  // range: 0.1-0.9

// Z-score normalization: center at 0, spread to ~1.
// TFJS has no tensor.std(); moments() gives mean and variance together.
function zNormalize(tensor: tf.Tensor) {
return tf.tidy(() => {
const { mean, variance } = tf.moments(tensor);
const std = variance.sqrt();
// where(condition, yes, no) selects per feature. A constant feature
// has std 0: use a divisor of 1, so its training values become 0.
const divisor = tf.where(std.equal(0), tf.onesLike(std), std);
return tensor.sub(mean).div(divisor);
});
}

// One argument you'll need the moment features live side by side in a
// 2D batch: the AXIS. tf.moments(batch, 0) reduces DOWN axis 0 (down
// the rows), producing one mean and one variance PER COLUMN, so each
// feature is scaled by its own statistics. Without the axis you get a
// single global mean over every number in the batch, which stirs
// temperature and signal strength into one meaningless average.
const batch = tf.stack([temperature, signal], 1);  // [5, 2]: 5 readings x 2 features
const colMeans = tf.tidy(() => tf.moments(batch, 0).mean);
// colMeans: [24.8, 0.52], one per column, not one for the whole batch

const tempNorm = zNormalize(temperature);
// [-0.80, -0.47, -0.97, 1.71, 0.54]: centered around 0

const signalNorm = zNormalize(signal);
// [-1.40, 0.94, -0.73, 1.27, -0.07]: same scale!

// Min-max normalization: squeeze to [0, 1] range
// (the scroll-progress formula, applied to sensors)
function minMaxNormalize(tensor: tf.Tensor) {
return tf.tidy(() => {
const min = tensor.min();
const max = tensor.max();
const range = max.sub(min);
const divisor = tf.where(range.equal(0), tf.onesLike(range), range);
return tensor.sub(min).div(divisor);
});
}

const tempMinMax = minMaxNormalize(temperature);
// [0.06, 0.19, 0.0, 1.0, 0.56]: all between 0 and 1

A constant training feature has zero variance and zero range, so ordinary division produces invalid values. The examples use a divisor of 1 only for those zero scales. Constant training values then map to 0. Reuse that rule at inference: a changed value becomes its difference from the saved mean or minimum. That change needs investigation because training showed no variation in that feature.

One practical wrinkle that bites in production: whatever mean and std you compute here, you must save. Inference data has to be normalized with the training statistics, not its own; otherwise the same reading maps to different numbers on different days, and your model quietly answers a different question each time.

Which rows you compute them from matters too. Lesson 4 kept the validation rows out of training, and the statistics follow the same rule. Compute mean and std on the training rows only, then apply them to validation and to every later reading. Fitting them on training plus validation lets held-out information affect the model’s inputs.

When to Use Which

Z-score is a common choice for neural network inputs. It centers each nonconstant training feature at mean 0 and standard deviation 1. Extreme values still affect both statistics; check outliers rather than assuming this scaler removes their influence.

Min-max maps the fitted minimum and maximum to 0 and 1. Known physical bounds can supply those limits, as with pixel values. Fitted bounds are also usable, but later values can fall outside 0-1. One extreme value can compress the rest into a narrow range.

What the Preview Neuron Computes

The exercise ends at a model, so here is all of it. tf.sequential() holds an ordered list of layers, and here the list is one entry long: a single dense unit that reads two features. Farah's frozen numbers are one weight per feature, 0.9 for temperature and -1.4 for signal strength, plus one bias of -0.3.

preview-neuron.tstypescript
// One neuron, built and frozen: two features in, one verdict out.
const neuron = tf.sequential();
neuron.add(tf.layers.dense({ units: 1, activation: 'sigmoid', inputShape: [2] }));

// setWeights is positional: the kernel first, one weight per input feature
// arranged as a column, then the bias, one number per unit. Farah's numbers
// arrive as plain JS arrays, so each becomes a tensor on the way in.
tf.tidy(() => neuron.setWeights([tf.tensor2d([[0.9], [-1.4]]), tf.tensor1d([-0.3])]));

// predict reads a batch: [n, 2] rows in, [n, 1] verdicts out. Stack the two
// z-scored columns from above the same way the raw batch was stacked.
const normalizedBatch = tf.stack([tempNorm, signalNorm], 1);  // [5, 2]
const verdicts = neuron.predict(normalizedBatch) as tf.Tensor;
console.log(await verdicts.array());
tf.dispose([temperature, signal, batch, colMeans, tempNorm, signalNorm,
tempMinMax, normalizedBatch, verdicts]);
neuron.dispose();

The neuron multiplies each normalized feature by its weight and adds the bias. It passes the total through sigmoid, 1 / (1 + Math.exp(-x)), which squashes any number into the range 0 to 1.

The numbers below come from the exercise's four-reading batch, not the five readings above: its per-column mean is [23, 0.5] and its std is [2.24, 0.224]. Against those stats, reading [20, 0.2] z-scores to [-1.34, -1.34]. The total is 0.9 * -1.34 + -1.4 * -1.34 - 0.3, or 0.37, and sigmoid of that is 0.59. Reading [22, 0.8] z-scores to [-0.45, 1.34], total -2.58, verdict 0.07. Both are worth checking on a calculator. predict() runs that arithmetic for a whole batch, one row in, one score out. It always wants a batch, so a single reading goes in as one row of a [1, 2] tensor rather than the bare [2] you are holding.

Now run those same four readings through the same neuron raw, without normalizing, and every verdict comes back 1.000000. The temperature term alone gets there: 0.9 * 20 is 18, and sigmoid is flat at the top long before signal strength contributes its -1.4 * 0.2. Normalized, the same neuron answers 0.59, 0.07, 0.67 and 0.57, and the lowest of the four belongs to [22, 0.8], the reading with the strongest signal. Same weights, same bias, both times. Only the scales changed.

Module 2 builds these layers and module 3 trains their weights. Tonight the layer is a fixed function you feed. Its score is not a calibrated probability. A familiar reading near the decision boundary can score near 0.5, while an unfamiliar reading can receive a confident score. Neither value tells you whether the model has seen that pattern before.

Challenge

Normalized data exists to be fed to a model, so this challenge ends at one. Farah hands you one frozen off her scratch pad, weights fixed: two features in, one verdict out. That is enough to show why the saved statistics matter. Feed it a reading normalized with different stats and it answers a different question.

Normalize the Archimedes sensor data using z-score normalization, then run every reading through the preview neuron.