Skip to content

Commit 8514a8d

Browse files
Restructure examples in documentation using drop down menus
1 parent 130a380 commit 8514a8d

File tree

3 files changed

+470
-364
lines changed

3 files changed

+470
-364
lines changed

doc/guides/using_low_level.rst

Lines changed: 144 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -85,143 +85,192 @@ clear from their name.
8585
)
8686
8787
88-
Step 1: Import Libraries
89-
-------------------------
88+
Detailed Guides
89+
------------------------------
9090

91-
.. code-block:: python
9291

93-
import torch
94-
import torch_concepts as pyc
92+
.. dropdown:: Concept Bottleneck Model
93+
:icon: package
9594

96-
Step 2: Create Sample Data
97-
---------------------------
95+
**Import Libraries**
9896

99-
Generate random inputs and targets for demonstration:
97+
To get started, import |pyc_logo| PyC and |pytorch_logo| PyTorch:
10098

101-
.. code-block:: python
99+
.. code-block:: python
102100
103-
batch_size = 32
104-
input_dim = 64
105-
n_concepts = 5
106-
n_tasks = 3
101+
import torch
102+
import torch_concepts as pyc
107103
108-
# Random input
109-
x = torch.randn(batch_size, input_dim)
104+
**Create Sample Data**
110105

111-
# Random concept labels (binary)
112-
concept_labels = torch.randint(0, 2, (batch_size, n_concepts)).float()
106+
Generate random inputs and targets for demonstration:
113107

114-
# Random task labels
115-
task_labels = torch.randint(0, n_tasks, (batch_size,))
108+
.. code-block:: python
116109
117-
Step 3: Build a Concept Bottleneck Model
118-
-----------------------------------------
110+
batch_size = 32
111+
input_dim = 64
112+
n_concepts = 5
113+
n_tasks = 3
119114
120-
Use a ModuleDict to combine encoder and predictor:
115+
# Random input
116+
x = torch.randn(batch_size, input_dim)
121117
122-
.. code-block:: python
118+
# Random concept labels (binary)
119+
concept_labels = torch.randint(0, 2, (batch_size, n_concepts)).float()
123120
124-
# Create model using ModuleDict
125-
model = torch.nn.ModuleDict({
126-
'encoder': pyc.nn.LinearZC(
127-
in_features=input_dim,
128-
out_features=n_concepts
129-
),
130-
'predictor': pyc.nn.LinearCC(
131-
in_features_endogenous=n_concepts,
132-
out_features=n_tasks
133-
),
134-
})
121+
# Random task labels
122+
task_labels = torch.randint(0, n_tasks, (batch_size,))
135123
136-
Step 4: Forward Pass
137-
---------------------
124+
**Step 3: Build a Concept Bottleneck Model**
138125

139-
Compute concept endogenous, then task predictions:
126+
Use a ModuleDict to combine encoder and predictor:
140127

141-
.. code-block:: python
128+
.. code-block:: python
142129
143-
# Get concept endogenous from input
144-
concept_endogenous = model['encoder'](input=x)
130+
# Create model using ModuleDict
131+
model = torch.nn.ModuleDict({
132+
'encoder': pyc.nn.LinearZC(
133+
in_features=input_dim,
134+
out_features=n_concepts
135+
),
136+
'predictor': pyc.nn.LinearCC(
137+
in_features_endogenous=n_concepts,
138+
out_features=n_tasks
139+
),
140+
})
145141
146-
# Get task predictions from concept endogenous
147-
task_endogenous = model['predictor'](endogenous=concept_endogenous)
148142
149-
print(f"Concept endogenous shape: {concept_endogenous.shape}") # [32, 5]
150-
print(f"Task endogenous shape: {task_endogenous.shape}") # [32, 3]
143+
.. dropdown:: Inference and Training
144+
:icon: rocket
151145

152-
Step 5: Compute Loss and Train
153-
-------------------------------
146+
**Step 1: Inference**
154147

155-
Train with both concept and task supervision:
148+
Once a concept bottleneck model is built, we can perform inference by first obtaining
149+
concept activations from the encoder, and then task predictions from the predictor:
156150

157-
.. code-block:: python
151+
.. code-block:: python
158152
159-
import torch.nn.functional as F
153+
# Get concept endogenous from input
154+
concept_endogenous = model['encoder'](input=x)
160155
161-
# Compute losses
162-
concept_loss = F.binary_cross_entropy(torch.sigmoid(concept_endogenous), concept_labels)
163-
task_loss = F.cross_entropy(task_endogenous, task_labels)
164-
total_loss = task_loss + 0.5 * concept_loss
156+
# Get task predictions from concept endogenous
157+
task_endogenous = model['predictor'](endogenous=concept_endogenous)
165158
166-
# Backpropagation
167-
total_loss.backward()
159+
print(f"Concept endogenous shape: {concept_endogenous.shape}") # [32, 5]
160+
print(f"Task endogenous shape: {task_endogenous.shape}") # [32, 3]
168161
169-
print(f"Concept loss: {concept_loss.item():.4f}")
170-
print(f"Task loss: {task_loss.item():.4f}")
162+
**Step 2: Compute Loss and Train**
171163

172-
Step 6: Perform Interventions
173-
------------------------------
164+
Train with both concept and task supervision:
174165

175-
Intervene using the ``intervention`` context manager which replaces the encoder layer temporarily.
176-
The context manager takes two main arguments: **strategies** and **policies**.
166+
.. code-block:: python
177167
178-
- Intervention strategies define how the layer behaves during the intervention, e.g., setting concept endogenous to ground truth values.
179-
- Intervention policies define the priority/order of concepts to intervene on.
168+
import torch.nn.functional as F
180169
181-
.. code-block:: python
170+
# Compute losses
171+
concept_loss = F.binary_cross_entropy(torch.sigmoid(concept_endogenous), concept_labels)
172+
task_loss = F.cross_entropy(task_endogenous, task_labels)
173+
total_loss = task_loss + 0.5 * concept_loss
182174
183-
from torch_concepts.nn import GroundTruthIntervention, UniformPolicy
184-
from torch_concepts.nn import intervention
175+
# Backpropagation
176+
total_loss.backward()
185177
186-
ground_truth = 10 * torch.rand_like(concept_endogenous)
187-
strategy = GroundTruthIntervention(model=model['encoder'], ground_truth=ground_truth)
188-
policy = UniformPolicy(out_features=n_concepts)
178+
print(f"Concept loss: {concept_loss.item():.4f}")
179+
print(f"Task loss: {task_loss.item():.4f}")
189180
190-
# Apply intervention to encoder
191-
with intervention(
192-
policies=policy,
193-
strategies=strategy,
194-
target_concepts=[0, 2]
195-
) as new_encoder_layer:
196-
intervened_concepts = new_encoder_layer(input=x)
197-
intervened_tasks = model['predictor'](endogenous=intervened_concepts)
198181
199-
print(f"Original concept endogenous: {concept_endogenous[0]}")
200-
print(f"Original task predictions: {task_endogenous[0]}")
201-
print(f"Intervened concept endogenous: {intervened_concepts[0]}")
202-
print(f"Intervened task predictions: {intervened_tasks[0]}")
182+
.. dropdown:: Interventions
183+
:icon: tools
203184

204-
Using Special Layers
205-
--------------------
185+
Intervene using the ``intervention`` context manager which replaces the encoder layer temporarily.
186+
The context manager takes two main arguments: **strategies** and **policies**.
206187

207-
Add a graph learner to discover concept relationships:
188+
- Intervention strategies define how the layer behaves during the intervention, e.g., setting concept endogenous to ground truth values.
189+
- Intervention policies define the priority/order of concepts to intervene on.
190+
191+
.. code-block:: python
192+
193+
from torch_concepts.nn import GroundTruthIntervention, UniformPolicy
194+
from torch_concepts.nn import intervention
195+
196+
ground_truth = 10 * torch.rand_like(concept_endogenous)
197+
strategy = GroundTruthIntervention(model=model['encoder'], ground_truth=ground_truth)
198+
policy = UniformPolicy(out_features=n_concepts)
199+
200+
# Apply intervention to encoder
201+
with intervention(
202+
policies=policy,
203+
strategies=strategy,
204+
target_concepts=[0, 2]
205+
) as new_encoder_layer:
206+
intervened_concepts = new_encoder_layer(input=x)
207+
intervened_tasks = model['predictor'](endogenous=intervened_concepts)
208+
209+
print(f"Original concept endogenous: {concept_endogenous[0]}")
210+
print(f"Original task predictions: {task_endogenous[0]}")
211+
print(f"Intervened concept endogenous: {intervened_concepts[0]}")
212+
print(f"Intervened task predictions: {intervened_tasks[0]}")
208213
209-
.. code-block:: python
210214
211-
# Define concept and task names
212-
concept_names = ['round', 'smooth', 'bright', 'large', 'centered']
215+
.. dropdown:: (Advanced) Graph Learning
216+
:icon: workflow
213217

214-
# Create WANDA graph learner
215-
graph_learner = pyc.nn.WANDAGraphLearner(
216-
row_labels=concept_names,
217-
col_labels=concept_names
218-
)
218+
Add a graph learner to discover concept relationships:
219219

220-
print(f"Learned graph shape: {graph_learner.weighted_adj}")
220+
.. code-block:: python
221+
222+
# Define concept and task names
223+
concept_names = ['round', 'smooth', 'bright', 'large', 'centered']
224+
225+
# Create WANDA graph learner
226+
graph_learner = pyc.nn.WANDAGraphLearner(
227+
row_labels=concept_names,
228+
col_labels=concept_names
229+
)
230+
231+
print(f"Learned graph shape: {graph_learner.weighted_adj}")
232+
233+
234+
The ``graph_learner.weighted_adj`` tensor contains a learnable adjacency matrix representing relationships
235+
between concepts.
236+
237+
238+
.. dropdown:: (Advanced) Verifiable Concept-Based Models
239+
:icon: shield-check
240+
241+
To design more complex concept-based models, you can combine multiple interpretable layers.
242+
For example, to build a verifiable concept-based model we can use an encoder to predict concept activations,
243+
a selector to select relevant exogenous information, and a hyper-network predictor to make final predictions
244+
based on both concept activations and exogenous information.
245+
246+
.. code-block:: python
247+
248+
from torch_concepts.nn import LinearZC, SelectorZU, HyperLinearCUC
249+
250+
memory_size = 7
251+
exogenous_size = 16
252+
embedding_size = 5
253+
254+
# Create model using ModuleDict
255+
model = torch.nn.ModuleDict({
256+
'encoder': LinearZC(
257+
in_features=input_dim,
258+
out_features=n_concepts
259+
),
260+
'selector': SelectorZU(
261+
in_features=input_dim,
262+
memory_size=memory_size,
263+
exogenous_size=exogenous_size,
264+
out_features=n_tasks
265+
),
266+
'predictor': HyperLinearCUC(
267+
in_features_endogenous=n_concepts,
268+
in_features_exogenous=exogenous_size,
269+
embedding_size=embedding_size,
270+
)
271+
})
221272
222273
223-
The ``graph_learner.weighted_adj`` tensor contains a learnable adjacency matrix representing relationships
224-
between concepts.
225274
226275
Next Steps
227276
----------
@@ -230,4 +279,3 @@ Next Steps
230279
- Try the :doc:`Mid-Level API </guides/using_mid_level_proba>` for probabilistic modeling
231280
- Try the :doc:`Mid-Level API </guides/using_mid_level_causal>` for causal modeling
232281
- Check out :doc:`example notebooks <https://github.com/pyc-team/pytorch_concepts/tree/master/examples>`
233-

0 commit comments

Comments
 (0)