NumPy Norm: Understanding np.linalg.norm()

Ben Cook • Posted 2021-01-08 • Last updated 2021-10-15

A norm is a measure of the size of a matrix or vector and you can compute it in NumPy with the np.linalg.norm() function:

import numpy as np

x = np.eye(4)
np.linalg.norm(x)

# Expected result
# 2.0

When np.linalg.norm() is called on an array-like input without any additional arguments, the default behavior is to compute the L2 norm on a flattened view of the array. This is the square root of the sum of squared elements and can be interpreted as the length of the vector in Euclidean space.

Since the ravel() method flattens an array without making any copies and ord specifies the type of norm that will be computed, the above usage is equivalent to:

np.linalg.norm(x.ravel(), ord=2)

# Expected result
# 2.0

But watch out! The function can calculate many different kinds of norms. And if you specify the ord argument, then matrices (arrays with ndim=2) are treated differently than vectors (arrays with ndim=1). This leads to a somewhat surprising result:

np.linalg.norm(x, ord=2)

# Expected result
# 1.0

That is, even though ord=2 is the default behavior for vectors (and for vectors ord=2 does mean L2 norm), np.linalg.norm(x, ord=2) does not compute the L2 norm if x has more than 1 dimension. In fact, somewhat stupidly, ord=2 actually means something different for matrices in np.linalg.norm().

In order to avoid getting tricked by this behavior, it’s worth taking a look at the API and some example use cases.

API

The np.linalg.norm() function has three important arguments: xord, and axis.

  • x: this is an array-like input. If ord and axis are both None, then np.linalg.norm() will return the L2 norm of x.ravel(), which is a flattened (i.e. 1-dimensional) view of the array.
  • ord: the type of norm. If you just pass in x and ord leaving axis as None, then x must be 1-dimensional or 2-dimensional, otherwise you will get an exception. Most commonly, when x is a vector, you will want ord=2 or ord=1 for L2 and L1 norms respectively. And when x is a matrix, you will want ord='fro' for the Frobenius norm. But NumPy does support other norms which you can look up in their docs.
  • axis: the axis (or axes) to reduce with the norm operation. If this is an int then you will get vector norms along that dimension and if this is a 2-tuple, then you will get matrix norms along those dimensions.

That’s all a little too confusing for my preference. So instead of worrying about the combination of the number of dimensions of your x argument and ord, my recommendation is to use x by itself when you want an L2 norm or Frobenius norm (which is the same as the L2 norm on the flattened matrix):

np.linalg.norm(x)

Sometimes the ord and axis arguments are unavoidable (and I’ll show an example below), but only if 1) you need to reduce one or two of the dimensions or 2) you want to compute a norm other than L2.

Examples

Relative error

Let’s start with an easy example. A great use case for norms is computing the relative error between two arrays. For scalars, relative error is usually calculated with |x - x'| / |x|. Think of this like the size of the difference divided by the size of the original number.

Since norms are a way to encode the size of an array with a single number, you can use norms to do something very similar for arrays:

x_prime = x + np.random.uniform(0, 0.1)
np.linalg.norm(x_prime - x) / np.linalg.norm(x)

# Expected result like...
# 0.05465174120478311

Easy!

Normalization

You can normalize an array in order to force it to have a norm that you specify. For example, you can generate a random array that has an L2 norm of (approximately) 3. Just multiply every element by 3 and divide by the L2 norm:

x = np.random.uniform(size=10)
x = 3 * x / np.linalg.norm(x)
np.linalg.norm(x)

# Expected result
2.9999999999999996

If you wanted the vector have a unit norm, you would simply divide every element by the norm.

Sometimes, you may want to do this for your dataset. Say you have a matrix of data where every row is a sample and every column is a feature. If you want every row to have a unit norm, you can:

  1. Compute the row-wise norms (reducing the column dimension)
  2. Divide every element by its row norm

Here’s the code to normalize rows by their L2 norms for a randomly generated dataset with 10 rows and 3 columns:

data = np.random.uniform(size=(10, 3))
row_l2_norms = np.linalg.norm(data, axis=1)
data /= row_l2_norms[:, None]

# Now the rows all have a L2 norm of 1
np.linalg.norm(data, axis=1)

# Expected result
array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])

Notice: row_l2_norms will be a vector with size 10. If we want to use broadcasting rules to divide every element in data, which has shape (10, 3), we need to add a dummy dimension to give row_l2_norms shape (10, 1). That’s what the None index is doing.

Additionally, since the input is a matrix and we’re passing in axis=1, the function will compute the vector norm of each row. This means it’s safe to pass in ord=1 to get the row-wise L1 norms:

data = np.random.uniform(size=(10, 3))
row_l1_norms = np.linalg.norm(data, ord=1, axis=1)
data /= row_l1_norms[:, None]

# Now the rows all have a L1 norm of 1
np.linalg.norm(data, ord=1, axis=1)

# Expected result
array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])

This would not work if data had more than 2 dimensions.

By the way, scikit-learn provides a convenience function so you can more easily normalize rows of a dataset to have L1 or L2 unit norms. Here’s an example of normalizing every row by its L1 norm:

from sklearn import preprocessing

data = np.random.uniform(size=(10, 3))
data = preprocessing.normalize(data, norm='l1')

# Now the rows all have a L1 norm of 1
np.linalg.norm(data, ord=1, axis=1)

# Expected result
array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])

Pairwise distance

You can also use np.linalg.norm() to compute pairwise Euclidean distance between two sets of points. This is a little more involved and I have a separate post about computing pairwise distance.