yaxlib 0.1.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.
- yaxlib-0.1.0/.gitignore +77 -0
- yaxlib-0.1.0/PKG-INFO +72 -0
- yaxlib-0.1.0/README.md +56 -0
- yaxlib-0.1.0/demos/cnn_demo_classif.py +123 -0
- yaxlib-0.1.0/demos/gnn_demo_nodes.py +134 -0
- yaxlib-0.1.0/demos/mlp_demo.py +82 -0
- yaxlib-0.1.0/demos/rnn_demo_classif.py +110 -0
- yaxlib-0.1.0/demos/transformer_demo_charlm.py +143 -0
- yaxlib-0.1.0/demos/unet_demo_segmentation.py +117 -0
- yaxlib-0.1.0/demos/yolo_demo_detection.py +131 -0
- yaxlib-0.1.0/out/charlm/0/best_loss +0 -0
- yaxlib-0.1.0/out/charlm/0/config +0 -0
- yaxlib-0.1.0/out/charlm/0/history +0 -0
- yaxlib-0.1.0/out/charlm/0/model +0 -0
- yaxlib-0.1.0/out/charlm/0/opt_state +0 -0
- yaxlib-0.1.0/out/charlm/1/best_loss +0 -0
- yaxlib-0.1.0/out/charlm/1/config +0 -0
- yaxlib-0.1.0/out/charlm/1/history +0 -0
- yaxlib-0.1.0/out/charlm/1/model +0 -0
- yaxlib-0.1.0/out/charlm/1/opt_state +0 -0
- yaxlib-0.1.0/out/cnn_classif/0/best_loss +0 -0
- yaxlib-0.1.0/out/cnn_classif/0/config +0 -0
- yaxlib-0.1.0/out/cnn_classif/0/history +0 -0
- yaxlib-0.1.0/out/cnn_classif/0/model +0 -0
- yaxlib-0.1.0/out/cnn_classif/0/opt_state +0 -0
- yaxlib-0.1.0/out/cnn_classif/1/best_loss +0 -0
- yaxlib-0.1.0/out/cnn_classif/1/config +0 -0
- yaxlib-0.1.0/out/cnn_classif/1/history +0 -0
- yaxlib-0.1.0/out/cnn_classif/1/model +0 -0
- yaxlib-0.1.0/out/cnn_classif/1/opt_state +0 -0
- yaxlib-0.1.0/out/cnn_classif/2/best_loss +0 -0
- yaxlib-0.1.0/out/cnn_classif/2/config +0 -0
- yaxlib-0.1.0/out/cnn_classif/2/history +0 -0
- yaxlib-0.1.0/out/cnn_classif/2/model +0 -0
- yaxlib-0.1.0/out/cnn_classif/2/opt_state +0 -0
- yaxlib-0.1.0/out/rnn_classif_gru/0/best_loss +0 -0
- yaxlib-0.1.0/out/rnn_classif_gru/0/config +0 -0
- yaxlib-0.1.0/out/rnn_classif_gru/0/history +0 -0
- yaxlib-0.1.0/out/rnn_classif_gru/0/model +0 -0
- yaxlib-0.1.0/out/rnn_classif_gru/0/opt_state +0 -0
- yaxlib-0.1.0/out/rnn_classif_gru/1/best_loss +0 -0
- yaxlib-0.1.0/out/rnn_classif_gru/1/config +0 -0
- yaxlib-0.1.0/out/rnn_classif_gru/1/history +0 -0
- yaxlib-0.1.0/out/rnn_classif_gru/1/model +0 -0
- yaxlib-0.1.0/out/rnn_classif_gru/1/opt_state +0 -0
- yaxlib-0.1.0/out/rnn_classif_gru/2/best_loss +0 -0
- yaxlib-0.1.0/out/rnn_classif_gru/2/config +0 -0
- yaxlib-0.1.0/out/rnn_classif_gru/2/history +0 -0
- yaxlib-0.1.0/out/rnn_classif_gru/2/model +0 -0
- yaxlib-0.1.0/out/rnn_classif_gru/2/opt_state +0 -0
- yaxlib-0.1.0/out/rnn_classif_lstm/0/best_loss +0 -0
- yaxlib-0.1.0/out/rnn_classif_lstm/0/config +0 -0
- yaxlib-0.1.0/out/rnn_classif_lstm/0/history +0 -0
- yaxlib-0.1.0/out/rnn_classif_lstm/0/model +0 -0
- yaxlib-0.1.0/out/rnn_classif_lstm/0/opt_state +0 -0
- yaxlib-0.1.0/out/rnn_classif_lstm/1/best_loss +0 -0
- yaxlib-0.1.0/out/rnn_classif_lstm/1/config +0 -0
- yaxlib-0.1.0/out/rnn_classif_lstm/1/history +0 -0
- yaxlib-0.1.0/out/rnn_classif_lstm/1/model +0 -0
- yaxlib-0.1.0/out/rnn_classif_lstm/1/opt_state +0 -0
- yaxlib-0.1.0/out/sin_3/0/best_loss +0 -0
- yaxlib-0.1.0/out/sin_3/0/config +0 -0
- yaxlib-0.1.0/out/sin_3/0/history +0 -0
- yaxlib-0.1.0/out/sin_3/0/model +0 -0
- yaxlib-0.1.0/out/sin_3/0/opt_state +0 -0
- yaxlib-0.1.0/out/sin_3/1/best_loss +0 -0
- yaxlib-0.1.0/out/sin_3/1/config +0 -0
- yaxlib-0.1.0/out/sin_3/1/history +0 -0
- yaxlib-0.1.0/out/sin_3/1/model +0 -0
- yaxlib-0.1.0/out/sin_3/1/opt_state +0 -0
- yaxlib-0.1.0/out/unet_seg/0/best_loss +0 -0
- yaxlib-0.1.0/out/unet_seg/0/config +0 -0
- yaxlib-0.1.0/out/unet_seg/0/history +0 -0
- yaxlib-0.1.0/out/unet_seg/0/model +0 -0
- yaxlib-0.1.0/out/unet_seg/0/opt_state +0 -0
- yaxlib-0.1.0/out/unet_seg/1/best_loss +0 -0
- yaxlib-0.1.0/out/unet_seg/1/config +0 -0
- yaxlib-0.1.0/out/unet_seg/1/history +0 -0
- yaxlib-0.1.0/out/unet_seg/1/model +0 -0
- yaxlib-0.1.0/out/unet_seg/1/opt_state +0 -0
- yaxlib-0.1.0/out/unet_seg/2/best_loss +0 -0
- yaxlib-0.1.0/out/unet_seg/2/config +0 -0
- yaxlib-0.1.0/out/unet_seg/2/history +0 -0
- yaxlib-0.1.0/out/unet_seg/2/model +0 -0
- yaxlib-0.1.0/out/unet_seg/2/opt_state +0 -0
- yaxlib-0.1.0/out/yolo_detection/0/best_loss +0 -0
- yaxlib-0.1.0/out/yolo_detection/0/config +0 -0
- yaxlib-0.1.0/out/yolo_detection/0/history +0 -0
- yaxlib-0.1.0/out/yolo_detection/0/model +0 -0
- yaxlib-0.1.0/out/yolo_detection/0/opt_state +0 -0
- yaxlib-0.1.0/out/yolo_detection/1/best_loss +0 -0
- yaxlib-0.1.0/out/yolo_detection/1/config +0 -0
- yaxlib-0.1.0/out/yolo_detection/1/history +0 -0
- yaxlib-0.1.0/out/yolo_detection/1/model +0 -0
- yaxlib-0.1.0/out/yolo_detection/1/opt_state +0 -0
- yaxlib-0.1.0/out/yolo_detection/2/best_loss +0 -0
- yaxlib-0.1.0/out/yolo_detection/2/config +0 -0
- yaxlib-0.1.0/out/yolo_detection/2/history +0 -0
- yaxlib-0.1.0/out/yolo_detection/2/model +0 -0
- yaxlib-0.1.0/out/yolo_detection/2/opt_state +0 -0
- yaxlib-0.1.0/out/yolo_detection/3/best_loss +0 -0
- yaxlib-0.1.0/out/yolo_detection/3/config +0 -0
- yaxlib-0.1.0/out/yolo_detection/3/history +0 -0
- yaxlib-0.1.0/out/yolo_detection/3/model +0 -0
- yaxlib-0.1.0/out/yolo_detection/3/opt_state +0 -0
- yaxlib-0.1.0/out/yolo_detection/4/best_loss +0 -0
- yaxlib-0.1.0/out/yolo_detection/4/config +0 -0
- yaxlib-0.1.0/out/yolo_detection/4/history +0 -0
- yaxlib-0.1.0/out/yolo_detection/4/model +0 -0
- yaxlib-0.1.0/out/yolo_detection/4/opt_state +0 -0
- yaxlib-0.1.0/programme_detaille.md +1359 -0
- yaxlib-0.1.0/pyproject.toml +27 -0
- yaxlib-0.1.0/tests/test_core.py +219 -0
- yaxlib-0.1.0/tests/test_layers.py +86 -0
- yaxlib-0.1.0/tests/test_training.py +69 -0
- yaxlib-0.1.0/yax/__init__.py +28 -0
- yaxlib-0.1.0/yax/core.py +346 -0
- yaxlib-0.1.0/yax/image/__init__.py +0 -0
- yaxlib-0.1.0/yax/image/augmentation.py +123 -0
- yaxlib-0.1.0/yax/layers/Conv_layer.py +70 -0
- yaxlib-0.1.0/yax/layers/Dropout.py +63 -0
- yaxlib-0.1.0/yax/layers/Embedding.py +29 -0
- yaxlib-0.1.0/yax/layers/LayerNorm.py +42 -0
- yaxlib-0.1.0/yax/layers/Linear.py +20 -0
- yaxlib-0.1.0/yax/layers/MLP.py +62 -0
- yaxlib-0.1.0/yax/layers/MessagePassing_layer.py +80 -0
- yaxlib-0.1.0/yax/layers/MultiHeadAttention.py +127 -0
- yaxlib-0.1.0/yax/layers/RNN_layer.py +156 -0
- yaxlib-0.1.0/yax/layers/TransformerBlock.py +78 -0
- yaxlib-0.1.0/yax/layers/__init__.py +0 -0
- yaxlib-0.1.0/yax/layers/positional_encoding.py +37 -0
- yaxlib-0.1.0/yax/models/MiniYOLO.py +200 -0
- yaxlib-0.1.0/yax/models/UNet.py +107 -0
- yaxlib-0.1.0/yax/models/__init__.py +0 -0
- yaxlib-0.1.0/yax/training/History.py +52 -0
- yaxlib-0.1.0/yax/training/Trainer.py +240 -0
- yaxlib-0.1.0/yax/training/__init__.py +0 -0
- yaxlib-0.1.0/yax/training/configs.py +20 -0
- yaxlib-0.1.0/yax/training/losses.py +77 -0
yaxlib-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# ===== Environnements virtuels =====
|
|
2
|
+
.venv/
|
|
3
|
+
venv/
|
|
4
|
+
env/
|
|
5
|
+
ENV/
|
|
6
|
+
.conda/
|
|
7
|
+
.python-version
|
|
8
|
+
|
|
9
|
+
# ===== Artefacts de build / cache Python =====
|
|
10
|
+
__pycache__/
|
|
11
|
+
*.py[cod]
|
|
12
|
+
*$py.class
|
|
13
|
+
build/
|
|
14
|
+
dist/
|
|
15
|
+
*.egg
|
|
16
|
+
*.egg-info/
|
|
17
|
+
.eggs/
|
|
18
|
+
.pytest_cache/
|
|
19
|
+
.mypy_cache/
|
|
20
|
+
.ruff_cache/
|
|
21
|
+
.ipynb_checkpoints/
|
|
22
|
+
|
|
23
|
+
# ===== Node / JS (au cas où) =====
|
|
24
|
+
node_modules/
|
|
25
|
+
|
|
26
|
+
# ===== Éditeurs & assistants IA =====
|
|
27
|
+
.idea/
|
|
28
|
+
.vscode/
|
|
29
|
+
.claude/
|
|
30
|
+
.gemini/
|
|
31
|
+
.agent/
|
|
32
|
+
.agents/
|
|
33
|
+
*.swp
|
|
34
|
+
*~
|
|
35
|
+
.DS_Store
|
|
36
|
+
|
|
37
|
+
# ===== Archives =====
|
|
38
|
+
*.zip
|
|
39
|
+
|
|
40
|
+
# ===== Médias lourds — sauvegardés par Drive, hors git =====
|
|
41
|
+
*.mp4
|
|
42
|
+
*.mov
|
|
43
|
+
*.webm
|
|
44
|
+
*.mkv
|
|
45
|
+
*.avi
|
|
46
|
+
*.mp3
|
|
47
|
+
*.wav
|
|
48
|
+
*.flac
|
|
49
|
+
*.m4a
|
|
50
|
+
*.ogg
|
|
51
|
+
# (images gardées dans git)
|
|
52
|
+
|
|
53
|
+
# ===== Données scientifiques lourdes — sauvegardées par Drive =====
|
|
54
|
+
*.npy
|
|
55
|
+
*.npz
|
|
56
|
+
# Tables EOS supernova (données CompOSE; attention: pas 'eos.*' qui
|
|
57
|
+
# ignorerait aussi les eos.py du code)
|
|
58
|
+
**/eos.thermo
|
|
59
|
+
**/eos.compo
|
|
60
|
+
**/eos.micro
|
|
61
|
+
**/eos.t
|
|
62
|
+
**/eos.nb
|
|
63
|
+
**/eos.yq
|
|
64
|
+
# (tout autre fichier > 50 Mo est bloqué par le hook pre-commit)
|
|
65
|
+
|
|
66
|
+
# ===== Fichiers-pointeurs Google Drive (liens, pas du vrai contenu) =====
|
|
67
|
+
*.gdoc
|
|
68
|
+
*.gsheet
|
|
69
|
+
*.gslides
|
|
70
|
+
*.gdraw
|
|
71
|
+
*.gform
|
|
72
|
+
*.gmap
|
|
73
|
+
*.gsite
|
|
74
|
+
*.gtable
|
|
75
|
+
*.glink
|
|
76
|
+
*.gjam
|
|
77
|
+
*.gnote
|
yaxlib-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: yaxlib
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Un mini-framework de réseaux de neurones pour jax, à but pédagogique (import : yax)
|
|
5
|
+
Author: Vincent Vigon
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: jax>=0.4.30
|
|
9
|
+
Requires-Dist: numpy
|
|
10
|
+
Requires-Dist: optax>=0.2
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: equinox; extra == 'dev'
|
|
13
|
+
Requires-Dist: matplotlib; extra == 'dev'
|
|
14
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# yaxlib
|
|
18
|
+
|
|
19
|
+
Un mini-framework de réseaux de neurones pour jax, à but pédagogique.
|
|
20
|
+
Distribution `yaxlib`, import `yax` :
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install yaxlib # ou : pip install <url du wheel>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
import jax.random as jr
|
|
28
|
+
import yax
|
|
29
|
+
|
|
30
|
+
model = yax.MLP((2, 32, 32, 1), "tanh", jr.key(0))
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Principes
|
|
34
|
+
|
|
35
|
+
- **Un modèle est un pytree.** `yax.Module` range les tableaux dans les
|
|
36
|
+
feuilles et tout le reste (`yax.StaticField`) dans la structure :
|
|
37
|
+
`jax.grad(loss)(model)`, `jax.jit` et `optimizer.init(model)` acceptent le
|
|
38
|
+
modèle tel quel, sans machinerie de filtrage. Les champs dynamiques ne
|
|
39
|
+
peuvent contenir que des tableaux jax, des sous-modules ou des conteneurs de
|
|
40
|
+
ceux-ci — tout écart est une erreur immédiate et explicite à la
|
|
41
|
+
construction. Un `StaticField` peut contenir un tableau : il devient une
|
|
42
|
+
constante du modèle (encodage positionnel, grille figée), invisible pour les
|
|
43
|
+
gradients.
|
|
44
|
+
- **Signature uniforme `apply(x, rkey=None)`**, écrite pour UN échantillon
|
|
45
|
+
(le batch vient de `jax.vmap`). `rkey` est une *source d'aléatoire*
|
|
46
|
+
(dropout, échantillonnage), jamais un mode.
|
|
47
|
+
- **Le mode se bascule par `model = model.set_inference(True/False)`**
|
|
48
|
+
(récursif, immuable). En pratique on n'y touche pas : le `Trainer` entraîne
|
|
49
|
+
en `False`, valide et rend le meilleur modèle en `True`.
|
|
50
|
+
- **Immutabilité** : on « modifie » un module avec `yax.tree_at`.
|
|
51
|
+
|
|
52
|
+
## Contenu
|
|
53
|
+
|
|
54
|
+
- `yax.layers` : Linear, MLP, Dropout, LayerNorm, Embedding, Conv_layer,
|
|
55
|
+
RNN_layer (GRU/LSTM), MultiHeadAttention, TransformerBlock,
|
|
56
|
+
MessagePassing_layer, encodage positionnel.
|
|
57
|
+
- `yax.models` : UNet, MiniYOLO (références des mini-projets).
|
|
58
|
+
- `yax.training` : Trainer (checkpoints par `mother_folder`), History, pertes
|
|
59
|
+
(`loss_fn(model, x, y, rkey)`).
|
|
60
|
+
- `yax.image` : augmentation différentiable et vmap-able.
|
|
61
|
+
- `demos/` : une démonstration synthétique par famille de modèles, qui
|
|
62
|
+
converge en quelques secondes sur CPU.
|
|
63
|
+
|
|
64
|
+
## Tests
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install yaxlib[dev]
|
|
68
|
+
pytest tests/
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Les couches réimplémentées (GRU, LSTM, convolution) sont vérifiées
|
|
72
|
+
numériquement contre `equinox.nn`, qui ne sert qu'à cela.
|
yaxlib-0.1.0/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# yaxlib
|
|
2
|
+
|
|
3
|
+
Un mini-framework de réseaux de neurones pour jax, à but pédagogique.
|
|
4
|
+
Distribution `yaxlib`, import `yax` :
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
pip install yaxlib # ou : pip install <url du wheel>
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
```python
|
|
11
|
+
import jax.random as jr
|
|
12
|
+
import yax
|
|
13
|
+
|
|
14
|
+
model = yax.MLP((2, 32, 32, 1), "tanh", jr.key(0))
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Principes
|
|
18
|
+
|
|
19
|
+
- **Un modèle est un pytree.** `yax.Module` range les tableaux dans les
|
|
20
|
+
feuilles et tout le reste (`yax.StaticField`) dans la structure :
|
|
21
|
+
`jax.grad(loss)(model)`, `jax.jit` et `optimizer.init(model)` acceptent le
|
|
22
|
+
modèle tel quel, sans machinerie de filtrage. Les champs dynamiques ne
|
|
23
|
+
peuvent contenir que des tableaux jax, des sous-modules ou des conteneurs de
|
|
24
|
+
ceux-ci — tout écart est une erreur immédiate et explicite à la
|
|
25
|
+
construction. Un `StaticField` peut contenir un tableau : il devient une
|
|
26
|
+
constante du modèle (encodage positionnel, grille figée), invisible pour les
|
|
27
|
+
gradients.
|
|
28
|
+
- **Signature uniforme `apply(x, rkey=None)`**, écrite pour UN échantillon
|
|
29
|
+
(le batch vient de `jax.vmap`). `rkey` est une *source d'aléatoire*
|
|
30
|
+
(dropout, échantillonnage), jamais un mode.
|
|
31
|
+
- **Le mode se bascule par `model = model.set_inference(True/False)`**
|
|
32
|
+
(récursif, immuable). En pratique on n'y touche pas : le `Trainer` entraîne
|
|
33
|
+
en `False`, valide et rend le meilleur modèle en `True`.
|
|
34
|
+
- **Immutabilité** : on « modifie » un module avec `yax.tree_at`.
|
|
35
|
+
|
|
36
|
+
## Contenu
|
|
37
|
+
|
|
38
|
+
- `yax.layers` : Linear, MLP, Dropout, LayerNorm, Embedding, Conv_layer,
|
|
39
|
+
RNN_layer (GRU/LSTM), MultiHeadAttention, TransformerBlock,
|
|
40
|
+
MessagePassing_layer, encodage positionnel.
|
|
41
|
+
- `yax.models` : UNet, MiniYOLO (références des mini-projets).
|
|
42
|
+
- `yax.training` : Trainer (checkpoints par `mother_folder`), History, pertes
|
|
43
|
+
(`loss_fn(model, x, y, rkey)`).
|
|
44
|
+
- `yax.image` : augmentation différentiable et vmap-able.
|
|
45
|
+
- `demos/` : une démonstration synthétique par famille de modèles, qui
|
|
46
|
+
converge en quelques secondes sur CPU.
|
|
47
|
+
|
|
48
|
+
## Tests
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install yaxlib[dev]
|
|
52
|
+
pytest tests/
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Les couches réimplémentées (GRU, LSTM, convolution) sont vérifiées
|
|
56
|
+
numériquement contre `equinox.nn`, qui ne sert qu'à cela.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Classification d'images synthétiques par un petit CNN.
|
|
2
|
+
|
|
3
|
+
Trois classes de formes — disque, carré, croix — posées à position et taille
|
|
4
|
+
aléatoires sur un fond bruité. Le jeu se génère en une ligne de vmap, converge
|
|
5
|
+
en quelques secondes, et l'invariance par translation de la convolution y est
|
|
6
|
+
réellement mise à l'épreuve (la position varie d'une image à l'autre).
|
|
7
|
+
|
|
8
|
+
Le sous-échantillonnage est fait par stride=2 (l'alternative au pooling,
|
|
9
|
+
chapitre 11) : 16x16 -> 8x8 -> 4x4, puis tête dense sur l'aplati.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
from yax.core import Module
|
|
15
|
+
import jax
|
|
16
|
+
import jax.numpy as jnp
|
|
17
|
+
import jax.random as jr
|
|
18
|
+
|
|
19
|
+
from yax.layers.Conv_layer import Conv_layer
|
|
20
|
+
from yax.layers.Linear import Linear
|
|
21
|
+
from yax.training.Trainer import train, load_run, OUT_FOLDER
|
|
22
|
+
from yax.training.configs import TrainConfig
|
|
23
|
+
from yax.training.losses import softmax_ce_loss, batch_apply
|
|
24
|
+
|
|
25
|
+
IMG_SIZE = 16
|
|
26
|
+
NB_CLASSES = 3 # 0: disque, 1: carré, 2: croix
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def make_image(rkey, label):
|
|
30
|
+
"""UNE image (1, 16, 16) : une forme `label` sur fond bruité.
|
|
31
|
+
|
|
32
|
+
Les rayons restent assez grands (4.5 à 5.5) pour qu'un disque et un carré
|
|
33
|
+
diffèrent de plusieurs pixels aux coins : avec des formes plus petites,
|
|
34
|
+
seuls les coins les distinguent, la tâche devient réellement ambiguë sous
|
|
35
|
+
le bruit et la confusion disque/carré plafonne la justesse."""
|
|
36
|
+
k_pos, k_radius, k_noise = jr.split(rkey, 3)
|
|
37
|
+
center = jr.uniform(k_pos, (2,), minval=6.0, maxval=IMG_SIZE - 6.0)
|
|
38
|
+
radius = jr.uniform(k_radius, minval=4.5, maxval=5.5)
|
|
39
|
+
|
|
40
|
+
rows = jnp.arange(IMG_SIZE)[:, None]
|
|
41
|
+
cols = jnp.arange(IMG_SIZE)[None, :]
|
|
42
|
+
dr, dc = rows - center[0], cols - center[1]
|
|
43
|
+
|
|
44
|
+
disque = dr**2 + dc**2 < radius**2
|
|
45
|
+
carre = jnp.maximum(jnp.abs(dr), jnp.abs(dc)) < radius
|
|
46
|
+
croix = ((jnp.abs(dr) < 1.0) | (jnp.abs(dc) < 1.0)) & (jnp.abs(dr) + jnp.abs(dc) < 2 * radius)
|
|
47
|
+
|
|
48
|
+
shape = jnp.stack([disque, carre, croix])[label]
|
|
49
|
+
img = jnp.where(shape, 1.0, 0.0) + 0.25 * jr.normal(k_noise, (IMG_SIZE, IMG_SIZE))
|
|
50
|
+
return img[None, :, :] # channels-first : (1, H, W)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def make_data(rkey, nb):
|
|
54
|
+
k_labels, k_images = jr.split(rkey)
|
|
55
|
+
labels = jr.randint(k_labels, (nb,), 0, NB_CLASSES)
|
|
56
|
+
X = jax.vmap(make_image)(jr.split(k_images, nb), labels)
|
|
57
|
+
return X, labels
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def present_data():
|
|
61
|
+
from matplotlib import pyplot as plt
|
|
62
|
+
|
|
63
|
+
X, Y = make_data(jr.key(0), 12)
|
|
64
|
+
fig, axs = plt.subplots(3, 4, figsize=(8, 6))
|
|
65
|
+
for ax, img, label in zip(axs.flat, X, Y):
|
|
66
|
+
ax.imshow(img[0], cmap="gray")
|
|
67
|
+
ax.set_title(["disque", "carré", "croix"][int(label)])
|
|
68
|
+
ax.axis("off")
|
|
69
|
+
plt.tight_layout()
|
|
70
|
+
plt.show()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class CNN_classifier(Module):
|
|
74
|
+
conv1: Conv_layer
|
|
75
|
+
conv2: Conv_layer
|
|
76
|
+
head: Linear
|
|
77
|
+
|
|
78
|
+
def __init__(self, rkey):
|
|
79
|
+
k1, k2, k3 = jr.split(rkey, 3)
|
|
80
|
+
self.conv1 = Conv_layer(1, 8, 3, k1, stride=2) # (1,16,16) -> (8,8,8)
|
|
81
|
+
self.conv2 = Conv_layer(8, 16, 3, k2, stride=2) # -> (16,4,4)
|
|
82
|
+
self.head = Linear(16 * 4 * 4, NB_CLASSES, k3)
|
|
83
|
+
|
|
84
|
+
def apply(self, x, rkey=None):
|
|
85
|
+
x = jax.nn.relu(self.conv1.apply(x))
|
|
86
|
+
x = jax.nn.relu(self.conv2.apply(x))
|
|
87
|
+
return self.head.apply(x.reshape(-1)) # logits (NB_CLASSES,)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def accuracy(model, x, y):
|
|
91
|
+
logits = batch_apply(model, x, None)
|
|
92
|
+
return float(jnp.mean(jnp.argmax(logits, axis=-1) == y))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def train_classif(verbose=True):
|
|
96
|
+
key_train, key_val, key_model, key_shuffle = jr.split(jr.key(0), 4)
|
|
97
|
+
X_train, Y_train = make_data(key_train, 1024)
|
|
98
|
+
X_val, Y_val = make_data(key_val, 512)
|
|
99
|
+
|
|
100
|
+
model = CNN_classifier(key_model)
|
|
101
|
+
acc_before = accuracy(model, X_val, Y_val)
|
|
102
|
+
|
|
103
|
+
config = TrainConfig(batch_size=64, learning_rate=0.003, n_epoch=100,
|
|
104
|
+
lr_final_ratio=0.01)
|
|
105
|
+
mother_folder = os.path.join(OUT_FOLDER, "cnn_classif")
|
|
106
|
+
best_model, best_loss, folder = train(
|
|
107
|
+
model, mother_folder, config, X_train, Y_train, X_val, Y_val,
|
|
108
|
+
rkey=key_shuffle, loss_fn=softmax_ce_loss, verbose=verbose)
|
|
109
|
+
|
|
110
|
+
acc_after = accuracy(best_model, X_val, Y_val)
|
|
111
|
+
print(f"\naccuracy {acc_before:.3f} -> {acc_after:.3f} (best val CE = {best_loss:.3g})")
|
|
112
|
+
return folder, best_loss, acc_after
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
if __name__ == "__main__":
|
|
116
|
+
from matplotlib import pyplot as plt
|
|
117
|
+
|
|
118
|
+
present_data()
|
|
119
|
+
folder, best_loss, acc = train_classif()
|
|
120
|
+
history = load_run(folder, "history")["history"]
|
|
121
|
+
history.plot(title=f"CNN formes : CE={best_loss:.2g}, accuracy={acc:.3f}")
|
|
122
|
+
plt.tight_layout()
|
|
123
|
+
plt.show()
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Classification de nœuds sur UN graphe fixe, par message passing.
|
|
2
|
+
|
|
3
|
+
Un graphe à deux communautés (modèle à blocs stochastiques) : les liens sont
|
|
4
|
+
denses à l'intérieur d'une communauté, rares entre les deux. Chaque nœud porte
|
|
5
|
+
une caractéristique très bruitée de sa communauté — si bruitée qu'un nœud seul
|
|
6
|
+
ne dit presque rien : l'optimum théorique nœud par nœud est ~69 %, et le MLP,
|
|
7
|
+
qui sur-apprend ses nœuds d'entraînement dans les 7 dimensions de bruit pur,
|
|
8
|
+
fait pire encore. Le message passing moyenne l'information sur le voisinage,
|
|
9
|
+
qui est surtout de la même communauté : le bruit s'écrase, le GNN dépasse
|
|
10
|
+
90 %. C'est l'argument du chapitre en une expérience.
|
|
11
|
+
|
|
12
|
+
La moitié des nœuds est étiquetée, la justesse est mesurée sur les autres.
|
|
13
|
+
Boucle d'entraînement full-batch écrite à la main (comme mlp_demo) : un seul
|
|
14
|
+
graphe, pas de mini-batchs — le Trainer n'a rien à mélanger ici.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from yax.core import Module
|
|
18
|
+
import jax
|
|
19
|
+
import jax.numpy as jnp
|
|
20
|
+
import jax.random as jr
|
|
21
|
+
import numpy as np
|
|
22
|
+
import optax
|
|
23
|
+
|
|
24
|
+
from yax.layers.Linear import Linear
|
|
25
|
+
from yax.layers.MLP import MLP
|
|
26
|
+
from yax.layers.MessagePassing_layer import MessagePassing_layer
|
|
27
|
+
|
|
28
|
+
NB_PER_BLOCK = 60
|
|
29
|
+
DIM_FEATURES = 8
|
|
30
|
+
SIGMA = 2.0 # écart-type du bruit sur les caractéristiques
|
|
31
|
+
NB_LABELED = 30 # nœuds étiquetés par communauté (la moitié)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def make_graph(seed=0):
|
|
35
|
+
"""Rend (features, labels, senders, receivers, train_idx, test_idx).
|
|
36
|
+
Génération en numpy : les arêtes sont des tableaux concrets, construits une
|
|
37
|
+
fois pour toutes hors de tout jit — le graphe est FIXE."""
|
|
38
|
+
rng = np.random.default_rng(seed)
|
|
39
|
+
nb_nodes = 2 * NB_PER_BLOCK
|
|
40
|
+
labels = np.repeat([0, 1], NB_PER_BLOCK)
|
|
41
|
+
|
|
42
|
+
# liens : p_in dans une communauté, p_out entre communautés
|
|
43
|
+
p = np.where(labels[:, None] == labels[None, :], 0.2, 0.02)
|
|
44
|
+
upper = np.triu(rng.random((nb_nodes, nb_nodes)) < p, k=1)
|
|
45
|
+
i, j = np.nonzero(upper)
|
|
46
|
+
senders = np.concatenate([i, j]) # un lien non orienté = deux arêtes
|
|
47
|
+
receivers = np.concatenate([j, i])
|
|
48
|
+
|
|
49
|
+
# caractéristiques : +/-1 sur la première coordonnée, noyé dans le bruit
|
|
50
|
+
mu = np.zeros((2, DIM_FEATURES))
|
|
51
|
+
mu[0, 0], mu[1, 0] = 1.0, -1.0
|
|
52
|
+
features = mu[labels] + SIGMA * rng.normal(size=(nb_nodes, DIM_FEATURES))
|
|
53
|
+
|
|
54
|
+
labeled = np.concatenate([rng.choice(np.arange(b * NB_PER_BLOCK, (b + 1) * NB_PER_BLOCK),
|
|
55
|
+
NB_LABELED, replace=False) for b in range(2)])
|
|
56
|
+
test_idx = np.setdiff1d(np.arange(nb_nodes), labeled)
|
|
57
|
+
|
|
58
|
+
to_j = lambda a, dtype: jnp.asarray(a, dtype=dtype)
|
|
59
|
+
return (to_j(features, jnp.float32), to_j(labels, jnp.int32),
|
|
60
|
+
to_j(senders, jnp.int32), to_j(receivers, jnp.int32),
|
|
61
|
+
to_j(labeled, jnp.int32), to_j(test_idx, jnp.int32))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class GNN_node_classifier(Module):
|
|
65
|
+
mp1: MessagePassing_layer
|
|
66
|
+
mp2: MessagePassing_layer
|
|
67
|
+
head: Linear
|
|
68
|
+
|
|
69
|
+
def __init__(self, rkey):
|
|
70
|
+
k1, k2, k3 = jr.split(rkey, 3)
|
|
71
|
+
# agrégation "mean" : l'échelle de l'agrégat ne dépend pas du degré,
|
|
72
|
+
# qui varie beaucoup d'un nœud à l'autre dans ce graphe
|
|
73
|
+
self.mp1 = MessagePassing_layer(DIM_FEATURES, 16, 16, k1, aggregation="mean")
|
|
74
|
+
self.mp2 = MessagePassing_layer(16, 16, 16, k2, aggregation="mean")
|
|
75
|
+
self.head = Linear(16, 2, k3)
|
|
76
|
+
|
|
77
|
+
def apply(self, h, rkey=None, *, senders, receivers):
|
|
78
|
+
h = self.mp1.apply(h, senders=senders, receivers=receivers)
|
|
79
|
+
h = self.mp2.apply(h, senders=senders, receivers=receivers)
|
|
80
|
+
return self.head.apply(h) # logits (nb_noeuds, 2)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def train_nodes(logits_fn, model, labels, train_idx, nb_steps=300, lr=0.01):
|
|
84
|
+
"""logits_fn(model) -> (nb_noeuds, 2). Perte : CE sur les seuls nœuds
|
|
85
|
+
étiquetés — l'indexation par entiers reste valide sous jit."""
|
|
86
|
+
optimizer = optax.adam(lr)
|
|
87
|
+
opt_state = optimizer.init(model)
|
|
88
|
+
|
|
89
|
+
def loss_fn(model):
|
|
90
|
+
logits = logits_fn(model)
|
|
91
|
+
ce = optax.softmax_cross_entropy_with_integer_labels(
|
|
92
|
+
logits[train_idx], labels[train_idx])
|
|
93
|
+
return jnp.mean(ce)
|
|
94
|
+
|
|
95
|
+
@jax.jit
|
|
96
|
+
def step(model, opt_state):
|
|
97
|
+
loss, grads = jax.value_and_grad(loss_fn)(model)
|
|
98
|
+
updates, opt_state = optimizer.update(grads, opt_state)
|
|
99
|
+
return optax.apply_updates(model, updates), opt_state, loss
|
|
100
|
+
|
|
101
|
+
for _ in range(nb_steps):
|
|
102
|
+
model, opt_state, loss = step(model, opt_state)
|
|
103
|
+
return model, float(loss)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def accuracy_on(logits_fn, model, labels, idx):
|
|
107
|
+
pred = jnp.argmax(logits_fn(model)[idx], axis=-1)
|
|
108
|
+
return float(jnp.mean(pred == labels[idx]))
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def run_comparison(seed=0, verbose=True):
|
|
112
|
+
X, labels, senders, receivers, train_idx, test_idx = make_graph(seed)
|
|
113
|
+
k_gnn, k_mlp = jr.split(jr.key(seed))
|
|
114
|
+
|
|
115
|
+
# référence : un MLP nœud par nœud, aveugle au graphe
|
|
116
|
+
mlp = MLP((DIM_FEATURES, 32, 2), "relu", k_mlp)
|
|
117
|
+
mlp_logits = lambda m: m.apply(X)
|
|
118
|
+
mlp, _ = train_nodes(mlp_logits, mlp, labels, train_idx)
|
|
119
|
+
acc_mlp = accuracy_on(mlp_logits, mlp, labels, test_idx)
|
|
120
|
+
|
|
121
|
+
# le GNN : mêmes caractéristiques, plus la structure du graphe
|
|
122
|
+
gnn = GNN_node_classifier(k_gnn)
|
|
123
|
+
gnn_logits = lambda m: m.apply(X, senders=senders, receivers=receivers)
|
|
124
|
+
gnn, _ = train_nodes(gnn_logits, gnn, labels, train_idx)
|
|
125
|
+
acc_gnn = accuracy_on(gnn_logits, gnn, labels, test_idx)
|
|
126
|
+
|
|
127
|
+
if verbose:
|
|
128
|
+
print(f"MLP (sans le graphe) : {acc_mlp:.3f}")
|
|
129
|
+
print(f"GNN (2 couches de message passing) : {acc_gnn:.3f}")
|
|
130
|
+
return acc_mlp, acc_gnn
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
if __name__ == "__main__":
|
|
134
|
+
run_comparison()
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Approximation de sinus par un MLP.
|
|
2
|
+
|
|
3
|
+
Descente de gradient a la main : pas d'optax, pas de filter_grad, pas de
|
|
4
|
+
partition/combine. Tout repose sur le fait qu'un yax.Module est un pytree
|
|
5
|
+
dont les feuilles sont exactement les parametres, a condition d'avoir
|
|
6
|
+
declare en `static` tout ce qui n'est pas un tableau.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import jax
|
|
10
|
+
import jax.numpy as jnp
|
|
11
|
+
import jax.random as jr
|
|
12
|
+
import matplotlib.pyplot as plt
|
|
13
|
+
from yax.layers.MLP import MLP
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def make_data(rkey, nb, x_min, x_max):
|
|
17
|
+
x = jr.uniform(rkey, (nb, 1), minval=x_min, maxval=x_max)
|
|
18
|
+
y = jnp.sin(x)
|
|
19
|
+
return x, y
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def loss_fn(model, x, y):
|
|
23
|
+
y_pred = jax.vmap(model.apply)(x)
|
|
24
|
+
return jnp.mean((y_pred - y) ** 2)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@jax.jit
|
|
28
|
+
def step(model, x, y, lr):
|
|
29
|
+
loss, grads = jax.value_and_grad(loss_fn)(model, x, y)
|
|
30
|
+
new_model = jax.tree.map(lambda p, g: p - lr * g, model, grads)
|
|
31
|
+
return new_model, loss
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def train(model, x, y, lr, nb_steps):
|
|
35
|
+
losses = []
|
|
36
|
+
for i in range(nb_steps):
|
|
37
|
+
model, loss = step(model, x, y, lr)
|
|
38
|
+
losses.append(loss)
|
|
39
|
+
if i % (nb_steps // 10) == 0:
|
|
40
|
+
print(f"step {i:5d} loss = {loss:.3e}")
|
|
41
|
+
return model, jnp.array(losses)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def plot(model, x_train, y_train, losses, x_min, x_max):
|
|
45
|
+
x_plot = jnp.linspace(x_min, x_max, 400)[:, None]
|
|
46
|
+
y_plot = jax.vmap(model.apply)(x_plot)
|
|
47
|
+
fig, (ax0, ax1) = plt.subplots(1, 2, figsize=(11, 4))
|
|
48
|
+
|
|
49
|
+
ax0.plot(x_train[:, 0], y_train[:, 0], ".", ms=4, alpha=0.35,
|
|
50
|
+
color="tab:gray", label="donnees")
|
|
51
|
+
ax0.plot(x_plot[:, 0], jnp.sin(x_plot[:, 0]), "--", lw=2,
|
|
52
|
+
color="tab:blue", label="sin(x)")
|
|
53
|
+
ax0.plot(x_plot[:, 0], y_plot[:, 0], lw=2,
|
|
54
|
+
color="tab:red", label="MLP")
|
|
55
|
+
ax0.set_xlabel("x")
|
|
56
|
+
ax0.set_title("approximation")
|
|
57
|
+
ax0.legend()
|
|
58
|
+
|
|
59
|
+
ax1.semilogy(losses, lw=1, color="tab:red")
|
|
60
|
+
ax1.set_xlabel("step")
|
|
61
|
+
ax1.set_title("loss (MSE)")
|
|
62
|
+
ax1.grid(alpha=0.3)
|
|
63
|
+
|
|
64
|
+
fig.tight_layout()
|
|
65
|
+
return fig
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
x_min, x_max = -3.0, 3.0
|
|
70
|
+
lr = 0.02
|
|
71
|
+
nb_steps = 10000
|
|
72
|
+
key_data, key_model = jr.split(jr.PRNGKey(0))
|
|
73
|
+
x, y = make_data(key_data, 256, x_min, x_max)
|
|
74
|
+
model = MLP((1, 32, 32, 1), "tanh", key_model)
|
|
75
|
+
|
|
76
|
+
print("loss initiale :", loss_fn(model, x, y))
|
|
77
|
+
model, losses = train(model, x, y, lr, nb_steps)
|
|
78
|
+
print("loss finale :", losses[-1])
|
|
79
|
+
|
|
80
|
+
fig = plot(model, x, y, losses, x_min, x_max)
|
|
81
|
+
fig.savefig("mpl_demo.png", dpi=120)
|
|
82
|
+
plt.show()
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import jax.numpy as jnp
|
|
3
|
+
import jax.random as jr
|
|
4
|
+
from matplotlib import pyplot as plt
|
|
5
|
+
from yax.core import Module, StaticField
|
|
6
|
+
import jax
|
|
7
|
+
import optax
|
|
8
|
+
from yax.layers.Linear import Linear
|
|
9
|
+
from yax.layers.RNN_layer import RNN_layer
|
|
10
|
+
from yax.training.Trainer import train, load_run, OUT_FOLDER
|
|
11
|
+
from yax.training.configs import TrainConfig
|
|
12
|
+
from yax.training.losses import bce_logits_loss, batch_apply
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
#C'est l'exemple qui est proposé dans la doc d'equinox
|
|
16
|
+
def get_data(dataset_size:int, rkey):
|
|
17
|
+
t = jnp.linspace(0, 2 * jnp.pi, 16)
|
|
18
|
+
offset = jr.uniform(rkey, (dataset_size, 1), minval=0, maxval=2 * jnp.pi)
|
|
19
|
+
x1 = jnp.sin(t + offset) / (1 + t)
|
|
20
|
+
x2 = jnp.cos(t + offset) / (1 + t)
|
|
21
|
+
|
|
22
|
+
half_dataset_size = dataset_size // 2
|
|
23
|
+
x1 = x1.at[:half_dataset_size].multiply(-1)
|
|
24
|
+
y = jnp.ones((dataset_size, 1))
|
|
25
|
+
y = y.at[:half_dataset_size].set(0)
|
|
26
|
+
#axis=-1 : on veut (dataset_size, 16, 2), soit 16 pas de temps de 2 features.
|
|
27
|
+
#Avec axis=1 on obtiendrait (dataset_size, 2, 16), et le scan parcourrait
|
|
28
|
+
#2 pas de temps de 16 features.
|
|
29
|
+
x = jnp.stack([x1, x2], axis=-1)
|
|
30
|
+
|
|
31
|
+
#les 2 classes sont rangees en bloc, mais batchs_for_one_epoch melange a
|
|
32
|
+
#chaque epoque, et train/val proviennent de deux appels distincts, chacun
|
|
33
|
+
#equilibre. Ne PAS decouper un seul get_data en tranches train/val.
|
|
34
|
+
return x, y
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def present_data():
|
|
38
|
+
n_data = 10
|
|
39
|
+
x, y = get_data(n_data,jr.key(0))
|
|
40
|
+
print(x.shape, y.shape)
|
|
41
|
+
|
|
42
|
+
fig, axs = plt.subplots(n_data, 2, figsize=(8, 1.2 * n_data), sharex="all", sharey="all")
|
|
43
|
+
for i in range(n_data):
|
|
44
|
+
axs[i, 0].plot(x[i, :, 0])
|
|
45
|
+
axs[i, 1].plot(x[i, :, 1])
|
|
46
|
+
axs[i, 0].set_ylabel(f"label={int(y[i, 0])}")
|
|
47
|
+
|
|
48
|
+
axs[0, 0].set_title("feature 0")
|
|
49
|
+
axs[0, 1].set_title("feature 1")
|
|
50
|
+
plt.show()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class RNN_bin_classifier(Module):
|
|
54
|
+
rnn_layer: RNN_layer
|
|
55
|
+
linear_layer: Linear
|
|
56
|
+
|
|
57
|
+
dim_hidden:int = StaticField()
|
|
58
|
+
|
|
59
|
+
def __init__(self,dim_hidden,rkey,*,cell_type="gru"):
|
|
60
|
+
self.dim_hidden=dim_hidden
|
|
61
|
+
|
|
62
|
+
rk1,rk2=jr.split(rkey)
|
|
63
|
+
self.rnn_layer = RNN_layer(2,dim_hidden,False,rk1,cell_type=cell_type)
|
|
64
|
+
self.linear_layer=Linear(dim_hidden,1,rk2)
|
|
65
|
+
|
|
66
|
+
def apply(self,xs,rkey=None):
|
|
67
|
+
# rkey ignoré : modèle déterministe (signature uniforme apply(x, rkey))
|
|
68
|
+
y=self.rnn_layer.apply(xs)
|
|
69
|
+
y=self.linear_layer.apply(y)
|
|
70
|
+
return y #pas de sigmoid: il est ajouté dans la loss
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def accuracy(model, x, y):
|
|
74
|
+
logits = batch_apply(model, x, None)
|
|
75
|
+
return float(jnp.mean((logits > 0) == (y > 0.5))) #logit>0 <=> sigmoid>0.5
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def train_classif(cell_type="gru", dim_hidden=16, verbose=True):
|
|
79
|
+
key_train, key_val, key_model, key_shuffle = jr.split(jr.key(0), 4)
|
|
80
|
+
X_train, Y_train = get_data(512, key_train)
|
|
81
|
+
X_val, Y_val = get_data(256, key_val)
|
|
82
|
+
|
|
83
|
+
model = RNN_bin_classifier(dim_hidden, key_model, cell_type=cell_type)
|
|
84
|
+
acc_before = accuracy(model, X_val, Y_val)
|
|
85
|
+
|
|
86
|
+
config = TrainConfig(batch_size=32, learning_rate=0.05, n_epoch=60, lr_final_ratio=0.01)
|
|
87
|
+
mother_folder = os.path.join(OUT_FOLDER, f"rnn_classif_{cell_type}")
|
|
88
|
+
best_model, best_loss, folder = train(
|
|
89
|
+
model, mother_folder, config, X_train, Y_train, X_val, Y_val,
|
|
90
|
+
rkey=key_shuffle, loss_fn=bce_logits_loss, verbose=verbose)
|
|
91
|
+
|
|
92
|
+
acc_after = accuracy(best_model, X_val, Y_val)
|
|
93
|
+
print(f"{cell_type} : accuracy {acc_before:.3f} -> {acc_after:.3f}"
|
|
94
|
+
f" (best val BCE = {best_loss:.3g})")
|
|
95
|
+
return folder, best_loss, acc_after
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def train_and_plot(cell_type="gru"):
|
|
99
|
+
folder, best_loss, acc = train_classif(cell_type)
|
|
100
|
+
history = load_run(folder, "history")["history"]
|
|
101
|
+
history.plot(title=f"RNN {cell_type} : BCE={best_loss:.2g}, accuracy={acc:.3f}")
|
|
102
|
+
plt.tight_layout()
|
|
103
|
+
plt.show()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
if __name__ == "__main__":
|
|
107
|
+
train_and_plot("gru")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|