4

I have a 3D image with dimensions rows x cols x deps. For every voxel in the image, I have computed a 3x3 real symmetric matrix. They are stored in the array D, which therefore has shape (rows, cols, deps, 6).

D stores the 6 unique elements of the 3x3 symmetric matrix for every voxel in my image. I need to find the Moore-Penrose pseudo inverse of all row*cols*deps matrices simultaneously/in vectorized code (looping through every image voxel and inverting is far too slow in Python).

Some of these 3x3 symmetric matrices are non-singular, and I can find their inverses, in vectorized code, using the analytical formula for the true inverse of a non-singular 3x3 symmetric matrix, and I've done that.

However, for those matrices that ARE singular (and there are sure to be some) I need the Moore-Penrose pseudo inverse. I could derive an analytical formula for the MP of a real, singular, symmetric 3x3 matrix, but it's a really nasty/lengthy formula, and would therefore involve a VERY large number of (element-wise) matrix arithmetic and quite a bit of confusing looking code.

Hence, I would like to know if there is a way to simultaneously find the MP pseudo inverse for all these matrices at once numerically. Is there a way to do this?

Gratefully, GF

NLi10Me
  • 3,182
  • 2
  • 13
  • 15

2 Answers2

8

NumPy 1.8 included linear algebra gufuncs, which do exactly what you are after. While np.linalg.pinv is not gufunc-ed, np.linalg.svd is, and behind the scenes that is the function that gets called. So you can define your own gupinv function, based on the source code of the original function, as follows:

def gu_pinv(a, rcond=1e-15):
    a = np.asarray(a)
    swap = np.arange(a.ndim)
    swap[[-2, -1]] = swap[[-1, -2]]
    u, s, v = np.linalg.svd(a)
    cutoff = np.maximum.reduce(s, axis=-1, keepdims=True) * rcond
    mask = s > cutoff
    s[mask] = 1. / s[mask]
    s[~mask] = 0

    return np.einsum('...uv,...vw->...uw',
                     np.transpose(v, swap) * s[..., None, :],
                     np.transpose(u, swap))

And you can now do things like:

a = np.random.rand(50, 40, 30, 6)
b = np.empty(a.shape[:-1] + (3, 3), dtype=a.dtype)
# Expand the unique items into a full symmetrical matrix
b[..., 0, :] = a[..., :3]
b[..., 1:, 0] = a[..., 1:3]
b[..., 1, 1:] = a[..., 3:5]
b[..., 2, 1:] = a[..., 4:]
# make matrix at [1, 2, 3] singular
b[1, 2, 3, 2] = b[1, 2, 3, 0] + b[1, 2, 3, 1]

# Find all the pseudo-inverses
pi = gu_pinv(b)

And of course the results are correct, both for singular and non-singular matrices:

>>> np.allclose(pi[0, 0, 0], np.linalg.pinv(b[0, 0, 0]))
True
>>> np.allclose(pi[1, 2, 3], np.linalg.pinv(b[1, 2, 3]))
True

And for this example, with 50 * 40 * 30 = 60,000 pseudo-inverses calculated:

In [2]: %timeit pi = gu_pinv(b)
1 loops, best of 3: 422 ms per loop

Which is really not that bad, although it is noticeably (4x) slower than simply calling np.linalg.inv, but this of course fails to properly handle the singular arrays:

In [8]: %timeit np.linalg.inv(b)
10 loops, best of 3: 98.8 ms per loop
Jaime
  • 65,696
  • 17
  • 124
  • 159
  • 1
    Very cool, this is an amazing feature of the new release! An ipython notebook with a list of the new generalized ufunc is [here](http://nbviewer.ipython.org/gist/jaimefrio/8395341) – gg349 May 02 '14 at 15:01
  • 1
    Funny, that notebook is from a talk I gave to the San Diego Python Users Group! I would have never thought that anyone other than those attending that talk would ever find it. Internet is a small place... – Jaime May 02 '14 at 18:31
  • Thanks for your answer Jamie, this looks like some powerful code... but I don't totally understand it yet. I'm relatively new to Python. Is a gufunc a gpu implementation? I'm sure I can look this up, but thought I'd ask in case anyone else didn't know. Of course, taking the pseudoinverse of 60,000 arbitrary 3x3 real/symmetric matrices in 422 ms is pretty awesome. – NLi10Me May 02 '14 at 22:20
  • 1
    It has nothing to do with the GPU. You may want to read the docs on universal functions, aka ufuncs, [here](http://docs.scipy.org/doc/numpy/reference/ufuncs.html). You may then want to go on an read about generalized universal functions, aka gufuncs, [here](http://docs.scipy.org/doc/numpy/reference/c-api.generalized-ufuncs.html). Basically, ufuncs take an operation defined on scalars, like addition, and run it elementwise on all elements of its input arrays. Gufuncs do something similar with operations defined on arrays, so that a single call runs the same operation on many stacked arrays. – Jaime May 02 '14 at 22:41
1

EDIT: See @Jaime's answer. Only the discussion in the comments to this answer is useful now, and only for the specific problem at hand.

You can do this matrix by matrix, using scipy, that provides pinv (link) to calculate the Moore-Penrose pseudo inverse. An example follows:

from scipy.linalg import det,eig,pinv
from numpy.random import randint
#generate a random singular matrix M first
while True:
    M = randint(0,10,9).reshape(3,3)
    if det(M)==0:
        break
M = M.astype(float)
#this is the method you need
MPpseudoinverse = pinv(M)

This does not exploit the fact that the matrix is symmetric though. You may also want to try the version of pinv exposed by numpy, that is supposedely faster, and different. See this post.

Community
  • 1
  • 1
gg349
  • 21,996
  • 5
  • 54
  • 64
  • I'm aware of scipy.linalg.pinv, my question is how to AVOID doing it matrix by matrix. Looping though all rows*cols*deps matrices is too slow for my application. – NLi10Me May 01 '14 at 18:45
  • 1
    Sorry, I had the impression this wasn't the case, as only some of them are singular. Have you done some profiling on your code? Are you SURE that it is your call to `pinv` your problem? How many matrices are usually be singular? I suspect few (?). In that case, trying to vectorize `pinv` is premature optimization. – gg349 May 01 '14 at 19:09
  • I think you're right. It's a small portion of the matrices that are singular. I'll try to isolate them, loop over JUST those, and use pinv. Maybe that will be fast enough. Initially, I had many computations inside a loop over all voxels (not just the call to pinv), so I don't think it was pinv particularly that was slowing things down. Most of those other operations are already vectorized and this is the only trouble maker. – NLi10Me May 01 '14 at 19:22
  • 1
    Ok, keep us posted. You may also want to try to use the `numpy` version, which may be faster or slower, see the link above – gg349 May 01 '14 at 20:04
  • I did actually use the numpy version. It does happen to be fast enough, as the percentage of singular matrices is very small. Thank you very much for your help. The original question still stands however, is there a way in python to simultaneously invert a collection of small matrices? – NLi10Me May 01 '14 at 20:19
  • I suppose another "outside the python" solution is parallel processing, but I'm seeking a solution within python. – NLi10Me May 01 '14 at 20:20