How to add a new dimension to a PyTorch tensor? - python

In NumPy, I would do
a = np.zeros((4, 5, 6))
a = a[:, :, np.newaxis, :]
assert a.shape == (4, 5, 1, 6)
How to do the same in PyTorch?

a = torch.zeros(4, 5, 6)
a = a[:, :, None, :]
assert a.shape == (4, 5, 1, 6)

You can add a new axis with torch.unsqueeze() (first argument being the index of the new axis):
>>> a = torch.zeros(4, 5, 6)
>>> a = a.unsqueeze(2)
>>> a.shape
torch.Size([4, 5, 1, 6])
Or using the in-place version: torch.unsqueeze_():
>>> a = torch.zeros(4, 5, 6)
>>> a.unsqueeze_(2)
>>> a.shape
torch.Size([4, 5, 1, 6])

x = torch.tensor([1, 2, 3, 4])
y = torch.unsqueeze(x, 0)
y will be -> tensor([[ 1, 2, 3, 4]])
EDIT: see more details here: https://pytorch.org/docs/stable/generated/torch.unsqueeze.html

Related

How to reshape matrices using index instead of shape inputs?

Given an array of shape (8, 3, 4, 4), reshape them into an arbitrary new shape (8, 4, 4, 3) by inputting the new indices compared to the old positions (0, 2, 3, 1).
Bonus: perform numpy.dot of one of said array's non-last index and a 1-D second, i.e. numpy.dot(<array with shape (8, 3, 4, 4)>, [1, 2, 3]) # will return shape mismatch as it is
Numpy's transpose "reverses or permutes":
ni = (0, 2, 3, 1)
arr = arr.transpose(ni)
Old solution:
ni = (0, 2, 3, 1)
s = arr.shape
arr = arr.reshape(s[ni[0]], s[ni[1]]...)
Maybe this is what you are looking for:
arr = np.array([[[1, 2], [3, 4], [5, 6]]])
s = arr.shape
new_indexes = (1, 0, 2) # permutation
new_arr = arr.reshape(*[s[index] for index in new_indexes])
print(arr.shape) # (1, 3, 2)
print(new_arr.shape) # (3, 1, 2)

What is the most efficient way to use mesh grid with parameters in python?

def s(x,y,z,t1,t2):
return x + y + z + t1 + t2
X = [1,2,3]
Y = [4,5,6]
Z = [7,8,9]
Theta = [(1,2),(3,4),(5,6),(1,1)]
Is there any way for me to efficiently construct an array containing the evaluations of X cross Y cross Z cross Theta with respect to the function s ? Note that I do not want s(1,4,7,1,3), but I do want s(1,4,7,1,2); as in, I don't want s to be evaluated at X cross Y cross Z cross {1,3,5,1} cross {2,4,6,1}.
Thanks.
X = [1,2,3]
Y = [4,5,6]
Z = [7,8,9]
Theta = [(1,2),(3,4),(5,6),(1,1)]
[(a,b,c,d) for a,b,c,d in zip(X,Y,Z,Theta)]
#[(1, 4, 7, (1, 2)), (2, 5, 8, (3, 4)), (3, 6, 9, (5, 6))]
Or
[(a,b,c,*d) for a,b,c,d in zip(X,Y,Z,Theta)]
#[(1, 4, 7, 1, 2), (2, 5, 8, 3, 4), (3, 6, 9, 5, 6)]
If you want sum:
[sum([a,b,c,*d]) for a,b,c,d in zip(X,Y,Z,Theta)]
#[15, 22, 29]
List comprehension:
results = [s(x, y, z, t1, t2) for x in X
for y in Y
for z in Z
for t1, t2 in Theta]
is the most efficient pure python way of doing this.
Starting with your lists:
In [2]: X = [1,2,3]
...: Y = [4,5,6]
...: Z = [7,8,9]
...: Theta = [(1,2),(3,4),(5,6),(1,1)]
It looks like you should split Theta into two lists, as with:
In [5]: T1,T2 = zip(*Theta)
In [6]: T1,T2
Out[6]: ((1, 3, 5, 1), (2, 4, 6, 1))
A flat zip:
In [7]: list(zip(X,Y,Z,T1,T2))
Out[7]: [(1, 4, 7, 1, 2), (2, 5, 8, 3, 4), (3, 6, 9, 5, 6)]
But to get every combination:
In [8]: [(x,y,z,t1,t2) for x in X for y in Y for z in Z for t1 in T1 for t2 in T2]
Out[8]:
[(1, 4, 7, 1, 2),
(1, 4, 7, 1, 4),
(1, 4, 7, 1, 6),
(1, 4, 7, 1, 1),
(1, 4, 7, 3, 2),
(1, 4, 7, 3, 4),
...
]
For a total of:
In [9]: len(_)
Out[9]: 432
And you could easily pass those to your function or just use sum().
But you mention mesh grid and tag numpy, so using that:
Passing these lists to meshgrid:
In [10]: Xa,Ya,Za,T1a,T2a = np.meshgrid(X,Y,Z,T1,T2, indexing='ij', sparse=True)
That makes 5 arrays, with shapes like:
In [11]: Xa.shape
Out[11]: (3, 1, 1, 1, 1)
In [12]: T1a.shape
Out[12]: (1, 1, 1, 4, 1)
If I didn't specify sparse, the meshgrid arrays would all have shape as res below.
In [14]: def s(x,y,z,t1,t2):
...: return x + y + z + t1 + t2
...:
In [15]: res = s(Xa,Ya,Za,T1a,T2a)
In [16]: res.shape
Out[16]: (3, 3, 3, 4, 4)
That's same number of combinations as with the lists, but arranged as 5d array:
In [17]: res.size
Out[17]: 432
A sample 2d array:
In [19]: res[0,0,0]
Out[19]:
array([[15, 17, 19, 14],
[17, 19, 21, 16],
[19, 21, 23, 18],
[15, 17, 19, 14]])
If I made an array from Theta, I could have gotten the T1,T2 values by selecting columns:
In [20]: ThetaA = np.array(Theta)
In [21]: ThetaA
Out[21]:
array([[1, 2],
[3, 4],
[5, 6],
[1, 1]])
In [23]: ThetaA[:,0], T1
Out[23]: (array([1, 3, 5, 1]), (1, 3, 5, 1))
Read up on broadcasting to learn how the 'sparse' Xa, Ya, etc arrays work together to create the 5d res array.

x.reshape([1,28,28,1]) reshaping meaning

I can not understand what this reshaping actually do with an array of 28*28.
the code is:
x.reshape([1,28,28,1])
Reshape - as the name suggests - reshapes your array into an array of different shape.
>>> import numpy as np
>>> x = np.arange(28*28)
>>> x.shape
(784,)
>>> y = x.reshape(28,28)
>>> y.shape
(28, 28)
>>> z = y.reshape([1, 28, 28, 1])
>>> z.shape
(1, 28, 28, 1)
A shape of 1 implies that the respective dimension has a length of 1. This is most useful when working with broadcasting, as the array will be repeated along that dimension as needed.
>>> a = np.array([1, 2, 3]).reshape(3, 1)
>>> b = np.array([1, 2, 3]).reshape(1, 3)
>>> a * b
array([[1, 2, 3],
[2, 4, 6],
[3, 6, 9]])
Another use is to differentiate between row and column vectors, which you can understand as matrices of shape [1, X] or [X, 1] respectively.
>>> row_vector = np.array([1, 2, 3]).reshape(1,3)
>>> row_vector
array([[1, 2, 3]])
>>> column_vector = np.array([1,2,3]).reshape(3,1)
>>> column_vector
array([[1],
[2],
[3]])

In numpy, how to sort an array with the same order with another one?

There are two numpy array a and w,
both of which have the same shape (d1,d2,..,dk,N).
We can think there are N sample with shape (d1,d2,...,dk).
Now, I want to sort a and w along a's last axis.
For example, a and w have shape (2,4):
a = [[3,2,4,1],
[2,3,1,4]]
w = [[10,20,30,40],
[80,70,60,50]]
sorted_index = a.argsort()
# array([[3, 1, 0, 2],
# [2, 0, 1, 3]])
I want:
a = a.sort() # default axis = -1
# a = [[1,2,3,4],
# [1,2,3,4]]
and w should be:
# w = [[40,20,10,30],
# [60,80,70,50]]
Of course, in that case, the following code work
x = a.argsort()
w[0,:] = w[0,x[0]]
w[1,:] = w[1,x[1]]
But when the sample have many dimension (>1), that code doesn't work.
Can anyone come up with solutions? Thanks!
There's a function for that, np.take_along_axis:
>>> a = np.array([[3,2,4,1], [2,3,1,4]])
>>> w = np.array([[10,20,30,40], [80,70,60,50]])
>>> sorted_index = a.argsort()
>>> sorted_index
array([[3, 1, 0, 2],
[2, 0, 1, 3]])
>>> np.take_along_axis(a, sorted_index, axis=-1)
array([[1, 2, 3, 4],
[1, 2, 3, 4]])
>>> np.take_along_axis(w, sorted_index, axis=-1)
array([[40, 20, 10, 30],
[60, 80, 70, 50]])
>>>
It will also work when a and w have arbitrary shape.

Multidimensional Broadcasting in Python / NumPy - or inverse of `numpy.squeeze()`

What would be the best way of broadcasting two arrays together when a simple call to np.broadcast_to() would fail?
Consider the following example:
import numpy as np
arr1 = np.arange(2 * 3 * 4 * 5 * 6).reshape((2, 3, 4, 5, 6))
arr2 = np.arange(3 * 5).reshape((3, 5))
arr1 + arr2
# ValueError: operands could not be broadcast together with shapes (2,3,4,5,6) (3,5)
arr2_ = np.broadcast_to(arr2, arr1.shape)
# ValueError: operands could not be broadcast together with remapped shapes
arr2_ = arr2.reshape((1, 3, 1, 5, 1))
arr1 + arr2
# now this works because the singletons trigger the automatic broadcast
This only work if I manually select a shape for which automatic broadcasting is going to work.
What would be the most efficient way of doing this automatically?
Is there an alternative way other than reshape on a cleverly constructed broadcastable shape?
Note the relation to np.squeeze(): this would perform the inverse operation by removing singletons. So what I need is some sort of np.squeeze() inverse.
The official documentation (as of NumPy 1.13.0 suggests that the inverse of np.squeeze() is np.expand_dim(), but this is not nearly as flexible as I'd need it to be, and actually np.expand_dim() is roughly equivalent to np.reshape(array, shape + (1,)) or array[:, None].
This issue is also related to the keepdims keyword accepted by e.g. sum:
import numpy as np
arr1 = np.arange(2 * 3 * 4 * 5 * 6).reshape((2, 3, 4, 5, 6))
# not using `keepdims`
arr2 = np.sum(arr1, (0, 2, 4))
arr2.shape
# : (3, 5)
arr1 + arr2
# ValueError: operands could not be broadcast together with shapes (2,3,4,5,6) (3,5)
# now using `keepdims`
arr2 = np.sum(arr1, (0, 2, 4), keepdims=True)
arr2.shape
# : (1, 3, 1, 5, 1)
arr1 + arr2
# now this works because it has the correct shape
EDIT: Obviously, in cases where np.newaxis or keepdims mechanisms are an appropriate choice, there would be no need for a unsqueeze() function.
Yet, there are use-cases where none of these is an option.
For example, consider the case of the weighted average as implemented in numpy.average() over an arbitrary number of dimensions specified by axis.
Right now the weights parameter must have the same shape as the input.
However, weights there is no need specify the weights over the non-reduced dimensions as they are just repeating and the NumPy's broadcasting mechanism would appropriately take care of them.
So if we would like to have such a functionality, we would need to code something like (where some consistency checks are just omitted for simplicity):
def weighted_average(arr, weights=None, axis=None):
if weights is not None and weights.shape != arr.shape:
weights = unsqueeze(weights, ...)
weights = np.zeros_like(arr) + weights
result = np.sum(arr * weights, axis=axis)
result /= np.sum(weights, axis=axis)
return result
or, equivalently:
def weighted_average(arr, weights=None, axis=None):
if weights is not None and weights.shape != arr.shape:
weights = unsqueeze(weights, ...)
weights = np.zeros_like(arr) + weights
return np.average(arr, weights, axis)
In either of the two, it is not possible to replace unsqueeze() with weights[:, np.newaxis]-like statements because we do not know beforehand where the new axis will be needed, nor we can use the keepdims feature of sum because the code will fail at arr * weights.
This case could be relatively nicely handled if np.expand_dims() would support an iterable of ints for its axis parameter, but as of NumPy 1.13.0 does not.
My way of achieving this is by defining the following unsqueezing() function to handle cases where this can be done automatically and giving a warning when the inputs could be ambiguous (e.g. when some source elements of the source shape may match multiple elements of the target shape):
def unsqueezing(
source_shape,
target_shape):
"""
Generate a broadcasting-compatible shape.
The resulting shape contains *singletons* (i.e. `1`) for non-matching dims.
Assumes all elements of the source shape are contained in the target shape
(excepts for singletons) in the correct order.
Warning! The generated shape may not be unique if some of the elements
from the source shape are present multiple timesin the target shape.
Args:
source_shape (Sequence): The source shape.
target_shape (Sequence): The target shape.
Returns:
shape (tuple): The broadcast-safe shape.
Raises:
ValueError: if elements of `source_shape` are not in `target_shape`.
Examples:
For non-repeating elements, `unsqueezing()` is always well-defined:
>>> unsqueezing((2, 3), (2, 3, 4))
(2, 3, 1)
>>> unsqueezing((3, 4), (2, 3, 4))
(1, 3, 4)
>>> unsqueezing((3, 5), (2, 3, 4, 5, 6))
(1, 3, 1, 5, 1)
>>> unsqueezing((1, 3, 5, 1), (2, 3, 4, 5, 6))
(1, 3, 1, 5, 1)
If there is nothing to unsqueeze, the `source_shape` is returned:
>>> unsqueezing((1, 3, 1, 5, 1), (2, 3, 4, 5, 6))
(1, 3, 1, 5, 1)
>>> unsqueezing((2, 3), (2, 3))
(2, 3)
If some elements in `source_shape` are repeating in `target_shape`,
a user warning will be issued:
>>> unsqueezing((2, 2), (2, 2, 2, 2, 2))
(2, 2, 1, 1, 1)
>>> unsqueezing((2, 2), (2, 3, 2, 2, 2))
(2, 1, 2, 1, 1)
If some elements of `source_shape` are not presente in `target_shape`,
an error is raised.
>>> unsqueezing((2, 3), (2, 2, 2, 2, 2))
Traceback (most recent call last):
...
ValueError: Target shape must contain all source shape elements\
(in correct order). (2, 3) -> (2, 2, 2, 2, 2)
>>> unsqueezing((5, 3), (2, 3, 4, 5, 6))
Traceback (most recent call last):
...
ValueError: Target shape must contain all source shape elements\
(in correct order). (5, 3) -> (2, 3, 4, 5, 6)
"""
shape = []
j = 0
for i, dim in enumerate(target_shape):
if j < len(source_shape):
shape.append(dim if dim == source_shape[j] else 1)
if i + 1 < len(target_shape) and dim == source_shape[j] \
and dim != 1 and dim in target_shape[i + 1:]:
text = ('Multiple positions (e.g. {} and {})'
' for source shape element {}.'.format(
i, target_shape[i + 1:].index(dim) + (i + 1), dim))
warnings.warn(text)
if dim == source_shape[j] or source_shape[j] == 1:
j += 1
else:
shape.append(1)
if j < len(source_shape):
raise ValueError(
'Target shape must contain all source shape elements'
' (in correct order). {} -> {}'.format(source_shape, target_shape))
return tuple(shape)
This can be used to define unsqueeze() as a more flexible inverse of np.squeeze() compared to np.expand_dims() which can only append one singleton at a time:
def unsqueeze(
arr,
axis=None,
shape=None,
reverse=False):
"""
Add singletons to the shape of an array to broadcast-match a given shape.
In some sense, this function implements the inverse of `numpy.squeeze()`.
Args:
arr (np.ndarray): The input array.
axis (int|Iterable|None): Axis or axes in which to operate.
If None, a valid set axis is generated from `shape` when this is
defined and the shape can be matched by `unsqueezing()`.
If int or Iterable, specified how singletons are added.
This depends on the value of `reverse`.
If `shape` is not None, the `axis` and `shape` parameters must be
consistent.
Values must be in the range [-(ndim+1), ndim+1]
At least one of `axis` and `shape` must be specified.
shape (int|Iterable|None): The target shape.
If None, no safety checks are performed.
If int, this is interpreted as the number of dimensions of the
output array.
If Iterable, the result must be broadcastable to an array with the
specified shape.
If `axis` is not None, the `axis` and `shape` parameters must be
consistent.
At least one of `axis` and `shape` must be specified.
reverse (bool): Interpret `axis` parameter as its complementary.
If True, the dims of the input array are placed at the positions
indicated by `axis`, and singletons are placed everywherelse and
the `axis` length must be equal to the number of dimensions of the
input array; the `shape` parameter cannot be `None`.
If False, the singletons are added at the position(s) specified by
`axis`.
If `axis` is None, `reverse` has no effect.
Returns:
arr (np.ndarray): The reshaped array.
Raises:
ValueError: if the `arr` shape cannot be reshaped correctly.
Examples:
Let's define some input array `arr`:
>>> arr = np.arange(2 * 3 * 4).reshape((2, 3, 4))
>>> arr.shape
(2, 3, 4)
A call to `unsqueeze()` can be reversed by `np.squeeze()`:
>>> arr_ = unsqueeze(arr, (0, 2, 4))
>>> arr_.shape
(1, 2, 1, 3, 1, 4)
>>> arr = np.squeeze(arr_, (0, 2, 4))
>>> arr.shape
(2, 3, 4)
The order of the axes does not matter:
>>> arr_ = unsqueeze(arr, (0, 4, 2))
>>> arr_.shape
(1, 2, 1, 3, 1, 4)
If `shape` is an int, `axis` must be consistent with it:
>>> arr_ = unsqueeze(arr, (0, 2, 4), 6)
>>> arr_.shape
(1, 2, 1, 3, 1, 4)
>>> arr_ = unsqueeze(arr, (0, 2, 4), 7)
Traceback (most recent call last):
...
ValueError: Incompatible `[0, 2, 4]` axis and `7` shape for array of\
shape (2, 3, 4)
It is possible to reverse the meaning to `axis` to add singletons
everywhere except where specified (but requires `shape` to be defined
and the length of `axis` must match the array dims):
>>> arr_ = unsqueeze(arr, (0, 2, 4), 10, True)
>>> arr_.shape
(2, 1, 3, 1, 4, 1, 1, 1, 1, 1)
>>> arr_ = unsqueeze(arr, (0, 2, 4), reverse=True)
Traceback (most recent call last):
...
ValueError: When `reverse` is True, `shape` cannot be None.
>>> arr_ = unsqueeze(arr, (0, 2), 10, True)
Traceback (most recent call last):
...
ValueError: When `reverse` is True, the length of axis (2) must match\
the num of dims of array (3).
Axes values must be valid:
>>> arr_ = unsqueeze(arr, 0)
>>> arr_.shape
(1, 2, 3, 4)
>>> arr_ = unsqueeze(arr, 3)
>>> arr_.shape
(2, 3, 4, 1)
>>> arr_ = unsqueeze(arr, -1)
>>> arr_.shape
(2, 3, 4, 1)
>>> arr_ = unsqueeze(arr, -4)
>>> arr_.shape
(1, 2, 3, 4)
>>> arr_ = unsqueeze(arr, 10)
Traceback (most recent call last):
...
ValueError: Axis (10,) out of range.
If `shape` is specified, `axis` can be omitted (USE WITH CARE!) or its
value is used for addiotional safety checks:
>>> arr_ = unsqueeze(arr, shape=(2, 3, 4, 5, 6))
>>> arr_.shape
(2, 3, 4, 1, 1)
>>> arr_ = unsqueeze(
... arr, (3, 6, 8), (2, 5, 3, 2, 7, 2, 3, 2, 4, 5, 6), True)
>>> arr_.shape
(1, 1, 1, 2, 1, 1, 3, 1, 4, 1, 1)
>>> arr_ = unsqueeze(
... arr, (3, 7, 8), (2, 5, 3, 2, 7, 2, 3, 2, 4, 5, 6), True)
Traceback (most recent call last):
...
ValueError: New shape [1, 1, 1, 2, 1, 1, 1, 3, 4, 1, 1] cannot be\
broadcasted to shape (2, 5, 3, 2, 7, 2, 3, 2, 4, 5, 6)
>>> arr = unsqueeze(arr, shape=(2, 5, 3, 7, 2, 4, 5, 6))
>>> arr.shape
(2, 1, 3, 1, 1, 4, 1, 1)
>>> arr = np.squeeze(arr)
>>> arr.shape
(2, 3, 4)
>>> arr = unsqueeze(arr, shape=(5, 3, 7, 2, 4, 5, 6))
Traceback (most recent call last):
...
ValueError: Target shape must contain all source shape elements\
(in correct order). (2, 3, 4) -> (5, 3, 7, 2, 4, 5, 6)
The behavior is consistent with other NumPy functions and the
`keepdims` mechanism:
>>> axis = (0, 2, 4)
>>> arr1 = np.arange(2 * 3 * 4 * 5 * 6).reshape((2, 3, 4, 5, 6))
>>> arr2 = np.sum(arr1, axis, keepdims=True)
>>> arr2.shape
(1, 3, 1, 5, 1)
>>> arr3 = np.sum(arr1, axis)
>>> arr3.shape
(3, 5)
>>> arr3 = unsqueeze(arr3, axis)
>>> arr3.shape
(1, 3, 1, 5, 1)
>>> np.all(arr2 == arr3)
True
"""
# calculate `new_shape`
if axis is None and shape is None:
raise ValueError(
'At least one of `axis` and `shape` parameters must be specified.')
elif axis is None and shape is not None:
new_shape = unsqueezing(arr.shape, shape)
elif axis is not None:
if isinstance(axis, int):
axis = (axis,)
# calculate the dim of the result
if shape is not None:
if isinstance(shape, int):
ndim = shape
else: # shape is a sequence
ndim = len(shape)
elif not reverse:
ndim = len(axis) + arr.ndim
else:
raise ValueError('When `reverse` is True, `shape` cannot be None.')
# check that axis is properly constructed
if any([ax < -ndim - 1 or ax > ndim + 1 for ax in axis]):
raise ValueError('Axis {} out of range.'.format(axis))
# normalize axis using `ndim`
axis = sorted([ax % ndim for ax in axis])
# manage reverse mode
if reverse:
if len(axis) == arr.ndim:
axis = [i for i in range(ndim) if i not in axis]
else:
raise ValueError(
'When `reverse` is True, the length of axis ({})'
' must match the num of dims of array ({}).'.format(
len(axis), arr.ndim))
elif len(axis) + arr.ndim != ndim:
raise ValueError(
'Incompatible `{}` axis and `{}` shape'
' for array of shape {}'.format(axis, shape, arr.shape))
# generate the new shape from axis, ndim and shape
new_shape = []
i, j = 0, 0
for l in range(ndim):
if i < len(axis) and l == axis[i] or j >= arr.ndim:
new_shape.append(1)
i += 1
else:
new_shape.append(arr.shape[j])
j += 1
# check that `new_shape` is consistent with `shape`
if shape is not None:
if isinstance(shape, int):
if len(new_shape) != ndim:
raise ValueError(
'Length of new shape {} does not match '
'expected length ({}).'.format(len(new_shape), ndim))
else:
if not all([new_dim == 1 or new_dim == dim
for new_dim, dim in zip(new_shape, shape)]):
raise ValueError(
'New shape {} cannot be broadcasted to shape {}'.format(
new_shape, shape))
return arr.reshape(new_shape)
Using these, one can write:
import numpy as np
arr1 = np.arange(2 * 3 * 4 * 5 * 6).reshape((2, 3, 4, 5, 6))
arr2 = np.arange(3 * 5).reshape((3, 5))
arr3 = unsqueeze(arr2, (0, 2, 4))
arr1 + arr3
# now this works because it has the correct shape
arr3 = unsqueeze(arr2, shape=arr1.shape)
arr1 + arr3
# this also works because the shape can be expanded unambiguously
So dynamic broadcast can now happen, and this is consistent with the behavior of keepdims:
import numpy as np
axis = (0, 2, 4)
arr1 = np.arange(2 * 3 * 4 * 5 * 6).reshape((2, 3, 4, 5, 6))
arr2 = np.sum(arr1, axis, keepdims=True)
arr3 = np.sum(arr1, axis)
arr3 = unsqueeze(arr3, axis)
np.all(arr2 == arr3)
# : True
Effectively, this extends np.expand_dims() to handle more complex scenarios.
Improvements over this code are obviously more than welcome.

Categories

Resources