Reputation: 323
I have a function f
that compute the inner-product-like value of 2 vectors (the function is symmetrical, so f(x, y) = f(y, x)
). It take 2 1d array of same size and output a single value. Now I have a data matrix X
of shape (n, d)
(each row represents an input vector), and I would like to compute a matrix K
with shape (n, n)
such that K[i][j] = f(X[i], X[j])
. One way to do this is to use the following code.
import numpy as np
# ... some other code
K = np.zeros((n, n))
for i in range(n):
for j in range(n):
K[i][j] = f(X[i], X[j])
This works, but it's neither elegant nor performant.
Another way to do this is using vectorize
from numpy
import numpy as np
# ... some other code
vf = np.vectorize(f, signature="(n),(n)->()")
# this does not work (output shape (n,))
#K = vf(X, X)
# this works (output shape (n, n))
K = vf(X, X[: np.newaxis])
# this also works (output shape (n, n), result same as above)
#K = vf(X[: np.newaxis], X)
If we put in an array of (n, d)
and another array of (n, d)
, it will output an 1 dimensional array of (n,)
, essentially taking each of the n
rows of both matrix as input and thus produce n
output. However, if we transform it to X[: np.newaxis]
, which has shape (n, 1, d)
, it works, but why it works?
I already checked this and this, but still can't figure out.
Upvotes: 0
Views: 369
Reputation: 19322
Another example in addition to @hpaulj's answer
Broadcasting can be used to vectorize your code efficiently. Let's take an example. I have an array (10,2) and I want to take the Euclidean distance between each of the points.
X = np.random.uniform(-5,5,(10,2))
One way to do this is to broadcast my complete formula for Euclidean distance using NumPy function which supports broadcasting.
np.sqrt(np.sum(np.square(np.subtract(X[:,None,:], X[None,:,:])), axis=-1))
#This returns a (10,10) matrix, with euclidean distance of each of the 10 points to the other
Similarly, another thing you could do is vectorize your function to behave similar to a numpy function and support broadcasting.
def euclidean(point1, point2):
return ((point1[0]-point2[0])**2 + (point1[1]-point2[1])**2)**(1/2)
euclidean_v = np.vectorize(euclidean, signature="(n),(n)->()")
euclidean_v(X[:,None,:], X[None,:,:])
#Again returns a (10,10)
In both cases -
|--- (10,1,2)--|
(10,2)---| |---(10,10,2)--->(10,10)
|--- (1,10,2)--|
Upvotes: 1
Reputation: 231385
We can illustrate this broadcasting
with add:
In [111]: x = np.arange(12).reshape(3,4)
In [112]: x
Out[112]:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
In [113]: x + x[:,None]
Out[113]:
array([[[ 0, 2, 4, 6],
[ 4, 6, 8, 10],
[ 8, 10, 12, 14]],
[[ 4, 6, 8, 10],
[ 8, 10, 12, 14],
[12, 14, 16, 18]],
[[ 8, 10, 12, 14],
[12, 14, 16, 18],
[16, 18, 20, 22]]])
In [114]: _.shape
Out[114]: (3, 3, 4)
In [115]: (x + x[:,None]).sum(axis=-1)
Out[115]:
array([[12, 28, 44],
[28, 44, 60],
[44, 60, 76]])
The broadcasting goes: (3,4) and (3,1,4) => (1,3,4) and (3,1,4) => (3,3,4). Then we sum on the last axis to get (3,3)
Doing the same thing as you did:
In [116]: vf = np.vectorize(lambda a,b:(a+b).sum(), signature="(n),(n)->()")
In [117]: vf(x, x[:,None])
Out[117]:
array([[12, 28, 44],
[28, 44, 60],
[44, 60, 76]])
But as noted, np.vectorize
is not a speed tool, especially when using signature
:
In [118]: timeit (x + x[:,None]).sum(axis=-1)
13.8 µs ± 363 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [119]: timeit vf(x, x[:,None])
278 µs ± 7.68 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Without adding the dimension to the 2nd x
:
In [120]: vf(x, x)
Out[120]: array([12, 44, 76])
In [121]: (x + x).sum(axis=-1)
Out[121]: array([12, 44, 76])
Upvotes: 1