%%capture
!pip install phiml
from phiml import math
from phiml.math import stack, zeros, ones, spatial, channel
ΦML allows tensors of varying sizes to be stacked into a single non-uniform tensor. Unlike with many other libraries, the result shape is still well-defined.
Uniformity is not to be confused with homogeneity, which refers to data types.
Let's look at an example.
a = zeros(spatial(x=4, y=2))
b = ones(spatial(y=2, x=5))
stacked = stack([a, b], channel('s'))
stacked
(xˢ=(4, 5) along sᶜ int64, yˢ=2, sᶜ=2) 0.556 ± 0.497 (0e+00...1e+00)
Here, we have stacked two tensors with varying sizes along x
.
Looking at the result shape, we see that the sizes of y
and the stack dimension s
are simple integers, but the size of x
has become a Tensor
itself.
stacked.shape['y'].size
2
stacked.shape['x'].size
(4, 5) along sᶜ int64
We can easily verify that the shape is non-uniform.
stacked.shape.is_uniform
False
For uniform tensors, you can think of their shape as a list of sizes, i.e. a 1D tensor. For non-uniform tensors, this is not the case, as can be seen with the second-order shape.
stacked.shape.shape
(dimsᶜ=x,y,s, sᶜ=2)
This is the shape of the tensor shape.
While it may be confusing to think about at first, the second-order shape now contains all non-uniform dimensions in addition to the standard dims
dimension.
For tensors with a single non-uniform dimension, slicing along that dimension yields uniform tensors.
stacked.s[0]
(xˢ=4, yˢ=2) const 0.0
You can also get a slice of the shape without actually slicing the tensor.
stacked.shape.after_gather({'s': 0})
(xˢ=4, yˢ=2)
Tensors with multiple non-uniform dimensions are also supported.
stacked2 = stack([stacked, stacked.y[:1]], channel('s2'))
stacked2
(xˢ=(4, 5) along sᶜ int64, yˢ=(2, 1) along s2ᶜ int64, s2ᶜ=2, sᶜ=2) 0.556 ± 0.497 (0e+00...1e+00)
While most math
functions can handle non-uniform tensors correctly, we recommend using this feature cautiously.
math.mean(stacked2)
0.5555556
try:
math.std(stacked2, 's2')
except Exception as exc:
print(exc)
Cannot merge shapes [(xˢ=4, yˢ=2), (xˢ=4, yˢ=1)] because dimension 'y' exists with different sizes.