Foundations: Tensors & Data
Basic Tensor Operations
Tensors support element-wise and matrix operations like add, multiply, and matmul.
Demir's first pass at the amplifier is already on the main console when the shift starts, and so is its output: Error in matMul: inner shapes (64) and (1) of Tensors with shapes 1,64 and 1,64 and transposeA=false and transposeB=false must match. One line of code, one error, sixty-four channels of raw signal going nowhere.
This lesson starts where you are standing: at the bug. You'll read the error, meet the two multiplication ops the broken line mixed up, and repair the pipeline.
You will be able to
- Read a shape-mismatch error and identify which op it comes from
- Perform element-wise tensor operations (add, multiply, subtract)
- Combine tensors of different shapes using broadcasting
- Understand the matmul shape rule: [m,n] × [n,p] → [m,p]
- Use transpose when the intended matrix axes are reversed
Read the Error First
Here is the code that produced it. The intent: amplify each of the 64 channels by its own gain factor. Position by position, gain times reading.
The error is precise once you can read it: matMul multiplies rows into columns, so it needs the left 's column count (64) to equal the right tensor's row count (1). But matching positions was the intent all along, and that is a different op with no inner- rule at all. TensorFlow.js has both multiplications; the bug is reaching for the wrong one.
Element-wise Operations: The Repair
When you write arr.map((x, i) => x + arr2[i]), you're doing element-wise addition. tf.mul, tf.add, and tf.sub are the same idea across broadcast-compatible dimensions, on the selected backend. gains.mul(readings) is the one-word fix for the broken amplifier:
The do not have to be identical. Aligned dimensions must be equal, or one must be 1; otherwise fails. When they differ, TensorFlow.js lines the two tensors up from the last axis backwards and stretches any axis of size 1, or any axis the smaller tensor does not have at all, until it matches the larger one. That is why .mul(tf.scalar(2)) reaches every element, and why subtracting a [2] tensor of column means from a [5, 2] takes each column's mean down its own column. This is called broadcasting, and it is why an element-wise op forgives a shape difference that matMul will not.
Tensor operations are not automatically faster than JavaScript loops. Call overhead and readback can dominate a small array. Acceleration depends on the workload and backend; measure the version you intend to run. This lesson practices shape rules, not a speed guarantee.
When You DO Want matMul
compute weighted sums. matMul computes them for every row of its left input against every column of its right input. Element-wise multiplication alone leaves the products unsummed. The matrix operation has a shape rule:
[m, n] × [n, p] → [m, p]: the inner dimensions must match.
The left tensor's column count must equal the right tensor's row count. If a matrix stores the intended axes in the opposite order, transpose it. A shape error can also mean missing or an incorrect layout; transposing until code runs can silently change the computation. Here, rows and columns need to swap:
Why care? Every dense layer you'll build is essentially matMul(input, weights) + bias, and the shape rule above is the most common source of dimension errors in ML code. The cells in module 6 reuse the same rows-into-columns rule, wrapped in more machinery. The error that opened this lesson will find you again the day you build your first network layer; next time you'll read it in one pass.
Challenge
Repair Demir's amplifier: average the two passes, amplify the average, and let broadcasting handle the pass that arrives as a single row. Then fold the channels into a few weighted mixes, which is what is for.
Demir's first pass at the amplifier is already on the main console when the shift starts, and so is its output: Error in matMul: inner shapes (64) and (1) of Tensors with shapes 1,64 and 1,64 and transposeA=false and transposeB=false must match. One line of code, one error, sixty-four channels of raw signal going nowhere.
Raw data is useless to me. Clean it, combine the feeds, amplify what matters. And it runs at sensor speed or it doesn't run. The hull array takes 983 thousand raw samples a minute before integration boils them down to readings; call it a million if you must round. I won't. Whatever that red text is, it is not keeping up with 983 thousand.
This lesson starts where you are standing: at the bug. You'll read the error, meet the two multiplication ops the broken line mixed up, and repair the pipeline.
Read the Error First
Here is the code that produced it. The intent: amplify each of the 64 channels by its own gain factor. Position by position, gain times reading.
import * as tf from '@tensorflow/tfjs';
// channelGains: 64 amplification factors. latestReadings: 64 raw values.
const gains = tf.tensor2d([channelGains]); // shape [1, 64]
const readings = tf.tensor2d([latestReadings]); // shape [1, 64]
// BROKEN: this is not the multiplication you want.
const amplified = gains.matMul(readings);
// Error in matMul: inner shapes (64) and (1) of Tensors with
// shapes 1,64 and 1,64 and transposeA=false and transposeB=false
// must match.The error is precise once you can read it: matMul multiplies rows into columns, so it needs the left 's column count (64) to equal the right tensor's row count (1). But matching positions was the intent all along, and that is a different op with no inner- rule at all. TensorFlow.js has both multiplications; the bug is reaching for the wrong one.
Element-wise Operations: The Repair
When you write arr.map((x, i) => x + arr2[i]), you're doing element-wise addition. tf.mul, tf.add, and tf.sub are the same idea across broadcast-compatible dimensions, on the selected backend. gains.mul(readings) is the one-word fix for the broken amplifier:
JavaScript
Array.map()
arr.map((x, i) => x + arr2[i])Machine Learning
tf.add()
tf.add(tensor1, tensor2)import * as tf from '@tensorflow/tfjs';
// The amplifier from amplify-broken.ts, repaired: matching positions,
// so matMul's inner-dimension rule does not apply. The shapes still
// have to be broadcast-compatible.
const amplified = gains.mul(readings); // [1, 64], gain[i] * reading[i]
const sensor1 = tf.tensor([1, 2, 3]);
const sensor2 = tf.tensor([4, 5, 6]);
// Element-wise operations: same as map + zip
const sum = tf.add(sensor1, sensor2); // [5, 7, 9]
const product = tf.mul(sensor1, sensor2); // [4, 10, 18]
const diff = tf.sub(sensor2, sensor1); // [3, 3, 3]
// Chaining with method syntax
const result = tf.tidy(() => sensor1.add(sensor2).mul(2));
// [10, 14, 18]
tf.dispose([amplified, gains, readings, sensor1, sensor2, sum, product, diff, result]);The do not have to be identical. Aligned dimensions must be equal, or one must be 1; otherwise fails. When they differ, TensorFlow.js lines the two tensors up from the last axis backwards and stretches any axis of size 1, or any axis the smaller tensor does not have at all, until it matches the larger one. That is why .mul(tf.scalar(2)) reaches every element, and why subtracting a [2] tensor of column means from a [5, 2] takes each column's mean down its own column. This is called broadcasting, and it is why an element-wise op forgives a shape difference that matMul will not.
Tensor operations are not automatically faster than JavaScript loops. Call overhead and readback can dominate a small array. Acceleration depends on the workload and backend; measure the version you intend to run. This lesson practices shape rules, not a speed guarantee.
When You DO Want matMul
compute weighted sums. matMul computes them for every row of its left input against every column of its right input. Element-wise multiplication alone leaves the products unsummed. The matrix operation has a shape rule:
[m, n] × [n, p] → [m, p]: the inner dimensions must match.
The left tensor's column count must equal the right tensor's row count. If a matrix stores the intended axes in the opposite order, transpose it. A shape error can also mean missing or an incorrect layout; transposing until code runs can silently change the computation. Here, rows and columns need to swap:
// weights: [3, 2], input: [3, 1]
const weights = tf.tensor2d([[1, 2], [3, 4], [5, 6]]); // [3, 2]
const input = tf.tensor2d([[1], [1], [1]]); // [3, 1]
// weights.matMul(input) would throw:
// [3, 2] × [3, 1]: inner dims 2 ≠ 3. No deal.
// transpose() flips [3, 2] → [2, 3]. Now the rule is satisfied:
// [2, 3] × [3, 1] → [2, 1] ✓
const output = tf.tidy(() => weights.transpose().matMul(input));
output.print();
// [[9], ← 1+3+5: first column of weights, summed against input
// [12]] ← 2+4+6: second column
tf.dispose([weights, input, output]);Why care? Every dense layer you'll build is essentially matMul(input, weights) + bias, and the shape rule above is the most common source of dimension errors in ML code. The cells in module 6 reuse the same rows-into-columns rule, wrapped in more machinery. The error that opened this lesson will find you again the day you build your first network layer; next time you'll read it in one pass.
Challenge
Repair Demir's amplifier: average the two passes, amplify the average, and let broadcasting handle the pass that arrives as a single row. Then fold the channels into a few weighted mixes, which is what is for.

