dlspit 2.2.0__tar.gz → 2.3.0__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.
- {dlspit-2.2.0 → dlspit-2.3.0}/PKG-INFO +48 -2
- {dlspit-2.2.0 → dlspit-2.3.0}/README.md +47 -1
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/__init__.py +13 -2
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/__main__.py +1 -1
- dlspit-2.3.0/dl_experiments/display.py +229 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/genai/registry.py +5 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/xai/registry.py +5 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/pyproject.toml +1 -1
- dlspit-2.3.0/tests/test_display.py +245 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/dl/__init__.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/dl/experiments/__init__.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/dl/experiments/exp10_transfer.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/dl/experiments/exp1a_mcculloch_pitts.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/dl/experiments/exp1b_mlp_binary.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/dl/experiments/exp2a_activations.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/dl/experiments/exp2b_backprop_mnist.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/dl/experiments/exp3_regularisation.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/dl/experiments/exp4_cnn.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/dl/experiments/exp5_resnet.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/dl/experiments/exp6_rnn.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/dl/experiments/exp7_lstm.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/dl/experiments/exp8_transformer.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/dl/experiments/exp9_gan.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/dl/experiments/exp9b_vae.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/dl/registry.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/genai/__init__.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/genai/_nn.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/genai/_viz.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/genai/chatbot.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/genai/cyclegan.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/genai/diffusion.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/genai/face_swap.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/genai/gan.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/genai/mini_gpt.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/genai/music_generation.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/genai/text_to_image.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/genai/vae.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/ml_ese/__init__.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/ml_ese/experiments/__init__.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/ml_ese/experiments/decision_tree.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/ml_ese/experiments/eda_titanic.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/ml_ese/experiments/kmeans_clustering.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/ml_ese/experiments/linear_regression.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/ml_ese/experiments/random_forest.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/ml_ese/experiments/svm_classifier.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/ml_ese/experiments/svm_rbf_kernel.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/ml_ese/experiments/xgboost_classifier.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/ml_ese/registry.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/utils/__init__.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/xai/__init__.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/xai/_common.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/xai/_plot.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/xai/decision_tree_knn.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/xai/grad_cam.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/xai/gradient_input.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/xai/ice.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/xai/lime.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/xai/lrp.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/xai/pdp.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/xai/regression.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/xai/shap.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/dl_experiments/xai/xrai.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/tests/conftest.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/tests/test_genai_chatbot_diffusion_music.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/tests/test_genai_core.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/tests/test_genai_images.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/tests/test_genai_mini_gpt.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/tests/test_genai_vae_gan.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/tests/test_xai_core.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/tests/test_xai_neural.py +0 -0
- {dlspit-2.2.0 → dlspit-2.3.0}/tests/test_xai_tabular.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: dlspit
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: A multi-domain experiment package: Deep Learning (MLP, CNN, ResNet, RNN, LSTM, Transformer, GAN, VAE), ML ESE Lab (regression, SVM, decision tree, random forest, XGBoost, k-means) Generative AI from scratch in NumPy (VAE, GAN, mini GPT, text-to-image, face swap, music, CycleGAN, chatbot, diffusion) and Explainable AI from scratch (SHAP, PDP, ICE, LIME, Grad-CAM, XRAI-style, Gradient x Input, LRP).
|
|
5
5
|
Project-URL: Repository, https://github.com/PrathamShid07/DL_Expts
|
|
6
6
|
Project-URL: Issues, https://github.com/PrathamShid07/DL_Expts/issues
|
|
@@ -43,7 +43,7 @@ Provides-Extra: xai
|
|
|
43
43
|
Requires-Dist: matplotlib>=3.6; extra == 'xai'
|
|
44
44
|
Description-Content-Type: text/markdown
|
|
45
45
|
|
|
46
|
-
# dlspit v2.
|
|
46
|
+
# dlspit v2.3.0 — Multi-Domain Experiment Package
|
|
47
47
|
|
|
48
48
|
A pip-installable multi-domain experiment viewer and runner covering:
|
|
49
49
|
|
|
@@ -135,6 +135,52 @@ python -m dl_experiments ML-ESE --run random_forest
|
|
|
135
135
|
|
|
136
136
|
---
|
|
137
137
|
|
|
138
|
+
## Read an experiment in Google Colab / Jupyter
|
|
139
|
+
|
|
140
|
+
Install the package in a notebook and call one function. The cell output shows the experiment's **existing theory**
|
|
141
|
+
followed by its **complete Python source code** (syntax-highlighted), exactly as stored in the package. Nothing is executed
|
|
142
|
+
and nothing is rewritten: the theory is the module docstring of the experiment's `.py` file (GenAI, XAI) or its leading
|
|
143
|
+
`# THEORY` comment block (DL, ML-ESE), and the code is the file itself.
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
!pip install dlspit
|
|
147
|
+
|
|
148
|
+
from dl_experiments import show_experiment
|
|
149
|
+
|
|
150
|
+
show_experiment("genai", "chatbot") # theory + full source of the chatbot experiment
|
|
151
|
+
show_experiment("xai", "shap") # domains: "genai", "xai", "dl", "ml-ese"
|
|
152
|
+
show_experiment("dl", "4CNN")
|
|
153
|
+
show_experiment("ml-ese", "decision_tree")
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Useful options:
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
show_experiment("xai", "grad-cam", code=False) # theory only
|
|
160
|
+
show_experiment("xai", "grad-cam", theory=False) # code only
|
|
161
|
+
show_experiment("genai", "vae", helpers=True) # also the supporting modules it imports (e.g. the NumPy toolkit _nn.py)
|
|
162
|
+
show_experiment("xai", "lrp", output="text") # force plain text (default: HTML in a notebook, text in a terminal)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The valid names are listed by `dl_experiments.genai.list_experiments()`, `dl_experiments.xai.list_experiments()`,
|
|
166
|
+
`dl_experiments.dl.list_experiments()` and `dl_experiments.ml_ese.list_experiments()`; the same aliases as the command line work
|
|
167
|
+
(`"gradcam"`, `"text_to_image"`, ...). An invalid name raises an error that lists the valid ones.
|
|
168
|
+
|
|
169
|
+
To get the text itself (for example to copy it into a journal, or to print it):
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from dl_experiments import get_theory, get_source
|
|
173
|
+
|
|
174
|
+
print(get_theory("genai", "chatbot")) # the explanation only
|
|
175
|
+
print(get_source("genai", "chatbot")) # the complete file
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Syntax highlighting uses Pygments, which Colab and Jupyter already include (without it the code is shown as plain monospaced
|
|
179
|
+
text); no extra package is required. To *run* an experiment instead of reading it, use `dlspit genai chatbot` or
|
|
180
|
+
`from dl_experiments.genai.chatbot import run_experiment`.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
138
184
|
## DL Experiment Index
|
|
139
185
|
|
|
140
186
|
| Key | Description |
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# dlspit v2.
|
|
1
|
+
# dlspit v2.3.0 — Multi-Domain Experiment Package
|
|
2
2
|
|
|
3
3
|
A pip-installable multi-domain experiment viewer and runner covering:
|
|
4
4
|
|
|
@@ -90,6 +90,52 @@ python -m dl_experiments ML-ESE --run random_forest
|
|
|
90
90
|
|
|
91
91
|
---
|
|
92
92
|
|
|
93
|
+
## Read an experiment in Google Colab / Jupyter
|
|
94
|
+
|
|
95
|
+
Install the package in a notebook and call one function. The cell output shows the experiment's **existing theory**
|
|
96
|
+
followed by its **complete Python source code** (syntax-highlighted), exactly as stored in the package. Nothing is executed
|
|
97
|
+
and nothing is rewritten: the theory is the module docstring of the experiment's `.py` file (GenAI, XAI) or its leading
|
|
98
|
+
`# THEORY` comment block (DL, ML-ESE), and the code is the file itself.
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
!pip install dlspit
|
|
102
|
+
|
|
103
|
+
from dl_experiments import show_experiment
|
|
104
|
+
|
|
105
|
+
show_experiment("genai", "chatbot") # theory + full source of the chatbot experiment
|
|
106
|
+
show_experiment("xai", "shap") # domains: "genai", "xai", "dl", "ml-ese"
|
|
107
|
+
show_experiment("dl", "4CNN")
|
|
108
|
+
show_experiment("ml-ese", "decision_tree")
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Useful options:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
show_experiment("xai", "grad-cam", code=False) # theory only
|
|
115
|
+
show_experiment("xai", "grad-cam", theory=False) # code only
|
|
116
|
+
show_experiment("genai", "vae", helpers=True) # also the supporting modules it imports (e.g. the NumPy toolkit _nn.py)
|
|
117
|
+
show_experiment("xai", "lrp", output="text") # force plain text (default: HTML in a notebook, text in a terminal)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The valid names are listed by `dl_experiments.genai.list_experiments()`, `dl_experiments.xai.list_experiments()`,
|
|
121
|
+
`dl_experiments.dl.list_experiments()` and `dl_experiments.ml_ese.list_experiments()`; the same aliases as the command line work
|
|
122
|
+
(`"gradcam"`, `"text_to_image"`, ...). An invalid name raises an error that lists the valid ones.
|
|
123
|
+
|
|
124
|
+
To get the text itself (for example to copy it into a journal, or to print it):
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from dl_experiments import get_theory, get_source
|
|
128
|
+
|
|
129
|
+
print(get_theory("genai", "chatbot")) # the explanation only
|
|
130
|
+
print(get_source("genai", "chatbot")) # the complete file
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Syntax highlighting uses Pygments, which Colab and Jupyter already include (without it the code is shown as plain monospaced
|
|
134
|
+
text); no extra package is required. To *run* an experiment instead of reading it, use `dlspit genai chatbot` or
|
|
135
|
+
`from dl_experiments.genai.chatbot import run_experiment`.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
93
139
|
## DL Experiment Index
|
|
94
140
|
|
|
95
141
|
| Key | Description |
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
dl_experiments v2.
|
|
2
|
+
dl_experiments v2.3.0
|
|
3
3
|
=====================
|
|
4
4
|
A pip-installable multi-domain experiment package covering:
|
|
5
5
|
|
|
@@ -37,6 +37,12 @@ Quick start
|
|
|
37
37
|
dlspit DL --list # show DL experiments
|
|
38
38
|
dlspit ML-ESE --list # show ML-ESE experiments
|
|
39
39
|
|
|
40
|
+
Notebook / Colab: read the theory and the full code of any experiment
|
|
41
|
+
--------------------------------------------------------------------
|
|
42
|
+
from dl_experiments import show_experiment
|
|
43
|
+
show_experiment("genai", "chatbot") # theory, then the complete Python source (highlighted)
|
|
44
|
+
show_experiment("xai", "shap") # also "dl" and "ml-ese"; nothing is executed
|
|
45
|
+
|
|
40
46
|
Python API
|
|
41
47
|
----------
|
|
42
48
|
from dl_experiments.dl import list_experiments, show_source, run_experiment
|
|
@@ -74,7 +80,7 @@ ML-ESE Experiment keys
|
|
|
74
80
|
kmeans_clustering K-Means Clustering — customer segmentation
|
|
75
81
|
"""
|
|
76
82
|
|
|
77
|
-
__version__ = "2.
|
|
83
|
+
__version__ = "2.3.0"
|
|
78
84
|
__author__ = "Pratham"
|
|
79
85
|
|
|
80
86
|
from .dl.registry import (
|
|
@@ -99,6 +105,8 @@ from .xai.registry import (
|
|
|
99
105
|
show_source as xai_show_source,
|
|
100
106
|
)
|
|
101
107
|
|
|
108
|
+
from .display import get_source, get_theory, show_experiment
|
|
109
|
+
|
|
102
110
|
__all__ = [
|
|
103
111
|
"__version__",
|
|
104
112
|
"__author__",
|
|
@@ -114,4 +122,7 @@ __all__ = [
|
|
|
114
122
|
"xai_list_experiments",
|
|
115
123
|
"xai_run_experiment",
|
|
116
124
|
"xai_show_source",
|
|
125
|
+
"show_experiment",
|
|
126
|
+
"get_theory",
|
|
127
|
+
"get_source",
|
|
117
128
|
]
|
|
@@ -116,7 +116,7 @@ def cli() -> None:
|
|
|
116
116
|
parser = argparse.ArgumentParser(
|
|
117
117
|
prog="dlspit",
|
|
118
118
|
description=(
|
|
119
|
-
"dlspit v2.
|
|
119
|
+
"dlspit v2.3.0 — Multi-Domain Experiment Viewer & Runner\n\n"
|
|
120
120
|
"Domains:\n"
|
|
121
121
|
" DL Deep Learning (MLP, CNN, ResNet, RNN, LSTM, GAN, VAE …)\n"
|
|
122
122
|
" ML-ESE Machine Learning ESE Lab (regression, SVM, trees, clustering …)\n"
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Show an experiment's THEORY and its complete SOURCE CODE - in Google Colab, Jupyter, or a terminal.
|
|
3
|
+
|
|
4
|
+
from dl_experiments import show_experiment
|
|
5
|
+
|
|
6
|
+
show_experiment("genai", "chatbot") # theory, then the full Python source, highlighted
|
|
7
|
+
show_experiment("xai", "shap")
|
|
8
|
+
show_experiment("dl", "4CNN")
|
|
9
|
+
show_experiment("ml-ese", "decision_tree")
|
|
10
|
+
|
|
11
|
+
Nothing is written or generated here: the text comes from the experiment's own ``.py`` file
|
|
12
|
+
- the module docstring for GenAI / XAI, the leading ``# THEORY`` comment block for DL / ML-ESE -
|
|
13
|
+
and the code is the file itself, character for character. The experiment is NOT imported or
|
|
14
|
+
executed (the file is only read and parsed), so showing it is instant and has no side effects.
|
|
15
|
+
|
|
16
|
+
In a notebook the output is syntax-highlighted HTML (Pygments, which Jupyter and Colab already
|
|
17
|
+
include; without it the code is shown as plain monospaced text). Elsewhere it is printed as text.
|
|
18
|
+
|
|
19
|
+
Public API
|
|
20
|
+
----------
|
|
21
|
+
show_experiment(domain, name, theory=True, code=True, helpers=False, output="auto")
|
|
22
|
+
get_theory(domain, name) -> str (the explanation text)
|
|
23
|
+
get_source(domain, name, helpers=...) -> str (the file, or the file + supporting modules)
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import ast
|
|
27
|
+
import html as _html
|
|
28
|
+
import re
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
_DOMAIN_ALIASES = {
|
|
32
|
+
"genai": "genai", "gen-ai": "genai", "gen_ai": "genai", "generative": "genai",
|
|
33
|
+
"xai": "xai", "explainable": "xai",
|
|
34
|
+
"dl": "dl", "deep": "dl",
|
|
35
|
+
"ml-ese": "ml-ese", "ml_ese": "ml-ese", "mlese": "ml-ese", "ml": "ml-ese",
|
|
36
|
+
}
|
|
37
|
+
DOMAINS = ("genai", "xai", "dl", "ml-ese")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ── Locating an experiment ───────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
def _resolve(domain, name):
|
|
43
|
+
"""Return (canonical domain, canonical key, Path of the experiment's .py file)."""
|
|
44
|
+
canonical = _DOMAIN_ALIASES.get(str(domain).strip().lower())
|
|
45
|
+
if canonical is None:
|
|
46
|
+
raise ValueError(f"Invalid domain: {domain!r}. Valid domains: {', '.join(DOMAINS)}.")
|
|
47
|
+
if canonical == "genai":
|
|
48
|
+
from .genai import registry
|
|
49
|
+
elif canonical == "xai":
|
|
50
|
+
from .xai import registry
|
|
51
|
+
elif canonical == "dl":
|
|
52
|
+
from .dl import registry
|
|
53
|
+
else:
|
|
54
|
+
from .ml_ese import registry
|
|
55
|
+
|
|
56
|
+
if canonical in ("genai", "xai"):
|
|
57
|
+
key = registry.normalize_key(name) # accepts aliases such as "gradcam" or "text_to_image"
|
|
58
|
+
return canonical, key, registry.source_path(key)
|
|
59
|
+
files = registry._SOURCE_FILES # DL / ML-ESE registries keep a key -> file table
|
|
60
|
+
matches = [k for k in files if k.lower() == str(name).strip().lower()]
|
|
61
|
+
if not matches:
|
|
62
|
+
raise registry._invalid_key_error(str(name))
|
|
63
|
+
return canonical, matches[0], (registry._EXPERIMENTS_DIR / files[matches[0]]).resolve()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# ── Extracting the existing theory ───────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
def _leading_comment_block(source):
|
|
69
|
+
"""Theory of DL / ML-ESE files: the first block of consecutive '#' lines (banner lines of '=' dropped)."""
|
|
70
|
+
block, started = [], False
|
|
71
|
+
for line in source.splitlines():
|
|
72
|
+
stripped = line.strip()
|
|
73
|
+
if stripped.startswith("#"):
|
|
74
|
+
started = True
|
|
75
|
+
text = stripped[1:]
|
|
76
|
+
text = text[1:] if text.startswith(" ") else text
|
|
77
|
+
if not re.fullmatch(r"=+", text.strip()):
|
|
78
|
+
block.append(text.rstrip())
|
|
79
|
+
elif started:
|
|
80
|
+
break
|
|
81
|
+
return "\n".join(block).strip()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _theory_from_source(source):
|
|
85
|
+
docstring = ast.get_docstring(ast.parse(source), clean=True)
|
|
86
|
+
return docstring if docstring else _leading_comment_block(source)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _title_from_theory(theory, key):
|
|
90
|
+
"""First non-empty line: the docstring's title line, or the 'EXPERIMENT n: ...' line of a comment block."""
|
|
91
|
+
return next((line.strip() for line in theory.splitlines() if line.strip()), key)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def get_theory(domain, name):
|
|
95
|
+
"""The existing theory / explanation text of an experiment (module docstring or leading comment block)."""
|
|
96
|
+
_, _, path = _resolve(domain, name)
|
|
97
|
+
return _theory_from_source(path.read_text(encoding="utf-8"))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# ── Extracting the code (optionally with the modules it depends on) ──────────
|
|
101
|
+
|
|
102
|
+
def _local_imports(path):
|
|
103
|
+
"""Files of this package that ``path`` imports with relative imports (``from ._nn import ...``)."""
|
|
104
|
+
found = []
|
|
105
|
+
for node in ast.walk(ast.parse(path.read_text(encoding="utf-8"))):
|
|
106
|
+
if isinstance(node, ast.ImportFrom) and node.level > 0:
|
|
107
|
+
base = path.parent
|
|
108
|
+
for _ in range(node.level - 1):
|
|
109
|
+
base = base.parent
|
|
110
|
+
parts = node.module.split(".") if node.module else []
|
|
111
|
+
targets = [parts] if parts else [[alias.name] for alias in node.names] # "from . import _plot"
|
|
112
|
+
for target in targets:
|
|
113
|
+
candidate = base.joinpath(*target).with_suffix(".py")
|
|
114
|
+
if candidate.is_file() and candidate.name not in ("registry.py", "__init__.py"):
|
|
115
|
+
found.append(candidate.resolve())
|
|
116
|
+
return found
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _supporting_files(path):
|
|
120
|
+
"""Every supporting module the experiment needs, in the order they are first imported (no duplicates)."""
|
|
121
|
+
ordered, queue = [], _local_imports(path)
|
|
122
|
+
while queue:
|
|
123
|
+
current = queue.pop(0)
|
|
124
|
+
if current != path and current not in ordered:
|
|
125
|
+
ordered.append(current)
|
|
126
|
+
queue.extend(_local_imports(current))
|
|
127
|
+
return ordered
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _code_files(domain, name, helpers):
|
|
131
|
+
_, _, path = _resolve(domain, name)
|
|
132
|
+
return [path] + (_supporting_files(path) if helpers else [])
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def get_source(domain, name, helpers=False):
|
|
136
|
+
"""The experiment's source file, exactly as stored in the package.
|
|
137
|
+
|
|
138
|
+
helpers=True appends the supporting modules it imports (for example the NumPy neural-network
|
|
139
|
+
toolkit ``_nn.py``), each preceded by a comment line naming the file.
|
|
140
|
+
"""
|
|
141
|
+
files = _code_files(domain, name, helpers)
|
|
142
|
+
parts = [files[0].read_text(encoding="utf-8")]
|
|
143
|
+
for extra in files[1:]:
|
|
144
|
+
parts.append(f"# {'=' * 70}\n# Supporting module: {extra.name}\n# {'=' * 70}\n" + extra.read_text(encoding="utf-8"))
|
|
145
|
+
return "\n\n".join(parts)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# ── Rendering ────────────────────────────────────────────────────────────────
|
|
149
|
+
|
|
150
|
+
_BOX = ("background:#f6f8fa;color:#24292f;padding:10px 14px;border:1px solid #d0d7de;border-radius:6px;"
|
|
151
|
+
"overflow:auto;font-size:13px;line-height:1.45;")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _highlight_html(code):
|
|
155
|
+
"""Syntax-highlighted HTML with inline styles (no CSS dependencies); plain <pre> without Pygments."""
|
|
156
|
+
try:
|
|
157
|
+
from pygments import highlight
|
|
158
|
+
from pygments.formatters import HtmlFormatter
|
|
159
|
+
from pygments.lexers import PythonLexer
|
|
160
|
+
return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True, style="default", cssstyles=_BOX))
|
|
161
|
+
except ImportError:
|
|
162
|
+
return f'<pre style="{_BOX}">{_html.escape(code)}</pre>'
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _build_html(title, theory, code_files, show_theory, show_code):
|
|
166
|
+
out = [f'<div style="font-family:system-ui,sans-serif"><h2 style="margin-bottom:4px">{_html.escape(title)}</h2>']
|
|
167
|
+
if show_theory:
|
|
168
|
+
out.append('<h3>Theory and Underlying Concepts</h3>'
|
|
169
|
+
f'<pre style="{_BOX}white-space:pre-wrap">{_html.escape(theory)}</pre>')
|
|
170
|
+
if show_code:
|
|
171
|
+
out.append("<h3>Python Implementation</h3>")
|
|
172
|
+
for path, source in code_files:
|
|
173
|
+
out.append(f'<div style="margin:6px 0 2px;color:#57606a;font-size:12px">{_html.escape(path.name)}</div>'
|
|
174
|
+
+ _highlight_html(source))
|
|
175
|
+
out.append("</div>")
|
|
176
|
+
return "".join(out)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _display_html(markup):
|
|
180
|
+
"""Send HTML to the notebook front-end (separate function so tests can intercept it)."""
|
|
181
|
+
from IPython.display import HTML, display
|
|
182
|
+
display(HTML(markup))
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _in_notebook():
|
|
186
|
+
"""True inside Jupyter, Colab or VS Code notebooks (an IPython kernel), False in a terminal / script."""
|
|
187
|
+
try:
|
|
188
|
+
from IPython import get_ipython
|
|
189
|
+
except ImportError:
|
|
190
|
+
return False
|
|
191
|
+
shell = get_ipython()
|
|
192
|
+
return shell is not None and hasattr(shell, "kernel")
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def show_experiment(domain, name, theory=True, code=True, helpers=False, output="auto"):
|
|
196
|
+
"""Display an experiment's existing theory and its complete source code. Nothing is executed.
|
|
197
|
+
|
|
198
|
+
Parameters
|
|
199
|
+
----------
|
|
200
|
+
domain : "genai", "xai", "dl" or "ml-ese"
|
|
201
|
+
name : experiment key, e.g. "chatbot", "shap", "grad-cam", "4CNN", "decision_tree"
|
|
202
|
+
(``dl_experiments.genai.list_experiments()`` etc. list the valid names)
|
|
203
|
+
theory : show the "Theory and Underlying Concepts" section
|
|
204
|
+
code : show the "Python Implementation" section
|
|
205
|
+
helpers : also show the supporting modules the experiment imports (e.g. the ``_nn.py`` toolkit)
|
|
206
|
+
output : "auto" (HTML in a notebook, text elsewhere), "html" or "text"
|
|
207
|
+
"""
|
|
208
|
+
if not theory and not code:
|
|
209
|
+
raise ValueError("Nothing to show: at least one of theory / code must be True.")
|
|
210
|
+
if output not in ("auto", "html", "text"):
|
|
211
|
+
raise ValueError("output must be 'auto', 'html' or 'text'.")
|
|
212
|
+
_, key, path = _resolve(domain, name)
|
|
213
|
+
files = _code_files(domain, name, helpers)
|
|
214
|
+
sources = [(f, f.read_text(encoding="utf-8")) for f in files]
|
|
215
|
+
theory_text = _theory_from_source(sources[0][1])
|
|
216
|
+
title = _title_from_theory(theory_text, key)
|
|
217
|
+
|
|
218
|
+
use_html = output == "html" or (output == "auto" and _in_notebook())
|
|
219
|
+
if use_html:
|
|
220
|
+
_display_html(_build_html(title, theory_text, sources, theory, code))
|
|
221
|
+
return None
|
|
222
|
+
bar = "=" * 72
|
|
223
|
+
print(f"{bar}\n{title}\n{bar}")
|
|
224
|
+
if theory:
|
|
225
|
+
print(f"\n--- Theory and Underlying Concepts {'-' * 38}\n\n{theory_text}\n")
|
|
226
|
+
if code:
|
|
227
|
+
for f, source in sources:
|
|
228
|
+
print(f"\n--- Python Implementation: {f.name} {'-' * max(3, 46 - len(f.name))}\n\n{source.rstrip()}\n")
|
|
229
|
+
return None
|
|
@@ -90,6 +90,11 @@ def run_experiment(key, **options):
|
|
|
90
90
|
return function(**options)
|
|
91
91
|
|
|
92
92
|
|
|
93
|
+
def source_path(key):
|
|
94
|
+
"""Path of the .py file that implements ``key`` (used by ``dl_experiments.show_experiment``)."""
|
|
95
|
+
return (_EXPERIMENTS_DIR / f"{_EXPERIMENTS[normalize_key(key)][1]}.py").resolve()
|
|
96
|
+
|
|
97
|
+
|
|
93
98
|
def show_source(key):
|
|
94
99
|
"""Print the source file of an experiment (the code is not executed)."""
|
|
95
100
|
key = normalize_key(key)
|
|
@@ -88,6 +88,11 @@ def run_experiment(key, **options):
|
|
|
88
88
|
return function(**options)
|
|
89
89
|
|
|
90
90
|
|
|
91
|
+
def source_path(key):
|
|
92
|
+
"""Path of the .py file that implements ``key`` (used by ``dl_experiments.show_experiment``)."""
|
|
93
|
+
return (_EXPERIMENTS_DIR / f"{_EXPERIMENTS[normalize_key(key)][1]}.py").resolve()
|
|
94
|
+
|
|
95
|
+
|
|
91
96
|
def show_source(key):
|
|
92
97
|
"""Print the source file of an experiment (the code is not executed)."""
|
|
93
98
|
key = normalize_key(key)
|
|
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
|
|
|
5
5
|
# ── Project metadata ────────────────────────────────────────────────────────
|
|
6
6
|
[project]
|
|
7
7
|
name = "dlspit" # UNIQUE name on PyPI
|
|
8
|
-
version = "2.
|
|
8
|
+
version = "2.3.0"
|
|
9
9
|
description = "A multi-domain experiment package: Deep Learning (MLP, CNN, ResNet, RNN, LSTM, Transformer, GAN, VAE), ML ESE Lab (regression, SVM, decision tree, random forest, XGBoost, k-means) Generative AI from scratch in NumPy (VAE, GAN, mini GPT, text-to-image, face swap, music, CycleGAN, chatbot, diffusion) and Explainable AI from scratch (SHAP, PDP, ICE, LIME, Grad-CAM, XRAI-style, Gradient x Input, LRP)."
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
license = { text = "MIT" }
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""Tests for ``show_experiment`` / ``get_theory`` / ``get_source`` (theory + code display for notebooks)."""
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import html
|
|
5
|
+
import importlib.util
|
|
6
|
+
import re
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import types
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
import dl_experiments
|
|
14
|
+
from conftest import subprocess_env
|
|
15
|
+
from dl_experiments import display, get_source, get_theory, show_experiment
|
|
16
|
+
from dl_experiments.dl import registry as dl_registry
|
|
17
|
+
from dl_experiments.genai import registry as genai_registry
|
|
18
|
+
from dl_experiments.ml_ese import registry as ml_registry
|
|
19
|
+
from dl_experiments.xai import registry as xai_registry
|
|
20
|
+
|
|
21
|
+
ALL = ([("genai", k, genai_registry.source_path(k)) for k in genai_registry.list_experiments()]
|
|
22
|
+
+ [("xai", k, xai_registry.source_path(k)) for k in xai_registry.list_experiments()]
|
|
23
|
+
+ [("dl", k, dl_registry._EXPERIMENTS_DIR / f) for k, f in dl_registry._SOURCE_FILES.items()]
|
|
24
|
+
+ [("ml-ese", k, ml_registry._EXPERIMENTS_DIR / f) for k, f in ml_registry._SOURCE_FILES.items()])
|
|
25
|
+
has_pygments = importlib.util.find_spec("pygments") is not None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def strip_markup(markup):
|
|
29
|
+
"""Visible text of an HTML fragment (tags removed, entities decoded)."""
|
|
30
|
+
return html.unescape(re.sub(r"<[^>]+>", "", markup))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@pytest.fixture
|
|
34
|
+
def captured(monkeypatch):
|
|
35
|
+
"""Intercept what would be sent to the notebook front-end."""
|
|
36
|
+
shown = []
|
|
37
|
+
monkeypatch.setattr(display, "_display_html", shown.append)
|
|
38
|
+
return shown
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ── theory extraction ────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
def test_every_experiment_has_theory_and_source():
|
|
44
|
+
assert len(ALL) == 9 + 10 + 13 + 8
|
|
45
|
+
for domain, key, path in ALL:
|
|
46
|
+
assert get_theory(domain, key).strip(), (domain, key)
|
|
47
|
+
assert get_source(domain, key) == path.read_text(encoding="utf-8"), (domain, key) # verbatim file content
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@pytest.mark.parametrize("domain,key,path", [a for a in ALL if a[0] in ("genai", "xai")])
|
|
51
|
+
def test_genai_and_xai_theory_is_the_module_docstring(domain, key, path):
|
|
52
|
+
assert get_theory(domain, key) == ast.get_docstring(ast.parse(path.read_text(encoding="utf-8")), clean=True)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_dl_and_mlese_theory_is_the_leading_comment_block():
|
|
56
|
+
text = get_theory("dl", "4CNN")
|
|
57
|
+
assert text.startswith("EXPERIMENT 4: Convolutional Neural Network (CNN) on MNIST") and "THEORY:" in text
|
|
58
|
+
assert "=====" not in text and not text.startswith("#") # banner and '# ' prefixes removed
|
|
59
|
+
assert "Decision Tree" in get_theory("ml-ese", "decision_tree").splitlines()[0]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_theory_text_is_the_real_existing_explanation():
|
|
63
|
+
chatbot = get_theory("genai", "chatbot")
|
|
64
|
+
assert "this is NOT a generative LLM" in chatbot and "bag-of-words" in chatbot
|
|
65
|
+
shap = get_theory("xai", "shap")
|
|
66
|
+
assert "phi_i" in shap and "Limitations" in shap and "background" in shap
|
|
67
|
+
assert get_theory("xai", "lrp") == get_theory("XAI", "LRP") # domain / name are case-insensitive
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@pytest.mark.parametrize("domain,name,expected_file", [("GenAI", "text_to_image", "text_to_image.py"), ("xai", "gradcam", "grad_cam.py"),
|
|
71
|
+
("xai", "7", "grad_cam.py"), ("genai", "jukebox", "music_generation.py"),
|
|
72
|
+
("DL", "4cnn", "exp4_cnn.py"), ("ml_ese", "SVM_Classifier", "svm_classifier.py")])
|
|
73
|
+
def test_aliases_resolve_to_the_right_file(domain, name, expected_file):
|
|
74
|
+
assert display._resolve(domain, name)[2].name == expected_file
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# ── invalid input ────────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
def test_invalid_domain_and_experiment_names():
|
|
80
|
+
with pytest.raises(ValueError, match="Invalid domain"):
|
|
81
|
+
show_experiment("nlp", "bert")
|
|
82
|
+
with pytest.raises(ValueError, match="Did you mean"):
|
|
83
|
+
show_experiment("xai", "shapp")
|
|
84
|
+
with pytest.raises(ValueError, match="Invalid XAI"):
|
|
85
|
+
get_theory("xai", "vae") # a GenAI name is not an XAI name
|
|
86
|
+
with pytest.raises(ValueError, match="Invalid experiment key"):
|
|
87
|
+
get_source("dl", "99Nothing")
|
|
88
|
+
with pytest.raises(ValueError, match="Invalid experiment key"):
|
|
89
|
+
get_theory("ml-ese", "unknown_model")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_invalid_options():
|
|
93
|
+
with pytest.raises(ValueError, match="at least one"):
|
|
94
|
+
show_experiment("xai", "pdp", theory=False, code=False)
|
|
95
|
+
with pytest.raises(ValueError, match="output"):
|
|
96
|
+
show_experiment("xai", "pdp", output="pdf")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# ── text output (terminal / script) ──────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
def test_text_output_has_title_then_theory_then_the_complete_source(capsys):
|
|
102
|
+
show_experiment("genai", "diffusion", output="text")
|
|
103
|
+
out = capsys.readouterr().out
|
|
104
|
+
source = get_source("genai", "diffusion")
|
|
105
|
+
theory = get_theory("genai", "diffusion")
|
|
106
|
+
assert out.index("Theory and Underlying Concepts") < out.index("Python Implementation")
|
|
107
|
+
assert theory in out and source.rstrip() in out # the complete file, unchanged
|
|
108
|
+
assert out.startswith("=" * 72 + "\nGenAI Experiment 9 - Denoising Diffusion")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_theory_only_and_code_only(capsys):
|
|
112
|
+
show_experiment("xai", "ice", code=False, output="text")
|
|
113
|
+
only_theory = capsys.readouterr().out
|
|
114
|
+
assert "Theory and Underlying Concepts" in only_theory and "Python Implementation" not in only_theory
|
|
115
|
+
assert "def calculate_ice" not in only_theory
|
|
116
|
+
show_experiment("xai", "ice", theory=False, output="text")
|
|
117
|
+
only_code = capsys.readouterr().out
|
|
118
|
+
assert "def calculate_ice" in only_code and "Theory and Underlying Concepts" not in only_code
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_auto_mode_prints_text_outside_a_notebook(capsys, captured):
|
|
122
|
+
assert show_experiment("xai", "lime") is None # returns nothing (no double display)
|
|
123
|
+
assert "Python Implementation" in capsys.readouterr().out and captured == []
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_works_without_ipython_and_pygments_installed():
|
|
127
|
+
code = ("import sys\nsys.modules['IPython'] = None\nsys.modules['pygments'] = None\n"
|
|
128
|
+
"from dl_experiments import show_experiment\n"
|
|
129
|
+
"show_experiment('xai', 'pdp', output='text')\n"
|
|
130
|
+
"import dl_experiments.display as d\nd._display_html = lambda m: print('HTML-LEN', len(m), '<pre' in m)\n"
|
|
131
|
+
"show_experiment('xai', 'pdp', output='html')\n")
|
|
132
|
+
r = subprocess.run([sys.executable, "-c", code], capture_output=True, text=True, env=subprocess_env(), timeout=60)
|
|
133
|
+
assert r.returncode == 0, r.stderr
|
|
134
|
+
assert "def calculate_pdp" in r.stdout and "HTML-LEN" in r.stdout and "True" in r.stdout # plain <pre> fallback
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# ── HTML output (notebook / Colab) ───────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
def test_html_output_contains_title_theory_and_the_unchanged_source(captured):
|
|
140
|
+
show_experiment("xai", "shap", output="html")
|
|
141
|
+
assert len(captured) == 1
|
|
142
|
+
markup = captured[0]
|
|
143
|
+
visible = strip_markup(markup)
|
|
144
|
+
assert "Theory and Underlying Concepts" in markup and "Python Implementation" in markup
|
|
145
|
+
assert markup.index("Theory and Underlying Concepts") < markup.index("Python Implementation")
|
|
146
|
+
assert get_theory("xai", "shap") in visible # theory shown unchanged
|
|
147
|
+
assert get_source("xai", "shap").rstrip("\n") in visible # the source, character for character
|
|
148
|
+
assert "<h2" in markup and "XAI Experiment 3" in visible
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@pytest.mark.skipif(not has_pygments, reason="pygments not installed")
|
|
152
|
+
def test_html_code_is_syntax_highlighted_with_inline_styles(captured):
|
|
153
|
+
show_experiment("genai", "gan", output="html")
|
|
154
|
+
markup = captured[0]
|
|
155
|
+
assert re.search(r'<span style="[^"]*font-weight: bold[^"]*">def</span>', markup) # keyword styled
|
|
156
|
+
assert re.search(r'<span style="[^"]*font-style: italic[^"]*"># ', markup) # comment styled
|
|
157
|
+
assert "<style" not in markup and "<link" not in markup and "<script" not in markup # inline styles only
|
|
158
|
+
assert display._highlight_html("x = 1 < 2\n").count("<") == 1
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def test_html_is_escaped_so_code_cannot_break_the_page(captured):
|
|
162
|
+
show_experiment("genai", "vae", output="html") # source contains '<', '>' and '&' characters
|
|
163
|
+
markup = captured[0]
|
|
164
|
+
assert "<script" not in markup.lower()
|
|
165
|
+
assert "<" in markup or ">" in markup
|
|
166
|
+
assert strip_markup(markup).count("def ") >= get_source("genai", "vae").count("def ")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def test_html_shows_only_what_was_requested(captured):
|
|
170
|
+
show_experiment("dl", "4CNN", code=False, output="html")
|
|
171
|
+
assert "Theory and Underlying Concepts" in captured[0] and "Python Implementation" not in captured[0]
|
|
172
|
+
show_experiment("dl", "4CNN", theory=False, output="html")
|
|
173
|
+
assert "Python Implementation" in captured[1] and "Theory and Underlying Concepts" not in captured[1]
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def test_notebook_is_detected_and_ipython_display_is_used(monkeypatch):
|
|
177
|
+
shown = []
|
|
178
|
+
ipython = types.ModuleType("IPython")
|
|
179
|
+
ipython.get_ipython = lambda: types.SimpleNamespace(kernel=object()) # what Jupyter / Colab provide
|
|
180
|
+
display_mod = types.ModuleType("IPython.display")
|
|
181
|
+
display_mod.HTML = lambda markup: ("HTML", markup)
|
|
182
|
+
display_mod.display = shown.append
|
|
183
|
+
monkeypatch.setitem(sys.modules, "IPython", ipython)
|
|
184
|
+
monkeypatch.setitem(sys.modules, "IPython.display", display_mod)
|
|
185
|
+
assert display._in_notebook() is True
|
|
186
|
+
show_experiment("xai", "regression") # output="auto" -> HTML in a notebook
|
|
187
|
+
assert len(shown) == 1 and shown[0][0] == "HTML" and "Theory and Underlying Concepts" in shown[0][1]
|
|
188
|
+
ipython.get_ipython = lambda: types.SimpleNamespace() # a terminal IPython has no kernel
|
|
189
|
+
assert display._in_notebook() is False
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# ── helpers (supporting modules) ─────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
def test_supporting_modules_are_optional_and_resolved_from_the_real_imports():
|
|
195
|
+
plain = get_source("xai", "ice")
|
|
196
|
+
with_helpers = get_source("xai", "ice", helpers=True)
|
|
197
|
+
assert with_helpers.startswith(plain) and len(with_helpers) > len(plain)
|
|
198
|
+
names = re.findall(r"# Supporting module: (\S+)", with_helpers)
|
|
199
|
+
assert names[:1] == ["_nn.py"] and {"_common.py", "pdp.py"} <= set(names) and len(names) == len(set(names))
|
|
200
|
+
assert "Supporting module" not in plain and "class Dense" not in plain and "class Dense" in with_helpers
|
|
201
|
+
assert "run_experiment" in with_helpers and with_helpers.count("def calculate_pdp") == 1
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def test_helpers_for_experiments_without_dependencies(capsys):
|
|
205
|
+
assert get_source("dl", "4CNN", helpers=True) == get_source("dl", "4CNN") # DL files import nothing local
|
|
206
|
+
show_experiment("genai", "vae", theory=False, helpers=True, output="text")
|
|
207
|
+
out = capsys.readouterr().out
|
|
208
|
+
assert "Python Implementation: vae.py" in out and "Python Implementation: _nn.py" in out and "Python Implementation: _viz.py" in out
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# ── showing an experiment never runs it ──────────────────────────────────────
|
|
212
|
+
|
|
213
|
+
def test_showing_does_not_import_or_execute_the_experiment():
|
|
214
|
+
code = ("import sys, time\nfrom dl_experiments import show_experiment\n"
|
|
215
|
+
"t = time.time()\n"
|
|
216
|
+
"for d, n in (('genai','diffusion'), ('genai','cyclegan'), ('xai','grad-cam'), ('xai','lrp')):\n"
|
|
217
|
+
" show_experiment(d, n, output='text')\n"
|
|
218
|
+
"bad = [m for m in ('dl_experiments.genai.diffusion','dl_experiments.genai.cyclegan','dl_experiments.xai.grad_cam','dl_experiments.xai.lrp') if m in sys.modules]\n"
|
|
219
|
+
"print('IMPORTED:', bad, 'SECONDS<2:', time.time() - t < 2)\n")
|
|
220
|
+
r = subprocess.run([sys.executable, "-c", code], capture_output=True, text=True, env=subprocess_env(), timeout=60)
|
|
221
|
+
assert r.returncode == 0, r.stderr
|
|
222
|
+
assert "IMPORTED: [] SECONDS<2: True" in r.stdout
|
|
223
|
+
assert "Chamfer distance to real data: 0." not in r.stdout # no experiment output appeared
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def test_public_names_are_exported():
|
|
227
|
+
for name in ("show_experiment", "get_theory", "get_source"):
|
|
228
|
+
assert name in dl_experiments.__all__ and callable(getattr(dl_experiments, name))
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
# ── a real Jupyter kernel (only when nbclient + ipykernel are installed) ─────
|
|
232
|
+
|
|
233
|
+
@pytest.mark.skipif(any(importlib.util.find_spec(m) is None for m in ("nbclient", "nbformat", "ipykernel")),
|
|
234
|
+
reason="needs nbclient, nbformat and ipykernel")
|
|
235
|
+
def test_real_notebook_cell_renders_theory_and_code_as_html(tmp_path):
|
|
236
|
+
import nbformat
|
|
237
|
+
from nbclient import NotebookClient
|
|
238
|
+
nb = nbformat.v4.new_notebook(cells=[nbformat.v4.new_code_cell(
|
|
239
|
+
"from dl_experiments import show_experiment\nshow_experiment('genai', 'chatbot')")])
|
|
240
|
+
NotebookClient(nb, timeout=120, kernel_name="python3", resources={"metadata": {"path": str(tmp_path)}}).execute()
|
|
241
|
+
outputs = nb.cells[0].outputs
|
|
242
|
+
assert len(outputs) == 1 and outputs[0].output_type == "display_data" and "text/html" in outputs[0].data
|
|
243
|
+
visible = strip_markup(outputs[0].data["text/html"])
|
|
244
|
+
assert "Theory and Underlying Concepts" in visible and get_source("genai", "chatbot").rstrip("\n") in visible
|
|
245
|
+
assert "text/plain" in outputs[0].data
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|