ffca 0.1.0a1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. ffca-0.1.0a1/.gitignore +51 -0
  2. ffca-0.1.0a1/LICENSE +21 -0
  3. ffca-0.1.0a1/PKG-INFO +324 -0
  4. ffca-0.1.0a1/README.md +283 -0
  5. ffca-0.1.0a1/docs/PACKAGE_DESIGN_V0_1.md +111 -0
  6. ffca-0.1.0a1/docs/adapters.md +121 -0
  7. ffca-0.1.0a1/docs/quickstart.md +112 -0
  8. ffca-0.1.0a1/examples/01_tabular_breast_cancer.py +83 -0
  9. ffca-0.1.0a1/examples/02_image_cifar10_pixel.py +127 -0
  10. ffca-0.1.0a1/examples/03_image_cifar10_channel.py +116 -0
  11. ffca-0.1.0a1/ffca/__init__.py +41 -0
  12. ffca-0.1.0a1/ffca/adapters/__init__.py +16 -0
  13. ffca-0.1.0a1/ffca/adapters/channel.py +145 -0
  14. ffca-0.1.0a1/ffca/adapters/pixel.py +66 -0
  15. ffca-0.1.0a1/ffca/adapters/tabular.py +40 -0
  16. ffca-0.1.0a1/ffca/adapters/transformer.py +236 -0
  17. ffca-0.1.0a1/ffca/checkpoint.py +58 -0
  18. ffca-0.1.0a1/ffca/cli.py +662 -0
  19. ffca-0.1.0a1/ffca/core/__init__.py +45 -0
  20. ffca-0.1.0a1/ffca/core/adapter.py +150 -0
  21. ffca-0.1.0a1/ffca/core/archetypes.py +142 -0
  22. ffca-0.1.0a1/ffca/core/derivatives.py +152 -0
  23. ffca-0.1.0a1/ffca/core/scalars.py +110 -0
  24. ffca-0.1.0a1/ffca/core/signature.py +72 -0
  25. ffca-0.1.0a1/ffca/core/smoothing.py +162 -0
  26. ffca-0.1.0a1/ffca/diagnostics.py +495 -0
  27. ffca-0.1.0a1/ffca/improvements_pkg/__init__.py +6 -0
  28. ffca-0.1.0a1/ffca/improvements_pkg/cauchy_hvp.py +205 -0
  29. ffca-0.1.0a1/ffca/improvements_pkg/co_sensitivity.py +193 -0
  30. ffca-0.1.0a1/ffca/improvements_pkg/trust_score.py +121 -0
  31. ffca-0.1.0a1/ffca/report.py +437 -0
  32. ffca-0.1.0a1/ffca/viz/__init__.py +93 -0
  33. ffca-0.1.0a1/ffca/viz/diagnostics.py +126 -0
  34. ffca-0.1.0a1/ffca/viz/dynamic.py +148 -0
  35. ffca-0.1.0a1/ffca/viz/spatial.py +181 -0
  36. ffca-0.1.0a1/ffca/viz/static.py +147 -0
  37. ffca-0.1.0a1/pyproject.toml +58 -0
  38. ffca-0.1.0a1/tests/ffca_improvements.py +553 -0
  39. ffca-0.1.0a1/tests/test_biased_cifar10.py +508 -0
  40. ffca-0.1.0a1/tests/test_biased_cifar10_v2.py +296 -0
  41. ffca-0.1.0a1/tests/test_breast_cancer.py +251 -0
  42. ffca-0.1.0a1/tests/test_cauchy_hvp_validation.py +159 -0
  43. ffca-0.1.0a1/tests/test_package_smoke.py +179 -0
  44. ffca-0.1.0a1/tests/test_srdrn_phase2.py +158 -0
  45. ffca-0.1.0a1/tests/test_waterbirds.py +514 -0
  46. ffca-0.1.0a1/tests/test_waterbirds_v2.py +235 -0
@@ -0,0 +1,51 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+
8
+ # Distribution / packaging
9
+ build/
10
+ dist/
11
+ wheels/
12
+ *.egg-info/
13
+ *.egg
14
+ .eggs/
15
+
16
+ # Virtual environments
17
+ venv/
18
+ .venv/
19
+ env/
20
+ .env
21
+
22
+ # Testing / coverage
23
+ .pytest_cache/
24
+ .coverage
25
+ .coverage.*
26
+ htmlcov/
27
+ .tox/
28
+
29
+ # IDE / editor
30
+ .idea/
31
+ .vscode/
32
+ *.swp
33
+ *.swo
34
+ *~
35
+ .DS_Store
36
+
37
+ # FFCA-specific
38
+ data/
39
+ *.pt
40
+ *.pth
41
+ *.ckpt
42
+ *.nc
43
+ *.npz
44
+ validation_runs/*/checkpoints/
45
+
46
+ # Notebooks
47
+ .ipynb_checkpoints/
48
+
49
+ # mypy / ruff
50
+ .mypy_cache/
51
+ .ruff_cache/
ffca-0.1.0a1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Hamed Najafi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
ffca-0.1.0a1/PKG-INFO ADDED
@@ -0,0 +1,324 @@
1
+ Metadata-Version: 2.4
2
+ Name: ffca
3
+ Version: 0.1.0a1
4
+ Summary: Feature-Function Curvature Analysis — universal XAI for any differentiable PyTorch model
5
+ Project-URL: Homepage, https://github.com/Hnajafi95/FFCA
6
+ Project-URL: Issues, https://github.com/Hnajafi95/FFCA/issues
7
+ Author-email: Hamed Najafi <hnaja002@fiu.edu>
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: explainability,ffca,hessian,interpretability,pytorch,xai
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Requires-Python: >=3.9
21
+ Requires-Dist: matplotlib>=3.5
22
+ Requires-Dist: numpy>=1.20
23
+ Requires-Dist: pandas>=1.3
24
+ Requires-Dist: scikit-learn>=1.0
25
+ Requires-Dist: scipy>=1.7
26
+ Requires-Dist: torch>=1.13
27
+ Provides-Extra: dev
28
+ Requires-Dist: mypy; extra == 'dev'
29
+ Requires-Dist: pytest>=7.0; extra == 'dev'
30
+ Requires-Dist: ruff; extra == 'dev'
31
+ Provides-Extra: image
32
+ Requires-Dist: pillow>=9.0; extra == 'image'
33
+ Requires-Dist: torchvision>=0.14; extra == 'image'
34
+ Provides-Extra: netcdf
35
+ Requires-Dist: netcdf4>=1.6; extra == 'netcdf'
36
+ Requires-Dist: xarray>=2022.0; extra == 'netcdf'
37
+ Provides-Extra: test
38
+ Requires-Dist: pytest-cov; extra == 'test'
39
+ Requires-Dist: pytest>=7.0; extra == 'test'
40
+ Description-Content-Type: text/markdown
41
+
42
+ # FFCA
43
+
44
+ [![PyPI - Version](https://img.shields.io/pypi/v/ffca.svg)](https://pypi.org/project/ffca/)
45
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
46
+ [![Python](https://img.shields.io/badge/python-3.9%2B-blue.svg)](https://www.python.org/)
47
+
48
+ **Feature-Function Curvature Analysis (FFCA)** — universal explainability for
49
+ any differentiable PyTorch model. From a trained model + a DataLoader, FFCA
50
+ produces a 4-D *signature* per feature (Impact, Volatility, Non-linearity,
51
+ Interaction), classifies each feature into one of 8 archetypes, and emits a
52
+ report that flags overfitting, data leakage, shortcut learning, unstable
53
+ feature roles, and prune-safe / load-bearing features.
54
+
55
+ Works on tabular MLPs, CNNs (pixel- *or* channel-level), Transformer
56
+ embeddings, and attention heads — all through the same primitives.
57
+
58
+ ---
59
+
60
+ ## Install
61
+
62
+ ```bash
63
+ pip install ffca # core: tabular FFCA
64
+ pip install "ffca[image]" # +torchvision for CNNs
65
+ pip install "ffca[netcdf]" # +xarray for scientific data (.nc files)
66
+ ```
67
+
68
+ > The PyPI distribution is **`ffca`**, the import name is `ffca`, and the CLI
69
+ > binary is **`ffca-report`**.
70
+
71
+ ## Easiest way to run it — the interactive wizard
72
+
73
+ If you've never used FFCA before, this is the path:
74
+
75
+ ```bash
76
+ ffca-report --interactive
77
+ ```
78
+
79
+ The wizard walks you through every question:
80
+
81
+ 1. **Where is your model defined?** — give the importable Python class
82
+ (e.g. `mypkg.models:MyCNN`).
83
+ 2. **Path to the trained weights** — `.pt` / `.pth` file.
84
+ 3. **What kind of model is this?** — MLP, CNN, or Transformer.
85
+ 4. **(CNN only) Pixel-level or channel-level analysis?** — and for
86
+ channel-level, it scans your model, prints every Conv/Linear layer with
87
+ an index, and asks which ones to investigate:
88
+ ```
89
+ Found 12 candidate layer(s) in your model:
90
+
91
+ [ 0] Conv2d conv1
92
+ [ 1] Conv2d layer1.0.conv1
93
+ [ 2] Conv2d layer1.0.conv2
94
+ [ 3] Conv2d layer2.0.conv1
95
+ ...
96
+ [ 11] Linear fc
97
+
98
+ Pick one or more layer indices to investigate.
99
+ Examples: '0' (just the first); '0,3,5' (three layers); 'all'.
100
+ Layer indices: 0,3,11
101
+ ```
102
+ FFCA then produces one report per layer (`out/ch_conv1/`,
103
+ `out/ch_layer2_0_conv1/`, `out/ch_fc/`).
104
+ 5. **Where is the data?** — the wizard explicitly reminds you this must be
105
+ the **same data the model was trained on** (or held-out data from the same
106
+ distribution). Pass arbitrary data and the FFCA signature is meaningless,
107
+ because the derivatives are taken against your specific trained weights.
108
+ 6. **Where should the report go?** — output directory.
109
+
110
+ ## One-liner (for scripts)
111
+
112
+ When you already know what you want, skip the wizard and pass flags:
113
+
114
+ ```bash
115
+ # Simplest: --model-type does the right thing for MLPs and CNNs
116
+ ffca-report \
117
+ --model-class my_pkg.models:MyMLP \
118
+ --weights ckpt/final.pt \
119
+ --model-type mlp \
120
+ --data data.csv \
121
+ --out out/
122
+ ```
123
+
124
+ ```bash
125
+ # CNN, channel-level on one specific layer
126
+ ffca-report \
127
+ --model-class torchvision.models:resnet50 \
128
+ --weights ckpt/final.pt \
129
+ --model-type cnn --layer layer4.2.conv2 \
130
+ --data data/imagenet_val/ --image-size 224 \
131
+ --out out/
132
+ ```
133
+
134
+ ```bash
135
+ # Scientific data (NetCDF), e.g. climate / SR models
136
+ ffca-report \
137
+ --model-class my_pkg.models:SRNet --weights net.pt \
138
+ --model-type cnn --layer encoder.block4 \
139
+ --data climate.nc --data-channels precip,temp,humidity \
140
+ --target-column rainfall \
141
+ --out out/
142
+ ```
143
+
144
+ `--data-format` accepts `auto | csv | imagefolder | netcdf | npy` — `auto`
145
+ (default) infers from the path.
146
+
147
+ ## What you get
148
+
149
+ Each run writes three things into `--out`:
150
+
151
+ - **`report.md`** — a human-readable report. Diagnostic findings have a
152
+ `headline`, an `observation`, a `why_it_matters`, and a `recommendation`.
153
+ - **`report.json`** — the full numerical results (signatures, archetypes,
154
+ Trust Scores, Co-Sensitivity groups, findings, timings). Use this for
155
+ downstream analysis.
156
+ - **`plots/`** — six to ten PNGs (depending on adapter and whether you
157
+ supplied checkpoints).
158
+
159
+ ## What the plots show
160
+
161
+ Each PNG answers a specific question. Read the file name as a hint, and
162
+ use this table to know what to look for.
163
+
164
+ | File | What it shows | What to look for |
165
+ | ----------------------------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
166
+ | `01_signature_radar.png` | The 4-D signature (I, V, N, X) per checkpoint, as a radar. | A growing **Volatility** axis late in training ⇒ overfitting. A flat **Interaction** axis on a deep model ⇒ the model isn't actually using cross-feature info. |
167
+ | `02_archetype_distribution.png` | Stacked bar of which of the 8 archetypes each feature belongs to, per checkpoint. | A swelling **Noise** column ⇒ features dying out. A single archetype dominating ⇒ low-capacity / brittle model. |
168
+ | `03_impact_ranking.png` | Top features by Impact (final checkpoint). | Are the model's top features the ones you *expected*? If not — investigate. |
169
+ | `04_interaction_ci.png` | Interaction strength per feature with 95 % Cauchy-HVP confidence intervals. | Wide CIs ⇒ probe count too low (bump `--n-probes`). Tight CIs ⇒ trust the ordering. |
170
+ | `05_*_archetype_grid.png` | One small panel per feature/channel showing its archetype across checkpoints. | Look for features whose colour band changes mid-training — those are unstable. |
171
+ | `05_pixel_interaction_map.png` | (Pixel adapter only) The Interaction map projected back to image space. | A bright centre ⇒ the model is using the subject. A bright border / ring ⇒ background reliance (shortcut). |
172
+ | `06_fbr_diagnostic.png` | (Pixel adapter) Foreground/Background Ratio histogram. | FBR < 0.5 ⇒ background-shortcut learning. |
173
+ | `10_impact_evolution.png` | Each feature's Impact over training checkpoints. | Features that suddenly spike at the last checkpoint are typical overfitting signatures. |
174
+ | `11_ranking_evolution.png` | Bump chart of feature rank across checkpoints. | Stable lines ⇒ stable model. Lines that cross repeatedly ⇒ the model hasn't settled. |
175
+ | `12_archetype_evolution.png` | Heatmap of (feature × checkpoint) → archetype. | Rows that are one solid colour ⇒ stable feature roles. Rows that change colour ⇒ unstable. |
176
+ | `13_trust_scatter.png` | Trust Score: Stability vs. Importance per feature. | Top-right = "Confidently Keep". Bottom-left = "Confidently Prune". The crowded middle is where you actually have decisions to make. |
177
+ | `20_co_sensitivity_groups.png` | Functional groups of features (gradient-correlation k-medoids). | Groups with high Noise fraction *and* statistical support are prune candidates. The package will refuse to recommend a prune when the evidence is weak. |
178
+
179
+ ## Diagnostic findings — what they mean
180
+
181
+ Every run produces a `findings` list. These are the types you can encounter,
182
+ along with what triggers each one and the action FFCA recommends:
183
+
184
+ | Finding | Severity | When it fires | Typical recommendation |
185
+ | --------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------- |
186
+ | `overfitting` | critical/warn/info | Mean Volatility at the final checkpoint is N× higher than the median of earlier checkpoints. | Early stop earlier, add regularisation, or re-check held-out generalisation. |
187
+ | `data_leakage` | warn | One or more features have very high Impact AND very low Non-linearity + Interaction (z-scores > 3, < −0.5). | Audit those features for post-hoc derivation from the target; ablate and re-train. |
188
+ | `shortcut_learning` | info/warn | Foreground/Background interaction ratio is below 0.5 (i.e. the model relies on the image border). | Re-balance dataset or use augmentation that breaks the spurious correlation. |
189
+ | `trust_instability` | warn | More than half of features change archetype between checkpoints. | Train longer, change LR schedule, or examine whether your data is too noisy. |
190
+ | `trust_keep_recommended` | info | Features that retain a useful archetype across all checkpoints — the load-bearing set. | Protect with extra production logging; do not prune. |
191
+ | `trust_prune_recommended` | info | Features that are stably Noise across all checkpoints. | Prune — expected accuracy loss ≈ 0. |
192
+ | `co_sensitivity` | info | Co-Sensitivity ran but refused to recommend a prune because statistical support was insufficient. | Don't prune from this signal; use magnitude/movement pruning instead. |
193
+ | `capacity` | info | Healthy archetype distribution detected. | None — informational. |
194
+ | `saturation` | warn | A large fraction of features have near-zero Impact (under-utilised feature space). | Reduce model size, or look for dying-ReLU / under-fitting patterns. |
195
+ | `tabular_shortcut` | warn | One feature carries a disproportionate share of total Impact. | Audit it for leakage; consider feature-shuffle test. |
196
+
197
+ Each finding in `report.json` carries `headline`, `observation`,
198
+ `why_it_matters`, and `recommendation` — they read like a code-review
199
+ comment, not a numerical dump.
200
+
201
+ ## What FFCA actually computes
202
+
203
+ | Axis | Symbol | Definition | What it tells you |
204
+ | --- | --- | --- | --- |
205
+ | Impact | I | E[\|∂f/∂xᵢ\|] | How much does feature *i* move the output? |
206
+ | Volatility | V | Var[∂f/∂xᵢ] | Is the effect consistent or context-dependent? |
207
+ | Non-linearity | N | E[\|∂²f/∂xᵢ²\|] | Does the relationship curve? |
208
+ | Interaction | X | Σⱼ E[\|∂²f/∂xᵢ∂xⱼ\|] | Does feature *i* act through others? |
209
+
210
+ Classified into 8 archetypes:
211
+
212
+ | Archetype | High in | Practitioner read |
213
+ | --- | --- | --- |
214
+ | Noise | nothing | safe to prune (verify with Trust Score) |
215
+ | Hidden Interactor | X only | weak alone, strong through interactions |
216
+ | Workhorse | I (clean) | linear, reliable, independent |
217
+ | Catalyst | I + X | strong + couples with others |
218
+ | Nonlinear Driver | I + N | curved, important |
219
+ | Volatile Specialist | I + V | strong in some contexts |
220
+ | Stable Contributor | I (moderate) | mild but reliable |
221
+ | Complex Driver | high everywhere | inspect with the full toolkit |
222
+
223
+ ## Built-in adapters
224
+
225
+ | Adapter | Use for | Feature axis |
226
+ | --- | --- | --- |
227
+ | `TabularAdapter` | MLPs, tabular transformers | input columns |
228
+ | `PixelAdapter` | image classifiers | C × H × W pixels |
229
+ | `ChannelAdapter` | any CNN — intermediate layer | C channels (mean-pooled) |
230
+ | `TransformerEmbeddingAdapter` | HF Transformers — input embeddings | hidden-dim |
231
+ | `TransformerHeadAdapter` | HF Transformers — attention heads | n_layers × n_heads |
232
+
233
+ Any model not covered by these gets a ~20-line custom adapter:
234
+
235
+ ```python
236
+ from ffca.core import FFCAModelAdapter
237
+
238
+ class MyAdapter(FFCAModelAdapter):
239
+ n_features = 768
240
+ feature_shape = (768,)
241
+ feature_names = None # optional
242
+
243
+ def feature_input(self, batch):
244
+ return batch["embeddings"].clone().requires_grad_(True)
245
+
246
+ def scalar_output(self, x, batch):
247
+ out = self.model(inputs_embeds=x)
248
+ return out.logits[:, -1].max(dim=1).values.sum()
249
+ ```
250
+
251
+ See [`docs/adapters.md`](docs/adapters.md) for the full guide.
252
+
253
+ ## The three audit-v2 improvements
254
+
255
+ Pass `--no-improvements` to fall back to baseline FFCA. Otherwise three
256
+ extras are computed automatically and stored under `improvements` in the
257
+ report:
258
+
259
+ 1. **Cauchy-HVP** — interaction estimation via Pearlmutter HVP with
260
+ Cauchy(0,1) probes. Median Spearman 0.97 vs the exact Hessian at
261
+ `d=16`; ~150× wall-clock speedup at `d=12,288`.
262
+ 2. **Trust Score** — similarity-weighted entropy across checkpoints,
263
+ producing a two-axis (Stability × Importance) view per feature.
264
+ Drives the `trust_*` findings.
265
+ 3. **Co-Sensitivity** — gradient-correlation k-medoids with permutation
266
+ + bootstrap-ARI guardrails. Will refuse to recommend a prune unless
267
+ the statistical evidence backs it up.
268
+
269
+ ## Datasets in `validation_runs/`
270
+
271
+ The [`validation_runs/`](validation_runs/) directory ships real
272
+ baseline-vs-with-improvements report pairs on four model families. Each
273
+ has a `summary.md` produced by
274
+ [`summarize_validation.py`](validation_runs/summarize_validation.py). Brief
275
+ context for what's there:
276
+
277
+ - [`01_tabular/`](validation_runs/01_tabular/) — **Breast Cancer Wisconsin**
278
+ (sklearn). 30 hand-crafted cell-nucleus features (radius, texture,
279
+ perimeter, …); 569 samples, binary malignant/benign. A simple MLP across
280
+ 4 training checkpoints — sanity check that FFCA's classical XAI behaviour
281
+ is right.
282
+ - [`02_cnn/`](validation_runs/02_cnn/) — **CIFAR-10** (60 000 32×32 images
283
+ across 10 object classes). A small CNN trained for 8 epochs, run both
284
+ **pixel-level** (which input pixels matter?) and **channel-level**
285
+ (which feature channels matter inside the network?). Demonstrates how
286
+ FFCA differentiates between input-space and feature-space attribution.
287
+ - [`03_srdrn/`](validation_runs/03_srdrn/) — **SRDRN super-resolution on
288
+ real GCM climate data**. The network upsamples coarse precipitation
289
+ fields. Shows FFCA on a regression task with image-shaped scientific data.
290
+ - [`04_llm/`](validation_runs/04_llm/) — **distilgpt2** on a small text
291
+ corpus. Two adapters: the input **embedding** (12 288 hidden dims) and
292
+ the **attention head** (per (layer, head) pair). Demonstrates that the
293
+ signature framework scales past 10 000 features.
294
+
295
+ The "Waterbirds" benchmark referenced in the `shortcut_learning` finding
296
+ description is a separate dataset specifically designed to expose
297
+ background-shortcut learning — water-bird species photos placed on
298
+ land-bird backgrounds and vice versa. It's not shipped here, but the FBR
299
+ diagnostic in `06_fbr_diagnostic.png` is the metric used to detect that
300
+ exact failure mode.
301
+
302
+ To regenerate the per-directory summaries:
303
+
304
+ ```bash
305
+ python validation_runs/summarize_validation.py
306
+ ```
307
+
308
+ ## Citation
309
+
310
+ If you use FFCA, please cite:
311
+
312
+ ```bibtex
313
+ @article{najafi2025ffca,
314
+ title = {Feature-Function Curvature Analysis: A Geometric Framework
315
+ for Explaining Differentiable Models},
316
+ author = {Najafi, Hamed and Luo, Dingding and Liu, Jason},
317
+ journal = {arXiv preprint arXiv:2510.27207},
318
+ year = {2025}
319
+ }
320
+ ```
321
+
322
+ ## License
323
+
324
+ MIT — see [LICENSE](LICENSE).