# !pip install phiml
from phiml import math
The largest difference between ΦML and its backend libraries like PyTorch or Jax lies in the tensor shapes. When using ΦML's tensors, all dimensions must be assigned a name and type flag. To learn why this is useful, see here.
The following dimension types are available:
from phiml.math import batch, channel, spatial, instance, dual
BATCH = batch(examples=100)
BATCH
(examplesᵇ=100)
Here, we have created a Shape
containing a single batch dimension with name examples
.
Note the superscript b
to indicate that this is a batch dimension. Naturally the other superscripts are c
for channel, s
for spatial, i
for instance and d
for dual.
We can now use this shape to construct tensors:
x = math.zeros(BATCH)
x
(examplesᵇ=100) const 0.0
Let's create a tensor with this batch and multiple spatial dimensions! We can pass multiple shapes to tensor constructors and can construct multiple dimensions of the same type in one call.
x = math.ones(BATCH, spatial(x=28, y=28))
x
(examplesᵇ=100, xˢ=28, yˢ=28) const 1.0
We can retrieve the Shape
of x using either x.shape
or math.shape(x)
which also works on primitive types.
x.shape
(examplesᵇ=100, xˢ=28, yˢ=28)
The dimension constructors, such as math.spatial
, can also be used to filter for only these dimensions off an object.
spatial(x)
(xˢ=28, yˢ=28)
from phiml.math import non_batch, non_channel, non_spatial, non_instance, non_dual, primal, non_primal
math.random_uniform(non_batch(x))
(xˢ=28, yˢ=28) 0.507 ± 0.298 (3e-04...1e+00)
One major advantage of naming all dimensions is that reshaping operations can be performed under-the-hood.
Assuming we have a tensor with dimensions a,b
and another with the reverse dimension order.
t1 = math.random_normal(channel(a=2, b=3))
t2 = math.random_normal(channel(b=3, a=2))
When combining them in a tensor operation, ΦML automatically transposes the tensors to match.
t1 + t2
(1.635, 0.772, 0.120, 2.131, -0.400, 1.809) (aᶜ=2, bᶜ=3)
The resulting dimension order is generally undefined. However, this is of no consequence, because dimensions are never referenced by their index in the shape.
When one of the tensors is missing a dimension, it will be added automatically. In these cases, you can think of the value being constant along the missing dimension (like with singleton dimensions in NumPy).
t1 = math.random_normal(channel(a=2))
t2 = math.random_normal(channel(b=3))
t1 + t2
(0.782, 1.116, 1.929, -1.176, -0.842, -0.029) (aᶜ=2, bᶜ=3)
Here, we created a 2D tensor from two 1D tensors. No manual reshaping required.
All tensor creation functions accept a variable number of Shape
objects as input and concatenate the dimensions internally.
This can also be done explicitly using concat_shapes()
.
b = batch(examples=16)
s = spatial(x=28, y=28)
c = channel(channels='red,green,blue')
math.concat_shapes(s, c, b)
(xˢ=28, yˢ=28, channelsᶜ=3:red..., examplesᵇ=16)
This preserves the dimension order and fails if multiple dimensions with the same name are given.
Alternatively, merge_shapes()
can be used, which groups dimensions by type and allows for the same dimensions to be present on multiple inputs.
s = math.merge_shapes(s, c, b)
s
(examplesᵇ=16, xˢ=28, yˢ=28, channelsᶜ=3:red...)
This can also be done using the &
operator.
Notice how the batch dimension is moved to the first place.
s & c & b
(examplesᵇ=16, xˢ=28, yˢ=28, channelsᶜ=3:red...)
Filtering shapes for specific dimensions can be done using Shape[name]
, Shape.only()
and Shape.without()
.
s['x']
(xˢ=28)
s.only('x,y')
(xˢ=28, yˢ=28)
s.without('x,y')
(examplesᵇ=16, channelsᶜ=3:red...)
s.only(spatial)
(xˢ=28, yˢ=28)
Selecting only one type of dimension can also be done using the construction function or the corresponding Shape member variable.
s.spatial
(xˢ=28, yˢ=28)
spatial(s)
(xˢ=28, yˢ=28)
s.non_spatial
(examplesᵇ=16, channelsᶜ=3:red...)
non_spatial(s)
(examplesᵇ=16, channelsᶜ=3:red...)
s.sizes
(16, 28, 28, 3)
Likewise, the names of the dimensions can be read using Shape.names
.
s.names
('examples', 'x', 'y', 'channels')
For single-dimension shapes, the properties name
and size
return the value directly.
You can select
To get the size of a specific dimension, you can use one of the following methods:
s['x'].size
28
for dim in s:
print(dim.name, dim.size, dim.dim_type.__name__)
examples 16 batch x 28 spatial y 28 spatial channels 3 channel
The number of dimensions and total elements can be retrieved using len(Shape)
and Shape.volume
, respectively.
len(s)
4
s.non_batch.volume
2352
The names and types of dimensions can be changed, but this always returns a new object, leaving the original unaltered.
Assume, we want to rename the channels
dimension from above to color
.
math.rename_dims(s, 'channels', 'color')
(examplesᵇ=16, xˢ=28, yˢ=28, colorᶜ=3:red...)
The same can be done for tensors.
math.rename_dims(math.zeros(s), 'channels', 'color')
(examplesᵇ=16, xˢ=28, yˢ=28, colorᶜ=3:red...) const 0.0
To change the type, you may use replace_dims()
, which is an alias for rename_dims()
but clarifies the intended use.
math.replace_dims(s, 'channels', batch('channels'))
(examplesᵇ=16, xˢ=28, yˢ=28, channelsᵇ=3)
The dimension types serve an important role in indicating what role a dimension plays.
Many math
functions behave differently, depending on the given dimension types.
Vector operations like vec_length
or rotate_vector
require the input to have a channel dimension to list the vector components.
Spatial operations like fft
or convolve
,
as well as finite differences
spatial_gradient
, laplace
,
fourier_laplace
, fourier_poisson
,
and resampling operations like
downsample2x
,
upsample2x
,
grid_sample
act only on spatial dimensions.
Their dimensionality (1D/2D/3D/etc.) depends on the number of spatial dimensions of the input.
Dual dimensions are ignored (treated as batch dimensions) by almost all functions, except for matrix multiplications, matrix @ vector
, which reduces the dual dimensions of the matrix against the corresponding primal dimensions of the vector.
Dual dimensions are created by certain operations like pairwise_distances
.
All functions ignore batch dimensions.
This also applies to functions that would usually reduce all dimensions by default, such as
sum
, mean
, std
,
any
, all
,
max
, min
and many more, as well as loss functions like the l2_loss
.
The elementary functions
gather
and
scatter
act on spatial or instance dimensions of the grid.
The indices are listed along instance dimensions and the index components along a singular channel dimension.
See Advantages of Dimension Names and Types for additional examples with comparisons to other computing libraries.
Dimension names play an important role in slicing tensors. To make your code more readable, you can also name slices along dimensions.
The number of spatial dimensions dictates what dimensionality (1D, 2D, 3D) your code works in. You can therefore write code that works in 1D, 2D, 3D and beyond.
Dual dimensions are used to represent columns of matrices.
Stacking tensors with the same dimension names but different sizes results in non-uniform shapes.
🌐 ΦML • 📖 Documentation • 🔗 API • ▶ Videos • Examples