genai-spit 1.0.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.
- genai_spit-1.0.0/LICENSE +21 -0
- genai_spit-1.0.0/PKG-INFO +80 -0
- genai_spit-1.0.0/README.md +60 -0
- genai_spit-1.0.0/pyproject.toml +27 -0
- genai_spit-1.0.0/setup.cfg +4 -0
- genai_spit-1.0.0/src/genai_spit/__init__.py +68 -0
- genai_spit-1.0.0/src/genai_spit/cli.py +32 -0
- genai_spit-1.0.0/src/genai_spit/datasets.py +116 -0
- genai_spit-1.0.0/src/genai_spit/experiments/catalog.json +106 -0
- genai_spit-1.0.0/src/genai_spit/experiments/chatbot.py.txt +106 -0
- genai_spit-1.0.0/src/genai_spit/experiments/cyclegan.py.txt +112 -0
- genai_spit-1.0.0/src/genai_spit/experiments/deepfake.py.txt +129 -0
- genai_spit-1.0.0/src/genai_spit/experiments/diffusion.py.txt +145 -0
- genai_spit-1.0.0/src/genai_spit/experiments/gan.py.txt +118 -0
- genai_spit-1.0.0/src/genai_spit/experiments/music.py.txt +110 -0
- genai_spit-1.0.0/src/genai_spit/experiments/text_generation.py.txt +126 -0
- genai_spit-1.0.0/src/genai_spit/experiments/text_to_image.py.txt +124 -0
- genai_spit-1.0.0/src/genai_spit/experiments/vae.py.txt +149 -0
- genai_spit-1.0.0/src/genai_spit.egg-info/PKG-INFO +80 -0
- genai_spit-1.0.0/src/genai_spit.egg-info/SOURCES.txt +22 -0
- genai_spit-1.0.0/src/genai_spit.egg-info/dependency_links.txt +1 -0
- genai_spit-1.0.0/src/genai_spit.egg-info/entry_points.txt +2 -0
- genai_spit-1.0.0/src/genai_spit.egg-info/requires.txt +10 -0
- genai_spit-1.0.0/src/genai_spit.egg-info/top_level.txt +1 -0
genai_spit-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Amey Jagannath Sonvadkar
|
|
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.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: genai-spit
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: 9 Generative-AI lab experiments (VAE, GAN, text generation, text-to-image, DeepFake AE, music, CycleGAN, chatbot, diffusion) that run offline without pretrained models
|
|
5
|
+
Author: Amey Jagannath Sonvadkar
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: generative-ai,vae,gan,diffusion,cyclegan,education
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: numpy
|
|
12
|
+
Provides-Extra: all
|
|
13
|
+
Requires-Dist: torch; extra == "all"
|
|
14
|
+
Requires-Dist: matplotlib; extra == "all"
|
|
15
|
+
Requires-Dist: scikit-learn; extra == "all"
|
|
16
|
+
Provides-Extra: mnist
|
|
17
|
+
Requires-Dist: torch; extra == "mnist"
|
|
18
|
+
Requires-Dist: torchvision; extra == "mnist"
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# genai-spit
|
|
22
|
+
|
|
23
|
+
9 Generative-AI lab experiments that run **offline, without pretrained models or downloaded datasets** (every experiment creates its own data). For each one you can read the **title, theory and full Python source**, save it, run it, and create its datasets.
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install --upgrade "genai-spit[all]" # with torch, matplotlib, scikit-learn
|
|
29
|
+
pip install --upgrade genai-spit # code + theory only
|
|
30
|
+
```
|
|
31
|
+
Colab / Jupyter: `!pip install --upgrade "genai-spit[all]"`
|
|
32
|
+
|
|
33
|
+
## Command line
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
genai-spit --list # list the 9 experiments
|
|
37
|
+
genai-spit --show chatbot # title + theory + full source
|
|
38
|
+
genai-spit --theory vae # only theory
|
|
39
|
+
genai-spit --code diffusion # only code
|
|
40
|
+
genai-spit --save cyclegan --out cyclegan.py
|
|
41
|
+
genai-spit --save-all --out genai_code # all 9 (.py, .txt, theory)
|
|
42
|
+
genai-spit --data --out genai_data # create all datasets
|
|
43
|
+
genai-spit --all --out genai_all # code + theory + datasets in one folder
|
|
44
|
+
genai-spit --all --mnist # + MNIST (internet)
|
|
45
|
+
genai-spit --run gan # run an experiment
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Python
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from genai_spit import list_experiments, show_experiment, get_source, save_experiment, run_experiment, download_all
|
|
52
|
+
|
|
53
|
+
list_experiments()
|
|
54
|
+
show_experiment("chatbot") # title, theory, then the full Python source
|
|
55
|
+
code = get_source("diffusion")
|
|
56
|
+
save_experiment("vae", "vae.py")
|
|
57
|
+
run_experiment("cyclegan")
|
|
58
|
+
download_all("genai_all")
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Experiments
|
|
62
|
+
|
|
63
|
+
| # | Name (aliases) | Experiment |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| 1 | `vae` (autoencoder) | Autoencoder + Variational Autoencoder |
|
|
66
|
+
| 2 | `gan` | Basic GAN |
|
|
67
|
+
| 3 | `text_generation` (gpt2) | Tokenization, embedding, pre-train + fine-tune, temperature / top-k / top-p |
|
|
68
|
+
| 4 | `text_to_image` (dalle) | Text-to-image with a conditional VAE |
|
|
69
|
+
| 5 | `deepfake` (faceswap) | Shared encoder + two decoders, reconstruction analysis |
|
|
70
|
+
| 6 | `music` (jukebox) | Markov-chain music → WAV, waveform, spectrogram |
|
|
71
|
+
| 7 | `cyclegan` | Unpaired style transfer |
|
|
72
|
+
| 8 | `chatbot` | TF-IDF retrieval chatbot with context and exit loop |
|
|
73
|
+
| 9 | `diffusion` (ddpm) | DDPM, noise schedules, guided generation |
|
|
74
|
+
|
|
75
|
+
Source files: `src/genai_spit/experiments/*.py.txt`
|
|
76
|
+
|
|
77
|
+
See **PUBLISHING.md** for how this package is built and uploaded to PyPI.
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
MIT
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# genai-spit
|
|
2
|
+
|
|
3
|
+
9 Generative-AI lab experiments that run **offline, without pretrained models or downloaded datasets** (every experiment creates its own data). For each one you can read the **title, theory and full Python source**, save it, run it, and create its datasets.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install --upgrade "genai-spit[all]" # with torch, matplotlib, scikit-learn
|
|
9
|
+
pip install --upgrade genai-spit # code + theory only
|
|
10
|
+
```
|
|
11
|
+
Colab / Jupyter: `!pip install --upgrade "genai-spit[all]"`
|
|
12
|
+
|
|
13
|
+
## Command line
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
genai-spit --list # list the 9 experiments
|
|
17
|
+
genai-spit --show chatbot # title + theory + full source
|
|
18
|
+
genai-spit --theory vae # only theory
|
|
19
|
+
genai-spit --code diffusion # only code
|
|
20
|
+
genai-spit --save cyclegan --out cyclegan.py
|
|
21
|
+
genai-spit --save-all --out genai_code # all 9 (.py, .txt, theory)
|
|
22
|
+
genai-spit --data --out genai_data # create all datasets
|
|
23
|
+
genai-spit --all --out genai_all # code + theory + datasets in one folder
|
|
24
|
+
genai-spit --all --mnist # + MNIST (internet)
|
|
25
|
+
genai-spit --run gan # run an experiment
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Python
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from genai_spit import list_experiments, show_experiment, get_source, save_experiment, run_experiment, download_all
|
|
32
|
+
|
|
33
|
+
list_experiments()
|
|
34
|
+
show_experiment("chatbot") # title, theory, then the full Python source
|
|
35
|
+
code = get_source("diffusion")
|
|
36
|
+
save_experiment("vae", "vae.py")
|
|
37
|
+
run_experiment("cyclegan")
|
|
38
|
+
download_all("genai_all")
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Experiments
|
|
42
|
+
|
|
43
|
+
| # | Name (aliases) | Experiment |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| 1 | `vae` (autoencoder) | Autoencoder + Variational Autoencoder |
|
|
46
|
+
| 2 | `gan` | Basic GAN |
|
|
47
|
+
| 3 | `text_generation` (gpt2) | Tokenization, embedding, pre-train + fine-tune, temperature / top-k / top-p |
|
|
48
|
+
| 4 | `text_to_image` (dalle) | Text-to-image with a conditional VAE |
|
|
49
|
+
| 5 | `deepfake` (faceswap) | Shared encoder + two decoders, reconstruction analysis |
|
|
50
|
+
| 6 | `music` (jukebox) | Markov-chain music → WAV, waveform, spectrogram |
|
|
51
|
+
| 7 | `cyclegan` | Unpaired style transfer |
|
|
52
|
+
| 8 | `chatbot` | TF-IDF retrieval chatbot with context and exit loop |
|
|
53
|
+
| 9 | `diffusion` (ddpm) | DDPM, noise schedules, guided generation |
|
|
54
|
+
|
|
55
|
+
Source files: `src/genai_spit/experiments/*.py.txt`
|
|
56
|
+
|
|
57
|
+
See **PUBLISHING.md** for how this package is built and uploaded to PyPI.
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
MIT
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "genai-spit"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "9 Generative-AI lab experiments (VAE, GAN, text generation, text-to-image, DeepFake AE, music, CycleGAN, chatbot, diffusion) that run offline without pretrained models"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [{name = "Amey Jagannath Sonvadkar"}]
|
|
13
|
+
keywords = ["generative-ai", "vae", "gan", "diffusion", "cyclegan", "education"]
|
|
14
|
+
dependencies = ["numpy"]
|
|
15
|
+
|
|
16
|
+
[project.optional-dependencies]
|
|
17
|
+
all = ["torch", "matplotlib", "scikit-learn"]
|
|
18
|
+
mnist = ["torch", "torchvision"]
|
|
19
|
+
|
|
20
|
+
[project.scripts]
|
|
21
|
+
genai-spit = "genai_spit.cli:main"
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.packages.find]
|
|
24
|
+
where = ["src"]
|
|
25
|
+
|
|
26
|
+
[tool.setuptools.package-data]
|
|
27
|
+
genai_spit = ["experiments/*.json", "experiments/*.txt"]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""genai-spit — 9 Generative-AI lab experiments (offline, no pretrained models).
|
|
2
|
+
|
|
3
|
+
>>> from genai_spit import list_experiments, show_experiment
|
|
4
|
+
>>> list_experiments()
|
|
5
|
+
>>> show_experiment("chatbot") # title, theory, then the full Python source
|
|
6
|
+
"""
|
|
7
|
+
import json, runpy, tempfile
|
|
8
|
+
from importlib import resources
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
__version__ = "1.0.0"
|
|
12
|
+
_CAT = json.loads(resources.files("genai_spit").joinpath("experiments", "catalog.json").read_text(encoding="utf-8"))
|
|
13
|
+
|
|
14
|
+
def _find(name):
|
|
15
|
+
q = str(name).lower().replace("-", "_").replace(" ", "_")
|
|
16
|
+
for e in _CAT:
|
|
17
|
+
if q in (e["key"], str(e["number"])) or q in e["aliases"]:
|
|
18
|
+
return e
|
|
19
|
+
raise ValueError(f"unknown experiment '{name}'. Choose from: " + ", ".join(e["key"] for e in _CAT))
|
|
20
|
+
|
|
21
|
+
def list_experiments():
|
|
22
|
+
"""Print number, name and title of the 9 experiments."""
|
|
23
|
+
for e in _CAT:
|
|
24
|
+
print(f"{e['number']}. {e['key']:<16} {e['title']}")
|
|
25
|
+
|
|
26
|
+
def get_source(name):
|
|
27
|
+
"""Full Python source of an experiment (str)."""
|
|
28
|
+
return resources.files("genai_spit").joinpath("experiments", _find(name)["file"]).read_text(encoding="utf-8")
|
|
29
|
+
|
|
30
|
+
def get_theory(name):
|
|
31
|
+
return _find(name)["theory"]
|
|
32
|
+
|
|
33
|
+
def show_experiment(name, theory=True, source=True):
|
|
34
|
+
"""Print title, theory and full Python source."""
|
|
35
|
+
e = _find(name); bar = "=" * 90
|
|
36
|
+
print(f"{bar}\nExperiment {e['number']}: {e['title']}\n{bar}")
|
|
37
|
+
if theory: print("\nTHEORY\n------\n" + e["theory"] + "\n")
|
|
38
|
+
if source: print("SOURCE\n------\n" + get_source(name))
|
|
39
|
+
|
|
40
|
+
def save_experiment(name, path=None):
|
|
41
|
+
"""Save the source of one experiment as a .py (or .txt) file."""
|
|
42
|
+
e = _find(name); path = Path(path or f"{e['key']}.py")
|
|
43
|
+
path.write_text(get_source(name), encoding="utf-8"); return path
|
|
44
|
+
|
|
45
|
+
def save_all(folder="genai_code"):
|
|
46
|
+
"""Save all 9 experiments as .py, .txt and _theory.txt files."""
|
|
47
|
+
folder = Path(folder); folder.mkdir(parents=True, exist_ok=True)
|
|
48
|
+
for e in _CAT:
|
|
49
|
+
stem = f"{e['number']:02d}_{e['key']}"
|
|
50
|
+
save_experiment(e["key"], folder / f"{stem}.py"); save_experiment(e["key"], folder / f"{stem}.txt")
|
|
51
|
+
(folder / f"{stem}_theory.txt").write_text(f"{e['title']}\n\n{e['theory']}\n", encoding="utf-8")
|
|
52
|
+
return folder
|
|
53
|
+
|
|
54
|
+
def run_experiment(name):
|
|
55
|
+
"""Run an experiment as a normal Python script."""
|
|
56
|
+
with tempfile.TemporaryDirectory() as d:
|
|
57
|
+
runpy.run_path(str(save_experiment(name, Path(d) / "experiment.py")), run_name="__main__")
|
|
58
|
+
|
|
59
|
+
def download_data(folder="genai_data", mnist=False):
|
|
60
|
+
"""Create every dataset used by the experiments (mnist=True also downloads MNIST, needs internet)."""
|
|
61
|
+
from . import datasets
|
|
62
|
+
return datasets.save_all(folder, mnist=mnist)
|
|
63
|
+
|
|
64
|
+
def download_all(folder="genai_all", mnist=False):
|
|
65
|
+
"""Everything in one call: code + theory + datasets."""
|
|
66
|
+
folder = Path(folder)
|
|
67
|
+
save_all(folder / "code"); download_data(folder / "data", mnist=mnist)
|
|
68
|
+
print(f"Everything saved in: {folder.resolve()}"); return folder
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""genai-spit [options]"""
|
|
2
|
+
import argparse, sys
|
|
3
|
+
import genai_spit as g
|
|
4
|
+
|
|
5
|
+
def main(argv=None):
|
|
6
|
+
p = argparse.ArgumentParser(prog="genai-spit", description="9 Generative-AI lab experiments (offline, no pretrained models)")
|
|
7
|
+
p.add_argument("--list", action="store_true", help="list the experiments")
|
|
8
|
+
p.add_argument("--show", metavar="NAME", help="print title, theory and source (name or number)")
|
|
9
|
+
p.add_argument("--theory", metavar="NAME", help="print only the theory")
|
|
10
|
+
p.add_argument("--code", metavar="NAME", help="print only the source code")
|
|
11
|
+
p.add_argument("--save", metavar="NAME", help="save one experiment as a .py file")
|
|
12
|
+
p.add_argument("--save-all", action="store_true", help="save all experiments")
|
|
13
|
+
p.add_argument("--data", action="store_true", help="create all datasets")
|
|
14
|
+
p.add_argument("--all", action="store_true", help="code + theory + datasets in one folder")
|
|
15
|
+
p.add_argument("--mnist", action="store_true", help="with --data / --all: also download MNIST (internet)")
|
|
16
|
+
p.add_argument("--run", metavar="NAME", help="run an experiment")
|
|
17
|
+
p.add_argument("--out", metavar="PATH", help="output file / folder")
|
|
18
|
+
a = p.parse_args(argv)
|
|
19
|
+
did = False
|
|
20
|
+
if a.list: g.list_experiments(); did = True
|
|
21
|
+
if a.show: g.show_experiment(a.show); did = True
|
|
22
|
+
if a.theory: g.show_experiment(a.theory, source=False); did = True
|
|
23
|
+
if a.code: print(g.get_source(a.code)); did = True
|
|
24
|
+
if a.save: print("saved", g.save_experiment(a.save, a.out)); did = True
|
|
25
|
+
if a.save_all: print("saved in", g.save_all(a.out or "genai_code")); did = True
|
|
26
|
+
if a.data: g.download_data(a.out or "genai_data", mnist=a.mnist); did = True
|
|
27
|
+
if a.all: g.download_all(a.out or "genai_all", mnist=a.mnist); did = True
|
|
28
|
+
if a.run: g.run_experiment(a.run); did = True
|
|
29
|
+
if not did: g.list_experiments()
|
|
30
|
+
|
|
31
|
+
if __name__ == "__main__":
|
|
32
|
+
main()
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Dataset generators used by the 9 experiments (all offline), plus optional standard datasets."""
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
# ---------------- Exp 1, 2: shapes ----------------
|
|
7
|
+
def shapes(n=3000, size=16, seed=0):
|
|
8
|
+
rng = np.random.default_rng(seed); yy, xx = np.mgrid[0:size, 0:size]
|
|
9
|
+
X = np.zeros((n, size, size), np.float32); y = rng.integers(0, 3, n)
|
|
10
|
+
for i in range(n):
|
|
11
|
+
r = rng.uniform(3, 5); cx, cy = rng.uniform(r, size - r, 2)
|
|
12
|
+
if y[i] == 0: m = (xx - cx)**2 + (yy - cy)**2 <= r*r
|
|
13
|
+
elif y[i] == 1: m = (abs(xx - cx) <= 0.8*r) & (abs(yy - cy) <= 0.8*r)
|
|
14
|
+
else: m = ((abs(xx - cx) <= 0.3*r) & (abs(yy - cy) <= r)) | ((abs(yy - cy) <= 0.3*r) & (abs(xx - cx) <= r))
|
|
15
|
+
X[i][m] = 1.0
|
|
16
|
+
return X, y
|
|
17
|
+
|
|
18
|
+
# ---------------- Exp 4: coloured shapes with captions ----------------
|
|
19
|
+
def coloured_shapes(n=6000, size=16, seed=0):
|
|
20
|
+
SH, CO, SI = ["circle", "square", "plus"], ["red", "green", "blue"], ["small", "big"]
|
|
21
|
+
rng = np.random.default_rng(seed); yy, xx = np.mgrid[0:size, 0:size]; imgs, caps = [], []
|
|
22
|
+
for _ in range(n):
|
|
23
|
+
sh, co, si = rng.choice(SH), rng.choice(CO), rng.choice(SI)
|
|
24
|
+
r = rng.uniform(2.5, 3.5) if si == "small" else rng.uniform(4.5, 5.5); cx, cy = rng.uniform(r, size - r, 2)
|
|
25
|
+
if sh == "circle": m = (xx-cx)**2 + (yy-cy)**2 <= r*r
|
|
26
|
+
elif sh == "square": m = (abs(xx-cx) <= 0.8*r) & (abs(yy-cy) <= 0.8*r)
|
|
27
|
+
else: m = ((abs(xx-cx) <= 0.3*r) & (abs(yy-cy) <= r)) | ((abs(yy-cy) <= 0.3*r) & (abs(xx-cx) <= r))
|
|
28
|
+
img = np.zeros((size, size, 3), np.float32); img[m, CO.index(co)] = 1.0
|
|
29
|
+
imgs.append(img); caps.append(f"{si} {co} {sh}")
|
|
30
|
+
return np.stack(imgs), np.array(caps)
|
|
31
|
+
|
|
32
|
+
# ---------------- Exp 5: two synthetic people ----------------
|
|
33
|
+
def face(person, expr, pose, S=24):
|
|
34
|
+
yy, xx = np.mgrid[0:S, 0:S]; img = np.zeros((S, S), np.float32); cx, cy = 12, 12
|
|
35
|
+
skin = (xx-cx)**2 + (yy-cy)**2 <= 100 if person == "A" else (abs(xx-cx) <= 9) & (abs(yy-cy) <= 10)
|
|
36
|
+
img[skin] = 0.8; fx = cx + pose; er = 1.2 if person == "A" else 2.0
|
|
37
|
+
for ex in (fx - 4, fx + 4): img[(xx-ex)**2 + (yy-9)**2 <= er**2] = 0.05
|
|
38
|
+
if person == "B":
|
|
39
|
+
img[(abs(yy-6) <= 0.6) & (abs(xx-fx) <= 7) & (abs(xx-fx) >= 1.5)] = 0.05; img[skin & (yy >= 18)] = 0.35
|
|
40
|
+
mouth_y = 16 - expr * 0.12 * (xx-fx)**2 + expr * 1.5
|
|
41
|
+
img[(abs(yy-mouth_y) <= 0.7) & (abs(xx-fx) <= 4)] = 0.05
|
|
42
|
+
return img
|
|
43
|
+
|
|
44
|
+
def faces(person, n=1500, seed=1):
|
|
45
|
+
rng = np.random.default_rng(seed); P = np.c_[rng.uniform(-1, 1, n), rng.uniform(-3, 3, n)]
|
|
46
|
+
return np.stack([face(person, e, p) for e, p in P]), P
|
|
47
|
+
|
|
48
|
+
# ---------------- Exp 7: light / dark domains (unpaired) ----------------
|
|
49
|
+
def light_dark(n=2000, S=16, seed=1, style="light"):
|
|
50
|
+
rng = np.random.default_rng(seed); yy, xx = np.mgrid[0:S, 0:S]; out = []
|
|
51
|
+
for _ in range(n):
|
|
52
|
+
r = rng.uniform(4, 6.5); sh = rng.integers(2); cx, cy = rng.uniform(r, S - r, 2)
|
|
53
|
+
m = (xx-cx)**2 + (yy-cy)**2 <= r*r if sh == 0 else (abs(xx-cx) <= 0.85*r) & (abs(yy-cy) <= 0.85*r)
|
|
54
|
+
img = m.astype(np.float32); out.append(img if style == "light" else 1 - img)
|
|
55
|
+
return np.stack(out)
|
|
56
|
+
|
|
57
|
+
# ---------------- Exp 9: 2-D data ----------------
|
|
58
|
+
def two_moons(n=4000, noise=0.05, seed=0):
|
|
59
|
+
rng = np.random.default_rng(seed); t = rng.random(n) * np.pi; h = n // 2
|
|
60
|
+
X = np.r_[np.c_[np.cos(t[:h]), np.sin(t[:h])], np.c_[1 - np.cos(t[h:]), 0.5 - np.sin(t[h:])]] + rng.normal(0, noise, (n, 2))
|
|
61
|
+
return (X - X.mean(0)) / X.std(0)
|
|
62
|
+
|
|
63
|
+
def three_clusters(n=3000, seed=0):
|
|
64
|
+
rng = np.random.default_rng(seed); C = np.array([[-2.0, 0.0], [2.0, 0.0], [0.0, 2.5]])
|
|
65
|
+
y = rng.integers(0, 3, n); return C[y] + 0.35 * rng.standard_normal((n, 2)), y
|
|
66
|
+
|
|
67
|
+
# ---------------- Exp 3, 6, 8: text / music / chatbot data ----------------
|
|
68
|
+
GENERAL_TEXT = ("the sun rises in the east and sets in the west. people walk to work in the morning.\n"
|
|
69
|
+
"children play in the park after school. the weather is cool and pleasant today.\n"
|
|
70
|
+
"we read books to learn new things. the market is busy in the evening. birds sing in the trees.\n"
|
|
71
|
+
"my friend likes to cook food at home. the train arrives at the station on time.\n")
|
|
72
|
+
AI_TEXT = ("artificial intelligence is the science of making machines intelligent.\n"
|
|
73
|
+
"machine learning is a branch of artificial intelligence that learns patterns from data.\n"
|
|
74
|
+
"deep learning uses neural networks with many layers. a neural network learns by updating its weights.\n"
|
|
75
|
+
"generative models create new data such as text, images and music.\n"
|
|
76
|
+
"a language model predicts the next token in a sentence. transformers use attention to understand context.\n"
|
|
77
|
+
"training a model means reducing the loss on the training data.\n")
|
|
78
|
+
MELODIES = {"classical": [60, 62, 64, 65, 67, 65, 64, 62, 60, 64, 67, 72, 71, 69, 67, 65, 64, 62, 64, 60,
|
|
79
|
+
67, 69, 71, 72, 71, 69, 67, 65, 64, 65, 67, 64, 62, 60],
|
|
80
|
+
"jazz": [60, 63, 65, 66, 67, 70, 72, 70, 67, 66, 65, 63, 60, 63, 67, 70, 67, 65, 63, 65,
|
|
81
|
+
66, 67, 63, 60, 70, 72, 75, 72, 70, 67, 63, 60]}
|
|
82
|
+
CHATBOT_KB = {
|
|
83
|
+
"what is artificial intelligence": "Artificial intelligence is the science of making machines perform tasks that need human intelligence.",
|
|
84
|
+
"what is machine learning": "Machine learning lets computers learn patterns from data instead of being explicitly programmed.",
|
|
85
|
+
"what is deep learning": "Deep learning uses neural networks with many layers to learn features directly from data.",
|
|
86
|
+
"what is a gan generative adversarial network": "A GAN has a generator that makes fake data and a discriminator that tries to tell real from fake.",
|
|
87
|
+
"what is a vae variational autoencoder": "A VAE encodes data to a distribution in latent space and decodes samples from it.",
|
|
88
|
+
"what is a diffusion model": "A diffusion model adds noise step by step and learns to remove it to generate new samples.",
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
def save_all(folder="genai_ese_data", mnist=False):
|
|
92
|
+
"""Create every dataset used by the 9 experiments and save it in `folder`."""
|
|
93
|
+
f = Path(folder); f.mkdir(parents=True, exist_ok=True)
|
|
94
|
+
X, y = shapes(); np.savez_compressed(f / "exp1_exp2_shapes.npz", X=X, y=y)
|
|
95
|
+
X, c = coloured_shapes(); np.savez_compressed(f / "exp4_coloured_shapes.npz", X=X, captions=c)
|
|
96
|
+
(XA, PA), (XB, PB) = faces("A", seed=1), faces("B", seed=2)
|
|
97
|
+
np.savez_compressed(f / "exp5_faces_A_B.npz", XA=XA, params_A=PA, XB=XB, params_B=PB)
|
|
98
|
+
np.savez_compressed(f / "exp7_light_dark.npz", A=light_dark(seed=1, style="light"), B=light_dark(seed=2, style="dark"))
|
|
99
|
+
X3, y3 = three_clusters()
|
|
100
|
+
np.savez_compressed(f / "exp9_2d_data.npz", two_moons=two_moons(), clusters=X3, cluster_labels=y3)
|
|
101
|
+
(f / "exp3_general_text.txt").write_text(GENERAL_TEXT); (f / "exp3_ai_text.txt").write_text(AI_TEXT)
|
|
102
|
+
(f / "exp6_melodies.json").write_text(json.dumps(MELODIES, indent=1))
|
|
103
|
+
(f / "exp8_chatbot_kb.json").write_text(json.dumps(CHATBOT_KB, indent=1))
|
|
104
|
+
try: # bundled inside scikit-learn, no internet needed
|
|
105
|
+
from sklearn.datasets import load_digits
|
|
106
|
+
d = load_digits(); np.savez_compressed(f / "sklearn_digits_8x8.npz", X=(d.images / 16).astype(np.float32), y=d.target)
|
|
107
|
+
except ImportError:
|
|
108
|
+
print("scikit-learn not installed: skipped sklearn digits")
|
|
109
|
+
if mnist: # standard dataset, needs internet
|
|
110
|
+
from torchvision import datasets as tvd
|
|
111
|
+
m = tvd.MNIST(str(f / "mnist_raw"), train=True, download=True)
|
|
112
|
+
np.savez_compressed(f / "mnist_train.npz", X=m.data.numpy(), y=m.targets.numpy())
|
|
113
|
+
for p in sorted(f.glob("*")):
|
|
114
|
+
if p.is_file(): print(f" {p.name:<32} {p.stat().st_size/1024:8.1f} KB")
|
|
115
|
+
return f
|
|
116
|
+
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"number": 1,
|
|
4
|
+
"key": "vae",
|
|
5
|
+
"aliases": [
|
|
6
|
+
"autoencoder",
|
|
7
|
+
"ae",
|
|
8
|
+
"1"
|
|
9
|
+
],
|
|
10
|
+
"title": "Autoencoder + Variational Autoencoder (VAE)",
|
|
11
|
+
"theory": "# ESE Practice — Exp 1: Autoencoder + Variational Autoencoder (VAE)\nNo pretrained model, no internet (default). Runs on CPU in about 1 minute.\n\nWhat to remember\n* AE: x → encoder → z → decoder → x̂, loss = reconstruction (BCE/MSE).\n* VAE: the encoder outputs μ and log σ²; reparameterization z = μ + σ·ε, with ε ~ N(0, 1).\n* VAE loss = reconstruction + KL, KL = −0.5 · Σ(1 + logσ² − μ² − σ²).\n* Generate: sample z ~ N(0, I) → decoder.\n\n## Dataset — 3 options (change DATA in the next cell)\nDATA = | What it is | Internet?\n\"shapes\" (default, exam-safe) | Images drawn with NumPy: circle / square / plus at random size and position | No\n\"digits\" | sklearn.datasets.load_digits(): 1,797 handwritten digits, 8×8, ships inside scikit-learn | No\n\"mnist\" | Standard MNIST 28×28 via torchvision | Yes\n\nThe rest of the code does not change: it only uses IMG (side length) and X (N × IMG × IMG, values 0–1).",
|
|
12
|
+
"file": "vae.py.txt"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"number": 2,
|
|
16
|
+
"key": "gan",
|
|
17
|
+
"aliases": [
|
|
18
|
+
"2"
|
|
19
|
+
],
|
|
20
|
+
"title": "Basic GAN",
|
|
21
|
+
"theory": "# ESE Practice — Exp 2: Basic GAN\nNo pretrained model, no internet (default). Runs on CPU in about 1–2 minutes.\n\nWhat to remember\n* Generator G: noise z → fake image (tanh output, so real images are scaled to −1…1).\n* Discriminator D: image → probability of being real (here a logit, used with BCEWithLogitsLoss).\n* D step: real → label 1, fake → label 0. G step: fake → label 1 (fool D). Use fake.detach() in the D step.\n* Equilibrium: D(x) ≈ D(G(z)) ≈ 0.5, L_D ≈ 1.386, L_G ≈ 0.693. Mode collapse: all samples look alike.\n\n## Dataset — 3 options (change DATA in the next cell)\nDATA = | What it is | Internet?\n\"shapes\" (default, exam-safe) | Images drawn with NumPy: circle / square / plus at random size and position | No\n\"digits\" | sklearn.datasets.load_digits(): 1,797 handwritten digits, 8×8, ships inside scikit-learn | No\n\"mnist\" | Standard MNIST 28×28 via torchvision | Yes\n\nThe rest of the code does not change: it only uses IMG (side length) and X (N × IMG × IMG, values 0–1).",
|
|
22
|
+
"file": "gan.py.txt"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"number": 3,
|
|
26
|
+
"key": "text_generation",
|
|
27
|
+
"aliases": [
|
|
28
|
+
"gpt2",
|
|
29
|
+
"gpt",
|
|
30
|
+
"finetune",
|
|
31
|
+
"3"
|
|
32
|
+
],
|
|
33
|
+
"title": "Text generation: tokenization, embedding, pre-train + fine-tune, sampling (GPT-2 idea)",
|
|
34
|
+
"theory": "# ESE Practice — Exp 3: Text generation (GPT-2 idea, without a pretrained model)\nThe lab fine-tuned DistilGPT2. In the ESE no pretrained model is allowed, so the same pipeline is built from scratch with a small character-level language model:\n\nLab concept | ESE version\nTokenization (BPE) | Character tokenization: char → id\nEmbedding | nn.Embedding(vocab, 32): id → vector\nGPT-2 Transformer | Small GRU language model (option: tiny Transformer layer)\nPre-training + fine-tuning | Train on general text, then continue training on AI text\nCausal LM loss, perplexity | Cross-entropy of the next character, perplexity = e^loss\nTemperature / top-k / top-p | Written by hand in sample()\n\nText is typed inside the code, so there is no dataset and no internet. Runs on CPU in about 1 minute.",
|
|
35
|
+
"file": "text_generation.py.txt"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"number": 4,
|
|
39
|
+
"key": "text_to_image",
|
|
40
|
+
"aliases": [
|
|
41
|
+
"dalle",
|
|
42
|
+
"dalle_mini",
|
|
43
|
+
"4"
|
|
44
|
+
],
|
|
45
|
+
"title": "Text-to-image with a conditional VAE (DALL·E Mini idea)",
|
|
46
|
+
"theory": "# ESE Practice — Exp 4: Text-to-image generation (DALL·E Mini idea, without a pretrained model)\nDALL·E Mini needs huge pretrained weights. The ESE version builds a tiny text-to-image model from scratch:\n* Images: coloured shapes drawn with NumPy (circle / square / plus × red / green / blue × small / big).\n* Text prompt → condition vector: each known word switches on one position, a multi-hot \"text embedding\". Attributes the prompt does not mention are chosen at random, so short prompts give varied images.\n* Model: a Conditional VAE (CVAE). The decoder receives [z, text vector], so the prompt controls *what* is drawn and z controls *how* (position, size details).\n* Studies: richer prompts, random seed, and \"temperature\" = scale of z.\n\nNo dataset, no internet. Runs on CPU in about 1 minute.",
|
|
47
|
+
"file": "text_to_image.py.txt"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"number": 5,
|
|
51
|
+
"key": "deepfake",
|
|
52
|
+
"aliases": [
|
|
53
|
+
"deepfake_ae",
|
|
54
|
+
"faceswap",
|
|
55
|
+
"5"
|
|
56
|
+
],
|
|
57
|
+
"title": "Autoencoder DeepFake: shared encoder + two decoders, reconstruction analysis",
|
|
58
|
+
"theory": "# ESE Practice — Exp 5: Autoencoder DeepFake demo (face swap + reconstruction analysis)\nNo pretrained model, no internet: the faces are drawn with NumPy, so the experiment runs anywhere in about 1 minute.\n\nWhat to remember\n* Reconstruction system: image → preprocess (grayscale, resize, normalise) → autoencoder → reconstruction → error (MSE / PSNR) → show side by side.\n* Face swap = one shared encoder + two decoders. Train A → Enc → Dec_A → A and B → Enc → Dec_B → B.\n * The shared encoder learns *pose / expression*; each decoder learns one *identity*.\n * Swap: Dec_B(Enc(A)) gives A's expression and pose with B's face.",
|
|
59
|
+
"file": "deepfake.py.txt"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"number": 6,
|
|
63
|
+
"key": "music",
|
|
64
|
+
"aliases": [
|
|
65
|
+
"jukebox",
|
|
66
|
+
"music_generation",
|
|
67
|
+
"6"
|
|
68
|
+
],
|
|
69
|
+
"title": "AI music generation (Markov chain) + WAV, waveform, spectrogram (Jukebox idea)",
|
|
70
|
+
"theory": "# ESE Practice — Exp 6: AI music generation (Jukebox idea, without a pretrained model)\nJukebox is a huge pretrained model. The ESE version learns from a melody written in code and generates new music, then does exactly the lab's audio analysis:\n\n1. \"Training data\": short melodies (note sequences) per genre, typed in code.\n2. Model: a Markov chain that learns P(next note | current note) by counting transitions. It is a tiny autoregressive model, the same idea as Jukebox's prior (next token given previous tokens).\n3. Conditioning: genre chooses the training melody, the scale and the tempo; mood = tempo + temperature.\n4. Synthesis: note → frequency f = 440·2^((m−69)/12) → sine wave + harmonics + envelope → WAV with Python's built-in wave module.\n5. Analysis: play the file, verify it, plot the waveform and the spectrogram, and compare genres.\n\nNo dataset, no internet, only NumPy and Matplotlib.",
|
|
71
|
+
"file": "music.py.txt"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"number": 7,
|
|
75
|
+
"key": "cyclegan",
|
|
76
|
+
"aliases": [
|
|
77
|
+
"style_transfer",
|
|
78
|
+
"7"
|
|
79
|
+
],
|
|
80
|
+
"title": "CycleGAN unpaired style transfer",
|
|
81
|
+
"theory": "# ESE Practice — Exp 7: CycleGAN for unpaired style transfer (small, offline)\nHorse2Zebra cannot be used offline. The ESE version keeps the *same algorithm* with two image domains drawn in code:\n* Domain A = white shapes on a dark background (like \"day / summer photos\")\n* Domain B = dark shapes on a light background (like \"night / winter photos\": same kind of content, different *style*)\n\nThe two sets are generated independently (unpaired). CycleGAN has to learn A ↔ B without being told which image matches which.\n\nWhat to remember\n* 2 generators G_AB, G_BA and 2 discriminators D_A, D_B.\n* Adversarial loss (LSGAN): D(real) → 1, D(fake) → 0; the generators want D(fake) → 1.\n* Cycle loss: |G_BA(G_AB(a)) − a| + |G_AB(G_BA(b)) − b|, weighted by λ_cycle (10 in the lab). Optional identity loss: |G_AB(b) − b|.\n* Adam, lr = 0.0002, β = (0.5, 0.999), as in the lab.\n\nRuns on CPU in about 1–2 minutes.",
|
|
82
|
+
"file": "cyclegan.py.txt"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"number": 8,
|
|
86
|
+
"key": "chatbot",
|
|
87
|
+
"aliases": [
|
|
88
|
+
"8"
|
|
89
|
+
],
|
|
90
|
+
"title": "Chatbot: tokenizer, TF-IDF + cosine similarity, context memory, exit loop",
|
|
91
|
+
"theory": "# ESE Practice — Exp 8: Chatbot (without a pretrained Transformer)\nDistilGPT2 is pretrained, so it is not allowed. The ESE chatbot is built from scratch while keeping the lab's pipeline steps:\n\nLab step | ESE version\nLoad tokenizer + model | Own tokenizer (lowercase, words, stop-words) + a knowledge base of Q&A pairs typed in code\nTokenize the input | tokenize()\nModel produces a response | TF-IDF vectors + cosine similarity: pick the best-matching answer (retrieval chatbot)\nControlled sampling | Confidence threshold + fallback reply; small random choice between equivalent replies\nContext awareness | Remembers the last topic: \"tell me more\" or \"what about it\" continue the previous topic\nLoop until exit | while True with input()\n\nEverything uses NumPy only. No internet.",
|
|
92
|
+
"file": "chatbot.py.txt"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"number": 9,
|
|
96
|
+
"key": "diffusion",
|
|
97
|
+
"aliases": [
|
|
98
|
+
"ddpm",
|
|
99
|
+
"stable_diffusion",
|
|
100
|
+
"9"
|
|
101
|
+
],
|
|
102
|
+
"title": "Diffusion model (DDPM): forward/reverse process, schedules, guided generation",
|
|
103
|
+
"theory": "# ESE Practice — Exp 9: Simple diffusion model (DDPM) — fully offline\nWhat to remember\n* Forward: x_t = √ᾱ_t · x0 + √(1−ᾱ_t) · ε, with ᾱ_t = Π(1−β_s).\n* Train: network predicts the noise ε, loss = MSE(ε, ε̂).\n* Sample: x_{t−1} = (x_t − β_t/√(1−ᾱ_t) · ε̂) / √α_t + √β_t · z, starting from pure noise.\n* Schedules: linear β, or cosine ᾱ_t = cos²(((t/T + s)/(1 + s)) · π/2).\n* Part 3 (instead of Stable Diffusion): a *class-conditional* diffusion model with classifier-free guidance, the same \"guidance scale\" idea as Stable Diffusion, on 2-D data.\n\nRuns on CPU in about 2–3 minutes.",
|
|
104
|
+
"file": "diffusion.py.txt"
|
|
105
|
+
}
|
|
106
|
+
]
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# GENAI Experiment 8: Chatbot: tokenizer, TF-IDF + cosine similarity, context memory, exit loop
|
|
2
|
+
# ===========================================================================
|
|
3
|
+
|
|
4
|
+
# # Part 1 — Knowledge base and tokenization
|
|
5
|
+
|
|
6
|
+
import re, math, random
|
|
7
|
+
import numpy as np
|
|
8
|
+
random.seed(0)
|
|
9
|
+
|
|
10
|
+
KB = {
|
|
11
|
+
"what is artificial intelligence": "Artificial intelligence is the science of making machines perform tasks that need human intelligence.",
|
|
12
|
+
"what is machine learning": "Machine learning lets computers learn patterns from data instead of being explicitly programmed.",
|
|
13
|
+
"what is deep learning": "Deep learning uses neural networks with many layers to learn features directly from data.",
|
|
14
|
+
"what is a neural network": "A neural network is layers of connected neurons that compute weighted sums followed by activations.",
|
|
15
|
+
"what is a gan generative adversarial network": "A GAN has a generator that makes fake data and a discriminator that tries to tell real from fake.",
|
|
16
|
+
"what is a vae variational autoencoder": "A VAE encodes data to a distribution in latent space and decodes samples from it to generate new data.",
|
|
17
|
+
"what is a transformer attention": "A transformer uses self-attention so every word can look at every other word to understand context.",
|
|
18
|
+
"what is a diffusion model": "A diffusion model adds noise to data step by step and learns to remove it, turning noise into new samples.",
|
|
19
|
+
"how do i study for exams tips": "Make a timetable, revise in short sessions, practise previous papers and sleep well.",
|
|
20
|
+
"hello hi hey good morning": "Hello! I am your GenAI lab assistant. Ask me about AI topics.",
|
|
21
|
+
"how are you": "I am just a program, but I am working fine. How can I help you?",
|
|
22
|
+
"thank you thanks": "You are welcome!",
|
|
23
|
+
}
|
|
24
|
+
MORE = { # second-level answers used for "tell me more" (context)
|
|
25
|
+
"what is machine learning": "There are three main types: supervised, unsupervised and reinforcement learning.",
|
|
26
|
+
"what is deep learning": "Popular deep networks are CNNs for images, RNNs/LSTMs for sequences and transformers for language.",
|
|
27
|
+
"what is a gan generative adversarial network": "Training alternates: update the discriminator on real and fake, then update the generator to fool it.",
|
|
28
|
+
"what is a vae variational autoencoder": "Its loss is reconstruction loss plus KL divergence, and it uses the reparameterization trick z = mu + sigma * eps.",
|
|
29
|
+
"what is a diffusion model": "The network is trained to predict the added noise; sampling runs the denoising steps from t = T down to 1.",
|
|
30
|
+
}
|
|
31
|
+
STOP = {"what", "is", "a", "an", "the", "of", "me", "about", "please", "can", "you", "tell", "do", "i", "to", "how", "it"}
|
|
32
|
+
|
|
33
|
+
def tokenize(text):
|
|
34
|
+
return [w for w in re.findall(r"[a-z]+", text.lower()) if w not in STOP]
|
|
35
|
+
|
|
36
|
+
print(tokenize("What is Machine Learning, please?"))
|
|
37
|
+
|
|
38
|
+
# # Part 2 — TF-IDF vectors and cosine similarity (the "model")
|
|
39
|
+
|
|
40
|
+
questions = list(KB.keys())
|
|
41
|
+
vocab = sorted({w for q in questions for w in tokenize(q)})
|
|
42
|
+
widx = {w: i for i, w in enumerate(vocab)}
|
|
43
|
+
df = np.zeros(len(vocab))
|
|
44
|
+
for q in questions:
|
|
45
|
+
for w in set(tokenize(q)): df[widx[w]] += 1
|
|
46
|
+
idf = np.log((1 + len(questions)) / (1 + df)) + 1 # inverse document frequency
|
|
47
|
+
|
|
48
|
+
def tfidf(text):
|
|
49
|
+
v = np.zeros(len(vocab))
|
|
50
|
+
for w in tokenize(text):
|
|
51
|
+
if w in widx: v[widx[w]] += 1 # term frequency
|
|
52
|
+
v *= idf
|
|
53
|
+
n = np.linalg.norm(v); return v / n if n > 0 else v
|
|
54
|
+
|
|
55
|
+
Q = np.stack([tfidf(q) for q in questions]) # matrix: one row per known question
|
|
56
|
+
def best_match(text):
|
|
57
|
+
sims = Q @ tfidf(text) # cosine similarity (vectors are normalised)
|
|
58
|
+
i = int(np.argmax(sims)); return questions[i], float(sims[i])
|
|
59
|
+
|
|
60
|
+
print(best_match("explain neural networks"))
|
|
61
|
+
|
|
62
|
+
# # Part 3 — Response generation with context and a confidence threshold
|
|
63
|
+
|
|
64
|
+
state = {"last_topic": None}
|
|
65
|
+
FALLBACK = ["Sorry, I don't know that yet. Try asking about AI, ML, GANs, VAEs or diffusion.",
|
|
66
|
+
"I am not sure. Could you rephrase your question?"]
|
|
67
|
+
|
|
68
|
+
def chatbot_reply(user_text, threshold=0.3):
|
|
69
|
+
t = user_text.lower()
|
|
70
|
+
if re.search(r"\b(more|elaborate|explain it|what about it|continue)\b", t) and state["last_topic"]:
|
|
71
|
+
return MORE.get(state["last_topic"], "That's all I know about this topic.") # context
|
|
72
|
+
q, score = best_match(user_text)
|
|
73
|
+
if score < threshold:
|
|
74
|
+
return random.choice(FALLBACK) # low confidence
|
|
75
|
+
state["last_topic"] = q
|
|
76
|
+
return KB[q]
|
|
77
|
+
|
|
78
|
+
for q in ["Hi!", "What is deep learning?", "Tell me more", "What's a GAN?", "can you elaborate",
|
|
79
|
+
"what is the capital of France", "how do I prepare for exams", "thanks"]:
|
|
80
|
+
print(f"You: {q}\nBot: {chatbot_reply(q)}\n")
|
|
81
|
+
|
|
82
|
+
# # Part 4 — Interactive loop (type exit to stop)
|
|
83
|
+
|
|
84
|
+
def run_chatbot():
|
|
85
|
+
print("GenAI lab chatbot - type 'exit' to quit")
|
|
86
|
+
while True:
|
|
87
|
+
try:
|
|
88
|
+
user_text = input("You: ")
|
|
89
|
+
except Exception: # no keyboard (e.g. auto-run) -> short demo
|
|
90
|
+
for user_text in ["what is a vae", "tell me more", "exit"]:
|
|
91
|
+
print("You:", user_text)
|
|
92
|
+
if user_text == "exit": print("Bot: Goodbye!"); return
|
|
93
|
+
print("Bot:", chatbot_reply(user_text))
|
|
94
|
+
return
|
|
95
|
+
if user_text.strip().lower() == "exit":
|
|
96
|
+
print("Bot: Goodbye!"); break
|
|
97
|
+
print("Bot:", chatbot_reply(user_text))
|
|
98
|
+
|
|
99
|
+
run_chatbot()
|
|
100
|
+
|
|
101
|
+
# Exam explanation: the input is tokenized (lowercase words without stop-words), turned into a TF-IDF
|
|
102
|
+
# vector and compared with every known question by cosine similarity. The most similar answer is
|
|
103
|
+
# returned. A threshold rejects unrelated questions (e.g. capitals of countries) instead of
|
|
104
|
+
# hallucinating, and a last-topic memory gives context-aware follow-ups ("tell me more"). Compared
|
|
105
|
+
# with DistilGPT2: retrieval answers are always correct for known topics but cannot invent new
|
|
106
|
+
# sentences, while the pretrained model is fluent but often wrong.
|