Source code for serket._src.nn.convolution

# Copyright 2024 serket authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Convolutional layers."""

from __future__ import annotations

import abc
import functools as ft
import operator as op
from itertools import product
from typing import Sequence

import jax
import jax.numpy as jnp
import jax.random as jr
from typing_extensions import Annotated

from serket import TreeClass
from serket._src.nn.initialization import resolve_init
from serket._src.utils.convert import (
    calculate_transpose_padding,
    canonicalize,
    delayed_canonicalize_padding,
)
from serket._src.utils.lazy import maybe_lazy_call, maybe_lazy_init
from serket._src.utils.typing import (
    DilationType,
    DType,
    InitType,
    KernelSizeType,
    PaddingType,
    StridesType,
    Weight,
)
from serket._src.utils.validate import (
    validate_in_features_shape,
    validate_pos_int,
    validate_spatial_ndim,
)


def calculate_convolution_output_shape(
    shape: tuple[int, ...],
    kernel_size: tuple[int, ...],
    padding: tuple[tuple[int, int], ...],
    strides: tuple[int, ...],
):
    """Compute the shape of the output of a convolutional layer."""
    return tuple(
        (xi + (li + ri) - ki) // si + 1
        for xi, ki, si, (li, ri) in zip(shape, kernel_size, strides, padding)
    )


@ft.lru_cache(maxsize=None)
def generate_conv_dim_numbers(spatial_ndim) -> jax.lax.ConvDimensionNumbers:
    return jax.lax.ConvDimensionNumbers(*((tuple(range(spatial_ndim + 2)),) * 3))


def fft_conv_general_dilated(
    lhs: jax.Array,
    rhs: jax.Array,
    strides: Sequence[int],
    padding: Sequence[tuple[int, int]],
    groups: int,
    dilation: Sequence[int],
) -> jax.Array:
    def pad(x: jax.Array, pad_width: tuple[tuple[int, int], ...]) -> jax.Array:
        return jnp.pad(x, [(max(lhs, 0), max(rhs, 0)) for (lhs, rhs) in (pad_width)])

    def intersperse(x: jax.Array, dilation: tuple[int, ...], axis: tuple[int, ...]):
        def along_axis(x: jax.Array, dilation: int, axis: int) -> jax.Array:
            shape = list(x.shape)
            shape[axis] = (dilation) * shape[axis] - (dilation - 1)
            z = jnp.zeros(shape)
            z = z.at[(slice(None),) * axis + (slice(None, None, (dilation)),)].set(x)
            return z

        for di, ai in zip(dilation, axis):
            x = along_axis(x, di, ai) if di > 1 else x
        return x

    def matmul(x, y, groups: int = 1):
        def ungrouped_matmul(x, y) -> jax.Array:
            alpha = "".join(map(str, range(max(x.ndim, y.ndim))))
            lhs = "a" + alpha[: x.ndim - 1]
            rhs = "b" + alpha[: y.ndim - 1]
            out = "ab" + lhs[2:]
            return jnp.einsum(f"{lhs},{rhs}->{out}", x, y)

        def grouped_matmul(x, y, groups) -> jax.Array:
            b, c, *s = x.shape  # batch, channels, spatial
            o, i, *k = y.shape  # out_channels, in_channels, kernel
            # groups, batch, channels, spatial
            x = x.reshape(groups, b, c // groups, *s)
            y = y.reshape(groups, o // groups, *(i, *k))
            z = jax.vmap(ungrouped_matmul, in_axes=(0, 0), out_axes=1)(x, y)
            return z.reshape(z.shape[0], z.shape[1] * z.shape[2], *z.shape[3:])

        return ungrouped_matmul(x, y) if groups == 1 else grouped_matmul(x, y, groups)

    spatial_ndim = lhs.ndim - 2  # spatial dimensions
    rhs = intersperse(rhs, dilation=dilation, axis=range(2, 2 + spatial_ndim))
    lhs = pad(lhs, ((0, 0), (0, 0), *padding))

    x_shape, w_shape = lhs.shape, rhs.shape

    if lhs.shape[-1] % 2 != 0:
        lhs = jnp.pad(lhs, tuple([(0, 0)] * (lhs.ndim - 1) + [(0, 1)]))

    kernel_pad = ((0, lhs.shape[i] - rhs.shape[i]) for i in range(2, spatial_ndim + 2))
    rhs = pad(rhs, ((0, 0), (0, 0), *kernel_pad))

    x_fft = jnp.fft.rfftn(lhs, axes=range(2, spatial_ndim + 2))
    w_fft = jnp.conjugate(jnp.fft.rfftn(rhs, axes=range(2, spatial_ndim + 2)))
    z_fft = matmul(x_fft, w_fft, groups)
    z = jnp.fft.irfftn(z_fft, axes=range(2, spatial_ndim + 2))

    start = (0,) * (spatial_ndim + 2)
    end = [z.shape[0], z.shape[1]]
    end += [max((x_shape[i] - w_shape[i] + 1), 0) for i in range(2, spatial_ndim + 2)]

    return (
        jax.lax.dynamic_slice(z, start, end)
        if all(s == 1 for s in strides)
        else jax.lax.slice(z, start, end, (1, 1, *strides))
    )


[docs] def fft_conv_nd( input: jax.Array, weight: Weight, bias: jax.Array | None, strides: Sequence[int], padding: Sequence[tuple[int, int]], dilation: Sequence[int], groups: int, mask: Weight | None = None, ) -> jax.Array: """Convolution function using fft. Note: Use ``jax.vmap`` to apply the convolution to a batch of input. Args: input: input array. shape is ``(in_features, spatial)``. weight: convolutional kernel. shape is ``(out_features, in_features, kernel)``. bias: bias. shape is ``(out_features, spatial)``. set to ``None`` to not use a bias. strides: stride of the convolution accepts tuple of integers for different strides in each dimension. padding: padding of the input before convolution accepts tuple of integers for different padding in each dimension. dilation: dilation of the convolutional kernel accepts tuple of integers for different dilation in each dimension. groups: number of groups to use for grouped convolution. mask: a binary mask multiplied with the convolutional kernel. shape is ``(out_features, in_features, kernel)``. set to ``None`` to not use a mask. """ x = fft_conv_general_dilated( lhs=jnp.expand_dims(input, 0), rhs=weight if mask is None else weight * mask, strides=strides, padding=padding, dilation=dilation, groups=groups, ) return jnp.squeeze(x, 0) if bias is None else jnp.squeeze((x + bias), 0)
[docs] def fft_conv_nd_transpose( input: jax.Array, weight: Weight, bias: jax.Array | None, strides: Sequence[int], padding: Sequence[tuple[int, int]], dilation: Sequence[int], out_padding: int, mask: Weight | None = None, ) -> jax.Array: """Transposed convolution function using fft. Args: input: input array. shape is ``(in_features, spatial)``. weight: convolutional kernel. shape is ``(out_features, in_features, kernel)``. bias: bias. shape is ``(out_features, spatial)``. set to ``None`` to not use a bias. strides: stride of the convolution accepts tuple of integers for different strides in each dimension. padding: padding of the input before convolution accepts tuple of integers for different padding in each dimension. dilation: dilation of the convolutional kernel accepts tuple of integers for different dilation in each dimension. out_padding: padding of the output after convolution. mask: a binary mask multiplied with the convolutional kernel. shape is ``(out_features, in_features, kernel)``. set to ``None`` to not use a mask. """ transposed_padding = calculate_transpose_padding( padding=padding, extra_padding=out_padding, kernel_size=weight.shape[2:], input_dilation=dilation, ) x = fft_conv_general_dilated( lhs=jnp.expand_dims(input, 0), rhs=weight if mask is None else weight * mask, strides=strides, padding=transposed_padding, dilation=dilation, groups=1, ) return jnp.squeeze(x + bias, 0) if bias is not None else jnp.squeeze(x, 0)
[docs] def depthwise_fft_conv_nd( input: jax.Array, weight: Weight, bias: jax.Array | None, strides: Sequence[int], padding: Sequence[tuple[int, int]], mask: Weight | None = None, ) -> jax.Array: """Depthwise convolution function using fft. Args: input: input array. shape is ``(in_features, spatial)``. weight: convolutional kernel. shape is ``(out_features, in_features, kernel)``. bias: bias. shape is ``(out_features, spatial)``. set to ``None`` to not use a bias. strides: stride of the convolution accepts tuple of integers for different strides in each dimension. padding: padding of the input before convolution accepts tuple of integers for different padding in each dimension. mask: a binary mask multiplied with the convolutional kernel. shape is ``(out_features, in_features, kernel)``. set to ``None`` to not use a mask. """ x = fft_conv_general_dilated( lhs=jnp.expand_dims(input, 0), rhs=weight if mask is None else weight * mask, strides=strides, padding=padding, dilation=(1,) * (input.ndim - 1), groups=input.shape[0], # in_features ) return jnp.squeeze(x + bias, 0) if bias is not None else jnp.squeeze(x, 0)
[docs] def separable_fft_conv_nd( input: jax.Array, depthwise_weight: Weight, pointwise_weight: Weight, pointwise_bias: jax.Array | None, strides: Sequence[int], depthwise_padding: Sequence[tuple[int, int]], pointwise_padding: Sequence[tuple[int, int]], depthwise_mask: Weight | None = None, pointwise_mask: Weight | None = None, ) -> jax.Array: """Separable convolution function using fft. Args: input: input array. shape is ``(in_features, spatial)``. depthwise_weight: depthwise convolutional kernel. pointwise_weight: pointwise convolutional kernel. pointwise_bias: bias for the pointwise convolution. strides: stride of the convolution accepts tuple of integers for different strides in each dimension. depthwise_padding: padding of the input before depthwise convolution accepts Sequence of integers for different padding in each dimension. pointwise_padding: padding of the input before pointwise convolution accepts Sequence of integers for different padding in each dimension. depthwise_mask: a binary mask multiplied with the depthwise convolutional kernel. shape is ``(depth_multiplier * in_features, 1, *self.kernel_size)``. set to ``None`` to not use a mask. pointwise_mask: a binary mask multiplied with the pointwise convolutional kernel. shape is ``(out_features, depth_multiplier * in_features, 1, *self.kernel_size)``. set to ``None`` to not use a mask. """ input = depthwise_fft_conv_nd( input=input, weight=depthwise_weight, bias=None, strides=strides, padding=depthwise_padding, mask=depthwise_mask, ) return fft_conv_nd( input=input, weight=pointwise_weight, bias=pointwise_bias, strides=strides, padding=pointwise_padding, dilation=(1,) * (input.ndim - 1), groups=1, mask=pointwise_mask, )
[docs] def conv_nd( input: jax.Array, weight: Weight, bias: jax.Array | None, strides: Sequence[int], padding: Sequence[tuple[int, int]], dilation: Sequence[int], groups: int, mask: Weight | None = None, ) -> jax.Array: """Convolution function wrapping ``jax.lax.conv_general_dilated``. Args: input: input array. shape is ``(in_features, spatial)``. weight: convolutional kernel. shape is ``(out_features, in_features, kernel)``. bias: bias. shape is ``(out_features, spatial)``. set to ``None`` to not use a bias. strides: stride of the convolution accepts tuple of integers for different strides in each dimension. padding: padding of the input before convolution accepts tuple of two integers for different padding in each dimension. dilation: dilation of the convolutional kernel accepts tuple of integers for different dilation in each dimension. groups: number of groups to use for grouped convolution. mask: a binary mask multiplied with the convolutional kernel. shape is ``(out_features, in_features, kernel)``. set """ x = jax.lax.conv_general_dilated( lhs=jnp.expand_dims(input, 0), rhs=weight if mask is None else weight * mask, window_strides=strides, padding=padding, rhs_dilation=dilation, dimension_numbers=generate_conv_dim_numbers(input.ndim - 1), # OIH... feature_group_count=groups, ) return jnp.squeeze(x, 0) if bias is None else jnp.squeeze((x + bias), 0)
[docs] def conv_nd_transpose( input: jax.Array, weight: Weight, bias: jax.Array | None, strides: Sequence[int], padding: Sequence[tuple[int, int]], dilation: Sequence[int], out_padding: int, mask: Weight | None = None, ) -> jax.Array: """Transposed convolution function wrapping ``jax.lax.conv_general_dilated``. Args: input: input array. shape is ``(in_features, spatial)``. weight: convolutional kernel. shape is ``(out_features, in_features, kernel)``. bias: bias. shape is ``(out_features, spatial)``. set to ``None`` to not use a bias. strides: stride of the convolution accepts tuple of integers for different strides in each dimension. padding: padding of the input before convolution accepts tuple of integers for different padding in each dimension. dilation: dilation of the convolutional kernel accepts tuple of integers for different dilation in each dimension. out_padding: padding of the output after convolution. mask: a binary mask multiplied with the convolutional kernel. shape is ``(out_features, in_features, kernel)``. set to ``None`` to not use a mask. """ transposed_padding = calculate_transpose_padding( padding=padding, extra_padding=out_padding, kernel_size=weight.shape[2:], input_dilation=dilation, ) x = jax.lax.conv_transpose( lhs=jnp.expand_dims(input, 0), rhs=weight if mask is None else weight * mask, strides=strides, padding=transposed_padding, rhs_dilation=dilation, dimension_numbers=generate_conv_dim_numbers(input.ndim - 1), ) return jnp.squeeze(x + bias, 0) if bias is not None else jnp.squeeze(x, 0)
[docs] def separable_conv_nd( input: jax.Array, depthwise_weight: Weight, pointwise_weight: Weight, pointwise_bias: jax.Array | None, strides: Sequence[int], depthwise_padding: Sequence[tuple[int, int]], pointwise_padding: Sequence[tuple[int, int]], depthwise_mask: Weight | None = None, pointwise_mask: Weight | None = None, ) -> jax.Array: """Seprable convolution function wrapping ``jax.lax.conv_general_dilated``. Args: input: input array. shape is ``(in_features, spatial)``. depthwise_weight: depthwise convolutional kernel. pointwise_weight: pointwise convolutional kernel. pointwise_bias: bias for the pointwise convolution. strides: stride of the convolution accepts tuple of integers for different strides in each dimension. depthwise_padding: padding of the input before depthwise convolution accepts Sequence of integers for different padding in each dimension. pointwise_padding: padding of the input before pointwise convolution accepts Sequence of integers for different padding in each dimension. depthwise_mask: a binary mask multiplied with the depthwise convolutional kernel. shape is ``(depth_multiplier * in_features, 1, *self.kernel_size)`` set to ``None`` to not use a mask. pointwise_mask: a binary mask multiplied with the pointwise convolutional kernel. shape is ``(out_features, depth_multiplier * in_features, *kernel_size)`` """ input = depthwise_conv_nd( input=input, weight=depthwise_weight, bias=None, strides=strides, padding=depthwise_padding, mask=depthwise_mask, ) return conv_nd( input=input, weight=pointwise_weight, bias=pointwise_bias, strides=strides, padding=pointwise_padding, dilation=(1,) * (input.ndim - 1), groups=1, mask=pointwise_mask, )
[docs] def depthwise_conv_nd( input: jax.Array, weight: Weight, bias: jax.Array | None, strides: Sequence[int], padding: Sequence[tuple[int, int]], mask: Weight | None = None, ) -> jax.Array: """Depthwise convolution function wrapping ``jax.lax.conv_general_dilated``. Args: input: input array. shape is ``(in_features, spatial)``. weight: convolutional kernel. shape is ``(out_features, in_features, kernel)``. bias: bias. shape is ``(out_features, spatial)``. set to ``None`` to not use a bias. strides: stride of the convolution accepts tuple of integers for different strides in each dimension. padding: padding of the input before convolution accepts tuple of integers for different padding in each dimension. mask: a binary mask multiplied with the convolutional kernel. shape is ``(out_features, in_features, kernel)``. set to ``None`` to not use a mask. """ x = jax.lax.conv_general_dilated( lhs=jnp.expand_dims(input, 0), rhs=weight if mask is None else weight * mask, window_strides=strides, padding=padding, rhs_dilation=(1,) * (input.ndim - 1), dimension_numbers=generate_conv_dim_numbers(input.ndim - 1), feature_group_count=input.shape[0], # in_features ) return jnp.squeeze(x, 0) if bias is None else jnp.squeeze(x + bias, 0)
[docs] def spectral_conv_nd( input: Annotated[jax.Array, "I..."], weight: Weight, modes: Sequence[int], ) -> Annotated[jax.Array, "O..."]: """fourier neural operator convolution function. Args: input: input array. shape is ``(in_features, spatial size)``. weight: real and complex convolutional kernel. shape is ``(2 , 2 ** (dim-1), out_features, in_features, modes)``. where dim is the number of spatial dimensions on the modes: number of modes included in the fft representation of the input. """ def generate_modes_slices(modes: Sequence[int]): *ms, ml = modes slices_ = [[slice(None, ml)]] slices_ += [[slice(None, mode), slice(-mode, None)] for mode in reversed(ms)] return [[slice(None)] + list(reversed(i)) for i in product(*slices_)] _, *si, sl = input.shape weight_r, weight_i = jnp.split(weight, 2, axis=0) weight = weight_r + 1j * weight_i _, o, *_ = weight.shape x_fft = jnp.fft.rfftn(input, s=(*si, sl)) out = jnp.zeros([o, *si, sl // 2 + 1], dtype=input.dtype) + 0j for i, slice_i in enumerate(generate_modes_slices(modes)): matmul_out = jnp.einsum("i...,oi...->o...", x_fft[tuple(slice_i)], weight[i]) out = out.at[tuple(slice_i)].set(matmul_out) return jnp.fft.irfftn(out, s=(*si, sl))
[docs] def local_conv_nd( input: jax.Array, weight: jax.Array, bias: jax.Array | None, strides: Sequence[int], padding: Sequence[tuple[int, int]], dilation: Sequence[int], kernel_size: Sequence[int], mask: Weight | None = None, ) -> jax.Array: """Local convolution function wrapping ``jax.lax.conv_general_dilated_local``. Args: input: input array. shape is ``(in_features, spatial)``. weight: convolutional kernel. shape is ``(out_features, in_features, kernel)``. bias: bias. shape is ``(out_features, spatial)``. set to ``None`` to not use a bias. strides: stride of the convolution accepts tuple of integers for different strides in each dimension. padding: padding of the input before convolution accepts tuple of integers for different padding in each dimension. dilation: dilation of the convolution accepts tuple of integers for different dilation in each dimension. kernel_size: size of the convolutional kernel accepts tuple of integers for different kernel sizes in each dimension. mask: a binary mask multiplied with the convolutional kernel. shape is ``(out_features, in_features, kernel)``. set to ``None`` to not use a mask. """ x = jax.lax.conv_general_dilated_local( lhs=jnp.expand_dims(input, 0), rhs=weight if mask is None else weight * mask, window_strides=strides, padding=padding, filter_shape=kernel_size, rhs_dilation=dilation, dimension_numbers=generate_conv_dim_numbers(input.ndim - 1), ) return jnp.squeeze(x + bias, 0) if bias is not None else jnp.squeeze(x, 0)
def is_lazy_call(instance, *_1, **_2) -> bool: return getattr(instance, "in_features", False) is None def is_lazy_init(_1, in_features, *_2, **_3) -> bool: return in_features is None def infer_in_features(_1, x, *_2, **_3) -> int: return x.shape[0] updates = dict(in_features=infer_in_features) class ConvND(TreeClass): @ft.partial(maybe_lazy_init, is_lazy=is_lazy_init) def __init__( self, in_features: int | None, out_features: int, kernel_size: KernelSizeType, *, key: jax.Array, strides: StridesType = 1, padding: PaddingType = "same", dilation: DilationType = 1, weight_init: InitType = "glorot_uniform", bias_init: InitType = "zeros", groups: int = 1, dtype: DType = jnp.float32, ): self.in_features = validate_pos_int(in_features) self.out_features = validate_pos_int(out_features) self.kernel_size = canonicalize(kernel_size, self.spatial_ndim, "kernel_size") self.strides = canonicalize(strides, self.spatial_ndim, "strides") self.padding = padding self.dilation = canonicalize(dilation, self.spatial_ndim, "dilation") self.weight_init = weight_init self.bias_init = bias_init self.groups = validate_pos_int(groups) if self.out_features % self.groups != 0: raise ValueError(f"{(out_features % groups == 0)=}") weight_shape = (out_features, in_features // groups, *self.kernel_size) self.weight = resolve_init(self.weight_init)(key, weight_shape, dtype) bias_shape = (out_features, *(1,) * self.spatial_ndim) self.bias = resolve_init(self.bias_init)(key, bias_shape, dtype) @ft.partial(maybe_lazy_call, is_lazy=is_lazy_call, updates=updates) @ft.partial(validate_spatial_ndim, argnum=0) @ft.partial(validate_in_features_shape, axis=0) def __call__(self, input: jax.Array, mask: Weight | None = None) -> jax.Array: """Apply the layer. Args: input: input array. shape is ``(in_features, spatial size)``. spatial size is length for 1D convolution, height, width for 2D convolution and height, width, depth for 3D convolution. mask: a binary mask multiplied with the convolutional kernel. shape is ``(out_features, in_features // groups, kernel size)``. set to ``None`` to not use a mask. """ padding = delayed_canonicalize_padding( in_dim=input.shape[1:], padding=self.padding, kernel_size=self.kernel_size, strides=self.strides, ) return self.conv_op( input=input, weight=self.weight, bias=self.bias, strides=self.strides, padding=padding, dilation=self.dilation, groups=self.groups, mask=mask, ) spatial_ndim = property(abc.abstractmethod(lambda _: ...)) conv_op = staticmethod(abc.abstractmethod(lambda _: ...))
[docs] class Conv1D(ConvND): """1D Convolutional layer. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. out_features: Number of output features maps, for 1D convolution this is the length of the output, for 2D convolution this is the number of output channels, for 3D convolution this is the number of output channels. kernel_size: Size of the convolutional kernel. accepts: - Single integer for same kernel size in all dimensions. - Sequence of integers for different kernel sizes in each dimension. strides: stride of the convolution. accepts: - Single integer for same stride in all dimensions. - Sequence of integers for different strides in each dimension. key: key to use for initializing the weights. padding: Padding of the input before convolution. accepts: - Single integer for same padding in all dimensions. - Sequence of integers for different padding in each dimension. - Sequnece of a tuple of two integers for before and after padding in each dimension. - ``same``/``SAME`` for padding such that the output has the same shape as the input. - ``valid``/``VALID`` for no padding. dilation: Dilation of the convolutional kernel accepts: - Single integer for same dilation in all dimensions. - Sequence of integers for different dilation in each dimension. weight_init: Function to use for initializing the weights. defaults to ``glorot uniform``. bias_init: Function to use for initializing the bias. defaults to ``zeros``. set to ``None`` to not use a bias. groups: number of groups to use for grouped convolution. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax >>> import jax.random as jr >>> key = jr.key(0) >>> layer = sk.nn.Conv1D(1, 2, 3, key=key) >>> # single sample >>> input = jnp.ones((1, 5)) >>> print(layer(input).shape) (2, 5) >>> # batch of samples >>> input = jnp.ones((2, 1, 5)) >>> print(jax.vmap(layer)(input).shape) (2, 2, 5) Note: :class:`.Conv1D` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.Conv1D(None, 12, 3, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 Reference: - https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv.html """ spatial_ndim: int = 1 conv_op = staticmethod(conv_nd)
[docs] class Conv2D(ConvND): """2D Convolutional layer. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. out_features: Number of output features maps, for 1D convolution this is the length of the output, for 2D convolution this is the number of output channels, for 3D convolution this is the number of output channels. kernel_size: Size of the convolutional kernel. accepts: - Single integer for same kernel size in all dimensions. - Sequence of integers for different kernel sizes in each dimension. strides: Stride of the convolution. accepts: - Single integer for same stride in all dimensions. - Sequence of integers for different strides in each dimension. key: key to use for initializing the weights. padding: Padding of the input before convolution. accepts: - Single integer for same padding in all dimensions. - Sequence of integers for different padding in each dimension. - Sequnece of a tuple of two integers for before and after padding in each dimension. - ``same``/``SAME`` for padding such that the output has the same shape as the input. - ``valid``/``VALID`` for no padding. dilation: dilation of the convolutional kernel accepts: - Single integer for same dilation in all dimensions. - Sequence of integers for different dilation in each dimension. weight_init: Function to use for initializing the weights. defaults to ``glorot uniform``. bias_init: Function to use for initializing the bias. defaults to ``zeros``. set to ``None`` to not use a bias. groups: number of groups to use for grouped convolution. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax >>> import jax.random as jr >>> key = jr.key(0) >>> layer = sk.nn.Conv2D(1, 2, 3, key=key) >>> # single sample >>> input = jnp.ones((1, 5, 5)) >>> print(layer(input).shape) (2, 5, 5) >>> # batch of samples >>> input = jnp.ones((2, 1, 5, 5)) >>> print(jax.vmap(layer)(input).shape) (2, 2, 5, 5) Note: :class:`.Conv2D` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.Conv2D(None, 12, 3, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 Reference: - https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv.html """ spatial_ndim: int = 2 conv_op = staticmethod(conv_nd)
[docs] class Conv3D(ConvND): """3D Convolutional layer. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. out_features: Number of output features maps, for 1D convolution this is the length of the output, for 2D convolution this is the number of output channels, for 3D convolution this is the number of output channels. kernel_size: Size of the convolutional kernel. accepts: - Single integer for same kernel size in all dimensions. - Sequence of integers for different kernel sizes in each dimension. strides: Stride of the convolution. accepts: - Single integer for same stride in all dimensions. - Sequence of integers for different strides in each dimension. key: key to use for initializing the weights. padding: Padding of the input before convolution. accepts: - Single integer for same padding in all dimensions. - Sequence of integers for different padding in each dimension. - Sequnece of a tuple of two integers for before and after padding in each dimension. - ``same``/``SAME`` for padding such that the output has the same shape as the input. - ``valid``/``VALID`` for no padding. dilation: dilation of the convolutional kernel accepts: - Single integer for same dilation in all dimensions. - Sequence of integers for different dilation in each dimension. weight_init: Function to use for initializing the weights. defaults to ``glorot uniform``. bias_init: Function to use for initializing the bias. defaults to ``zeros``. set to ``None`` to not use a bias. groups: number of groups to use for grouped convolution. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax >>> import jax.random as jr >>> key = jr.key(0) >>> layer = sk.nn.Conv3D(1, 2, 3, key=key) >>> # single sample >>> input = jnp.ones((1, 5, 5, 5)) >>> print(layer(input).shape) (2, 5, 5, 5) >>> # batch of samples >>> input = jnp.ones((2, 1, 5, 5, 5)) >>> print(jax.vmap(layer)(input).shape) (2, 2, 5, 5, 5) Note: :class:`.Conv3D` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10, 10, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.Conv3D(None, 12, 3, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 Reference: - https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv.html """ spatial_ndim: int = 3 conv_op = staticmethod(conv_nd)
[docs] class FFTConv1D(ConvND): """1D Convolutional layer. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. out_features: Number of output features maps, for 1D convolution this is the length of the output, for 2D convolution this is the number of output channels, for 3D convolution this is the number of output channels. kernel_size: Size of the convolutional kernel. accepts: - Single integer for same kernel size in all dimensions. - Sequence of integers for different kernel sizes in each dimension. strides: Stride of the convolution. accepts: - Single integer for same stride in all dimensions. - Sequence of integers for different strides in each dimension. key: key to use for initializing the weights. padding: Padding of the input before convolution. accepts: - Single integer for same padding in all dimensions. - Sequence of integers for different padding in each dimension. - Sequnece of a tuple of two integers for before and after padding in each dimension. - ``same``/``SAME`` for padding such that the output has the same shape as the input. - ``valid``/``VALID`` for no padding. dilation: Dilation of the convolutional kernel accepts: - Single integer for same dilation in all dimensions. - Sequence of integers for different dilation in each dimension. weight_init: function to use for initializing the weights. defaults to ``glorot uniform``. bias_init: function to use for initializing the bias. defaults to ``zeros``. set to ``None`` to not use a bias. groups: number of groups to use for grouped convolution. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax >>> import jax.random as jr >>> key = jr.key(0) >>> layer = sk.nn.FFTConv1D(1, 2, 3, key=key) >>> # single sample >>> input = jnp.ones((1, 5)) >>> print(layer(input).shape) (2, 5) >>> # batch of samples >>> input = jnp.ones((2, 1, 5)) >>> print(jax.vmap(layer)(input).shape) (2, 2, 5) Note: :class:`.FFTConv1D` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.FFTConv1D(None, 12, 3, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 References: https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv.html """ spatial_ndim: int = 1 conv_op = staticmethod(fft_conv_nd)
[docs] class FFTConv2D(ConvND): """2D FFT Convolutional layer. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. out_features: Number of output features maps, for 1D convolution this is the length of the output, for 2D convolution this is the number of output channels, for 3D convolution this is the number of output channels. kernel_size: Size of the convolutional kernel. accepts: - Single integer for same kernel size in all dimensions. - Sequence of integers for different kernel sizes in each dimension. strides: Stride of the convolution. accepts: - Single integer for same stride in all dimensions. - Sequence of integers for different strides in each dimension. key: key to use for initializing the weights. padding: Padding of the input before convolution. accepts: - Single integer for same padding in all dimensions. - Sequence of integers for different padding in each dimension. - Sequnece of a tuple of two integers for before and after padding in each dimension. - ``same``/``SAME`` for padding such that the output has the same shape as the input. - ``valid``/``VALID`` for no padding. dilation: Dilation of the convolutional kernel accepts: - Single integer for same dilation in all dimensions. - Sequence of integers for different dilation in each dimension. weight_init: function to use for initializing the weights. defaults to ``glorot uniform``. bias_init: function to use for initializing the bias. defaults to ``zeros``. set to ``None`` to not use a bias. groups: number of groups to use for grouped convolution. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax >>> import jax.random as jr >>> key = jr.key(0) >>> layer = sk.nn.FFTConv2D(1, 2, 3, key=key) >>> # single sample >>> input = jnp.ones((1, 5, 5)) >>> print(layer(input).shape) (2, 5, 5) >>> # batch of samples >>> input = jnp.ones((2, 1, 5, 5)) >>> print(jax.vmap(layer)(input).shape) (2, 2, 5, 5) Note: :class:`.FFTConv2D` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.FFTConv2D(None, 12, 3, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 References: - https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv.html """ spatial_ndim: int = 2 conv_op = staticmethod(fft_conv_nd)
[docs] class FFTConv3D(ConvND): """3D FFT Convolutional layer. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. out_features: Number of output features maps, for 1D convolution this is the length of the output, for 2D convolution this is the number of output channels, for 3D convolution this is the number of output channels. kernel_size: Size of the convolutional kernel. accepts: - Single integer for same kernel size in all dimensions. - Sequence of integers for different kernel sizes in each dimension. strides: Stride of the convolution. accepts: - Single integer for same stride in all dimensions. - Sequence of integers for different strides in each dimension. key: key to use for initializing the weights. padding: Padding of the input before convolution. accepts: - Single integer for same padding in all dimensions. - Sequence of integers for different padding in each dimension. - Sequnece of a tuple of two integers for before and after padding in each dimension. - ``same``/``SAME`` for padding such that the output has the same shape as the input. - ``valid``/``VALID`` for no padding. dilation: Dilation of the convolutional kernel accepts: - Single integer for same dilation in all dimensions. - Sequence of integers for different dilation in each dimension. weight_init: function to use for initializing the weights. defaults to ``glorot uniform``. bias_init: function to use for initializing the bias. defaults to ``zeros``. set to ``None`` to not use a bias. groups: number of groups to use for grouped convolution. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax >>> import jax.random as jr >>> key = jr.key(0) >>> layer = sk.nn.FFTConv3D(1, 2, 3, key=key) >>> # single sample >>> input = jnp.ones((1, 5, 5, 5)) >>> print(layer(input).shape) (2, 5, 5, 5) >>> # batch of samples >>> input = jnp.ones((2, 1, 5, 5, 5)) >>> print(jax.vmap(layer)(input).shape) (2, 2, 5, 5, 5) Note: :class:`.FFTConv3D` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10, 10, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.FFTConv3D(None, 12, 3, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 References: - https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv.html """ spatial_ndim: int = 3 conv_op = staticmethod(fft_conv_nd)
class ConvNDTranspose(TreeClass): @ft.partial(maybe_lazy_init, is_lazy=is_lazy_init) def __init__( self, in_features: int | None, out_features: int, kernel_size: KernelSizeType, *, key: jax.Array, strides: StridesType = 1, padding: PaddingType = "same", out_padding: int = 0, dilation: DilationType = 1, weight_init: InitType = "glorot_uniform", bias_init: InitType = "zeros", groups: int = 1, dtype: DType = jnp.float32, ): self.in_features = validate_pos_int(in_features) self.out_features = validate_pos_int(out_features) self.kernel_size = canonicalize(kernel_size, self.spatial_ndim, "kernel_size") self.strides = canonicalize(strides, self.spatial_ndim, "strides") self.padding = padding # delayed canonicalization self.out_padding = canonicalize(out_padding, self.spatial_ndim, "out_padding") self.dilation = canonicalize(dilation, self.spatial_ndim, "dilation") self.weight_init = weight_init self.bias_init = bias_init self.groups = validate_pos_int(groups) if self.out_features % self.groups != 0: raise ValueError(f"{(self.out_features % self.groups ==0)=}") in_features = validate_pos_int(self.in_features) weight_shape = (out_features, in_features // groups, *self.kernel_size) self.weight = resolve_init(self.weight_init)(key, weight_shape, dtype) bias_shape = (out_features, *(1,) * self.spatial_ndim) self.bias = resolve_init(self.bias_init)(key, bias_shape, dtype) @ft.partial(maybe_lazy_call, is_lazy=is_lazy_call, updates=updates) @ft.partial(validate_spatial_ndim, argnum=0) @ft.partial(validate_in_features_shape, axis=0) def __call__(self, input: jax.Array, mask: Weight | None = None) -> jax.Array: """Apply the layer. Args: input: input array. shape is ``(in_features, spatial size)``. spatial size is length for 1D convolution, height, width for 2D convolution and height, width, depth for 3D convolution. mask: a binary mask multiplied with the convolutional kernel. shape is ``(out_features, in_features // groups, kernel size)``. set to ``None`` to not use a mask. """ padding = delayed_canonicalize_padding( in_dim=input.shape[1:], padding=self.padding, kernel_size=self.kernel_size, strides=self.strides, ) return self.conv_op( input=input, weight=self.weight, bias=self.bias, strides=self.strides, padding=padding, dilation=self.dilation, out_padding=self.out_padding, mask=mask, ) spatial_ndim = property(abc.abstractmethod(lambda _: ...)) conv_op = staticmethod(abc.abstractmethod(lambda _: ...))
[docs] class Conv1DTranspose(ConvNDTranspose): """1D Convolution transpose layer. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. out_features: Number of output features maps, for 1D convolution this is the length of the output, for 2D convolution this is the number of output channels, for 3D convolution this is the number of output channels. kernel_size: Size of the convolutional kernel. accepts: - Single integer for same kernel size in all dimensions. - Sequence of integers for different kernel sizes in each dimension. strides: Stride of the convolution. accepts: - Single integer for same stride in all dimensions. - Sequence of integers for different strides in each dimension. key: key to use for initializing the weights. padding: Padding of the input before convolution. accepts: - Single integer for same padding in all dimensions. - Sequence of integers for different padding in each dimension. - Sequnece of a tuple of two integers for before and after padding in each dimension. - ``same``/``SAME`` for padding such that the output has the same shape as the input. - ``valid``/``VALID`` for no padding. out_padding: padding of the output after convolution. accepts: - Single integer for same padding in all dimensions. dilation: dilation of the convolutional kernel accepts: - Single integer for same dilation in all dimensions. - Sequence of integers for different dilation in each dimension. weight_init: Function to use for initializing the weights. defaults to ``glorot uniform``. bias_init: Function to use for initializing the bias. defaults to ``zeros``. set to ``None`` to not use a bias. groups: number of groups to use for grouped convolution. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax >>> import jax.random as jr >>> key = jr.key(0) >>> layer = sk.nn.Conv1DTranspose(1, 2, 3, key=key) >>> # single sample >>> input = jnp.ones((1, 5)) >>> print(layer(input).shape) (2, 5) >>> # batch of samples >>> input = jnp.ones((2, 1, 5)) >>> print(jax.vmap(layer)(input).shape) (2, 2, 5) Note: :class:`.Conv1DTranspose` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.Conv1DTranspose(None, 12, 3, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 Reference: - https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv.html """ spatial_ndim: int = 1 conv_op = staticmethod(conv_nd_transpose)
[docs] class Conv2DTranspose(ConvNDTranspose): """2D Convolution transpose layer. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. out_features: Number of output features maps, for 1D convolution this is the length of the output, for 2D convolution this is the number of output channels, for 3D convolution this is the number of output channels. kernel_size: Size of the convolutional kernel. accepts: - Single integer for same kernel size in all dimensions. - Sequence of integers for different kernel sizes in each dimension. strides: Stride of the convolution. accepts: - Single integer for same stride in all dimensions. - Sequence of integers for different strides in each dimension. key: key to use for initializing the weights. padding: Padding of the input before convolution. accepts: - Single integer for same padding in all dimensions. - Sequence of integers for different padding in each dimension. - Sequnece of a tuple of two integers for before and after padding in each dimension. - ``same``/``SAME`` for padding such that the output has the same shape as the input. - ``valid``/``VALID`` for no padding. out_padding: padding of the output after convolution. accepts: - Single integer for same padding in all dimensions. dilation: dilation of the convolutional kernel accepts: - Single integer for same dilation in all dimensions. - Sequence of integers for different dilation in each dimension. weight_init: Function to use for initializing the weights. defaults to ``glorot uniform``. bias_init: Function to use for initializing the bias. defaults to ``zeros``. set to ``None`` to not use a bias. groups: number of groups to use for grouped convolution. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax >>> key = jr.key(0) >>> layer = sk.nn.Conv2DTranspose(1, 2, 3, key=key) >>> # single sample >>> input = jnp.ones((1, 5, 5)) >>> print(layer(input).shape) (2, 5, 5) >>> # batch of samples >>> input = jnp.ones((2, 1, 5, 5)) >>> print(jax.vmap(layer)(input).shape) (2, 2, 5, 5) Note: :class:`.Conv2DTranspose` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.Conv2DTranspose(None, 12, 3, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 Reference: - https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv.html """ spatial_ndim: int = 2 conv_op = staticmethod(conv_nd_transpose)
[docs] class Conv3DTranspose(ConvNDTranspose): """3D Convolution transpose layer. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. out_features: Number of output features maps, for 1D convolution this is the length of the output, for 2D convolution this is the number of output channels, for 3D convolution this is the number of output channels. kernel_size: Size of the convolutional kernel. accepts: - Single integer for same kernel size in all dimensions. - Sequence of integers for different kernel sizes in each dimension. strides: Stride of the convolution. accepts: - Single integer for same stride in all dimensions. - Sequence of integers for different strides in each dimension. key: key to use for initializing the weights. padding: Padding of the input before convolution. accepts: - Single integer for same padding in all dimensions. - Sequence of integers for different padding in each dimension. - Sequnece of a tuple of two integers for before and after padding in each dimension. - ``same``/``SAME`` for padding such that the output has the same shape as the input. - ``valid``/``VALID`` for no padding. out_padding: padding of the output after convolution. accepts: - Single integer for same padding in all dimensions. dilation: dilation of the convolutional kernel accepts: - Single integer for same dilation in all dimensions. - Sequence of integers for different dilation in each dimension. weight_init: Function to use for initializing the weights. defaults to ``glorot uniform``. bias_init: Function to use for initializing the bias. defaults to ``zeros``. set to ``None`` to not use a bias. groups: number of groups to use for grouped convolution. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax >>> import jax.random as jr >>> key = jr.key(0) >>> layer = sk.nn.Conv3DTranspose(1, 2, 3, key=key) >>> # single sample >>> input = jnp.ones((1, 5, 5, 5)) >>> print(layer(input).shape) (2, 5, 5, 5) >>> # batch of samples >>> input = jnp.ones((2, 1, 5, 5, 5)) >>> print(jax.vmap(layer)(input).shape) (2, 2, 5, 5, 5) Note: :class:`.Conv3DTranspose` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10, 10, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.Conv3DTranspose(None, 12, 3, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 Reference: - https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv.html """ spatial_ndim: int = 3 conv_op = staticmethod(conv_nd_transpose)
[docs] class FFTConv1DTranspose(ConvNDTranspose): """1D FFT Convolution transpose layer. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. out_features: Number of output features maps, for 1D convolution this is the length of the output, for 2D convolution this is the number of output channels, for 3D convolution this is the number of output channels. kernel_size: Size of the convolutional kernel. accepts: - Single integer for same kernel size in all dimensions. - Sequence of integers for different kernel sizes in each dimension. strides: Stride of the convolution. accepts: - Single integer for same stride in all dimensions. - Sequence of integers for different strides in each dimension. key: key to use for initializing the weights. padding: Padding of the input before convolution. accepts: - Single integer for same padding in all dimensions. - Sequence of integers for different padding in each dimension. - Sequnece of a tuple of two integers for before and after padding in each dimension. - ``same``/``SAME`` for padding such that the output has the same shape as the input. - ``valid``/``VALID`` for no padding. out_padding: Padding of the output after convolution. accepts: - Single integer for same padding in all dimensions. dilation: Dilation of the convolutional kernel accepts: - Single integer for same dilation in all dimensions. - Sequence of integers for different dilation in each dimension. weight_init: function to use for initializing the weights. defaults to ``glorot uniform``. bias_init: function to use for initializing the bias. defaults to ``zeros``. set to ``None`` to not use a bias. groups: number of groups to use for grouped convolution. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax >>> import jax.random as jr >>> key = jr.key(0) >>> layer = sk.nn.FFTConv1DTranspose(1, 2, 3, key=key) >>> # single sample >>> input = jnp.ones((1, 5)) >>> print(layer(input).shape) (2, 5) >>> # batch of samples >>> input = jnp.ones((2, 1, 5)) >>> print(jax.vmap(layer)(input).shape) (2, 2, 5) Note: :class:`.FFTConv1DTranspose` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.FFTConv1DTranspose(None, 12, 3, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 References: - https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv.html """ spatial_ndim: int = 1 conv_op = staticmethod(fft_conv_nd_transpose)
[docs] class FFTConv2DTranspose(ConvNDTranspose): """2D FFT Convolution transpose layer. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. out_features: Number of output features maps, for 1D convolution this is the length of the output, for 2D convolution this is the number of output channels, for 3D convolution this is the number of output channels. kernel_size: Size of the convolutional kernel. accepts: - Single integer for same kernel size in all dimensions. - Sequence of integers for different kernel sizes in each dimension. strides: Stride of the convolution. accepts: - Single integer for same stride in all dimensions. - Sequence of integers for different strides in each dimension. key: key to use for initializing the weights. padding: Padding of the input before convolution. accepts: - Single integer for same padding in all dimensions. - Sequence of integers for different padding in each dimension. - Sequnece of a tuple of two integers for before and after padding in each dimension. - ``same``/``SAME`` for padding such that the output has the same shape as the input. - ``valid``/``VALID`` for no padding. out_padding: Padding of the output after convolution. accepts: - Single integer for same padding in all dimensions. dilation: Dilation of the convolutional kernel accepts: - Single integer for same dilation in all dimensions. - Sequence of integers for different dilation in each dimension. weight_init: function to use for initializing the weights. defaults to ``glorot uniform``. bias_init: function to use for initializing the bias. defaults to ``zeros``. set to ``None`` to not use a bias. groups: number of groups to use for grouped convolution. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax >>> import jax.random as jr >>> key = jr.key(0) >>> layer = sk.nn.FFTConv2DTranspose(1, 2, 3, key=key) >>> # single sample >>> input = jnp.ones((1, 5, 5)) >>> print(layer(input).shape) (2, 5, 5) >>> # batch of samples >>> input = jnp.ones((2, 1, 5, 5)) >>> print(jax.vmap(layer)(input).shape) (2, 2, 5, 5) Note: :class:`.FFTConv2DTranspose` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.FFTConv2DTranspose(None, 12, 3, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 References: - https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv.html """ spatial_ndim: int = 2 conv_op = staticmethod(fft_conv_nd_transpose)
[docs] class FFTConv3DTranspose(ConvNDTranspose): """3D FFT Convolution transpose layer. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. out_features: Number of output features maps, for 1D convolution this is the length of the output, for 2D convolution this is the number of output channels, for 3D convolution this is the number of output channels. kernel_size: Size of the convolutional kernel. accepts: - Single integer for same kernel size in all dimensions. - Sequence of integers for different kernel sizes in each dimension. strides: Stride of the convolution. accepts: - Single integer for same stride in all dimensions. - Sequence of integers for different strides in each dimension. key: key to use for initializing the weights. padding: Padding of the input before convolution. accepts: - Single integer for same padding in all dimensions. - Sequence of integers for different padding in each dimension. - Sequnece of a tuple of two integers for before and after padding in each dimension. - ``same``/``SAME`` for padding such that the output has the same shape as the input. - ``valid``/``VALID`` for no padding. out_padding: Padding of the output after convolution. accepts: - Single integer for same padding in all dimensions. dilation: Dilation of the convolutional kernel accepts: - Single integer for same dilation in all dimensions. - Sequence of integers for different dilation in each dimension. weight_init: function to use for initializing the weights. defaults to ``glorot uniform``. bias_init: function to use for initializing the bias. defaults to ``zeros``. set to ``None`` to not use a bias. groups: number of groups to use for grouped convolution. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax >>> import jax.random as jr >>> key = jr.key(0) >>> layer = sk.nn.FFTConv3DTranspose(1, 2, 3, key=key) >>> # single sample >>> input = jnp.ones((1, 5, 5, 5)) >>> print(layer(input).shape) (2, 5, 5, 5) >>> # batch of samples >>> input = jnp.ones((2, 1, 5, 5, 5)) >>> print(jax.vmap(layer)(input).shape) (2, 2, 5, 5, 5) Note: :class:`.FFTConv3DTranspose` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10, 10, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.FFTConv3DTranspose(None, 12, 3, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 References: - https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv.html """ spatial_ndim: int = 3 conv_op = staticmethod(fft_conv_nd_transpose)
class DepthwiseConvND(TreeClass): @ft.partial(maybe_lazy_init, is_lazy=is_lazy_init) def __init__( self, in_features: int | None, kernel_size: KernelSizeType, *, key: jax.Array, depth_multiplier: int = 1, strides: int = 1, padding: PaddingType = "same", weight_init: InitType = "glorot_uniform", bias_init: InitType = "zeros", dtype: DType = jnp.float32, ): self.in_features = validate_pos_int(in_features) self.kernel_size = canonicalize(kernel_size, self.spatial_ndim, "kernel_size") self.depth_multiplier = validate_pos_int(depth_multiplier) self.strides = canonicalize(strides, self.spatial_ndim, "strides") self.padding = padding # delayed canonicalization self.weight_init = weight_init self.bias_init = bias_init weight_shape = (depth_multiplier * in_features, 1, *self.kernel_size) # OIH... self.weight = resolve_init(self.weight_init)(key, weight_shape, dtype) bias_shape = (depth_multiplier * in_features, *(1,) * self.spatial_ndim) self.bias = resolve_init(self.bias_init)(key, bias_shape, dtype) @ft.partial(maybe_lazy_call, is_lazy=is_lazy_call, updates=updates) @ft.partial(validate_spatial_ndim, argnum=0) @ft.partial(validate_in_features_shape, axis=0) def __call__(self, input: jax.Array, mask: Weight | None = None) -> jax.Array: """Apply the layer. Args: input: input array. shape is ``(in_features, spatial size)``. spatial size is length for 1D convolution, height, width for 2D convolution and height, width, depth for 3D convolution. mask: a binary mask multiplied with the convolutional kernel. shape is ``(depth_multiplier * in_features, 1, *self.kernel_size)``. set to ``None`` to not use a mask. """ padding = delayed_canonicalize_padding( in_dim=input.shape[1:], padding=self.padding, kernel_size=self.kernel_size, strides=self.strides, ) return self.conv_op( input=input, weight=self.weight, bias=self.bias, strides=self.strides, padding=padding, mask=mask, ) spatial_ndim = property(abc.abstractmethod(lambda _: ...)) conv_op = staticmethod(abc.abstractmethod(lambda _: ...))
[docs] class DepthwiseConv1D(DepthwiseConvND): """1D Depthwise convolution layer. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. kernel_size: Size of the convolutional kernel. accepts: - Single integer for same kernel size in all dimensions. - Sequence of integers for different kernel sizes in each dimension. depth_multiplier: multiplier for the number of output channels. for example if the input has 32 channels and the depth multiplier is 2 then the output will have 64 channels. strides: Stride of the convolution. accepts: - Single integer for same stride in all dimensions. - Sequence of integers for different strides in each dimension. key: key to use for initializing the weights. padding: Padding of the input before convolution. accepts: - Single integer for same padding in all dimensions. - Sequence of integers for different padding in each dimension. - Sequnece of a tuple of two integers for before and after padding in each dimension. - ``same``/``SAME`` for padding such that the output has the same shape as the input. - ``valid``/``VALID`` for no padding. weight_init: Function to use for initializing the weights. defaults to ``glorot uniform``. bias_init: Function to use for initializing the bias. defaults to ``zeros``. set to ``None`` to not use a bias. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax.random as jr >>> key = jr.key(0) >>> l1 = sk.nn.DepthwiseConv1D(3, 3, depth_multiplier=2, strides=2, key=key) >>> l1(jnp.ones((3, 32))).shape (6, 16) Note: :class:`.DepthwiseConv1D` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.DepthwiseConv1D(None, 3, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 Reference: - https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv.html - https://github.com/google/flax/blob/main/flax/linen/linear.py """ spatial_ndim: int = 1 conv_op = staticmethod(depthwise_conv_nd)
[docs] class DepthwiseConv2D(DepthwiseConvND): """2D Depthwise convolution layer. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. kernel_size: Size of the convolutional kernel. accepts: - Single integer for same kernel size in all dimensions. - Sequence of integers for different kernel sizes in each dimension. depth_multiplier: multiplier for the number of output channels. for example if the input has 32 channels and the depth multiplier is 2 then the output will have 64 channels. strides: Stride of the convolution. accepts: - Single integer for same stride in all dimensions. - Sequence of integers for different strides in each dimension. key: key to use for initializing the weights. padding: Padding of the input before convolution. accepts: - Single integer for same padding in all dimensions. - Sequence of integers for different padding in each dimension. - Sequnece of a tuple of two integers for before and after padding in each dimension. - ``same``/``SAME`` for padding such that the output has the same shape as the input. - ``valid``/``VALID`` for no padding. weight_init: Function to use for initializing the weights. defaults to ``glorot uniform``. bias_init: Function to use for initializing the bias. defaults to ``zeros``. set to ``None`` to not use a bias. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax.random as jr >>> key = jr.key(0) >>> l1 = sk.nn.DepthwiseConv2D(3, 3, depth_multiplier=2, strides=2, key=key) >>> l1(jnp.ones((3, 32, 32))).shape (6, 16, 16) Note: :class:`.DepthwiseConv2D` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.DepthwiseConv2D(None, 3, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 Reference: - https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv.html - https://github.com/google/flax/blob/main/flax/linen/linear.py """ spatial_ndim: int = 2 conv_op = staticmethod(depthwise_conv_nd)
[docs] class DepthwiseConv3D(DepthwiseConvND): """3D Depthwise convolution layer. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. kernel_size: Size of the convolutional kernel. accepts: - Single integer for same kernel size in all dimensions. - Sequence of integers for different kernel sizes in each dimension. depth_multiplier: multiplier for the number of output channels. for example if the input has 32 channels and the depth multiplier is 2 then the output will have 64 channels. strides: stride of the convolution. accepts: - Single integer for same stride in all dimensions. - Sequence of integers for different strides in each dimension. padding: adding of the input before convolution. accepts: - Single integer for same padding in all dimensions. - Sequence of integers for different padding in each dimension. - Sequnece of a tuple of two integers for before and after padding in each dimension. - ``same``/``SAME`` for padding such that the output has the same shape as the input. - ``valid``/``VALID`` for no padding. weight_init: Function to use for initializing the weights. defaults to ``glorot uniform``. bias_init: Function to use for initializing the bias. defaults to ``zeros``. set to ``None`` to not use a bias. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax.random as jr >>> key = jr.key(0) >>> l1 = sk.nn.DepthwiseConv3D(3, 3, depth_multiplier=2, strides=2, key=key) >>> l1(jnp.ones((3, 32, 32, 32))).shape (6, 16, 16, 16) Note: :class:`.DepthwiseConv3D` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10, 10, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.DepthwiseConv3D(None, 3, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 Reference: - https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv.html - https://github.com/google/flax/blob/main/flax/linen/linear.py """ spatial_ndim: int = 3 conv_op = staticmethod(depthwise_conv_nd)
[docs] class DepthwiseFFTConv1D(DepthwiseConvND): """1D Depthwise FFT convolution layer. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. kernel_size: Size of the convolutional kernel. accepts: - Single integer for same kernel size in all dimensions. - Sequence of integers for different kernel sizes in each dimension. depth_multiplier: multiplier for the number of output channels. for example if the input has 32 channels and the depth multiplier is 2 then the output will have 64 channels. strides: Stride of the convolution. accepts: - Single integer for same stride in all dimensions. - Sequence of integers for different strides in each dimension. key: key to use for initializing the weights. padding: Padding of the input before convolution. accepts: - Single integer for same padding in all dimensions. - Sequence of integers for different padding in each dimension. - Sequnece of a tuple of two integers for before and after padding in each dimension. - ``same``/``SAME`` for padding such that the output has the same shape as the input. - ``valid``/``VALID`` for no padding. weight_init: function to use for initializing the weights. defaults to ``glorot uniform``. bias_init: function to use for initializing the bias. defaults to ``zeros``. set to ``None`` to not use a bias. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax.random as jr >>> key = jr.key(0) >>> l1 = sk.nn.DepthwiseFFTConv1D(3, 3, depth_multiplier=2, strides=2, key=key) >>> l1(jnp.ones((3, 32))).shape (6, 16) Note: :class:`.DepthwiseFFTConv1D` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.DepthwiseFFTConv1D(None, 3, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 References: - https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv.html - https://github.com/google/flax/blob/main/flax/linen/linear.py """ spatial_ndim: int = 1 conv_op = staticmethod(depthwise_fft_conv_nd)
[docs] class DepthwiseFFTConv2D(DepthwiseConvND): """2D Depthwise convolution layer. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. kernel_size: Size of the convolutional kernel. accepts: - single integer for same kernel size in all dimnsions. - Sequence of integers for different kernel sizes in each dimension. depth_multiplier: multiplier for the number of output channels. for example if the input has 32 channels and the depth multiplier is 2 then the output will have 64 channels. strides: Stride of the convolution. accepts: - Single integer for same stride in all dimensions. - Sequence of integers for different strides in each dimension. key: key to use for initializing the weights. padding: Padding of the input before convolution. accepts: - Single integer for same padding in all dimensions. - Sequence of integers for different padding in each dimension. - Sequnece of a tuple of two integers for before and after padding in each dimension. - ``same``/``SAME`` for padding such that the output has the same shape as the input. - ``valid``/``VALID`` for no padding. weight_init: function to use for initializing the weights. defaults to ``glorot uniform``. bias_init: function to use for initializing the bias. defaults to ``zeros``. set to ``None`` to not use a bias. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax.random as jr >>> key = jr.key(0) >>> l1 = sk.nn.DepthwiseFFTConv2D(3, 3, depth_multiplier=2, strides=2, key=key) >>> l1(jnp.ones((3, 32, 32))).shape (6, 16, 16) Note: :class:`.DepthwiseFFTConv2D` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.DepthwiseFFTConv2D(None, 3, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 References: - https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv.html - https://github.com/google/flax/blob/main/flax/linen/linear.py """ spatial_ndim: int = 2 conv_op = staticmethod(depthwise_fft_conv_nd)
[docs] class DepthwiseFFTConv3D(DepthwiseConvND): """3D Depthwise FFT convolution layer. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. kernel_size: Size of the convolutional kernel. accepts: - single integer for same kernel size in all dimnsions. - Sequence of integers for different kernel sizes in each dimension. depth_multiplier: multiplier for the number of output channels. for example if the input has 32 channels and the depth multiplier is 2 then the output will have 64 channels. strides: Stride of the convolution. accepts: - Single integer for same stride in all dimensions. - Sequence of integers for different strides in each dimension. key: key to use for initializing the weights. padding: Padding of the input before convolution. accepts: - Single integer for same padding in all dimensions. - Sequence of integers for different padding in each dimension. - Sequnece of a tuple of two integers for before and after padding in each dimension. - ``same``/``SAME`` for padding such that the output has the same shape as the input. - ``valid``/``VALID`` for no padding. weight_init: function to use for initializing the weights. defaults to ``glorot uniform``. bias_init: function to use for initializing the bias. defaults to ``zeros``. set to ``None`` to not use a bias. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax.random as jr >>> key = jr.key(0) >>> l1 = sk.nn.DepthwiseFFTConv3D(3, 3, depth_multiplier=2, strides=2, key=key) >>> l1(jnp.ones((3, 32, 32, 32))).shape (6, 16, 16, 16) Note: :class:`.DepthwiseFFTConv3D` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10, 10, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.DepthwiseFFTConv3D(None, 3, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 Reference: - https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv.html - https://github.com/google/flax/blob/main/flax/linen/linear.py """ spatial_ndim: int = 3 conv_op = staticmethod(depthwise_fft_conv_nd)
class SeparableConvND(TreeClass): @ft.partial(maybe_lazy_init, is_lazy=is_lazy_init) def __init__( self, in_features: int | None, out_features: int, kernel_size: KernelSizeType, *, key: jax.Array, depth_multiplier: int = 1, strides: StridesType = 1, padding: PaddingType = "same", depthwise_weight_init: InitType = "glorot_uniform", pointwise_weight_init: InitType = "glorot_uniform", pointwise_bias_init: InitType = "zeros", dtype: DType = jnp.float32, ): self.in_features = validate_pos_int(in_features) self.kernel_size = canonicalize(kernel_size, self.spatial_ndim, "kernel_size") self.depth_multiplier = validate_pos_int(depth_multiplier) self.strides = canonicalize(strides, self.spatial_ndim, "strides") self.padding = padding # delayed canonicalization self.depthwise_weight_init = depthwise_weight_init self.pointwise_weight_init = pointwise_weight_init self.pointwise_bias_init = pointwise_bias_init # depthwise initialization weight_shape = (depth_multiplier * in_features, 1, *self.kernel_size) args = (key, weight_shape, dtype) self.depthwise_weight = resolve_init(self.depthwise_weight_init)(*args) # pointwise initialization kernel_size = canonicalize(1, self.spatial_ndim) weight_shape = (out_features, depth_multiplier * in_features, *kernel_size) args = (key, weight_shape, dtype) self.pointwise_weight = resolve_init(self.pointwise_bias_init)(*args) bias_shape = (out_features, *(1,) * self.spatial_ndim) args = (key, bias_shape, dtype) self.pointwise_bias = resolve_init(self.pointwise_bias_init)(*args) @ft.partial(maybe_lazy_call, is_lazy=is_lazy_call, updates=updates) @ft.partial(validate_spatial_ndim, argnum=0) @ft.partial(validate_in_features_shape, axis=0) def __call__( self, input: jax.Array, depthwise_mask: Weight | None = None, pointwise_mask: Weight | None = None, ) -> jax.Array: """Apply the layer. Args: input: input array. shape is ``(in_features, spatial size)``. spatial size is length for 1D convolution, height, width for 2D convolution and height, width, depth for 3D convolution. depthwise_mask: a binary mask multiplied with the depthwise convolutional kernel. shape is ``(depth_multiplier * in_features, 1, *self.kernel_size)``. set to ``None`` to not use a mask. pointwise_mask: a binary mask multiplied with the pointwise convolutional kernel. shape is ``(out_features, depth_multiplier * in_features, 1, *self.kernel_size)``. set to ``None`` to not use a mask. """ depthwise_padding = delayed_canonicalize_padding( in_dim=input.shape[1:], padding=self.padding, kernel_size=self.kernel_size, strides=self.strides, ) pointwise_padding = delayed_canonicalize_padding( in_dim=input.shape[1:], padding=self.padding, kernel_size=canonicalize(1, self.spatial_ndim), strides=self.strides, ) return self.conv_op( input=input, depthwise_weight=self.depthwise_weight, pointwise_weight=self.pointwise_weight, pointwise_bias=self.pointwise_bias, strides=self.strides, depthwise_padding=depthwise_padding, pointwise_padding=pointwise_padding, depthwise_mask=depthwise_mask, pointwise_mask=pointwise_mask, ) spatial_ndim = property(abc.abstractmethod(lambda _: ...)) conv_op = staticmethod(abc.abstractmethod(lambda _: ...))
[docs] class SeparableConv1D(SeparableConvND): """1D Separable convolution layer. Separable convolution is a depthwise convolution followed by a pointwise convolution. The objective is to reduce the number of parameters in the convolutional layer. For example, for I input features and O output features, and a kernel size = Ki, then standard convolution has I * O * K0 ... * Kn + O parameters, whereas separable convolution has I * K0 ... * Kn + I * O + O parameters. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. out_features: Number of output features maps, for 1D convolution this is the length of the output, for 2D convolution this is the number of output channels, for 3D convolution this is the number of output channels. kernel_size: Size of the convolutional kernel. accepts: - Single integer for same kernel size in all dimensions. - Sequence of integers for different kernel sizes in each dimension. depth_multiplier: multiplier for the number of output channels. for example if the input has 32 channels and the depth multiplier is 2 then the output will have 64 channels. strides: Stride of the convolution. accepts: - Single integer for same stride in all dimensions. - Sequence of integers for different strides in each dimension. key: key to use for initializing the weights. padding: Padding of the input before convolution. accepts: - Single integer for same padding in all dimensions. - Sequence of integers for different padding in each dimension. - Sequnece of a tuple of two integers for before and after padding in each dimension. - ``same``/``SAME`` for padding such that the output has the same shape as the input. - ``valid``/``VALID`` for no padding. weight_init: Function to use for initializing the weights. defaults to ``glorot uniform``. bias_init: Function to use for initializing the bias. defaults to ``zeros``. set to ``None`` to not use a bias. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax.random as jr >>> key = jr.key(0) >>> l1 = sk.nn.SeparableConv1D(3, 3, 3, depth_multiplier=2, key=key) >>> l1(jnp.ones((3, 32))).shape (3, 32) Note: :class:`.SeparableConv1D` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.SeparableConv1D(None, 2, 3, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 Reference: - https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv.html - https://github.com/google/flax/blob/main/flax/linen/linear.py """ spatial_ndim: int = 1 conv_op = staticmethod(separable_conv_nd)
[docs] class SeparableConv2D(SeparableConvND): """2D Separable convolution layer. Separable convolution is a depthwise convolution followed by a pointwise convolution. The objective is to reduce the number of parameters in the convolutional layer. For example, for I input features and O output features, and a kernel size = Ki, then standard convolution has I * O * K0 ... * Kn + O parameters, whereas separable convolution has I * K0 ... * Kn + I * O + O parameters. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. out_features: Number of output features maps, for 1D convolution this is the length of the output, for 2D convolution this is the number of output channels, for 3D convolution this is the number of output channels. kernel_size: Size of the convolutional kernel. accepts: - Single integer for same kernel size in all dimensions. - Sequence of integers for different kernel sizes in each dimension. depth_multiplier: multiplier for the number of output channels. for example if the input has 32 channels and the depth multiplier is 2 then the output will have 64 channels. strides: Stride of the convolution. accepts: - Single integer for same stride in all dimensions. - Sequence of integers for different strides in each dimension. key: key to use for initializing the weights. padding: Padding of the input before convolution. accepts: - Single integer for same padding in all dimensions. - Sequence of integers for different padding in each dimension. - Sequnece of a tuple of two integers for before and after padding in each dimension. - ``same``/``SAME`` for padding such that the output has the same shape as the input. - ``valid``/``VALID`` for no padding. weight_init: Function to use for initializing the weights. defaults to ``glorot uniform``. bias_init: Function to use for initializing the bias. defaults to ``zeros``. set to ``None`` to not use a bias. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax.random as jr >>> key = jr.key(0) >>> l1 = sk.nn.SeparableConv2D(3, 3, 3, depth_multiplier=2, key=key) >>> l1(jnp.ones((3, 32, 32))).shape (3, 32, 32) Note: :class:`.SeparableConv2D` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.SeparableConv2D(None, 2, 3, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 Reference: - https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv.html - https://github.com/google/flax/blob/main/flax/linen/linear.py """ spatial_ndim: int = 2 conv_op = staticmethod(separable_conv_nd)
[docs] class SeparableConv3D(SeparableConvND): """3D Separable convolution layer. Separable convolution is a depthwise convolution followed by a pointwise convolution. The objective is to reduce the number of parameters in the convolutional layer. For example, for I input features and O output features, and a kernel size = Ki, then standard convolution has I * O * K0 ... * Kn + O parameters, whereas separable convolution has I * K0 ... * Kn + I * O + O parameters. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. out_features: Number of output features maps, for 1D convolution this is the length of the output, for 2D convolution this is the number of output channels, for 3D convolution this is the number of output channels. kernel_size: Size of the convolutional kernel. accepts: - Single integer for same kernel size in all dimensions. - Sequence of integers for different kernel sizes in each dimension. depth_multiplier: multiplier for the number of output channels. for example if the input has 32 channels and the depth multiplier is 2 then the output will have 64 channels. strides: Stride of the convolution. accepts: - Single integer for same stride in all dimensions. - Sequence of integers for different strides in each dimension. key: key to use for initializing the weights. padding: Padding of the input before convolution. accepts: - Single integer for same padding in all dimensions. - Sequence of integers for different padding in each dimension. - Sequnece of a tuple of two integers for before and after padding in each dimension. - ``same``/``SAME`` for padding such that the output has the same shape as the input. - ``valid``/``VALID`` for no padding. weight_init: Function to use for initializing the weights. defaults to ``glorot uniform``. bias_init: Function to use for initializing the bias. defaults to ``zeros``. set to ``None`` to not use a bias. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax.random as jr >>> key = jr.key(0) >>> l1 = sk.nn.SeparableConv3D(3, 3, 3, depth_multiplier=2, key=key) >>> l1(jnp.ones((3, 32, 32, 32))).shape (3, 32, 32, 32) Note: :class:`.SeparableConv3D` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10, 10, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.SeparableConv3D(None, 2, 3, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 Reference: - https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv.html - https://github.com/google/flax/blob/main/flax/linen/linear.py """ spatial_ndim: int = 3 conv_op = staticmethod(separable_conv_nd)
[docs] class SeparableFFTConv1D(SeparableConvND): """1D Separable FFT convolution layer. Separable convolution is a depthwise convolution followed by a pointwise convolution. The objective is to reduce the number of parameters in the convolutional layer. For example, for I input features and O output features, and a kernel size = Ki, then standard convolution has I * O * K0 ... * Kn + O parameters, whereas separable convolution has I * K0 ... * Kn + I * O + O parameters. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. out_features: Number of output features maps, for 1D convolution this is the length of the output, for 2D convolution this is the number of output channels, for 3D convolution this is the number of output channels. kernel_size: Size of the convolutional kernel. accepts: - single integer for same kernel size in all dimnsions. - Sequence of integers for different kernel sizes in each dimension. depth_multiplier: multiplier for the number of output channels. for example if the input has 32 channels and the depth multiplier is 2 then the output will have 64 channels. strides: Stride of the convolution. accepts: - Single integer for same stride in all dimensions. - Sequence of integers for different strides in each dimension. key: key to use for initializing the weights. padding: Padding of the input before convolution. accepts: - Single integer for same padding in all dimensions. - Sequence of integers for different padding in each dimension. - Sequnece of a tuple of two integers for before and after padding in each dimension. - ``same``/``SAME`` for padding such that the output has the same shape as the input. - ``valid``/``VALID`` for no padding. weight_init: function to use for initializing the weights. defaults to ``glorot uniform``. bias_init: function to use for initializing the bias. defaults to ``zeros``. set to ``None`` to not use a bias. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax.random as jr >>> key = jr.key(0) >>> l1 = sk.nn.SeparableFFTConv1D(3, 3, 3, depth_multiplier=2, key=key) >>> l1(jnp.ones((3, 32))).shape (3, 32) Note: :class:`.SeparableFFTConv1D` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.SeparableFFTConv1D(None, 2, 3, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 References: - https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv.html - https://github.com/google/flax/blob/main/flax/linen/linear.py """ spatial_ndim: int = 1 conv_op = staticmethod(separable_fft_conv_nd)
[docs] class SeparableFFTConv2D(SeparableConvND): """2D Separable FFT convolution layer. Separable convolution is a depthwise convolution followed by a pointwise convolution. The objective is to reduce the number of parameters in the convolutional layer. For example, for I input features and O output features, and a kernel size = Ki, then standard convolution has I * O * K0 ... * Kn + O parameters, whereas separable convolution has I * K0 ... * Kn + I * O + O parameters. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. out_features: Number of output features maps, for 1D convolution this is the length of the output, for 2D convolution this is the number of output channels, for 3D convolution this is the number of output channels. kernel_size: Size of the convolutional kernel. accepts: - single integer for same kernel size in all dimnsions. - Sequence of integers for different kernel sizes in each dimension. depth_multiplier: multiplier for the number of output channels. for example if the input has 32 channels and the depth multiplier is 2 then the output will have 64 channels. strides: Stride of the convolution. accepts: - Single integer for same stride in all dimensions. - Sequence of integers for different strides in each dimension. key: key to use for initializing the weights. padding: Padding of the input before convolution. accepts: - Single integer for same padding in all dimensions. - Sequence of integers for different padding in each dimension. - Sequnece of a tuple of two integers for before and after padding in each dimension. - ``same``/``SAME`` for padding such that the output has the same shape as the input. - ``valid``/``VALID`` for no padding. weight_init: function to use for initializing the weights. defaults to ``glorot uniform``. bias_init: function to use for initializing the bias. defaults to ``zeros``. set to ``None`` to not use a bias. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax.random as jr >>> key = jr.key(0) >>> l1 = sk.nn.SeparableFFTConv2D(3, 3, 3, depth_multiplier=2, key=key) >>> l1(jnp.ones((3, 32, 32))).shape (3, 32, 32) Note: :class:`.SeparableFFTConv2D` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.SeparableFFTConv2D(None, 2, 3, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 References: - https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv.html - https://github.com/google/flax/blob/main/flax/linen/linear.py """ spatial_ndim: int = 2 conv_op = staticmethod(separable_fft_conv_nd)
[docs] class SeparableFFTConv3D(SeparableConvND): """3D Separable FFT convolution layer. Separable convolution is a depthwise convolution followed by a pointwise convolution. The objective is to reduce the number of parameters in the convolutional layer. For example, for I input features and O output features, and a kernel size = Ki, then standard convolution has I * O * K0 ... * Kn + O parameters, whereas separable convolution has I * K0 ... * Kn + I * O + O parameters. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. out_features: Number of output features maps, for 1D convolution this is the length of the output, for 2D convolution this is the number of output channels, for 3D convolution this is the number of output channels. kernel_size: Size of the convolutional kernel. accepts: - single integer for same kernel size in all dimnsions. - Sequence of integers for different kernel sizes in each dimension. depth_multiplier: multiplier for the number of output channels. for example if the input has 32 channels and the depth multiplier is 2 then the output will have 64 channels. strides: Stride of the convolution. accepts: - Single integer for same stride in all dimensions. - Sequence of integers for different strides in each dimension. key: key to use for initializing the weights. padding: Padding of the input before convolution. accepts: - Single integer for same padding in all dimensions. - Sequence of integers for different padding in each dimension. - Sequnece of a tuple of two integers for before and after padding in each dimension. - ``same``/``SAME`` for padding such that the output has the same shape as the input. - ``valid``/``VALID`` for no padding. weight_init: function to use for initializing the weights. defaults to ``glorot uniform``. bias_init: function to use for initializing the bias. defaults to ``zeros``. set to ``None`` to not use a bias. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax.random as jr >>> key = jr.key(0) >>> l1 = sk.nn.SeparableFFTConv3D(3, 3, 3, depth_multiplier=2, key=key) >>> l1(jnp.ones((3, 32, 32, 32))).shape (3, 32, 32, 32) Note: :class:`.SeparableFFTConv3D` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10, 10, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.SeparableFFTConv3D(None, 2, 3, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 References: - https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv.html - https://github.com/google/flax/blob/main/flax/linen/linear.py """ spatial_ndim: int = 3 conv_op = staticmethod(separable_fft_conv_nd)
class SpectralConvND(TreeClass): @ft.partial(maybe_lazy_init, is_lazy=is_lazy_init) def __init__( self, in_features: int, out_features: int, *, modes: int | tuple[int, ...], key: jax.Array, dtype: DType = jnp.float32, ): self.in_features = validate_pos_int(in_features) self.out_features = validate_pos_int(out_features) self.modes: tuple[int, ...] = canonicalize(modes, self.spatial_ndim, "modes") weight_shape = (1, out_features, in_features, *self.modes) scale = 1 / (in_features * out_features) k1, k2 = jr.split(key) self.weight_r = scale * jr.normal(k1, weight_shape).astype(dtype) self.weight_i = scale * jr.normal(k2, weight_shape).astype(dtype) @ft.partial(maybe_lazy_call, is_lazy=is_lazy_call, updates=updates) @ft.partial(validate_spatial_ndim, argnum=0) @ft.partial(validate_in_features_shape, axis=0) def __call__(self, input: jax.Array) -> jax.Array: return self.conv_op( input=input, weight=jnp.concatenate([self.weight_r, self.weight_i], axis=0), modes=self.modes, ) spatial_ndim = property(abc.abstractmethod(lambda _: ...)) conv_op = staticmethod(abc.abstractmethod(lambda _: ...))
[docs] class SpectralConv1D(SpectralConvND): """1D Spectral convolutional layer. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. out_features: Number of output features maps, for 1D convolution this is the length of the output, for 2D convolution this is the number of output channels, for 3D convolution this is the number of output channels. modes: Number of modes to use in the spectral convolution. key: key to use for initializing the weights. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax.random as jr >>> key = jr.key(0) >>> l1 = sk.nn.SpectralConv1D(3, 3, modes=1, key=key) >>> l1(jnp.ones((3, 32))).shape (3, 32) Note: :class:`.SpectralConv1D` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.SpectralConv1D(None, 2, modes=3, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 Reference: - https://zongyi-li.github.io/blog/2020/fourier-pde/ - https://arxiv.org/abs/2010.08895 """ spatial_ndim: int = 1 conv_op = staticmethod(spectral_conv_nd)
[docs] class SpectralConv2D(SpectralConvND): """2D Spectral convolutional layer. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. out_features: Number of output features maps, for 1D convolution this is the length of the output, for 2D convolution this is the number of output channels, for 3D convolution this is the number of output channels. modes: Number of modes to use in the spectral convolution. accepts two integer tuple for different modes in each dimension. or a single integer for the same number of modes in each dimension. key: key to use for initializing the weights. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax.random as jr >>> key = jr.key(0) >>> l1 = sk.nn.SpectralConv2D(3, 3, modes=(1, 2), key=key) >>> l1(jnp.ones((3, 32 ,32))).shape (3, 32, 32) Note: :class:`.SpectralConv2D` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.SpectralConv2D(None, 2, modes=3, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 Reference: - https://zongyi-li.github.io/blog/2020/fourier-pde/ - https://arxiv.org/abs/2010.08895 """ spatial_ndim: int = 2 conv_op = staticmethod(spectral_conv_nd)
[docs] class SpectralConv3D(SpectralConvND): """3D Spectral convolutional layer. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. out_features: Number of output features maps, for 1D convolution this is the length of the output, for 2D convolution this is the number of output channels, for 3D convolution this is the number of output channels. modes: Number of modes to use in the spectral convolution. accepts three integer tuple for different modes in each dimension. or a single integer for the same number of modes in each dimension. key: key to use for initializing the weights. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax.random as jr >>> key = jr.key(0) >>> l1 = sk.nn.SpectralConv3D(3, 3, modes=(1, 2, 2), key=key) >>> l1(jnp.ones((3, 32, 32, 32))).shape (3, 32, 32, 32) Note: :class:`.SpectralConv3D` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10, 10, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.SpectralConv3D(None, 2, modes=3, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 Reference: - https://zongyi-li.github.io/blog/2020/fourier-pde/ - https://arxiv.org/abs/2010.08895 """ spatial_ndim: int = 3 conv_op = staticmethod(spectral_conv_nd)
def is_lazy_call(instance, *_, **__) -> bool: in_features = getattr(instance, "in_features", False) in_size = getattr(instance, "in_size", False) # either in_features or in_size must is None then # mark the layer as lazy at call time return in_features is None or in_size is None def is_lazy_init(_, in_features, *__, **k) -> bool: # either in_features or in_size must is None then mark the layer as lazy # at initialization time return in_features is None or k.get("in_size", False) is None def infer_in_features(instance, x, *__, **___) -> int: # in case `in_features` is None, infer it from the input shape # otherwise return the `in_features` attribute if getattr(instance, "in_features", False) is None: return x.shape[0] return instance.in_features def infer_in_size(instance, x, *__, **___) -> tuple[int, ...]: # in case `in_size` is None, infer it from the input shape # otherwise return the `in_size` attribute if getattr(instance, "in_size", False) is None: return x.shape[1:] return instance.in_size updates = dict(in_features=infer_in_features, in_size=infer_in_size) class ConvNDLocal(TreeClass): @ft.partial(maybe_lazy_init, is_lazy=is_lazy_init) def __init__( self, in_features: int | None, out_features: int, kernel_size: KernelSizeType, *, key: jax.Array, in_size: Sequence[int] | None, strides: StridesType = 1, padding: PaddingType = "same", dilation: DilationType = 1, weight_init: InitType = "glorot_uniform", bias_init: InitType = "zeros", dtype: DType = jnp.float32, ): self.in_features = validate_pos_int(in_features) self.out_features = validate_pos_int(out_features) self.kernel_size = canonicalize(kernel_size, self.spatial_ndim, "kernel_size") self.in_size = canonicalize(in_size, self.spatial_ndim, name="in_size") self.strides = canonicalize(strides, self.spatial_ndim, "strides") self.padding = delayed_canonicalize_padding( self.in_size, padding, self.kernel_size, self.strides, ) self.dilation = canonicalize(dilation, self.spatial_ndim, "dilation") self.weight_init = weight_init self.bias_init = bias_init out_size = calculate_convolution_output_shape( shape=self.in_size, kernel_size=self.kernel_size, padding=self.padding, strides=self.strides, ) # OIH... weight_shape = ( self.out_features, self.in_features * ft.reduce(op.mul, self.kernel_size), *out_size, ) self.weight = resolve_init(self.weight_init)(key, weight_shape, dtype) bias_shape = (self.out_features, *out_size) self.bias = resolve_init(self.bias_init)(key, bias_shape, dtype) @ft.partial(maybe_lazy_call, is_lazy=is_lazy_call, updates=updates) @ft.partial(validate_spatial_ndim, argnum=0) @ft.partial(validate_in_features_shape, axis=0) def __call__(self, input: jax.Array, mask: Weight | None = None) -> jax.Array: """Apply the layer. Args: input: input array. shape is ``(in_features, spatial size)``. spatial size is length for 1D convolution, height, width for 2D convolution and height, width, depth for 3D convolution. mask: mask to apply to the weights. shape is ``(out_features, in_features * prod(kernel_size), *out_size)`` use ``None`` for no mask. """ return self.conv_op( input=input, weight=self.weight, bias=self.bias, strides=self.strides, padding=self.padding, dilation=self.dilation, kernel_size=self.kernel_size, mask=mask, ) spatial_ndim = property(abc.abstractmethod(lambda _: ...)) conv_op = staticmethod(abc.abstractmethod(lambda _: ...))
[docs] class Conv1DLocal(ConvNDLocal): """1D Local convolutional layer. Local convolutional layer is a convolutional layer where the convolution kernel is applied to a local region of the input. The kernel weights are *not* shared across the spatial dimensions of the input. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. out_features: Number of output features maps, for 1D convolution this is the length of the output, for 2D convolution this is the number of output channels, for 3D convolution this is the number of output channels. kernel_size: Size of the convolutional kernel. accepts: - Single integer for same kernel size in all dimensions. - Sequence of integers for different kernel sizes in each dimension. in_size: the size of the spatial dimensions of the input. e.g excluding the first dimension. accepts a sequence of integer(s). strides: Stride of the convolution. accepts: - Single integer for same stride in all dimensions. - Sequence of integers for different strides in each dimension. key: key to use for initializing the weights. padding: Padding of the input before convolution. accepts: - Single integer for same padding in all dimensions. - Sequence of integers for different padding in each dimension. - Sequnece of a tuple of two integers for before and after padding in each dimension. - ``same``/``SAME`` for padding such that the output has the same shape as the input. - ``valid``/``VALID`` for no padding. weight_init: Function to use for initializing the weights. defaults to ``glorot uniform``. bias_init: Function to use for initializing the bias. defaults to ``zeros``. set to ``None`` to not use a bias. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax.random as jr >>> key = jr.key(0) >>> l1 = sk.nn.Conv1DLocal(3, 3, 3, in_size=(32,), key=key) >>> l1(jnp.ones((3, 32))).shape (3, 32) Note: :class:`.Conv1DLocal` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.Conv1DLocal(None, 3, 3, in_size=None, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 Reference: - https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv.html - https://github.com/google/flax/blob/main/flax/linen/linear.py """ spatial_ndim: int = 1 conv_op = staticmethod(local_conv_nd)
[docs] class Conv2DLocal(ConvNDLocal): """2D Local convolutional layer. Local convolutional layer is a convolutional layer where the convolution kernel is applied to a local region of the input. This means that the kernel weights are *not* shared across the spatial dimensions of the input. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. out_features: Number of output features maps, for 1D convolution this is the length of the output, for 2D convolution this is the number of output channels, for 3D convolution this is the number of output channels. kernel_size: Size of the convolutional kernel. accepts: - Single integer for same kernel size in all dimensions. - Sequence of integers for different kernel sizes in each dimension. in_size: the size of the spatial dimensions of the input. e.g excluding the first dimension. accepts a sequence of integer(s). strides: Stride of the convolution. accepts: - Single integer for same stride in all dimensions. - Sequence of integers for different strides in each dimension. key: key to use for initializing the weights. padding: Padding of the input before convolution. accepts: - Single integer for same padding in all dimensions. - Sequence of integers for different padding in each dimension. - Sequnece of a tuple of two integers for before and after padding in each dimension. - ``same``/``SAME`` for padding such that the output has the same shape as the input. - ``valid``/``VALID`` for no padding. weight_init: Function to use for initializing the weights. defaults to ``glorot uniform``. bias_init: Function to use for initializing the bias. defaults to ``zeros``. set to ``None`` to not use a bias. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax.random as jr >>> key = jr.key(0) >>> l1 = sk.nn.Conv2DLocal(3, 3, 3, in_size=(32, 32), key=key) >>> l1(jnp.ones((3, 32, 32))).shape (3, 32, 32) Note: :class:`.Conv2DLocal` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.Conv2DLocal(None, 3, 3, in_size=None, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 Reference: - https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv.html - https://github.com/google/flax/blob/main/flax/linen/linear.py """ spatial_ndim: int = 2 conv_op = staticmethod(local_conv_nd)
[docs] class Conv3DLocal(ConvNDLocal): """3D Local convolutional layer. Local convolutional layer is a convolutional layer where the convolution kernel is applied to a local region of the input. This means that the kernel weights are *not* shared across the spatial dimensions of the input. Args: in_features: Number of input feature maps, for 1D convolution this is the length of the input, for 2D convolution this is the number of input channels, for 3D convolution this is the number of input channels. out_features: Number of output features maps, for 1D convolution this is the length of the output, for 2D convolution this is the number of output channels, for 3D convolution this is the number of output channels. kernel_size: Size of the convolutional kernel. accepts: - Single integer for same kernel size in all dimensions. - Sequence of integers for different kernel sizes in each dimension. in_size: the size of the spatial dimensions of the input. e.g excluding the first dimension. accepts a sequence of integer(s). strides: Stride of the convolution. accepts: - Single integer for same stride in all dimensions. - Sequence of integers for different strides in each dimension. key: key to use for initializing the weights. padding: Padding of the input before convolution. accepts: - Single integer for same padding in all dimensions. - Sequence of integers for different padding in each dimension. - Sequnece of a tuple of two integers for before and after padding in each dimension. - ``same``/``SAME`` for padding such that the output has the same shape as the input. - ``valid``/``VALID`` for no padding. weight_init: Function to use for initializing the weights. defaults to ``glorot uniform``. bias_init: Function to use for initializing the bias. defaults to ``zeros``. set to ``None`` to not use a bias. dtype: dtype of the weights. defaults to ``float32`` Example: >>> import jax.numpy as jnp >>> import serket as sk >>> import jax.random as jr >>> key = jr.key(0) >>> l1 = sk.nn.Conv3DLocal(3, 3, 3, in_size=(32, 32, 32), key=key) >>> l1(jnp.ones((3, 32, 32, 32))).shape (3, 32, 32, 32) Note: :class:`.Conv3DLocal` supports lazy initialization, meaning that the weights and biases are not initialized until the first call to the layer. This is useful when the input shape is not known at initialization time. To use lazy initialization, pass ``None`` as the ``in_features`` argument and use :func:`.value_and_tree` to call the layer and return the method output and the material layer. >>> import serket as sk >>> import jax.numpy as jnp >>> import jax.random as jr >>> import jax >>> input = jnp.ones((5, 10, 10, 10)) >>> key = jr.key(0) >>> lazy = sk.nn.Conv3DLocal(None, 3, 3, in_size=None, key=key) >>> _, material = sk.value_and_tree(lambda lazy: lazy(input))(lazy) >>> print(material.in_features) 5 Reference: - https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.conv.html - https://github.com/google/flax/blob/main/flax/linen/linear.py """ spatial_ndim: int = 3 conv_op = staticmethod(local_conv_nd)