1
votes

Have been completely stuck on a rather silly issue: I'm trying to compute the dot product of some attributes between objects, but keep getting a Value Error - Shape Mismatch - but the shapes are identical (2,1) and (2,1), since the arrays are just attributes of different instances of the same class:

class MyClass(Object):
      def __init__(self, a,b, x,y):
          self.prop_1 = np.array((a,b))
          self.prop_2 = np.array((x,y))

where all a, b, x, and y are scalars. then further down I'm trying

def MyFunction(Obj1, Obj2):
    results = np.dot(Obj1.prop_1 - Obj2.prop_1, Obj2.prop_2 - Obj2.prop_3)

which keeps throwing the Value Error

ValueError: shapes (2,1) and (2,1) not aligned: 1 (dim 1) != 2 (dim 0)

Mathematically, this dot product should be fine - but the final bit of the error message kind of suggests I have to transpose one of the arrays. I'd be very thankful for a short explanation of the numpy shape interpretation to avoid this kind of error!

EDIT:

Think I misphrased this a bit. When I initiate my objects via (case a)

a,b = np.random.rand(2)
x,y = np.random.rand(2)
MyClass(a, b, x, y)

Everything works like a charm. If instead however I initiate as (case b)

a = np.random.rand(1)
b = np.random.rand(1)
x = np.random.rand(1)
y = np.random.rand(1)
MyClass(a, b, x, y)

the dot product later on fails to work because of the shape mismatch.

I have noticed that in case b, each individual value is of shape (1,) and it's clear to me that combining two of these will result in shape (2,1) instead of shape () in case a - but why do these two ways of declaring a variable result in different shapes?

As you can tell I'm relatively new to Python and thought this was just a neat way to perform multiple assignments - turns out there is some further reasoning behind it, and i'd be interested to hear about that.

1
should it be np.dot(a.T, b) ? np.dot(a.ravel(), b.ravel()) would also work. - Quang Hoang
How are you creating the instance of MyClass i.e. what are the values of a, b etc? Also please fix the typo in innit and where are all the self arguments? If a and b were scalars, then self.prop_1 would have a shape of (2,) and you wouldn't have a problem. - Dan
For 1d arrays. dot does the inner, scalar product. For 2d (which you have) it does matrix product - remember the manual row with columns method? - hpaulj
@Dan all arguments are scalars - shape 1, but once I combine them I end up with the shape mentioned in the question. - LoschmidtsSchnitzel
@LoschmidtsSchnitzel yes now the problem becomes clear. np.random.random creates numpy arrays. When you tried to initiate your array with other arrays you made an array of arrays. Numpy logically assumed you wanted to make a 2D array, otherwise you'd have passed in simple floats. When you went a, b = np.random.random(2) you took advantage of unpacking to assign each element of the sequence (the array in this case) to it's own variable. This results in a and b being simple floats. Later when you try np.array((a, b)), since you've passed in floats, numpy will you a 1D array. - Dan

1 Answers

2
votes

Part 1

The issue is that your arrays are full-blown 2-D matrices, not 1D "vectors" in the sense that np.dot understands it. To get your multiplication working, you need to either (a) convert your vectors to vectors:

np.dot(a.reshape(-1), b.reshape(-1))

(b) set up the matrix multiplication so that the dimensions work. Remember that the dot product of two Nx1 matrices is ATB:

np.dot(a.T, b)

or (c), use np.einsum to explicitly set the dimension of the sum:

np.einsum('ij,ij->j', a, b).item()

For all of the examples using dot, you can use np.matmul (or equivalently the @ operator), or np.tensordot, because you have 2D arrays.

In general, keep the following rules in mind when working with dot. Table cells are einsum subscripts

                                      A
      |         1D        |          2D         |                ND             |
   ---+-------------------+---------------------+-------------------------------+
   1D | i,i->             | ij,j->i             | a...yz,z->a...y               |
   ---+-------------------+---------------------+-------------------------------+
B  2D | i,ij->j           | ij,jk->ik           | a...xy,yz->a...xz             |
   ---+-------------------+---------------------+-------------------------------+
   ND | y,a...xyz->a...xz | ay,b...xyz->ab...xz | a...mxy,n...wyz->a...mxn...wz |
   ---+-------------------+---------------------+-------------------------------+

Basically, dot follows normal rules for matrix multiplication along the last two dimensions, but the leading dimensions are always combined. If you want the leading dimensions to be broadcast together for arrays > 2D (i.e., multiplying corresponding elements in a stack of matrices, rather all possible combinations), use matmul or @ instead.

Part 2

When you initialize the inputs as a, b = np.random.rand(2), you are unpacking the two elements of the array into scalars:

>>> a, b = np.random.rand(2)
>>> a
0.595823752387523
>>> type(a)
numpy.float64
>>> a.shape
()

Note that the type is not numpy.ndarray in this case. However, when you do a = np.random.rand(1), the result is a 1D array of one element:

>>> a = np.random.rand(1)a
>>> a
array([0.21983553])
>>> type(a)
numpy.ndarray
>>> a.shape
(1,)

When you create a numpy array from numpy arrays, the result is a 2D array:

>>> np.array([1, 2]).shape
(2,)
>>> np.array([np.array([1]), np.array([2])]).shape
(2, 1)

Going forward, you have two options. You can either be more careful with your inputs, or you can sanitize the array after you've created it.

You can expand the arrays that you feed in:

ab = np.random.rand(2)
xy = np.random.rand(2)
MyClass(*ab, *xy)

Or you can just flatten/ravel your arrays once you've created them:

def __init__(self, a, b, x, y):
     self.prop_1 = np.array([a, b]).ravel()
     self.prop_2 = np.array([x, y]).ravel()

You can use ....reshape(-1) instead of ...ravel().