Low-level API ============= Low-level APIs allow you to assemble custom interpretable architectures from basic interpretable layers in a plain pytorch-like interface. .. |pyc_logo| image:: https://raw.githubusercontent.com/pyc-team/pytorch_concepts/refs/heads/master/doc/_static/img/logos/pyc.svg :width: 20px :align: middle .. |pytorch_logo| image:: https://raw.githubusercontent.com/pyc-team/pytorch_concepts/refs/heads/master/doc/_static/img/logos/pytorch.svg :width: 20px :align: middle Documentation ---------------- .. toctree:: :maxdepth: 1 nn.base.low nn.encoders nn.predictors nn.inference nn.policy nn.graph nn.dense_layers Design principles ----------------- Overview of Data Representations ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In |pyc_logo| PyC, we distinguish between three types of data representations: - **Input**: High-dimensional representations where exogenous and endogenous information is entangled - **Exogenous**: Representations that are direct causes of endogenous variables - **Endogenous**: Representations of observable quantities of interest Layer Types ^^^^^^^^^^^ In |pyc_logo| PyC you will find three types of layers whose interfaces reflect the distinction between data representations: - ``Encoder`` layers: Never take as input endogenous variables - ``Predictor`` layers: Must take as input a set of endogenous variables - Special layers: Perform operations like memory selection or graph learning Layer Naming Standard ^^^^^^^^^^^^^^^^^^^^^ In order to easily identify the type of layer, |pyc_logo| PyC uses a consistent standard to assign names to layers. Each layer name follows the format: ```` where: - ``LayerType``: describes the type of layer (e.g., Linear, HyperLinear, Selector, Transformer, etc...) - ``InputType`` and ``OutputType``: describe the type of data representations the layer takes as input and produces as output. |pyc_logo| PyC uses the following abbreviations: - ``Z``: Input - ``U``: Exogenous - ``C``: Endogenous For instance, a layer named ``LinearZC`` is a linear layer that takes as input an ``Input`` representation and produces an ``Endogenous`` representation. Since it does not take as input any endogenous variables, it is an encoder layer. .. code-block:: python pyc.nn.LinearZC(in_features=10, out_features=3) As another example, a layer named ``HyperLinearCUC`` is a hyper-network layer that takes as input both ``Endogenous`` and ``Exogenous`` representations and produces an ``Endogenous`` representation. Since it takes as input endogenous variables, it is a predictor layer. .. code-block:: python pyc.nn.HyperLinearCUC( in_features_endogenous=10, in_features_exogenous=7, embedding_size=24, out_features=3 ) As a final example, graph learners are a special layers that learn relationships between concepts. They do not follow the standard naming convention of encoders and predictors, but their purpose should be clear from their name. .. code-block:: python wanda = pyc.nn.WANDAGraphLearner( ['c1', 'c2', 'c3'], ['task A', 'task B', 'task C'] ) Models ^^^^^^^^^^^ A model is built as in standard PyTorch (e.g., ModuleDict or Sequential) and may include standard |pytorch_logo| PyTorch layers + |pyc_logo| PyC layers: .. code-block:: python concept_bottleneck_model = torch.nn.ModuleDict({ 'encoder': pyc.nn.LinearZC(in_features=10, out_features=3), 'predictor': pyc.nn.LinearCC(in_features_endogenous=3, out_features=2), }) Inference ^^^^^^^^^^^^^^ At this API level, there are two types of inference that can be performed: - **Standard forward pass**: a standard forward pass using the forward method of each layer in the ModuleDict .. code-block:: python endogenous_concepts = concept_bottleneck_model['encoder'](input=x) endogenous_tasks = concept_bottleneck_model['predictor'](endogenous=endogenous_concepts) - **Interventions**: interventions are context managers that temporarily modify a layer. **Intervention strategies**: define how the intervened layer behaves within an intervention context e.g., we can fix the concept endogenous to a constant value: .. code-block:: python int_strategy = pyc.nn.DoIntervention( model=concept_bottleneck_model["encoder"], constants=-10 ) **Intervention Policies**: define the order/set of concepts to intervene on e.g., we can intervene on all concepts uniformly: .. code-block:: python int_policy = pyc.nn.UniformPolicy(out_features=3) When a forward pass is performed within an intervention context, the intervened layer behaves differently with a cascading effect on all subsequent layers: .. code-block:: python with pyc.nn.intervention( policies=int_policy, strategies=int_strategy, target_concepts=[0, 2] ) as new_encoder_layer: endogenous_concepts = new_encoder_layer(input=x) endogenous_tasks = concept_bottleneck_model['predictor']( endogenous=endogenous_concepts )