248

I would like to convert a NumPy array to a unit vector. More specifically, I am looking for an equivalent version of this function

def normalize(v):
    norm = np.linalg.norm(v)
    if norm == 0: 
       return v
    return v / norm

Is there something like that in skearn or numpy?

This function works in a situation where v is the 0 vector.

crypdick
  • 4,829
  • 3
  • 31
  • 50
Donbeo
  • 14,217
  • 30
  • 93
  • 162
  • 6
    What's wrong with what you've written? – ali_m Jan 09 '14 at 20:30
  • 6
    If this is really a concern, you should check for norm < epsilon, where epsilon is a small tolerance. In addition, I wouldn't silently pass back a norm zero vector, I would `raise` an exception! – Hooked Jan 09 '14 at 20:51
  • 5
    my function works but I would like to know if there is something inside the python's more common library. I am writing different machine learning functions and I would like to avoid to define too much new functions to make the code more clear and readable – Donbeo Jan 09 '14 at 21:08
  • 2
    I did a few quick tests and I found that `x/np.linalg.norm(x)` was not much slower (about 15-20%) than `x/np.sqrt((x**2).sum())` in numpy 1.15.1 on a CPU. – Bill Sep 10 '18 at 19:10

12 Answers12

196

If you're using scikit-learn you can use sklearn.preprocessing.normalize:

import numpy as np
from sklearn.preprocessing import normalize

x = np.random.rand(1000)*10
norm1 = x / np.linalg.norm(x)
norm2 = normalize(x[:,np.newaxis], axis=0).ravel()
print np.all(norm1 == norm2)
# True
ali_m
  • 62,795
  • 16
  • 193
  • 270
  • 3
    Thanks for the answer but are you sure that sklearn.preprocessing.normalize works also with vector of shape=(n,) or (n,1) ? I am having some problems with this library – Donbeo Jan 09 '14 at 21:17
  • `normalize` requires a 2D input. You can pass the `axis=` argument to specify whether you want to apply the normalization across the rows or columns of your input array. – ali_m Jan 09 '14 at 21:20
  • 9
    Note that the 'norm' argument of the normalize function can be either 'l1' or 'l2' and the default is 'l2'. If you want your vector's sum to be 1 (e.g. a probability distribution) you should use norm='l1' in the normalize function. – Ash Nov 06 '15 at 10:56
  • 2
    Also note that `np.linalg.norm(x)` calculates 'l2' norm by default. If you want your vector's sum to be 1 you should use `np.linalg.norm(x, ord=1)` – Omid Jul 28 '18 at 17:21
  • Note: x must be `ndarray` for it to work with the `normalize()` function. Otherwise it can be a `list`. – Ramin Melikov Apr 27 '20 at 04:17
55

I would agree that it were nice if such a function was part of the included batteries. But it isn't, as far as I know. Here is a version for arbitrary axes, and giving optimal performance.

import numpy as np

def normalized(a, axis=-1, order=2):
    l2 = np.atleast_1d(np.linalg.norm(a, order, axis))
    l2[l2==0] = 1
    return a / np.expand_dims(l2, axis)

A = np.random.randn(3,3,3)
print(normalized(A,0))
print(normalized(A,1))
print(normalized(A,2))

print(normalized(np.arange(3)[:,None]))
print(normalized(np.arange(3)))
Guillaume Jacquenot
  • 9,076
  • 5
  • 38
  • 47
Eelco Hoogendoorn
  • 9,321
  • 1
  • 39
  • 38
  • I did not deeply test the ali_m solution but in some simple case it seems to be working. Are there situtions where your function does better? – Donbeo Jan 09 '14 at 23:20
  • 2
    I don't know; but it works over arbitrary axes, and we have explicit control over what happens for length 0 vectors. – Eelco Hoogendoorn Jan 10 '14 at 06:52
  • 1
    Very nice! This should be in numpy — although order should probably come before axis in my opinion. – Neil G Jan 16 '15 at 15:57
  • 1
    @EelcoHoogendoorn Curious to understand why order=2 chosen over others? – Henry Thornton Jul 05 '15 at 07:35
  • 11
    Because the Euclidian/pythagoran norm happens to be the most frequently used one; wouldn't you agree? – Eelco Hoogendoorn Jul 06 '15 at 08:47
  • 1
    Pretty late, but I think it's worth mentioning that this is exactly why it is discouraged to use lowercase 'L' as a variable name... in my typeface 'l2' is indistinguishable from '12' – bendl Jun 13 '17 at 17:45
  • normalizing `[inf, 1, 2]` yields `[nan, 0, 0]`, but shouldn't it be `[1, 0, 0]`? – pasbi Mar 09 '18 at 16:31
  • 1
    If you'd like to endow the fp-inf symbol with such semantics, sure, but thatd be kinda nonstandard. The fp standard is full of quirks anyway but I think having such a function do anything but standard fp logic by default would just be confusing. – Eelco Hoogendoorn Mar 09 '18 at 17:18
  • Shouldn't the normalized array sum up to 1 (at least I would expect it to do)? Just tested this implementation with `[5,5]` what yields `[0.70710678, 0.70710678]` what in sum is about `1.41`. Doesn't sound right to me. – Spenhouet Jun 21 '18 at 16:01
  • Look up the concept of the order of a norm. What you want is the 1-norm, which you can get by setting the order kwarg to 1. – Eelco Hoogendoorn Jun 21 '18 at 21:36
  • @bendl I think that's exactly why it's encouraged to use a better typeface – anon01 Mar 25 '21 at 16:32
24

You can specify ord to get the L1 norm. To avoid zero division I use eps, but that's maybe not great.

def normalize(v):
    norm=np.linalg.norm(v, ord=1)
    if norm==0:
        norm=np.finfo(v.dtype).eps
    return v/norm
Eduard Feicho
  • 508
  • 3
  • 8
24

This might also work for you

import numpy as np
normalized_v = v / np.sqrt(np.sum(v**2))

but fails when v has length 0.

In that case, introducing a small constant to prevent the zero division solves this.

mrk
  • 5,654
  • 3
  • 41
  • 63
11

You mentioned sci-kit learn, so I want to share another solution.

sci-kit learn MinMaxScaler

In sci-kit learn, there is a API called MinMaxScaler which can customize the the value range as you like.

It also deal with NaN issues for us.

NaNs are treated as missing values: disregarded in fit, and maintained in transform. ... see reference [1]

Code sample

The code is simple, just type

# Let's say X_train is your input dataframe
from sklearn.preprocessing import MinMaxScaler
# call MinMaxScaler object
min_max_scaler = MinMaxScaler()
# feed in a numpy array
X_train_norm = min_max_scaler.fit_transform(X_train.values)
# wrap it up if you need a dataframe
df = pd.DataFrame(X_train_norm)
Reference
J.Hirsch
  • 119
  • 6
WY Hsu
  • 1,400
  • 2
  • 18
  • 30
  • 1
    This does a different type of transform. The OP wanted to scale the magnitude of the vector so that each vector has a length of 1; MinMaxScaler individually scales each column independently to be within a certain range. – crypdick Dec 08 '20 at 14:32
10

If you have multidimensional data and want each axis normalized to its max or its sum:

def normalize(_d, to_sum=True, copy=True):
    # d is a (n x dimension) np array
    d = _d if not copy else np.copy(_d)
    d -= np.min(d, axis=0)
    d /= (np.sum(d, axis=0) if to_sum else np.ptp(d, axis=0))
    return d

Uses numpys peak to peak function.

a = np.random.random((5, 3))

b = normalize(a, copy=False)
b.sum(axis=0) # array([1., 1., 1.]), the rows sum to 1

c = normalize(a, to_sum=False, copy=False)
c.max(axis=0) # array([1., 1., 1.]), the max of each row is 1
Jaden Travnik
  • 863
  • 12
  • 23
  • 1
    Watch out if all values are the same in the original matrix, then ptp would be 0. Division by 0 will return nan. – Milso Mar 10 '20 at 13:34
9

There is also the function unit_vector() to normalize vectors in the popular transformations module by Christoph Gohlke:

import transformations as trafo
import numpy as np

data = np.array([[1.0, 1.0, 0.0],
                 [1.0, 1.0, 1.0],
                 [1.0, 2.0, 3.0]])

print(trafo.unit_vector(data, axis=1))
Joe
  • 5,221
  • 2
  • 12
  • 34
7

If you're working with 3D vectors, you can do this concisely using the toolbelt vg. It's a light layer on top of numpy and it supports single values and stacked vectors.

import numpy as np
import vg

x = np.random.rand(1000)*10
norm1 = x / np.linalg.norm(x)
norm2 = vg.normalize(x)
print np.all(norm1 == norm2)
# True

I created the library at my last startup, where it was motivated by uses like this: simple ideas which are way too verbose in NumPy.

paulmelnikow
  • 16,036
  • 6
  • 56
  • 110
7

If you work with multidimensional array following fast solution is possible.

Say we have 2D array, which we want to normalize by last axis, while some rows have zero norm.

import numpy as np
arr = np.array([
    [1, 2, 3], 
    [0, 0, 0],
    [5, 6, 7]
], dtype=np.float)

lengths = np.linalg.norm(arr, axis=-1)
print(lengths)  # [ 3.74165739  0.         10.48808848]
arr[lengths > 0] = arr[lengths > 0] / lengths[lengths > 0][:, np.newaxis]
print(arr)
# [[0.26726124 0.53452248 0.80178373]
# [0.         0.         0.        ]
# [0.47673129 0.57207755 0.66742381]]
Stanislav Tsepa
  • 530
  • 6
  • 10
6

Without sklearn and using just numpy. Just define a function:.

Assuming that the rows are the variables and the columns the samples (axis= 1):

import numpy as np

# Example array
X = np.array([[1,2,3],[4,5,6]])

def stdmtx(X):
    means = X.mean(axis =1)
    stds = X.std(axis= 1, ddof=1)
    X= X - means[:, np.newaxis]
    X= X / stds[:, np.newaxis]
    return np.nan_to_num(X)

output:

X
array([[1, 2, 3],
       [4, 5, 6]])

stdmtx(X)
array([[-1.,  0.,  1.],
       [-1.,  0.,  1.]])

seralouk
  • 22,948
  • 5
  • 82
  • 101
  • These output arrays do not have unit norm. Subtracting the mean and giving the samples unit variance does not produce unit vectors. – crypdick Dec 08 '20 at 14:52
5

If you want to normalize n dimensional feature vectors stored in a 3D tensor, you could also use PyTorch:

import numpy as np
from torch import FloatTensor
from torch.nn.functional import normalize

vecs = np.random.rand(3, 16, 16, 16)
norm_vecs = normalize(FloatTensor(vecs), dim=0, eps=1e-16).numpy()
max0r
  • 311
  • 2
  • 6
5

If you don't need utmost precision, your function can be reduced to:

v_norm = v / (np.linalg.norm(v) + 1e-16)
sergio verduzco
  • 181
  • 1
  • 4