ML Grind

Broadcasting Operations - NumPy Fundamentals

Implement Broadcasting Operations in Python with NumPy. medium exercise with examples, hints, and instant feedback. No setup or signup required.

Broadcasting Operations

What you'll build

A function broadcasting_ops(arr, bias, scale) that performs four independent operations on the same matrix, each using broadcasting instead of loops.

Background

Broadcasting is how a bias vector gets added to every row of a layer's output, how features are standardised with per-column statistics, and how per-sample weights scale a batch. It lets NumPy combine arrays of different shapes without copying data.

Broadcasting rules

  1. Line the two shapes up from the right (trailing dimensions first).
  2. Each pair of dimensions must be equal, or one of them must be 1. A missing leading dimension counts as 1.
  3. Dimensions of size 1 are stretched to match the other array.
OperationShapesResult
arr - arr.mean(axis=1, keepdims=True)(m, n) and (m, 1)(m, n)
arr - arr.mean(axis=0)(m, n) and (n,)(m, n)
arr + bias(m, n) and (n,)(m, n)
arr * scale(m, n) and (m, 1)(m, n)

Inputs

  • arr: 2D numeric array of shape (m, n), e.g. (3, 4)
  • bias: 1D array of shape (n,)
  • scale: 2D array of shape (m, 1) (a column vector)

Expected Return Format

Return a dictionary with these keys. Each value is computed from the original arr (the operations are not chained) and has shape (m, n):

  • 'row_centered': arr minus each row's mean, so every row of the result has mean 0
  • 'col_centered': arr minus each column's mean, so every column of the result has mean 0
  • 'biased': arr + bias, i.e. bias[j] added to column j of every row
  • 'scaled': arr * scale, i.e. row i multiplied by scale[i, 0]

Edge cases and conventions

  • Means are floats, so the centred outputs are float even for integer input.
  • arr.mean(axis=1) has shape (m,). Subtracting it from an (m, n) array either fails (when m != n) or, for a square matrix, silently subtracts along the wrong axis. Keep the reduced axis with keepdims=True.

Constraints

Use broadcasting; no Python loops, np.tile or np.repeat.

Follow-up

  • Standardise each column to mean 0 and standard deviation 1. What goes wrong for a constant column?
  • What shape does np.ones((3, 1)) + np.ones((1, 4)) have, and why?

Examples

Input: arr = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
broadcasting_ops(arr, np.zeros(4), np.ones((3, 1)))["row_centered"][0]
Output: array([-1.5, -0.5,  0.5,  1.5])

Row 0 has mean 2.5, which is subtracted from each of its entries.

Input: broadcasting_ops(arr, np.zeros(4), np.ones((3, 1)))["col_centered"]
Output: array([[-4., -4., -4., -4.],
       [ 0.,  0.,  0.,  0.],
       [ 4.,  4.,  4.,  4.]])

The column means are [5, 6, 7, 8]; each row sits 4 below, at, or 4 above them.

Input: broadcasting_ops(arr, np.array([10, 20, 30, 40]), np.array([[1], [2], [3]]))["scaled"]
Output: array([[ 1,  2,  3,  4],
       [10, 12, 14, 16],
       [27, 30, 33, 36]])

The (3, 1) column scales row i by scale[i, 0].

Practice in Python

import numpy as np

def broadcasting_ops(arr: np.ndarray, bias: np.ndarray, scale: np.ndarray) -> dict:
    """
    Four independent broadcasting operations on arr.

    Args:
        arr:   2D array of shape (m, n), e.g. (3, 4)
        bias:  1D array of shape (n,)
        scale: 2D array of shape (m, 1)

    Returns:
        dict with keys (each computed from the original arr, shape (m, n)):
            'row_centered': arr minus each row's mean
            'col_centered': arr minus each column's mean
            'biased':       arr + bias (bias added to every row)
            'scaled':       arr * scale (row i multiplied by scale[i, 0])
    """
    # Your code here
    pass

The interactive editor loads with Python and NumPy. Run your implementation against 9 tests, or use the progressive hints.

More exercises in this section →