Tensorflow: Interlieving two ragged tensors - python

Is it possible to interleave two Ragged Tensors in Tensorflow? Example:
I have two RaggedTensors with the same "shape":
a = [[[10,10]],[[20,20],[21,21]]]
b = [[[30,30]],[[40,40],[41,41]]]
I would like to interleave them so the resulting tensor looks like this:
c = [[[10,10],[30,30]],[[20,20],[40,40],[21,21],[41,41]]]
Note that both tensors a and b always have the same "shape".
I have been trying to use the stack and the concat functions but both of them return non-desired shapes:
tf.stack([a,b],axis=-1)
c = [[[[10, 30], [10, 30]]], [[[20, 40], [20, 40]], [[21, 41], [21, 41]]]]
tf.concat([a,b],axis=-1)
c = [[[10, 10, 30, 30]], [[20, 20, 40, 40], [21, 21, 41, 41]]]
I have seen some other solutions for regular tensors that reshape the resulting tensor c after applying the stack/concat functions. E.g.,:
a = [[[10, 10]], [[20, 20]]]
b = [[[30, 30]], [[40, 40]]]
tf.reshape(
tf.concat([a[..., tf.newaxis], b[..., tf.newaxis]], axis=1),
[a.shape[0], -1, a.shape[-1]])
c = [[[10, 10],[30, 30]],[[20, 20],[40, 40]]]
However, as far as I know, since I am using Ragged Tensors the shape in some dimensions is None (I am using TF2.6).

If this is a part of a preprocessing step, the tf.data.Dataset API is one route. This has the benefit in using the "interleave" function of mixing up the interleaving pattern with different block_length settings and can interleave an arbitrary number of lists.
# I made a little longer to emphasize raggedness
a = tf.ragged.constant([[[10,10]],[[20,20],[21,21],[23,23]]])
b = tf.ragged.constant([[[30,30]],[[40,40],[41,41]]])
c = tf.concat([a,b],axis=1)
NUM_ELEMENTS=7
# Option 1) more flexible
def ragged_to_ds(x):
return tf.data.Dataset.from_tensor_slices(x)
tf.data.Dataset.from_tensor_slices(c).interleave(ragged_to_ds, block_length=1).batch(NUM_ELEMENTS).get_single_element()
# Option 2) less mess but unbatch makes copies of the data
tf.data.Dataset.from_tensor_slices(c).unbatch().batch(NUM_ELEMENTS).get_single_element()
The tf.data.Dataset API can be powerful and expressive and can help with a large number of data processing and rearranging tasks.

Related

numpy array with n prefilled columns

I need to specialized numpy arrays. Assume I have a function:
def gen_array(start, end, n_cols):
It should behave like this, generating three columns where each column goes from start (inclusive) to end (exclusive):
>>> gen_array(20, 25, 3)
array([[20, 20, 20],
[21, 21, 21],
[22, 22, 22],
[23, 23, 23],
[24, 24, 24]])
My rather naïve implementation looks like this:
def gen_array(start, end, n_columns):
a = np.arange(start, end).reshape(end-start, 1) # create a column vector from start to end
return np.dot(a, [np.ones(n_columns)]) # replicate across n_columns
(It's okay, though not required, that the np.dot converts values to floats.)
I'm sure there's a better, more efficient and more numpy-ish way to accomplish the same thing. Suggestions?
Update
Buildin on a suggestion by #msi_gerva to use np.tile, my latest best thought is:
def gen_array(start, end, n_cols):
return np.tile(np.arange(start, end).reshape(-1, 1), (1, n_cols))
... which seems pretty good to me.
In addition to numpy.arange and numpy.reshape, use numpy.repeat to extend your data.
import numpy as np
def gen_array(start, end, n_cols):
return np.arange(start, end).repeat(n_cols).reshape(-1, n_cols)
print(gen_array(20, 25, 3))
# [[20 20 20]
# [21 21 21]
# [22 22 22]
# [23 23 23]
# [24 24 24]]
The simplest I found:
The [:,None] adds a dimension to the array.
np.arange(start, end)[:,None]*np.ones(n_cols)
np.arange(start, end)[:, np.newaxis].repeat(n_cols, axis=1)

Find index where a sub-tensor does not equal to a given tensor in Pytorch

I have a tensor, for example,
a = [[15,30,0,2], [-1,-1,-1,-1], [10, 20, 40, 60], [-1,-1,-1,-1]]
which has the shape (4,4).
How can I find the index where a specific sub-tensor
[-1,-1,-1,-1]
that doesn't appear using PyTorch. The expected output I want to get is
[0,2]
You can compare the elements for each row of the tensor using torch.any(), and then use .nonzero() and .flatten() to generate the indices:
torch.any(a != torch.Tensor([-1, -1, -1, -1]), axis=1).nonzero().flatten()
For example,
import torch
a = torch.Tensor([[15,30,0,2], [-1,-1,-1,-1], [10, 20, 40, 60], [-1,-1,-1,-1]])
result = torch.any(a != torch.Tensor([-1, -1, -1, -1]), axis=1).nonzero().flatten()
print(result)
outputs:
tensor([0, 2])
You can also use where or nonzero:
a = torch.Tensor([[15,30,0,2], [-1,-1,-1,-1], [10, 20, 40, 60], [-1,-1,-1,-1]])
b = torch.Tensor([-1,-1,-1,-1])
result = torch.where(a != b)[0].unique()
result = torch.nonzero(a != b, as_tuple=True)[0].unique()
print(result)

Pytorch different outputs between with transpose

Let I have a tensor dimension of (B, N^2, C)
and I reshape it into (B, C, N, N).
I think that I have two choices below
A = torch.rand(5, 100, 20) # Original Tensor
# First Method
B = torch.transpose(2, 1)
B = B.view(5, 20, 10, 10)
# Second Method
C = A.view(5, 20, 10, 10)
Both methods work but the outputs are slightly different and I cannot catch the difference between them.
Thanks
The difference between B and C is that you have used torch.transpose which means you have swapped two axes, this means you have changed the layout of the memory. The view at the end is just a nice interface for you to access your data but it has no effect on the underlying data of your tensor. What it comes down to is a contiguous memory data buffer.
If you take a smaller example, something we can grasp more easily:
>>> A = torch.rand(1, 4, 3)
tensor([[[0.2656, 0.5920, 0.3774],
[0.8447, 0.5984, 0.0614],
[0.5160, 0.8048, 0.6260],
[0.1644, 0.3144, 0.1040]]])
Here swapping axis=1 and axis=2 comes down to a batched transpose (in mathematical terms):
>>> B = A.transpose(2, 1)
tensor([[[0.4543, 0.7447, 0.7814, 0.3444],
[0.9766, 0.2732, 0.4766, 0.0387],
[0.0123, 0.7260, 0.8939, 0.8581]]])
In terms of memory layout A has the following memory arangement:
>>> A.flatten()
tensor([0.4543, 0.9766, 0.0123, 0.7447, 0.2732, 0.7260, 0.7814, 0.4766, 0.8939,
0.3444, 0.0387, 0.8581])
While B has a different layout. By layout I mean memory arrangement, I am not referring to its shape which is irrelevant:
>>> B.flatten()
tensor([0.4543, 0.7447, 0.7814, 0.3444, 0.9766, 0.2732, 0.4766, 0.0387, 0.0123,
0.7260, 0.8939, 0.8581])
As I said reshaping i.e. building a view on top of a tensor doesn't change its memory layout, it's an abstraction level to better manipulate tensors.
So in the end, yes you end up with two different results: C shares the same data as A, while B is a copy and has a different memory layout.
Transposing/permuting and view/reshape are NOT the same!
reshape and view only affect the shape of a tensor, but d not change the underlying order of elements.
In contrast, transpose and permute change the underlying order of elements in the tensor. See this answer, and this one for more details.
Here's an example, with B=1, N=3 and C=2, the first channel has even numbers 0..16, and the second channel has odd numbers 1..17:
A = torch.arange(2*9).view(1,9,2)
tensor([[[ 0, 1],
[ 2, 3],
[ 4, 5],
[ 6, 7],
[ 8, 9],
[10, 11],
[12, 13],
[14, 15],
[16, 17]]])
If you correctly transpose and then reshape, you get the correct split into even and odd channels:
A.transpose(1,2).view(1,2,3,3)
tensor([[[[ 0, 2, 4],
[ 6, 8, 10],
[12, 14, 16]],
[[ 1, 3, 5],
[ 7, 9, 11],
[13, 15, 17]]]])
However, if you only change the shape (i.e., using view or reshape) you incorrectly "mix" the values from the two channels:
A.view(1,2,3,3)
tensor([[[[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8]],
[[ 9, 10, 11],
[12, 13, 14],
[15, 16, 17]]]])
Update (Aug 31st, 2022)
Take a look at this simple example:
# original tensor
x = torch.arange(12).view(3,4)
x.data_ptr() # -> 94308398597888
x.stride() # -> (4, 1)
# transpose
x1 = x.transpose(0, 1)
x1.data_ptr() # -> 94308398597888 (same data)
x1.stride() # -> (1, 4) efficient stride representation can handle this
# messing around a bit more:
x1.view(3,4)
# strides cannot cut it anymore - we get an error
RuntimeError: view size is not compatible with input tensor''s size and stride (at least one dimension spans across two contiguous subspaces). Use .reshape(...) instead.
# using reshape:
x2 = x1.reshape(3, 4)
x2.data_ptr() # -> 94308399099200 (NOT the same data)
x2.stride() # -> (4, 1)

How to sum a data cube with python

I am trying to collapse a fits data cube with Python. I know that special packages are doing it, but it is for a lecture purposes. I first extract a subcube in Z:
hdu.data = hdu.data[3365:3405, :, :]
subcube = hdu.data
The subcube has a dimension of Z=40, Y=50 and X=26. I want to collapse the cube in a all fashion way by a double loop in X and Y, in order to have a simple 2D image.
for i in range(1, xdim):
for j in range(1, ydim):
Sum[j,i] = subcube[:,j,i].sum()
I get an error message: IndexError: index 26 is out of bounds for axis 1 with size 26.
I know that python handle differently the cube dimensions as Z, Y, X and not X, Y, Z like IDL for example, but I can not figure out why I have the error.
Python indices start at 0. You need to do range(xdim) and range(ydim) in your for loops.
Python ranges starts with 0. Range for X is 0-25. For Y and Z the same.
Maybe simple double loop over subcube with new list creation can hel you?
z_flatten = [[sum(col) for col in row] for row in subcube]
The existing answers pointing out that Python is 0-indexed are correct, but no one pointed out yet that you don't even need to create an empty array with np.zeros or to use any for loops to do this.
Numpy already allows you to apply most operations along a specific axis of your array, as opposed to looping over the dimensions of your sub-cube and summing just one pixel at a time.
For example let's make a 3x4x4 data cube:
>>> cube = np.arange(3 * 4 * 4).reshape((3, 4, 4))
>>> cube
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]],
[[16, 17, 18, 19],
[20, 21, 22, 23],
[24, 25, 26, 27],
[28, 29, 30, 31]],
[[32, 33, 34, 35],
[36, 37, 38, 39],
[40, 41, 42, 43],
[44, 45, 46, 47]]])
Say you want to sum all layers of a 3x3 slice of this cube:
>>> cube[:, :3, :3].sum(axis=0)
array([[48, 51, 54],
[60, 63, 66],
[72, 75, 78]])
In your case, the equivalent would be
subcube[:, :ydim, :xdim].sum(axis=0)
This is equivalent to what you're trying to do, but much more efficient.
As a general note, although you read your data cube out of a FITS file, since astropy.io.fits returns a Numpy array, any documentation or questions you can find about Numpy arrays apply--it generally isn't important at that point that it came from a FITS file. I point this out, just because it might help you in the future if you're struggling to perform operations on Numpy arrays.

How to reverse engineer original array from boolean indexed array?

Ok so I wrote some code for vectorizing a symmetric matrix, it just takes the unique elements and turns them into a 1d vector, while also multiplying the off diagonal elements by root2:
def vectorize_mat(mat):
assert mat.shape[0] == mat.shape[1], 'Matrix is not square'
n = int(mat.shape[0])
vec_len = 0.5*n*(n+1)
weight_mat = (np.tri(n,k=-1)*np.sqrt(2))+np.identity(n)
mask_mat = np.tri(n).astype(bool)
vec_mat = (mat*weight_mat)[mask_mat]
return vec_mat
and this works really well, now I'm trying to figure out how to reconstruct the original array from the vector. I've gotten the original matrix dimensions like so:
v = len(vec_mat)
n = isqrt(2*v)
where isqrt() is an integer square root from:Integer square root in python
but I'm struggling with what to do next. I can now reconstruct the weight and mask matrices. So obviously I could vectorize the weight matrix and divide the vector by it, or divide the reconstructed matrix by the weight matrix to undo that step, but it's the reshaping and stuff (from the boolean indexing) that I don't know how to do. Maybe there's some super simple answer out there,but I can't seem to see it.
To answer your headline question. Indexing - including boolean indexing - can be used for assignment.
Here is an example. Let us first extract the lower triangle using a mask.
>>> a = np.arange(25).reshape(5, 5)
>>> y, x = np.ogrid[:5, :5]
>>> lower = y>=x
>>> b = a[lower]
Now b contains the lower triangle. We can use the same mask to reconstruct the lower triangle and fill the upper triangle symmetrically:
>>> recon = np.empty_like(a)
>>> recon[lower] = b
>>> recon.T[lower] = b
>>> recon
array([[ 0, 5, 10, 15, 20],
[ 5, 6, 11, 16, 21],
[10, 11, 12, 17, 22],
[15, 16, 17, 18, 23],
[20, 21, 22, 23, 24]])

Categories