Skip to content

data_modalities

data_modalities ¤

image_data(image_shape, region_graph='quad-graph', *, input_layer, num_input_units, sum_product_layer, num_sum_units, num_classes=1, input_params=None, sum_weight_param=None, use_mixing_weights=True) ¤

Constructs a symbolic circuit whose structure is tailored for image data sets.

Parameters:

Name Type Description Default
image_shape tuple[int, int, int]

The image shape (C, H, W), where C is the number of channels, H is the height of the images, and W is their width.

required
region_graph str

The name of the region graph to use. It can be one of the following: 'quad-tree-2' (the Quad-Tree with two splits per region node), 'quad-tree-4' (the Quad-Tree with four splits per region node), 'quad-graph' (the Quad-Graph region graph), 'random-binary-tree' (the random binary tree on flattened image pixels), 'poon-domingos' (the Poon-Domingos architecture).

'quad-graph'
input_layer str

The name of the input layer. It can be one of the following: 'categorical' (encoding a Categorical distribution over pixel channel values), 'binomial' (encoding a Binomial distribution over pixel channel values), 'embedding' (encoding an Embedding vector over pixel channel values), 'gaussian' (encoding a Gaussian distribution over pixel channel values).

required
num_input_units int

The number of input units per input layer.

required
sum_product_layer str

The name of the sum-product inner layer. It can be one of the following: 'cp' (the canonical decomposition layer, consisting of dense layers followed by a hadamard product layer), 'cp-t' (the transposed canonical decomposition layer, consisting of a hadamard product layer followed by a single dense layer), 'tucker' (the Tucker decomposition layer, consisting of a kronecker product layer followed by a single dense layer).

required
num_classes int

The number of output classes (default=1).

1
num_sum_units int

The number of sum units in each sum layer, i.e., either dense or mixing layer.

required
input_params dict[str, Parameterization] | None

A dictionary mapping each name of a parameter of the input layer to its parameterization. If it is None, then the default parameterization of the chosen input layer will be chosen.

None
sum_weight_param Parameterization | None

The parameterization to use for sum layers parameters. If it None, then a softmax parameterization of the sum weights will be used.

None
use_mixing_weights bool

Whether to parameterize sum layers having arity > 1 in a way such that they compute a linear combinations of the input vectors, instead of computing a matrix-vector product where the vector is the concatenation of input vectors. Sum layers having this semantics are also sometimes referred to as "mixing" layers. Defaults to True.

True

Returns:

Name Type Description
Circuit Circuit

A symbolic circuit.

Raises:

Type Description
ValueError

If one of the arguments is not one of the specified allowed ones.

Source code in cirkit/templates/data_modalities.py
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
def image_data(
    image_shape: tuple[int, int, int],
    region_graph: str = "quad-graph",
    *,
    input_layer: str,
    num_input_units: int,
    sum_product_layer: str,
    num_sum_units: int,
    num_classes: int = 1,
    input_params: dict[str, Parameterization] | None = None,
    sum_weight_param: Parameterization | None = None,
    use_mixing_weights: bool = True,
) -> Circuit:
    """Constructs a symbolic circuit whose structure is tailored for image data sets.

    Args:
        image_shape: The image shape (C, H, W), where C is the number of channels, H is the height
            of the images, and W is their width.
        region_graph: The name of the region graph to use. It can be one of the following:
            'quad-tree-2' (the Quad-Tree with two splits per region node),
            'quad-tree-4' (the Quad-Tree with four splits per region node),
            'quad-graph'  (the Quad-Graph region graph),
            'random-binary-tree' (the random binary tree on flattened image pixels),
            'poon-domingos' (the Poon-Domingos architecture).
        input_layer: The name of the input layer. It can be one of the following:
            'categorical' (encoding a Categorical distribution over pixel channel values),
            'binomial' (encoding a Binomial distribution over pixel channel values),
            'embedding' (encoding an Embedding vector over pixel channel values),
            'gaussian' (encoding a Gaussian distribution over pixel channel values).
        num_input_units: The number of input units per input layer.
        sum_product_layer: The name of the sum-product inner layer. It can be one of the following:
            'cp' (the canonical decomposition layer, consisting of dense layers followed by a
            hadamard product layer), 'cp-t' (the transposed canonical decomposition layer, consisting
            of a hadamard product layer followed by a single dense layer), 'tucker' (the Tucker
            decomposition layer, consisting of a kronecker product layer followed by a single dense
            layer).
        num_classes: The number of output classes (default=1).
        num_sum_units: The number of sum units in each sum layer, i.e., either dense or mixing
            layer.
        input_params: A dictionary mapping each name of a parameter of the input layer to
            its parameterization. If it is None, then the default parameterization of the chosen
            input layer will be chosen.
        sum_weight_param: The parameterization to use for sum layers parameters. If it None,
            then a softmax parameterization of the sum weights will be used.
        use_mixing_weights: Whether to parameterize sum layers having arity > 1 in a way such
            that they compute a linear combinations of the input vectors, instead of computing
            a matrix-vector product where the vector is the concatenation of input vectors.
            Sum layers having this semantics are also sometimes referred to as "mixing" layers.
            Defaults to True.

    Returns:
        Circuit: A symbolic circuit.

    Raises:
        ValueError: If one of the arguments is not one of the specified allowed ones.
    """
    if (
        not isinstance(image_shape, tuple)
        or len(image_shape) != 3
        or any(d <= 0 for d in image_shape)
    ):
        raise ValueError(
            "Expected the image shape to be a tuple of three positive integers,"
            f" but found {image_shape}"
        )
    if region_graph not in [
        "quad-tree-2",
        "quad-tree-4",
        "quad-graph",
        "random-binary-tree",
        "poon-domingos",
    ]:
        raise ValueError(f"Unknown region graph called {region_graph}")
    if input_layer not in ["categorical", "binomial", "embedding", "gaussian"]:
        raise ValueError(f"Unknown input layer called {input_layer}")

    # Construct the image-tailored region graph
    match region_graph:
        case "quad-tree-2":
            rg = QuadTree(image_shape, num_patch_splits=2)
        case "quad-tree-4":
            rg = QuadTree(image_shape, num_patch_splits=4)
        case "quad-graph":
            rg = QuadGraph(image_shape)
        case "random-binary-tree":
            rg = RandomBinaryTree(image_shape[0] * image_shape[1] * image_shape[2])
        case "poon-domingos":
            delta = int(max(np.ceil(image_shape[1] / 8), np.ceil(image_shape[2] / 8)))
            rg = PoonDomingos(image_shape, delta=delta)
        case _:
            raise ValueError(f"Unknown region graph called {region_graph}")

    # Get the input layer factory
    input_kwargs: dict[str, Any]
    match input_layer:
        case "categorical":
            input_kwargs = {"num_categories": 256}
        case "binomial":
            input_kwargs = {"total_count": 255}
        case "embedding":
            input_kwargs = {"num_states": 256}
        case "gaussian":
            input_kwargs = {}
        case _:
            assert False
    if input_params is not None:
        input_kwargs.update(
            (name + "_factory", parameterization_to_factory(param))
            for name, param in input_params.items()
        )
    input_factory = name_to_input_layer_factory(input_layer, **input_kwargs)

    # Set the sum weight factory
    if sum_weight_param is None:
        sum_weight_param = Parameterization(activation="softmax", initialization="normal")
    sum_weight_factory = parameterization_to_factory(sum_weight_param)

    # Set the nary sum weight factory
    nary_sum_weight_factory: ParameterFactory
    if use_mixing_weights:
        nary_sum_weight_factory = functools.partial(
            mixing_weight_factory, param_factory=sum_weight_factory
        )
    else:
        nary_sum_weight_factory = sum_weight_factory

    # Build and return the symbolic circuit
    return rg.build_circuit(
        input_factory=input_factory,
        sum_product=sum_product_layer,
        sum_weight_factory=sum_weight_factory,
        nary_sum_weight_factory=nary_sum_weight_factory,
        num_input_units=num_input_units,
        num_sum_units=num_sum_units,
        num_classes=num_classes,
        factorize_multivariate=True,
    )

tabular_data(region_graph='random-binary-tree', *, num_features=None, data=None, input_layers, num_input_units, sum_product_layer, num_sum_units, num_classes=1, sum_weight_param=None, use_mixing_weights=True) ¤

Constructs a symbolic circuit whose structure is tailored for tabular data sets, supporting either a fixed random-binary-tree or a learned Chow–Liu tree.

Parameters:

Name Type Description Default
region_graph str

Which region graph to use. - "random-binary-tree": build a random binary tree over the feature indices. - "chow-liu-tree": learn a Chow–Liu tree from data.

'random-binary-tree'
num_features int | None

Number of features (columns) in the dataset. Required if region_graph="random-binary-tree".

None
data Tensor | None

A Torch tensor of shape (n_samples, n_features). Required if region_graph="chow-liu-tree", since the tree structure is learned from these samples.

None
input_layers dict | list[dict]

Which per-feature distribution to use. The provided dictionaries should be of the following form: { 'name': , 'args': } for example: {'name': 'categorical', 'args': {'num_categories': 27}} or {'name': 'gaussian', 'args': {}} If a dict is provided, the same input layer is used for all features. If a list of dictionaries is provided, each feature will have its own input layer (input_layers[i] corresponds to feature i of the data).

required
num_input_units int

Number of parallel input units (e.g. mixtures/components) per feature.

required
sum_product_layer str

Which inner sum/product decomposition to use. E.g. "cp", "cp-t", or "tucker".

required
num_sum_units int

Number of sum (or mixing) units in each sum layer.

required
num_classes int

Number of output classes (or root-layer mixtures). Often 1 for pure density estimation.

1
sum_weight_param Parameterization | None

If provided, a Parameterization object specifying activation & initialization for sum-layer weights. Defaults internally to a softmax + Normal init.

None
use_mixing_weights bool

Whether to use “mixing” sum layers (i.e. learn a linear combination of child outputs) for nodes of arity >1. If False, falls back to a matrix-vector product.

True

Returns:

Type Description
Circuit

Circuit A fully-specified sum-product circuit over the given region graph with the chosen input distributions and inner decomposition layer.

Raises:

Type Description
ValueError
  • If one of the names of the input layers is not known, or the related arguments.
  • If the number of input layers (the length of the list) does not match the number of features (num_features or inferred from data).
  • If region_graph="random-binary-tree" but num_features is None and data is None.
  • If region_graph="chow-liu-tree" but data is None.
  • If region_graph is not one of the supported strings.
Source code in cirkit/templates/data_modalities.py
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
def tabular_data(
    region_graph: str = "random-binary-tree",
    *,
    num_features: int | None = None,
    data: Tensor | None = None,
    input_layers: dict | list[dict],
    num_input_units: int,
    sum_product_layer: str,
    num_sum_units: int,
    num_classes: int = 1,
    sum_weight_param: Parameterization | None = None,
    use_mixing_weights: bool = True,
) -> Circuit:
    """
    Constructs a symbolic circuit whose structure is tailored for tabular data sets,
    supporting either a fixed random-binary-tree or a learned Chow–Liu tree.

    Args:
        region_graph:
            Which region graph to use.
            - `"random-binary-tree"`: build a random binary tree over the feature indices.
            - `"chow-liu-tree"`: learn a Chow–Liu tree from data.
        num_features:
            Number of features (columns) in the dataset.
            **Required** if `region_graph="random-binary-tree"`.
        data:
            A Torch tensor of shape `(n_samples, n_features)`.
            **Required** if `region_graph="chow-liu-tree"`, since the tree structure is
                learned from these samples.
        input_layers:
            Which per-feature distribution to use.
            The provided dictionaries should be of the following form:
            {
                'name': <name: str>,
                'args': <dictionary of arguments: dict>
            }
            for example: {'name': 'categorical', 'args': {'num_categories': 27}}
                or {'name': 'gaussian', 'args': {}}
            If a dict is provided, the same input layer is used for all features.
                If a list of dictionaries is provided,
            each feature will have its own input layer (input_layers[i] corresponds
                to feature i of the data).
        num_input_units:
            Number of parallel input units (e.g. mixtures/components) per feature.
        sum_product_layer:
            Which inner sum/product decomposition to use. E.g. `"cp"`, `"cp-t"`, or `"tucker"`.
        num_sum_units:
            Number of sum (or mixing) units in each sum layer.
        num_classes:
            Number of output classes (or root-layer mixtures). Often 1 for pure density estimation.
        sum_weight_param:
            If provided, a `Parameterization` object specifying activation & initialization
            for sum-layer weights.  Defaults internally to a softmax + Normal init.
        use_mixing_weights:
            Whether to use “mixing” sum layers (i.e. learn a linear combination of child outputs)
            for nodes of arity >1.  If False, falls back to a matrix-vector product.

    Returns:
        Circuit
            A fully-specified sum-product circuit over the given region graph with the chosen
            input distributions and inner decomposition layer.

    Raises:
        ValueError:
            - If one of the names of the input layers is not known, or the related arguments.
            - If the number of input layers (the length of the list) does not match the number
                of features (`num_features` or inferred from `data`).
            - If `region_graph="random-binary-tree"` but `num_features` is `None` and `data` is None.
            - If `region_graph="chow-liu-tree"` but `data` is `None`.
            - If `region_graph` is not one of the supported strings.
    """

    match region_graph:
        case "random-binary-tree":
            if num_features is None:
                if data is not None:
                    num_features = data.shape[1]
                else:
                    raise ValueError(
                        f"You must pass `num_features=` if you ask for {region_graph}."
                    )
            rg = RandomBinaryTree(num_features)
        case "chow-liu-tree":
            if data is None:
                raise ValueError(f"You must pass `data=` if you ask for `chow-liu-tree`.")
            rg_result = ChowLiuTree(
                data=data,
                input_type=(
                    input_layers["name"]
                    if isinstance(input_layers, dict)
                    else [input_layers["name"] for input_layers in input_layers]
                ),
                num_categories=(
                    input_layers["args"]["num_categories"]
                    if isinstance(input_layers, dict) and input_layers["name"] == "categorical"
                    else None
                ),
                as_region_graph=True,
            )
            if not isinstance(rg_result, RegionGraph):
                raise ValueError(f"Expected a RegionGraph, but got {type(rg_result).__name__}.")
            rg = rg_result
        case _:
            raise ValueError(f"Unknown region graph called {region_graph}")

    if sum_weight_param is None:
        sum_weight_param = Parameterization(activation="softmax", initialization="normal")
    sum_weight_factory = parameterization_to_factory(sum_weight_param)

    nary_sum_weight_factory: ParameterFactory
    if use_mixing_weights:
        nary_sum_weight_factory = functools.partial(
            mixing_weight_factory, param_factory=sum_weight_factory
        )
    else:
        nary_sum_weight_factory = sum_weight_factory

    input_factories: InputLayerFactory | Mapping[Scope, InputLayerFactory]
    if isinstance(input_layers, dict):
        input_factories = name_to_input_layer_factory(input_layers["name"], **input_layers["args"])
    else:
        if len(input_layers) != len(rg.scope):
            raise ValueError(
                f"Number of provided input layers ({len(input_layers)}) does \
                not match the number of features ({len(rg.scope)})."
            )
        input_factories = {
            Scope([i]): name_to_input_layer_factory(input_layer["name"], **input_layer["args"])
            for i, input_layer in enumerate(input_layers)
        }

    return rg.build_circuit(
        input_factory=input_factories,
        sum_product=sum_product_layer,
        sum_weight_factory=sum_weight_factory,
        nary_sum_weight_factory=nary_sum_weight_factory,
        num_input_units=num_input_units,
        num_sum_units=num_sum_units,
        num_classes=num_classes,
        factorize_multivariate=True,
    )