How to retain 2D (or more) shape when using pytrorch masked_select - pytorch

Suppose I have the following two matching shape tensors:
a = tensor([[ 0.0113, -0.1666, 0.5960, -0.0667], [-0.0977, -0.1984, 0.5153, 0.0420]])
selectors = tensor([[ True, True, False, False], [ True, False, True, False]])
When using torch.masked_select to find the values in a that match True indices in selectors like this:
torch.masked_select(a, selectors)
The output will be in 1D shape instead of the original 2D shape:
tensor([ 0.0113, -0.1666, -0.0977, 0.5153])
This is consistent with masked_select behavior as it is given in the documentation (torch.masked_select). However, my goal is to get a result that matches the shape of the two original tensors. I.e.:
tensor([[0.0113, -0.1666], [-0.0977, 0.5153]])
Is there a way to get this without having to loop over all the elements in the tensors and find the mask for each one? Please note that I have also looked into using torch.where, but it doesn't fit the case I have as I see it.

As #jodag pointed out, for general inputs, each row on the desired masked result might have a different number of elements, depending on how many True values there are on the same row in selectors. However, you could overcome this by allowing trailing zero padding in the result.
Basic solution:
indices = torch.masked_fill(torch.cumsum(selectors.int(), dim=1), ~selectors, 0)
masked = torch.scatter(input=torch.zeros_like(a), dim=1, index=indices, src=a)[:,1:]
Explanation:
By applying cumsum() row-wise over selectors, we compute for each unmasked element in a the target column number it should be copied to in the output tensor. Then, scatter() performs a row-wise scattering of a's elements to these computed target locations. We leave all masked elements with the index 0, so that the first element in each row of the result would contain one of the masked elements (maybe arbitrarily. we don't care which). We then ignore these un-wanted 1st values by taking the slice [:,1:]. The output resulting masked tensor has the exact same size as the input a (this is the maximum needed size, for the case where there is a row of full True values in selectors).
Usage example:
>>> a = Torch.tensor([[ 1, 2, 3, 4, 5, 6], [10, 20, 30, 40, 50, 60]])
>>> selectors = Torch.tensor([[ True, False, False, True, False, True], [False, False, True, True, False, False]])
>>> torch.cumsum(selectors.int(), dim=1)
tensor([[1, 1, 1, 2, 2, 3],
[0, 0, 1, 2, 2, 2]])
>>> indices = torch.masked_fill(torch.cumsum(selectors.int(), dim=1), ~selectors, 0)
>>> indices
tensor([[1, 0, 0, 2, 0, 3],
[0, 0, 1, 2, 0, 0]])
>>> torch.scatter(input=torch.zeros_like(a), dim=1, index=indices, src=a)
tensor([[ 5, 1, 4, 6, 0, 0],
[60, 30, 40, 0, 0, 0]])
>>> torch.scatter(input=torch.zeros_like(a), dim=1, index=indices, src=a)[:,1:]
tensor([[ 1, 4, 6, 0, 0],
[30, 40, 0, 0, 0]])
Adapting output size: Here, the length of dim=1 of the output resulting masked tensor is the max number of un-masked items in a row. For your original show-case, the output shape would be (2,2) as you desired. Note that if this number is not previously known and a is on CUDA, it would cause an additional host-device synchronization that might affect the performance.
To do so, instead of allocating input=torch.zeros_like(a) for scatter(), allocate it by a.new_zeros(size=(a.size(0), torch.max(indices).item() + 1)). The +1 is for the 1st place which is later sliced-out. The host-device synchronization would occur by accessing the result of max() to calculate the allocated output size.
Example:
>>> torch.scatter(input=a.new_zeros(size=(a.size(0), torch.max(indices).item() + 1)), dim=1, index=indices, src=a)[:,1:]
tensor([[ 1, 4, 6],
[30, 40, 0]])
Changing the padding value: If another custom default value is wanted as a padding, one could use torch.full_like(my_custom_value) rather than torch.zeros_like() when allocating the output for scatter().

Related

Subtract the elements of every possible pair of a torch Tensor efficiently

I have a huge torch Tensor and I'm looking for an efficient approach to subtract the elements of every pair of that Tensor.
Of course I could use two nested for but it wouldn't be efficient.
For example giving
[1, 2, 3, 4]
The output I want is
[1-2, 1-3, 1-4, 2-3, 2-4, 3-4]
You can do this easily:
>>> x = torch.tensor([1, 2, 3, 4])
>>> x[:, None] - x[None, :]
tensor([[ 0, -1, -2, -3],
[ 1, 0, -1, -2],
[ 2, 1, 0, -1],
[ 3, 2, 1, 0]])
see more details here.

Count Unique elements in pytorch Tensor

Suppose I have the following tensor: y = torch.randint(0, 3, (10,)). How would you go about counting the 0's 1's and 2's in there?
The only way I can think of is by using collections.Counter(y) but was wondering if there was a more "pytorch" way of doing this. A use case for example would be when building the confusion matrix for predictions.
You can use torch.unique with the return_counts option:
>>> x = torch.randint(0, 3, (10,))
tensor([1, 1, 0, 2, 1, 0, 1, 1, 2, 1])
>>> x.unique(return_counts=True)
(tensor([0, 1, 2]), tensor([2, 6, 2]))

Pytorch permute not changing desired index

I am trying to use the permute function to swap the axis of my tensor but for some reason the output is not as expected. The output of the code is torch.Size([512, 256, 3, 3]) but I would expect it to be torch.Size([256, 512, 3, 3]). It doesn't look like I can use flip to switch 0, 1 index. Is there something i am missing? I wish to change the tensor such that the shape is (256, 512, 3, 3).
Reproducible code:
import torch
wtf = torch.rand(3, 3, 512, 256)
wtf = wtf.permute(2, 3, 1, 0)
print(wtf.shape)
The numbers provided to torch.permute are the indices of the axis in the order you want the new tensor to have.
Having set x as torch.rand(3, 3, 512, 256).
If you want to invert the order of axis: the initial order is 0, 1, 2, 3, you want 3, 2, 1, 0:
>>> wtf.permute(3, 2, 1, 0).shape
torch.Size([256, 512, 3, 3])
Inverting axis order is essentially the transpose operation:
>>> wtf.T
torch.Size([256, 512, 3, 3])
If you just want to invert and keep the order of the last two: original order is 0, 1, 2, 3 and resulting order is 3, 2, 0, 1:
>>> x.permute(3, 2, 0, 1).shape
torch.Size([256, 512, 3, 3])
The difference between the two options is that the last two axes of size 3 will be swapped.

How does the parameter 'dim' in torch.unique() work?

I am trying to extract the unique values in each row of a matrix and returning them into the same matrix (with repeated values set to say, 0) For example, I would like to transform
torch.Tensor(([1, 2, 3, 4, 3, 3, 4],
[1, 6, 3, 5, 3, 5, 4]])
to
torch.Tensor(([1, 2, 3, 4, 0, 0, 0],
[1, 6, 3, 5, 0, 0, 4]])
or
torch.Tensor(([1, 2, 3, 4, 0, 0, 0],
[1, 6, 3, 5, 4, 0, 0]])
I.e. the order does not matter in the rows. I have tried using pytorch.unique() and in the documentation it is mentioned that the dimension to take the unique values can be specified with the parameter dim. However, It doesn't seem to work for this case.
I've tried:
output= torch.unique(torch.Tensor([[4,2,52,2,2],[5,2,6,6,5]]), dim = 1)
output
Which gives
tensor([[ 2., 2., 2., 4., 52.],
[ 2., 5., 6., 5., 6.]])
Does anyone have a particular fix for this? If possible, I'm trying to avoid for loops.
One must admit the unique function can sometimes be very confusing without given proper examples and explanations.
The dim parameter specifies which dimension on the matrix tensor you want to apply on.
For instance, in a 2D matrix, dim=0 will let operation perform vertically where dim=1 means horizontally.
Example, let's consider a 4x4 matrix with dim=1. As you can see from my code below, the unique operation is applied row by row.
You notice the double occurrence of the number 11 in the first and last row. Numpy and Torch does this to preserve the shape of the final matrix.
However, if you do not specify any dimension, torch will automatically flatten your matrix and then apply unique to it and you will get a 1D array that contains unique data.
import torch
m = torch.Tensor([
[11, 11, 12,11],
[13, 11, 12,11],
[16, 11, 12, 11],
[11, 11, 12, 11]
])
output, indices = torch.unique(m, sorted=True, return_inverse=True, dim=1)
print("Ori \n{}".format(m.numpy()))
print("Sorted \n{}".format(output.numpy()))
print("Indices \n{}".format(indices.numpy()))
# without specifying dimension
output, indices = torch.unique(m, sorted=True, return_inverse=True)
print("Sorted (no dim) \n{}".format(output.numpy()))
Result (dim=1)
Ori
[[11. 11. 12. 11.]
[13. 11. 12. 11.]
[16. 11. 12. 11.]
[11. 11. 12. 11.]]
Sorted
[[11. 11. 12.]
[11. 13. 12.]
[11. 16. 12.]
[11. 11. 12.]]
Indices
[1 0 2 0]
Result (no dimension)
Sorted (no dim)
[11. 12. 13. 16.]
I was confused when using torch.unique the first time. After doing some experiments I have finally figured out how the dim argument works.
Docs of torch.unique says that:
counts (Tensor): (optional) if return_counts is True, there will be an additional returned tensor (same shape as output or output.size(dim), if dim was specified) representing the number of occurrences for each unique value or tensor.
For example, if your input tensor is a 3D tensor of size n x m x k and dim=2, unique will work separately on k matrices of size n x m. In other words, it will treat all dimensions other than the dim 2 as a single tensor.

Reshaping / Transforming pandas.Dataframe

Hej there,
I've got the following pandas.DataFrame
df = pandas.DataFrame({
"date": ["2016-12-11", "2016-12-12", "2016-12-13", "2016-12-14", "2016-12-15"],
"dim1": ["dim11", "dim12", "dim12", "dim11", "dim13"],
"dim2": ["dim22", "dim21", "dim21", "dim22", "dim23"],
"dim3": ["dim31", "dim32", "dim32", "dim31", "dim33"],
"val1": [1, 2, 3, 4, 5],
"val2": [6, 7, 8, 9, 10],
"val3": [11,12,13,14,15]
})
What I want now is to specify multiple "dimensions" and multiple "values", so that the DataFrame
is reshaped / transformed so that the specified dimensions and values are "combined" with each other.
Not specified values may vanish but specified dimensions should stay in the resulting DataFrame.
To make it clear a simple example of a resulting DataFrame.
Specified dimensions are: dim1, dim2
Specified values are: val1, val2
df_res = pandas.DataFrame({
"date": ["2016-12-11", "2016-12-12", "2016-12-13", "2016-12-14", "2016-12-15"],
"dim3": ["dim31", "dim32", "dim32", "dim31", "dim33"],
"dim11_dim22_val1": [1, 0, 0, 4, 0],
"dim12_dim21_val1": [0, 2, 3, 0, 0],
"dim13_dim23_val1": [0, 0, 0, 0, 5],
"dim11_dim22_val2": [6, 0, 0, 9, 0],
"dim12_dim21_val2": [0, 7, 8, 0, 0],
"dim13_dim23_val2": [0, 0, 0, 0, 10]
})
So basically there are multiple combinations of dim1, dim2, val1 and val2. val3 drops from the result but the dimensions date_id and dim3 stay in there.
As a side note: Afterwards I will do a df_res.to_dict(orient="records"), which should output
[
{"date_id": "2016-12-11", "dim3": "dim31", "dim11_dim22_val1": 1, "dim12_dim21_val1": 0, "dim13_dim23_val1": 0, "dim11_dim22_val2": 6, "dim12_dim21_val2": 0, "dim13_dim23_val2": 0}
...
]
Can I do this with some pandas magic?
Maybe in multiple steps of df.pivot?
Kind regards
Dennis
Part 1:
1) You could set the columns starting with dim along with date which would remain static during the whole operation as the index axis. Provide append=True to confront duplicated indices.
2) unstack the required levels. Drop the unwanted val3 column and fill missing values with 0's.
3) Rename the columns by joining the multi-index tuples with an underscore in between them.
4) Reset the same levels as unstacked and additionally sort the column names to match the required output.
df.set_index(df.filter(like='dim').columns.tolist()+['date'], append=True, inplace=True)
df = df.unstack(level=[2,1]).drop('val3', axis=1).fillna(0).astype(int)
df.columns = ['_'.join(c[::-1]) for c in df.columns]
df_res = df.reset_index(level=[2,1]).sort_index(axis=1)
df_res
Part 2:
df_res.to_dict('r')
produces:
[{'date': '2016-12-11',
'dim11_dim22_val1': 1,
'dim11_dim22_val2': 6,
'dim12_dim21_val1': 0,
'dim12_dim21_val2': 0,
'dim13_dim23_val1': 0,
'dim13_dim23_val2': 0,
'dim3': 'dim31'}, ..........

Resources