Low-level API¶
Low-level APIs allow you to assemble custom interpretable architectures from basic interpretable layers in a plain pytorch-like interface.
Documentation¶
Design principles¶
Overview of Data Representations¶
In 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 you will find three types of layers whose interfaces reflect the distinction between data representations:
Encoderlayers: Never take as input endogenous variablesPredictorlayers: Must take as input a set of endogenous variablesSpecial layers: Perform operations like memory selection or graph learning
Layer Naming Standard¶
In order to easily identify the type of layer, PyC uses a consistent standard to assign names to layers.
Each layer name follows the format:
<LayerType><InputType><OutputType>
where:
LayerType: describes the type of layer (e.g., Linear, HyperLinear, Selector, Transformer, etc…)InputTypeandOutputType: describe the type of data representations the layer takes as input and produces as output.PyC uses the following abbreviations:
Z: InputU: ExogenousC: 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.
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.
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.
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 layers +
PyC layers:
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
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:
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:
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:
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 )