Spaces:
Sleeping
Sleeping
Commit
·
bf5fb5f
1
Parent(s):
b002f5a
initialisation
Browse files- .gitattributes +2 -1
- .gitignore +183 -0
- Dockerfile +47 -0
- requirements.txt +23 -0
- src/base_trainer.py +174 -0
- src/conf/config.yaml +32 -0
- src/conf/model.yaml +56 -0
- src/conf/vectorization.yaml +26 -0
- src/config.py +79 -0
- src/cuml_trainer.py +120 -0
- src/dataset/dataset.csv +3 -0
- src/interfaces/cuml_tfidf_vectorizer.py +37 -0
- src/interfaces/hyperparameter_optimizer.py +28 -0
- src/interfaces/metrics_calculator.py +84 -0
- src/interfaces/vectorizer.py +40 -0
- src/main.py +142 -0
- src/mlflow_integration/mlflow_decorator.py +82 -0
- src/optimizers/optuna_optimizer.py +69 -0
- src/optimizers/ray_tune_optimizer.py +138 -0
- src/parameter_logging.py +67 -0
- src/trainers/cuml/linear_regression_trainer.py +80 -0
- src/trainers/cuml/logistic_regression_trainer.py +78 -0
- src/trainers/cuml/random_forest_trainer.py +103 -0
- src/trainers/cuml/svm_trainer.py +110 -0
- src/trainers/huggingface/huggingface_transformer_trainer.py +97 -0
- src/utilities/cuml_pyfunc_wrapper.py +56 -0
- uml.plantuml +333 -0
.gitattributes
CHANGED
@@ -25,7 +25,6 @@
|
|
25 |
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
26 |
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
27 |
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
29 |
*.tflite filter=lfs diff=lfs merge=lfs -text
|
30 |
*.tgz filter=lfs diff=lfs merge=lfs -text
|
31 |
*.wasm filter=lfs diff=lfs merge=lfs -text
|
@@ -33,3 +32,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
25 |
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
26 |
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
27 |
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
|
|
28 |
*.tflite filter=lfs diff=lfs merge=lfs -text
|
29 |
*.tgz filter=lfs diff=lfs merge=lfs -text
|
30 |
*.wasm filter=lfs diff=lfs merge=lfs -text
|
|
|
32 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
33 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
34 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
35 |
+
aim_repo.tar.gz filter=lfs diff=lfs merge=lfs -text
|
36 |
+
*.csv filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
@@ -0,0 +1,183 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Byte-compiled / optimized / DLL files
|
2 |
+
__pycache__/
|
3 |
+
*.py[cod]
|
4 |
+
*$py.class
|
5 |
+
|
6 |
+
# C extensions
|
7 |
+
*.so
|
8 |
+
|
9 |
+
# Distribution / packaging
|
10 |
+
.Python
|
11 |
+
build/
|
12 |
+
develop-eggs/
|
13 |
+
dist/
|
14 |
+
downloads/
|
15 |
+
eggs/
|
16 |
+
.eggs/
|
17 |
+
lib/
|
18 |
+
lib64/
|
19 |
+
parts/
|
20 |
+
sdist/
|
21 |
+
var/
|
22 |
+
wheels/
|
23 |
+
share/python-wheels/
|
24 |
+
*.egg-info/
|
25 |
+
.installed.cfg
|
26 |
+
*.egg
|
27 |
+
MANIFEST
|
28 |
+
|
29 |
+
# PyInstaller
|
30 |
+
# Usually these files are written by a python script from a template
|
31 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
32 |
+
*.manifest
|
33 |
+
*.spec
|
34 |
+
|
35 |
+
# Installer logs
|
36 |
+
pip-log.txt
|
37 |
+
pip-delete-this-directory.txt
|
38 |
+
|
39 |
+
# Unit test / coverage reports
|
40 |
+
htmlcov/
|
41 |
+
.tox/
|
42 |
+
.nox/
|
43 |
+
.coverage
|
44 |
+
.coverage.*
|
45 |
+
.cache
|
46 |
+
nosetests.xml
|
47 |
+
coverage.xml
|
48 |
+
*.cover
|
49 |
+
*.py,cover
|
50 |
+
.hypothesis/
|
51 |
+
.pytest_cache/
|
52 |
+
cover/
|
53 |
+
|
54 |
+
# Translations
|
55 |
+
*.mo
|
56 |
+
*.pot
|
57 |
+
|
58 |
+
# Django stuff:
|
59 |
+
*.log
|
60 |
+
local_settings.py
|
61 |
+
db.sqlite3
|
62 |
+
db.sqlite3-journal
|
63 |
+
|
64 |
+
# Flask stuff:
|
65 |
+
instance/
|
66 |
+
.webassets-cache
|
67 |
+
|
68 |
+
# Scrapy stuff:
|
69 |
+
.scrapy
|
70 |
+
|
71 |
+
# Sphinx documentation
|
72 |
+
docs/_build/
|
73 |
+
|
74 |
+
# PyBuilder
|
75 |
+
.pybuilder/
|
76 |
+
target/
|
77 |
+
|
78 |
+
# Jupyter Notebook
|
79 |
+
.ipynb_checkpoints
|
80 |
+
|
81 |
+
# IPython
|
82 |
+
profile_default/
|
83 |
+
ipython_config.py
|
84 |
+
|
85 |
+
# pyenv
|
86 |
+
# For a library or package, you might want to ignore these files since the code is
|
87 |
+
# intended to run in multiple environments; otherwise, check them in:
|
88 |
+
# .python-version
|
89 |
+
|
90 |
+
# pipenv
|
91 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
92 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
93 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
94 |
+
# install all needed dependencies.
|
95 |
+
#Pipfile.lock
|
96 |
+
|
97 |
+
# UV
|
98 |
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
99 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
100 |
+
# commonly ignored for libraries.
|
101 |
+
#uv.lock
|
102 |
+
|
103 |
+
# poetry
|
104 |
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
105 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
106 |
+
# commonly ignored for libraries.
|
107 |
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
108 |
+
#poetry.lock
|
109 |
+
|
110 |
+
# pdm
|
111 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
112 |
+
#pdm.lock
|
113 |
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
114 |
+
# in version control.
|
115 |
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
116 |
+
.pdm.toml
|
117 |
+
.pdm-python
|
118 |
+
.pdm-build/
|
119 |
+
|
120 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
121 |
+
__pypackages__/
|
122 |
+
|
123 |
+
# Celery stuff
|
124 |
+
celerybeat-schedule
|
125 |
+
celerybeat.pid
|
126 |
+
|
127 |
+
# SageMath parsed files
|
128 |
+
*.sage.py
|
129 |
+
|
130 |
+
# Environments
|
131 |
+
.env
|
132 |
+
.venv
|
133 |
+
env/
|
134 |
+
venv/
|
135 |
+
ENV/
|
136 |
+
env.bak/
|
137 |
+
venv.bak/
|
138 |
+
|
139 |
+
# Spyder project settings
|
140 |
+
.spyderproject
|
141 |
+
.spyproject
|
142 |
+
|
143 |
+
# Rope project settings
|
144 |
+
.ropeproject
|
145 |
+
|
146 |
+
# mkdocs documentation
|
147 |
+
/site
|
148 |
+
|
149 |
+
# mypy
|
150 |
+
.mypy_cache/
|
151 |
+
.dmypy.json
|
152 |
+
dmypy.json
|
153 |
+
|
154 |
+
# Pyre type checker
|
155 |
+
.pyre/
|
156 |
+
|
157 |
+
# pytype static type analyzer
|
158 |
+
.pytype/
|
159 |
+
|
160 |
+
# Cython debug symbols
|
161 |
+
cython_debug/
|
162 |
+
|
163 |
+
# PyCharm
|
164 |
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
165 |
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
166 |
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
167 |
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
168 |
+
#.idea/
|
169 |
+
|
170 |
+
# Ruff stuff:
|
171 |
+
.ruff_cache/
|
172 |
+
|
173 |
+
# PyPI configuration file
|
174 |
+
.pypirc
|
175 |
+
|
176 |
+
chunks/
|
177 |
+
|
178 |
+
check_ins
|
179 |
+
|
180 |
+
meta/
|
181 |
+
|
182 |
+
seqs/
|
183 |
+
locks/
|
Dockerfile
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM nvidia/cuda:12.1.1-cudnn8-runtime-ubuntu22.04 AS runtime
|
2 |
+
|
3 |
+
# Désactiver l'interactivité
|
4 |
+
ENV DEBIAN_FRONTEND=noninteractive
|
5 |
+
|
6 |
+
# Installer les outils de base nécessaires
|
7 |
+
RUN apt-get update && apt-get install -y \
|
8 |
+
bash \
|
9 |
+
curl \
|
10 |
+
git \
|
11 |
+
git-lfs \
|
12 |
+
wget \
|
13 |
+
procps \
|
14 |
+
build-essential \
|
15 |
+
&& rm -rf /var/lib/apt/lists/*
|
16 |
+
|
17 |
+
ENV XDG_BIN_HOME=/usr/local/bin
|
18 |
+
|
19 |
+
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
|
20 |
+
|
21 |
+
# Créer l'utilisateur non privilégié avec UID 1000 et préparer le répertoire de l'application
|
22 |
+
RUN useradd -m -u 1000 aim_user && \
|
23 |
+
mkdir -p /app && \
|
24 |
+
chown -R 1000:1000 /app
|
25 |
+
|
26 |
+
RUN echo "alias venv='source .venv/bin/activate'" >> /etc/bash.bashrc
|
27 |
+
|
28 |
+
WORKDIR /app
|
29 |
+
|
30 |
+
USER 1000
|
31 |
+
|
32 |
+
COPY --chown=1000:1000 requirements.txt /app/
|
33 |
+
|
34 |
+
# Initialiser UV, créer l'environnement virtuel, installer les dépendances et purger le cache pip
|
35 |
+
RUN uv init --python 3.11 --no-managed-python --no-workspace && \
|
36 |
+
uv venv && \
|
37 |
+
. /app/.venv/bin/activate && \
|
38 |
+
uv add -r requirements.txt && \
|
39 |
+
uv run --with pip --with spacy -- spacy download en_core_web_sm && \
|
40 |
+
uv cache prune
|
41 |
+
|
42 |
+
# Copier le reste du code source dans /app
|
43 |
+
COPY --chown=1000:1000 src/ /app/
|
44 |
+
|
45 |
+
ENV PATH="/app/.venv/bin:${PATH}"
|
46 |
+
|
47 |
+
CMD ["mlflow", "ui", "--backend-store-uri", "file:///data/mlruns", "--host", "0.0.0.0", "--port", "7860"]
|
requirements.txt
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
hydra-core
|
2 |
+
omegaconf
|
3 |
+
mlflow
|
4 |
+
pydantic
|
5 |
+
cupy
|
6 |
+
cudf
|
7 |
+
scikit-learn
|
8 |
+
scipy
|
9 |
+
numpy
|
10 |
+
pandas
|
11 |
+
optuna
|
12 |
+
ray[tune]
|
13 |
+
transformers
|
14 |
+
torch
|
15 |
+
datasets
|
16 |
+
seaborn
|
17 |
+
matplotlib
|
18 |
+
cupy-cuda12x
|
19 |
+
cuml-cu12==25.2.*
|
20 |
+
cudf-cu12==25.2.*
|
21 |
+
torchvision
|
22 |
+
accelerate
|
23 |
+
transformers
|
src/base_trainer.py
ADDED
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# =========================
|
2 |
+
# Fichier: base_trainer.py
|
3 |
+
# =========================
|
4 |
+
|
5 |
+
from abc import ABC, abstractmethod
|
6 |
+
from typing import Union, Optional
|
7 |
+
import cupy as cp
|
8 |
+
from scipy.sparse import csr_matrix
|
9 |
+
|
10 |
+
from config import Config
|
11 |
+
from interfaces.metrics_calculator import MetricsCalculator
|
12 |
+
|
13 |
+
|
14 |
+
class BaseTrainer(ABC):
|
15 |
+
"""
|
16 |
+
Classe de base abstraite représentant un entraîneur (trainer) générique,
|
17 |
+
tel que défini dans le diagramme UML.
|
18 |
+
|
19 |
+
Attributs:
|
20 |
+
config (Config): Configuration globale du système (modèle, data, etc.).
|
21 |
+
classifier (object): Référence vers le classifieur ou le modèle entraîné.
|
22 |
+
metrics_calculator (MetricsCalculator): Outil de calcul et de logging des métriques.
|
23 |
+
data_path (str): Chemin vers les données.
|
24 |
+
target_column (str): Nom de la colonne cible dans les données.
|
25 |
+
"""
|
26 |
+
|
27 |
+
def __init__(self, config: Config, data_path: str,
|
28 |
+
target_column: str) -> None:
|
29 |
+
"""
|
30 |
+
Initialise un trainer générique avec la configuration et les informations de chemin de données.
|
31 |
+
|
32 |
+
:param config: Objet de configuration global.
|
33 |
+
:param data_path: Chemin vers le fichier de données.
|
34 |
+
:param target_column: Nom de la colonne cible pour l'entraînement/prédiction.
|
35 |
+
"""
|
36 |
+
self.config: Config = config
|
37 |
+
self.data_path: str = data_path
|
38 |
+
self.target_column: str = target_column
|
39 |
+
self.classifier: object = None
|
40 |
+
self.metrics_calculator: MetricsCalculator = None
|
41 |
+
|
42 |
+
@abstractmethod
|
43 |
+
def build_components(self) -> None:
|
44 |
+
"""
|
45 |
+
Méthode abstraite. Instancie les composants nécessaires
|
46 |
+
(e.g. le classifieur, éventuellement le vectorizer) selon la config.
|
47 |
+
"""
|
48 |
+
pass
|
49 |
+
|
50 |
+
@abstractmethod
|
51 |
+
def train(self) -> None:
|
52 |
+
"""
|
53 |
+
Méthode abstraite. Lance la procédure d'entraînement.
|
54 |
+
"""
|
55 |
+
pass
|
56 |
+
|
57 |
+
@abstractmethod
|
58 |
+
def evaluate(self) -> None:
|
59 |
+
"""
|
60 |
+
Méthode abstraite. Évalue le modèle entraîné, par exemple
|
61 |
+
sur un jeu de validation ou de test, et calcule les métriques.
|
62 |
+
"""
|
63 |
+
pass
|
64 |
+
|
65 |
+
def optimize_if_needed(self) -> None:
|
66 |
+
"""
|
67 |
+
Vérifie la configuration pour déterminer si l'optimisation des hyperparamètres
|
68 |
+
est nécessaire. Si oui, instancie l'optimiseur approprié et lance
|
69 |
+
le processus d'optimisation. Met ensuite à jour la configuration du modèle
|
70 |
+
avec les meilleurs paramètres trouvés et reconstruit les composants.
|
71 |
+
"""
|
72 |
+
import logging
|
73 |
+
logger = logging.getLogger(__name__)
|
74 |
+
|
75 |
+
# Vérifier si l'optimisation est configurée
|
76 |
+
if (self.config.hyperparameters.optimizer and
|
77 |
+
self.config.hyperparameters.param_grid and
|
78 |
+
self.config.hyperparameters.n_trials > 0):
|
79 |
+
|
80 |
+
logger.info("Démarrage de l'optimisation des hyperparamètres")
|
81 |
+
|
82 |
+
# Importation et instanciation de l'optimiseur
|
83 |
+
optimizer_type = self.config.hyperparameters.optimizer.lower()
|
84 |
+
if optimizer_type == "optuna":
|
85 |
+
from optimizers.optuna_optimizer import OptunaOptimizer
|
86 |
+
optimizer = OptunaOptimizer()
|
87 |
+
elif optimizer_type == "raytune":
|
88 |
+
from optimizers.ray_tune_optimizer import RayTuneOptimizer
|
89 |
+
optimizer = RayTuneOptimizer()
|
90 |
+
else:
|
91 |
+
raise ValueError(f"Type d'optimizer non supporté: {optimizer_type}")
|
92 |
+
|
93 |
+
# Lancement de l'optimisation
|
94 |
+
best_params = optimizer.optimize(
|
95 |
+
trainer=self, # Passe l'instance actuelle du trainer
|
96 |
+
param_grid=self.config.hyperparameters.param_grid
|
97 |
+
)
|
98 |
+
|
99 |
+
logger.info(f"Meilleurs hyperparamètres trouvés: {best_params}")
|
100 |
+
|
101 |
+
# Mise à jour de la configuration du modèle avec les meilleurs paramètres
|
102 |
+
self.config.model.params.update(best_params)
|
103 |
+
|
104 |
+
# Reconstruire les composants avec les nouveaux paramètres
|
105 |
+
logger.info("Reconstruction des composants avec les hyperparamètres optimisés.")
|
106 |
+
self.build_components()
|
107 |
+
else:
|
108 |
+
logger.info("Aucune optimisation des hyperparamètres configurée.")
|
109 |
+
|
110 |
+
def log_parameters_to_mlflow(self) -> None:
|
111 |
+
"""
|
112 |
+
Appelle une fonction singledispatch (get_relevant_params_for_logging(trainer)) pour récupérer
|
113 |
+
les paramètres pertinents et les logguer, par exemple via MLflow.
|
114 |
+
Implementé ici en tant que méthode non-abstraite, mais la logique de logging
|
115 |
+
devrait être assurée dans l'environnement MLflow approprié.
|
116 |
+
"""
|
117 |
+
pass
|
118 |
+
|
119 |
+
def _prepare_input_for_fit(
|
120 |
+
self, X: Union[cp.ndarray,
|
121 |
+
csr_matrix]) -> Union[cp.ndarray, csr_matrix]:
|
122 |
+
"""
|
123 |
+
Méthode utilitaire pour préparer les données d'entraînement avant
|
124 |
+
l'ajustement du modèle.
|
125 |
+
|
126 |
+
:param X: Matrice (cupy.ndarray ou scipy.sparse.csr_matrix) représentant les données.
|
127 |
+
:return: Matrice transformée ou identique, prête pour l'entraînement.
|
128 |
+
"""
|
129 |
+
return X
|
130 |
+
|
131 |
+
def _prepare_input_for_predict(
|
132 |
+
self, X: Union[cp.ndarray,
|
133 |
+
csr_matrix]) -> Union[cp.ndarray, csr_matrix]:
|
134 |
+
"""
|
135 |
+
Méthode utilitaire pour préparer les données de prédiction avant
|
136 |
+
l'appel à la méthode `predict` du modèle.
|
137 |
+
|
138 |
+
:param X: Matrice (cupy.ndarray ou scipy.sparse.csr_matrix) représentant les données.
|
139 |
+
:return: Matrice transformée ou identique, prête pour la prédiction.
|
140 |
+
"""
|
141 |
+
return X
|
142 |
+
|
143 |
+
def _get_binary_predictions(self, X: cp.ndarray) -> cp.ndarray:
|
144 |
+
"""
|
145 |
+
Retourne un vecteur de prédictions binaires (0/1).
|
146 |
+
|
147 |
+
:param X: Matrice de données de dimension (n_samples, n_features),
|
148 |
+
déjà sous forme cupy.ndarray.
|
149 |
+
:return: Vecteur de prédictions binaires (cupy.ndarray).
|
150 |
+
"""
|
151 |
+
# Ici, la logique de conversion en 0/1 n'est pas spécifiée dans l'UML,
|
152 |
+
# donc on la laisse minimale (raise NotImplementedError si nécessaire).
|
153 |
+
raise NotImplementedError(
|
154 |
+
"La méthode '_get_binary_predictions' doit être implémentée dans une sous-classe."
|
155 |
+
)
|
156 |
+
|
157 |
+
def _get_positive_probabilities(self,
|
158 |
+
X: cp.ndarray) -> Optional[cp.ndarray]:
|
159 |
+
"""
|
160 |
+
Retourne la probabilité d'appartenir à la classe positive pour chaque échantillon,
|
161 |
+
si le modèle le permet. Sinon, retourne None.
|
162 |
+
|
163 |
+
:param X: Matrice de données en cupy.ndarray.
|
164 |
+
:return: Vecteur de probabilités (cupy.ndarray) ou None si non applicable.
|
165 |
+
"""
|
166 |
+
return None
|
167 |
+
|
168 |
+
def _get_label_dtype(self) -> cp.dtype:
|
169 |
+
"""
|
170 |
+
Retourne le type cupy.dtype approprié pour les labels.
|
171 |
+
|
172 |
+
:return: Par exemple, cp.int32.
|
173 |
+
"""
|
174 |
+
return cp.int32
|
src/conf/config.yaml
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# conf/config.yaml
|
2 |
+
|
3 |
+
defaults:
|
4 |
+
- models: models
|
5 |
+
- _self_
|
6 |
+
|
7 |
+
model:
|
8 |
+
type: "svm"
|
9 |
+
|
10 |
+
data:
|
11 |
+
path: "dataset/dataset.csv"
|
12 |
+
target_column: "label"
|
13 |
+
|
14 |
+
vectorization:
|
15 |
+
method: "tfidf"
|
16 |
+
|
17 |
+
mlflow:
|
18 |
+
experiment_name: "experiment-${model.type}"
|
19 |
+
tracking_uri: "file:///data/mlruns"
|
20 |
+
|
21 |
+
hyperparameters:
|
22 |
+
optimizer: "optuna"
|
23 |
+
param_grid:
|
24 |
+
C: [0.1, 1, 10, 100]
|
25 |
+
kernel:
|
26 |
+
- "linear"
|
27 |
+
- "rbf"
|
28 |
+
gamma:
|
29 |
+
low: 0.001
|
30 |
+
high: 0.1
|
31 |
+
log: true
|
32 |
+
n_trials: 50
|
src/conf/model.yaml
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# conf/models.yaml
|
2 |
+
|
3 |
+
###############################################################################
|
4 |
+
# Ce fichier rassemble TOUTES les config de modèles dans un seul YAML.
|
5 |
+
#
|
6 |
+
# L'idée : on centralise ici les paramètres, et dans le "main" ou dans
|
7 |
+
# un autre fichier config.yaml, on choisit un "type" => "svm", "random_forest",
|
8 |
+
# "huggingface_transformer"..., puis on récupère le bloc correspondant.
|
9 |
+
###############################################################################
|
10 |
+
|
11 |
+
models:
|
12 |
+
huggingface_transformer:
|
13 |
+
# Paramètres pour HuggingFaceTransformerTrainer
|
14 |
+
model_name: "bert-base-uncased"
|
15 |
+
num_labels: 2
|
16 |
+
learning_rate: 5e-5
|
17 |
+
epochs: 3
|
18 |
+
batch_size: 8
|
19 |
+
warmup_steps: 0
|
20 |
+
weight_decay: 0.0
|
21 |
+
adam_epsilon: 1e-8
|
22 |
+
|
23 |
+
svm:
|
24 |
+
# Paramètres pour SvmTrainer
|
25 |
+
kernel: "rbf"
|
26 |
+
C: 1.0
|
27 |
+
gamma: "scale"
|
28 |
+
degree: 3
|
29 |
+
tol: 1e-3
|
30 |
+
max_iter: 1000
|
31 |
+
probability: True
|
32 |
+
decision_function_shape: "ovr"
|
33 |
+
|
34 |
+
random_forest:
|
35 |
+
# Paramètres pour RandomForestTrainer
|
36 |
+
n_estimators: 100
|
37 |
+
max_depth: null
|
38 |
+
max_features: "auto"
|
39 |
+
min_samples_split: 2
|
40 |
+
min_samples_leaf: 1
|
41 |
+
bootstrap: true
|
42 |
+
criterion: "gini"
|
43 |
+
|
44 |
+
logistic_regression:
|
45 |
+
# Paramètres pour LogisticRegressionTrainer
|
46 |
+
penalty: "l2"
|
47 |
+
C: 1.0
|
48 |
+
solver: "qn"
|
49 |
+
max_iter: 100
|
50 |
+
fit_intercept: true
|
51 |
+
linesearch_max_iter: 50
|
52 |
+
|
53 |
+
linear_regression:
|
54 |
+
# Paramètres pour LinearRegressionTrainer
|
55 |
+
fit_intercept: true
|
56 |
+
algorithm: "eig"
|
src/conf/vectorization.yaml
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# conf/vectorization.yaml
|
2 |
+
vectorization :
|
3 |
+
tfidf:
|
4 |
+
lowercase: false # Géré par cuML
|
5 |
+
stop_words: null # Géré par cuML (None, 'english', ou une liste)
|
6 |
+
ngram_range: [1, 1] # Géré par cuML
|
7 |
+
max_df: 1.0 # Géré par cuML
|
8 |
+
min_df: 1 # Géré par cuML
|
9 |
+
max_features: 5000 # Géré par cuML
|
10 |
+
norm: l2 # Spécifique TF-IDF
|
11 |
+
use_idf: true # Spécifique TF-IDF
|
12 |
+
smooth_idf: true # Spécifique TF-IDF
|
13 |
+
sublinear_tf: false # Spécifique TF-IDF
|
14 |
+
# vocabulary: null # Optionnel, gardé si besoin
|
15 |
+
# binary: false # Optionnel, gardé si besoin
|
16 |
+
|
17 |
+
bow:
|
18 |
+
lowercase: false # Géré par cuML
|
19 |
+
stop_words: null # Géré par cuML
|
20 |
+
token_pattern: "(?u)\\b\\w\\w+\\b" # Géré par CountVectorizer cuML
|
21 |
+
ngram_range: [1, 1] # Géré par cuML
|
22 |
+
max_df: 1.0 # Géré par cuML
|
23 |
+
min_df: 1 # Géré par cuML
|
24 |
+
max_features: null # Géré par cuML
|
25 |
+
# vocabulary: null # Optionnel, gardé si besoin
|
26 |
+
# binary: false # Optionnel, gardé si besoin
|
src/config.py
ADDED
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# ======================
|
2 |
+
# Fichier: config.py
|
3 |
+
# ======================
|
4 |
+
|
5 |
+
from pydantic import BaseModel, Field
|
6 |
+
from typing import Dict, Any
|
7 |
+
|
8 |
+
|
9 |
+
class ModelConfig(BaseModel):
|
10 |
+
"""
|
11 |
+
Représente la configuration du modèle, incluant le type de modèle
|
12 |
+
et les paramètres associés.
|
13 |
+
"""
|
14 |
+
type: str = Field(
|
15 |
+
...,
|
16 |
+
description=
|
17 |
+
"Le type de modèle à entraîner (ex. 'svm', 'random_forest', 'logistic_regression', etc.)."
|
18 |
+
)
|
19 |
+
params: Dict[str, Any] = Field(
|
20 |
+
default_factory=dict,
|
21 |
+
description="Dictionnaire des paramètres propres au modèle choisi.")
|
22 |
+
|
23 |
+
|
24 |
+
class VectorizationConfig(BaseModel):
|
25 |
+
"""
|
26 |
+
Représente la configuration de la vectorisation, incluant la méthode
|
27 |
+
et les paramètres éventuels.
|
28 |
+
"""
|
29 |
+
method: str = Field(
|
30 |
+
...,
|
31 |
+
description="Le type de vectorisation à utiliser (ex. 'tfidf', 'bow')."
|
32 |
+
)
|
33 |
+
tfidf: Dict[str, Any] = Field(
|
34 |
+
default_factory=dict,
|
35 |
+
description="Paramètres spécifiques à une vectorisation TF-IDF.")
|
36 |
+
bow: Dict[str, Any] = Field(
|
37 |
+
default_factory=dict,
|
38 |
+
description="Paramètres spécifiques à une vectorisation bag-of-words.")
|
39 |
+
|
40 |
+
|
41 |
+
class DataConfig(BaseModel):
|
42 |
+
"""
|
43 |
+
Représente la configuration liée aux données, incluant
|
44 |
+
le chemin vers les données et le nom de la colonne cible.
|
45 |
+
"""
|
46 |
+
path: str = Field(...,
|
47 |
+
description="Chemin d'accès vers la source de données.")
|
48 |
+
target_column: str = Field(
|
49 |
+
..., description="Nom de la colonne contenant la variable cible.")
|
50 |
+
|
51 |
+
|
52 |
+
class HyperparameterConfig(BaseModel):
|
53 |
+
"""
|
54 |
+
Représente la configuration pour l'optimisation des hyperparamètres,
|
55 |
+
incluant le nom de l'optimiseur, la grille de paramètres et
|
56 |
+
le nombre d'itérations d'entraînement.
|
57 |
+
"""
|
58 |
+
optimizer: str = Field(
|
59 |
+
...,
|
60 |
+
description=
|
61 |
+
"Nom de l'optimiseur d'hyperparamètres (ex. 'optuna', 'raytune').")
|
62 |
+
param_grid: Dict[str, Any] = Field(
|
63 |
+
default_factory=dict,
|
64 |
+
description=
|
65 |
+
"Grille définissant l'espace de recherche pour chaque hyperparamètre.")
|
66 |
+
n_trials: int = Field(
|
67 |
+
default=1,
|
68 |
+
description="Nombre d'essais pour la recherche d'hyperparamètres.")
|
69 |
+
|
70 |
+
|
71 |
+
class Config(BaseModel):
|
72 |
+
"""
|
73 |
+
Objet de configuration global combinant la section modèle, vectorisation,
|
74 |
+
données et hyperparamètres.
|
75 |
+
"""
|
76 |
+
model: ModelConfig
|
77 |
+
vectorization: VectorizationConfig
|
78 |
+
data: DataConfig
|
79 |
+
hyperparameters: HyperparameterConfig
|
src/cuml_trainer.py
ADDED
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# ===========================
|
2 |
+
# Fichier: cuml_trainer.py
|
3 |
+
# ===========================
|
4 |
+
|
5 |
+
from abc import ABC, abstractmethod
|
6 |
+
from typing import Union
|
7 |
+
import cupy as cp
|
8 |
+
from scipy.sparse import csr_matrix
|
9 |
+
|
10 |
+
from config import Config
|
11 |
+
from base_trainer import BaseTrainer
|
12 |
+
from interfaces.vectorizer import Vectorizer
|
13 |
+
|
14 |
+
|
15 |
+
class CuMLTrainer(BaseTrainer, ABC):
|
16 |
+
"""
|
17 |
+
Classe abstraite, héritée de BaseTrainer, représentant un entraîneur
|
18 |
+
basé sur la librairie cuML. Elle ajoute notamment le concept de vectoriseur
|
19 |
+
et force le passage de la matrice d'entrée en cupy.ndarray pour la plupart
|
20 |
+
des opérations.
|
21 |
+
|
22 |
+
Attributs:
|
23 |
+
vectorizer (Vectorizer): Objet responsable de la vectorisation du texte.
|
24 |
+
"""
|
25 |
+
|
26 |
+
def __init__(self, config: Config, data_path: str,
|
27 |
+
target_column: str) -> None:
|
28 |
+
"""
|
29 |
+
Initialise un CuMLTrainer avec la configuration.
|
30 |
+
Appelle également le constructeur de BaseTrainer.
|
31 |
+
|
32 |
+
:param config: Configuration globale du système.
|
33 |
+
:param data_path: Chemin vers le fichier de données.
|
34 |
+
:param target_column: Nom de la colonne cible dans les données.
|
35 |
+
"""
|
36 |
+
super().__init__(config, data_path, target_column)
|
37 |
+
self.vectorizer: Vectorizer = None
|
38 |
+
# self.classifier est déjà défini dans BaseTrainer.
|
39 |
+
# On suppose que 'classifier' sera un modèle cuML (cuml.Base).
|
40 |
+
|
41 |
+
@abstractmethod
|
42 |
+
def build_components(self) -> None:
|
43 |
+
"""
|
44 |
+
Méthode abstraite. Instancie concrètement le vectorizer et le classifieur,
|
45 |
+
selon la configuration (ex. 'svm', 'random_forest', etc.).
|
46 |
+
"""
|
47 |
+
pass
|
48 |
+
|
49 |
+
def train(self) -> None:
|
50 |
+
"""
|
51 |
+
Entraîne le classifieur sur les données vectorisées.
|
52 |
+
Cette implémentation générique fonctionne pour tous les trainers cuML.
|
53 |
+
"""
|
54 |
+
# Chargement des données
|
55 |
+
import cudf
|
56 |
+
data = cudf.read_csv(self.data_path)
|
57 |
+
|
58 |
+
# Séparation des textes et des étiquettes
|
59 |
+
texts = data.drop(columns=[self.target_column])
|
60 |
+
labels = data[self.target_column].values
|
61 |
+
|
62 |
+
# Vectorisation des textes
|
63 |
+
X = self.vectorizer.fit_transform(texts)
|
64 |
+
X_prepared = self._prepare_input_for_fit(X)
|
65 |
+
|
66 |
+
# Entraînement du modèle
|
67 |
+
self.classifier.fit(X_prepared, labels)
|
68 |
+
|
69 |
+
def evaluate(self) -> None:
|
70 |
+
"""
|
71 |
+
Évalue le classifieur et calcule les métriques.
|
72 |
+
Cette implémentation générique fonctionne pour tous les trainers cuML.
|
73 |
+
"""
|
74 |
+
# Chargement des données (idéalement un jeu de test séparé)
|
75 |
+
import cudf
|
76 |
+
data = cudf.read_csv(self.data_path)
|
77 |
+
|
78 |
+
# Séparation des textes et des étiquettes
|
79 |
+
texts = data.drop(columns=[self.target_column])
|
80 |
+
y_true = data[self.target_column].values
|
81 |
+
|
82 |
+
# Vectorisation et prédiction
|
83 |
+
X = self.vectorizer.transform(texts)
|
84 |
+
X_prepared = self._prepare_input_for_predict(X)
|
85 |
+
y_pred = self.classifier.predict(X_prepared)
|
86 |
+
|
87 |
+
# Calcul et logging des métriques
|
88 |
+
prefix = self.config.model.type.lower()
|
89 |
+
metrics = self.metrics_calculator.calculate_and_log(
|
90 |
+
y_true=y_true,
|
91 |
+
y_pred=y_pred,
|
92 |
+
prefix=prefix
|
93 |
+
)
|
94 |
+
|
95 |
+
# Afficher les résultats
|
96 |
+
print(f"Métriques d'évaluation {prefix}: {metrics}")
|
97 |
+
|
98 |
+
def _prepare_input_for_fit(self, X: Union[cp.ndarray,
|
99 |
+
csr_matrix]) -> cp.ndarray:
|
100 |
+
"""
|
101 |
+
Convertit, si nécessaire, la matrice en cupy.ndarray pour l'entraînement.
|
102 |
+
|
103 |
+
:param X: Données d'entraînement (cupy.ndarray ou scipy.sparse.csr_matrix).
|
104 |
+
:return: Données converties en cupy.ndarray, pour compatibilité cuML.
|
105 |
+
"""
|
106 |
+
if isinstance(X, csr_matrix):
|
107 |
+
return cp.asarray(X.toarray())
|
108 |
+
return X # c'est déjà cupy.ndarray
|
109 |
+
|
110 |
+
def _prepare_input_for_predict(
|
111 |
+
self, X: Union[cp.ndarray, csr_matrix]) -> cp.ndarray:
|
112 |
+
"""
|
113 |
+
Convertit, si nécessaire, la matrice en cupy.ndarray pour la prédiction.
|
114 |
+
|
115 |
+
:param X: Données de prédiction (cupy.ndarray ou scipy.sparse.csr_matrix).
|
116 |
+
:return: Données converties en cupy.ndarray, pour compatibilité cuML.
|
117 |
+
"""
|
118 |
+
if isinstance(X, csr_matrix):
|
119 |
+
return cp.asarray(X.toarray())
|
120 |
+
return X
|
src/dataset/dataset.csv
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:a4c582a26c9391ad2e190298ad0a0d0951f2ee3c71e8183b461a4d765856d4ee
|
3 |
+
size 421000
|
src/interfaces/cuml_tfidf_vectorizer.py
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# ====
|
2 |
+
# Fichier: interfaces/cuml_tfidf_vectorizer.py
|
3 |
+
# ====
|
4 |
+
|
5 |
+
from typing import Union
|
6 |
+
import cupy as cp
|
7 |
+
import cudf
|
8 |
+
from scipy.sparse import csr_matrix
|
9 |
+
from cuml.feature_extraction.text import TfidfVectorizer as CuMLTfidf
|
10 |
+
from interfaces.vectorizer import Vectorizer
|
11 |
+
|
12 |
+
|
13 |
+
class CuMLTfidfVectorizer(Vectorizer):
|
14 |
+
"""
|
15 |
+
Implémentation concrète d'une vectorisation TF-IDF avec cuML.
|
16 |
+
Les paramètres se basent sur un dict (e.g. venant de config.vectorization.tfidf).
|
17 |
+
"""
|
18 |
+
def __init__(self, **kwargs):
|
19 |
+
self._vectorizer = CuMLTfidf(**kwargs)
|
20 |
+
self._fitted = False
|
21 |
+
|
22 |
+
def fit_transform(self, texts: cudf.Series) -> Union[cp.ndarray, csr_matrix]:
|
23 |
+
"""
|
24 |
+
Ajuste la TF-IDF sur les textes et retourne la matrice resultante.
|
25 |
+
"""
|
26 |
+
X = self._vectorizer.fit_transform(texts)
|
27 |
+
self._fitted = True
|
28 |
+
return X
|
29 |
+
|
30 |
+
def transform(self, texts: cudf.Series) -> Union[cp.ndarray, csr_matrix]:
|
31 |
+
"""
|
32 |
+
Applique la TF-IDF déjà apprise et retourne la matrice resultante.
|
33 |
+
"""
|
34 |
+
if not self._fitted:
|
35 |
+
raise ValueError("Vectorizer not yet fitted. Call fit_transform first.")
|
36 |
+
X = self._vectorizer.transform(texts)
|
37 |
+
return X
|
src/interfaces/hyperparameter_optimizer.py
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# ============================================
|
2 |
+
# Fichier: interfaces/hyperparameter_optimizer.py
|
3 |
+
# ============================================
|
4 |
+
|
5 |
+
from abc import ABC, abstractmethod
|
6 |
+
from typing import Dict, Any
|
7 |
+
from base_trainer import BaseTrainer
|
8 |
+
|
9 |
+
|
10 |
+
class HyperparameterOptimizer(ABC):
|
11 |
+
"""
|
12 |
+
Interface pour la classe responsable de l'optimisation
|
13 |
+
des hyperparamètres.
|
14 |
+
"""
|
15 |
+
|
16 |
+
@abstractmethod
|
17 |
+
def optimize(self, trainer: BaseTrainer,
|
18 |
+
param_grid: Dict[str, Any]) -> Dict[str, Any]:
|
19 |
+
"""
|
20 |
+
Recherche les meilleurs hyperparamètres pour un 'trainer' donné,
|
21 |
+
selon la grille 'param_grid'.
|
22 |
+
|
23 |
+
:param trainer: Instance d'une classe implémentant BaseTrainer.
|
24 |
+
:param param_grid: Dictionnaire définissant l'espace de recherche
|
25 |
+
pour chaque hyperparamètre.
|
26 |
+
:return: Un dictionnaire contenant les hyperparamètres optimaux trouvés.
|
27 |
+
"""
|
28 |
+
pass
|
src/interfaces/metrics_calculator.py
ADDED
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import cupy as cp
|
2 |
+
import numpy as np
|
3 |
+
from typing import Dict
|
4 |
+
import logging
|
5 |
+
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score
|
6 |
+
from interfaces.metrics_calculator import MetricsCalculator
|
7 |
+
|
8 |
+
logger = logging.getLogger(__name__)
|
9 |
+
|
10 |
+
class DefaultMetricsCalculator(MetricsCalculator):
|
11 |
+
"""
|
12 |
+
Implémentation concrète de MetricsCalculator qui calcule
|
13 |
+
accuracy, f1, precision, recall, et auc-roc.
|
14 |
+
Fonctionne pour binaire ou multiclasses (avec 'ovr' ou 'macro').
|
15 |
+
"""
|
16 |
+
|
17 |
+
def calculate_and_log(
|
18 |
+
self,
|
19 |
+
y_true: cp.ndarray,
|
20 |
+
y_pred: cp.ndarray,
|
21 |
+
prefix: str
|
22 |
+
) -> Dict[str, float]:
|
23 |
+
"""
|
24 |
+
Calcule et log les métriques pour un problème binaire
|
25 |
+
en supposant y_pred est dans {0,1} ou {True,False}.
|
26 |
+
"""
|
27 |
+
y_true_np = cp.asnumpy(y_true)
|
28 |
+
y_pred_np = cp.asnumpy(y_pred)
|
29 |
+
|
30 |
+
acc = accuracy_score(y_true_np, y_pred_np)
|
31 |
+
prec = precision_score(y_true_np, y_pred_np, zero_division=0)
|
32 |
+
rec = recall_score(y_true_np, y_pred_np, zero_division=0)
|
33 |
+
f1 = f1_score(y_true_np, y_pred_np, zero_division=0)
|
34 |
+
|
35 |
+
# Calcul AUC pour un problème binaire (si y_pred est 0/1)
|
36 |
+
# On treat y_pred_np as our "probabilities" only if truly 0/1.
|
37 |
+
# In a real pipeline you might store probabilities separately.
|
38 |
+
try:
|
39 |
+
auc = roc_auc_score(y_true_np, y_pred_np)
|
40 |
+
except ValueError:
|
41 |
+
auc = 0.0
|
42 |
+
|
43 |
+
metrics = {
|
44 |
+
f"{prefix}_accuracy" : acc,
|
45 |
+
f"{prefix}_precision" : prec,
|
46 |
+
f"{prefix}_recall" : rec,
|
47 |
+
f"{prefix}_f1" : f1,
|
48 |
+
f"{prefix}_auc_roc" : auc
|
49 |
+
}
|
50 |
+
logger.info(f"[{prefix}] Metrics: {metrics}")
|
51 |
+
return metrics
|
52 |
+
|
53 |
+
def calculate_and_log_multiclass(
|
54 |
+
self,
|
55 |
+
y_true: cp.ndarray,
|
56 |
+
y_pred: cp.ndarray,
|
57 |
+
prefix: str
|
58 |
+
) -> Dict[str, float]:
|
59 |
+
"""
|
60 |
+
Calcule et log les métriques pour un problème multiclasses.
|
61 |
+
AUC-ROC en mode 'macro' si possible.
|
62 |
+
"""
|
63 |
+
y_true_np = cp.asnumpy(y_true)
|
64 |
+
y_pred_np = cp.asnumpy(y_pred)
|
65 |
+
|
66 |
+
acc = accuracy_score(y_true_np, y_pred_np)
|
67 |
+
prec = precision_score(y_true_np, y_pred_np, average="macro", zero_division=0)
|
68 |
+
rec = recall_score(y_true_np, y_pred_np, average="macro", zero_division=0)
|
69 |
+
f1 = f1_score(y_true_np, y_pred_np, average="macro", zero_division=0)
|
70 |
+
|
71 |
+
# Pour le multiclasses, la roc_auc_score nécessite des scores proba
|
72 |
+
# ou "decision_function" => vous ajusterez selon votre cas.
|
73 |
+
# Ici, on met 0.0 en fallback.
|
74 |
+
auc = 0.0
|
75 |
+
|
76 |
+
metrics = {
|
77 |
+
f"{prefix}_accuracy" : acc,
|
78 |
+
f"{prefix}_precision" : prec,
|
79 |
+
f"{prefix}_recall" : rec,
|
80 |
+
f"{prefix}_f1" : f1,
|
81 |
+
f"{prefix}_auc_roc" : auc
|
82 |
+
}
|
83 |
+
logger.info(f"[{prefix}] Multiclass metrics: {metrics}")
|
84 |
+
return metrics
|
src/interfaces/vectorizer.py
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# =================================
|
2 |
+
# Fichier: interfaces/vectorizer.py
|
3 |
+
# =================================
|
4 |
+
|
5 |
+
from abc import ABC, abstractmethod
|
6 |
+
from typing import Union
|
7 |
+
import cupy as cp
|
8 |
+
import cudf
|
9 |
+
from scipy.sparse import csr_matrix
|
10 |
+
|
11 |
+
|
12 |
+
class Vectorizer(ABC):
|
13 |
+
"""
|
14 |
+
Interface pour la classe responsable de la vectorisation du texte.
|
15 |
+
Permet de convertir une série de textes en représentations
|
16 |
+
numériques (matrice sparse ou dense).
|
17 |
+
"""
|
18 |
+
|
19 |
+
@abstractmethod
|
20 |
+
def fit_transform(self,
|
21 |
+
texts: cudf.Series) -> Union[cp.ndarray, csr_matrix]:
|
22 |
+
"""
|
23 |
+
Ajuste la vectorisation sur les textes de la série 'texts'
|
24 |
+
puis renvoie la matrice vectorisée associée.
|
25 |
+
|
26 |
+
:param texts: Série cudf contenant des chaînes de caractères.
|
27 |
+
:return: Matrice vectorisée (cupy.ndarray ou scipy.sparse.csr_matrix).
|
28 |
+
"""
|
29 |
+
pass
|
30 |
+
|
31 |
+
@abstractmethod
|
32 |
+
def transform(self, texts: cudf.Series) -> Union[cp.ndarray, csr_matrix]:
|
33 |
+
"""
|
34 |
+
Applique la vectorisation déjà apprise à une nouvelle série de textes
|
35 |
+
pour produire la matrice vectorisée.
|
36 |
+
|
37 |
+
:param texts: Série cudf contenant des chaînes de caractères.
|
38 |
+
:return: Matrice vectorisée (cupy.ndarray ou scipy.sparse.csr_matrix).
|
39 |
+
"""
|
40 |
+
pass
|
src/main.py
ADDED
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
# =========================
|
3 |
+
# Fichier: main.py
|
4 |
+
# =========================
|
5 |
+
|
6 |
+
import os
|
7 |
+
import logging
|
8 |
+
import hydra
|
9 |
+
from omegaconf import DictConfig, OmegaConf
|
10 |
+
import mlflow
|
11 |
+
|
12 |
+
# Import des trainers
|
13 |
+
from trainers.cuml.svm_trainer import SvmTrainer
|
14 |
+
from trainers.cuml.random_forest_trainer import RandomForestTrainer
|
15 |
+
from trainers.cuml.logistic_regression_trainer import LogisticRegressionTrainer
|
16 |
+
from trainers.cuml.linear_regression_trainer import LinearRegressionTrainer
|
17 |
+
from trainers.huggingface.huggingface_transformer_trainer import HuggingFaceTransformerTrainer
|
18 |
+
|
19 |
+
# Import des optimizers
|
20 |
+
from optimizers.optuna_optimizer import OptunaOptimizer
|
21 |
+
from optimizers.ray_tune_optimizer import RayTuneOptimizer
|
22 |
+
|
23 |
+
# Import du décorateur MLflow
|
24 |
+
from mlflow_integration.mlflow_decorator import MLflowDecorator
|
25 |
+
|
26 |
+
# Import de la configuration
|
27 |
+
from config import Config
|
28 |
+
|
29 |
+
# Configuration du logging
|
30 |
+
logger = logging.getLogger(__name__)
|
31 |
+
|
32 |
+
|
33 |
+
def get_trainer(config: Config):
|
34 |
+
"""
|
35 |
+
Crée et retourne l'instance du trainer approprié en fonction de la configuration.
|
36 |
+
|
37 |
+
Args:
|
38 |
+
config: Objet de configuration
|
39 |
+
|
40 |
+
Returns:
|
41 |
+
Une instance concrète de BaseTrainer
|
42 |
+
"""
|
43 |
+
model_type = config.model.type.lower()
|
44 |
+
|
45 |
+
# Mapping des types de modèles vers leurs trainers
|
46 |
+
trainer_map = {
|
47 |
+
"svm": SvmTrainer,
|
48 |
+
"random_forest": RandomForestTrainer,
|
49 |
+
"logistic_regression": LogisticRegressionTrainer,
|
50 |
+
"linear_regression": LinearRegressionTrainer,
|
51 |
+
"transformer": HuggingFaceTransformerTrainer,
|
52 |
+
}
|
53 |
+
|
54 |
+
if model_type not in trainer_map:
|
55 |
+
raise ValueError(f"Type de modèle non supporté: {model_type}")
|
56 |
+
|
57 |
+
# Création de l'instance du trainer avec la configuration
|
58 |
+
trainer_class = trainer_map[model_type]
|
59 |
+
return trainer_class(
|
60 |
+
config=config,
|
61 |
+
data_path=config.data.path,
|
62 |
+
target_column=config.data.target_column
|
63 |
+
)
|
64 |
+
|
65 |
+
|
66 |
+
def get_optimizer(config: Config):
|
67 |
+
"""
|
68 |
+
Crée et retourne l'instance d'optimizer appropriée en fonction de la configuration.
|
69 |
+
|
70 |
+
Args:
|
71 |
+
config: Objet de configuration
|
72 |
+
|
73 |
+
Returns:
|
74 |
+
Une instance concrète de HyperparameterOptimizer
|
75 |
+
"""
|
76 |
+
optimizer_type = config.hyperparameters.optimizer.lower()
|
77 |
+
|
78 |
+
# Mapping des types d'optimizers
|
79 |
+
optimizer_map = {
|
80 |
+
"optuna": OptunaOptimizer,
|
81 |
+
"raytune": RayTuneOptimizer,
|
82 |
+
}
|
83 |
+
|
84 |
+
if optimizer_type not in optimizer_map:
|
85 |
+
raise ValueError(f"Type d'optimizer non supporté: {optimizer_type}")
|
86 |
+
|
87 |
+
# Création de l'instance de l'optimizer
|
88 |
+
optimizer_class = optimizer_map[optimizer_type]
|
89 |
+
return optimizer_class()
|
90 |
+
|
91 |
+
|
92 |
+
@hydra.main(config_path="conf", config_name="config")
|
93 |
+
def main(cfg: DictConfig) -> None:
|
94 |
+
"""
|
95 |
+
Point d'entrée principal de l'application.
|
96 |
+
|
97 |
+
Args:
|
98 |
+
cfg: Configuration Hydra sous forme de DictConfig
|
99 |
+
"""
|
100 |
+
# Conversion de la configuration Hydra en configuration Pydantic
|
101 |
+
config_dict = OmegaConf.to_container(cfg, resolve=True)
|
102 |
+
config = Config(**config_dict)
|
103 |
+
|
104 |
+
logger.info(f"Configuration chargée: {config}")
|
105 |
+
|
106 |
+
# Création du trainer approprié
|
107 |
+
trainer = get_trainer(config)
|
108 |
+
|
109 |
+
# Construction des composants (vectorizer, classifier, etc.)
|
110 |
+
trainer.build_components()
|
111 |
+
|
112 |
+
|
113 |
+
mlflow_decorator = MLflowDecorator(
|
114 |
+
experiment_name=config.mlflow.experiment_name,
|
115 |
+
tracking_uri=config.mlflow.tracking_uri
|
116 |
+
)
|
117 |
+
# Appliquer le décorateur aux méthodes clés
|
118 |
+
train_with_mlflow = mlflow_decorator(trainer.train)
|
119 |
+
evaluate_with_mlflow = mlflow_decorator(trainer.evaluate)
|
120 |
+
log_params_with_mlflow = mlflow_decorator(trainer.log_parameters_to_mlflow)
|
121 |
+
optimize_if_needed_with_mlflow = mlflow_decorator(trainer.optimize_if_needed) # Décorer aussi l'optimisation
|
122 |
+
|
123 |
+
logger.info("Vérification et lancement de l'optimisation des hyperparamètres si nécessaire (avec MLflow)...")
|
124 |
+
optimize_if_needed_with_mlflow()
|
125 |
+
|
126 |
+
# Exécuter l'entraînement (toujours avec MLflow)
|
127 |
+
logger.info("Lancement de l'entraînement avec MLflow...")
|
128 |
+
train_with_mlflow()
|
129 |
+
|
130 |
+
# Exécuter l'évaluation (toujours avec MLflow)
|
131 |
+
logger.info("Lancement de l'évaluation avec MLflow...")
|
132 |
+
evaluate_with_mlflow()
|
133 |
+
|
134 |
+
# Logger les paramètres (toujours avec MLflow)
|
135 |
+
logger.info("Logging des paramètres avec MLflow...")
|
136 |
+
log_params_with_mlflow()
|
137 |
+
|
138 |
+
logger.info("Entraînement, évaluation et logging des paramètres terminés avec succès via MLflow.")
|
139 |
+
|
140 |
+
|
141 |
+
if __name__ == "__main__":
|
142 |
+
main()
|
src/mlflow_integration/mlflow_decorator.py
ADDED
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# =======================================
|
2 |
+
# Fichier: mlflow_integration/mlflow_decorator.py
|
3 |
+
# =======================================
|
4 |
+
|
5 |
+
from typing import Callable, Dict, Any
|
6 |
+
import mlflow
|
7 |
+
|
8 |
+
|
9 |
+
class MLflowDecorator:
|
10 |
+
"""
|
11 |
+
Décorateur dédié à l'intégration MLflow. Permet d'initialiser un 'experiment_name',
|
12 |
+
un 'tracking_uri', et de gérer la logique de logging de paramètres, métriques,
|
13 |
+
et artefacts pour MLflow.
|
14 |
+
"""
|
15 |
+
|
16 |
+
def __init__(self, experiment_name: str, tracking_uri: str) -> None:
|
17 |
+
"""
|
18 |
+
Initialise le décorateur MLflow avec le nom d'expérience et l'URI du serveur de tracking.
|
19 |
+
|
20 |
+
:param experiment_name: Nom de l'expérience MLflow à utiliser.
|
21 |
+
:param tracking_uri: URI du serveur MLflow (peut être un chemin local,
|
22 |
+
ou une URL vers un serveur distant).
|
23 |
+
"""
|
24 |
+
self.experiment_name: str = experiment_name
|
25 |
+
self.tracking_uri: str = tracking_uri
|
26 |
+
|
27 |
+
def __call__(self, func: Callable) -> Callable:
|
28 |
+
"""
|
29 |
+
Rend la classe MLflowDecorator utilisable comme décorateur.
|
30 |
+
Lance la session MLflow avant d'appeler 'func', et la termine ensuite.
|
31 |
+
|
32 |
+
:param func: La fonction ou méthode à décorer.
|
33 |
+
:return: Une fonction enveloppée qui gère le cycle de vie d'une run MLflow.
|
34 |
+
"""
|
35 |
+
|
36 |
+
def wrapper(*args, **kwargs):
|
37 |
+
mlflow.set_tracking_uri(self.tracking_uri)
|
38 |
+
mlflow.set_experiment(self.experiment_name)
|
39 |
+
with mlflow.start_run():
|
40 |
+
result = func(*args, **kwargs)
|
41 |
+
return result
|
42 |
+
|
43 |
+
return wrapper
|
44 |
+
|
45 |
+
def _start_run(self) -> None:
|
46 |
+
"""
|
47 |
+
Démarre explicitement un run MLflow.
|
48 |
+
"""
|
49 |
+
mlflow.start_run()
|
50 |
+
|
51 |
+
def _log_params(self, params: Dict[str, Any]) -> None:
|
52 |
+
"""
|
53 |
+
Logue dans MLflow les paramètres passés en dictionnaire.
|
54 |
+
|
55 |
+
:param params: Dictionnaire de paramètres (clef : valeur).
|
56 |
+
"""
|
57 |
+
mlflow.log_params(params)
|
58 |
+
|
59 |
+
def _log_metrics(self, metrics: Dict[str, float]) -> None:
|
60 |
+
"""
|
61 |
+
Logue dans MLflow les métriques passées en dictionnaire.
|
62 |
+
|
63 |
+
:param metrics: Dictionnaire de métriques (clef : valeur).
|
64 |
+
"""
|
65 |
+
mlflow.log_metrics(metrics)
|
66 |
+
|
67 |
+
def _log_artifacts(self, artifacts: Dict[str, Any]) -> None:
|
68 |
+
"""
|
69 |
+
Logue dans MLflow différents artefacts (fichiers, images, etc.).
|
70 |
+
L'UML mentionne la méthode, mais pas la logique interne.
|
71 |
+
|
72 |
+
:param artifacts: Dictionnaire décrivant les artefacts à logguer.
|
73 |
+
"""
|
74 |
+
pass
|
75 |
+
|
76 |
+
def _end_run(self, status: str) -> None:
|
77 |
+
"""
|
78 |
+
Termine un run MLflow en spécifiant un statut (ex: 'FINISHED', 'FAILED').
|
79 |
+
|
80 |
+
:param status: Statut final à associer au run MLflow.
|
81 |
+
"""
|
82 |
+
mlflow.end_run(status=status)
|
src/optimizers/optuna_optimizer.py
ADDED
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# =====================================
|
2 |
+
# Fichier: optimizers/optuna_optimizer.py
|
3 |
+
# =====================================
|
4 |
+
|
5 |
+
from typing import Dict, Any
|
6 |
+
from interfaces.hyperparameter_optimizer import HyperparameterOptimizer
|
7 |
+
from base_trainer import BaseTrainer
|
8 |
+
|
9 |
+
|
10 |
+
class OptunaOptimizer(HyperparameterOptimizer):
|
11 |
+
"""
|
12 |
+
Optimiseur d'hyperparamètres basé sur la librairie Optuna.
|
13 |
+
Implémente l'interface HyperparameterOptimizer.
|
14 |
+
"""
|
15 |
+
|
16 |
+
def optimize(self, trainer: BaseTrainer,
|
17 |
+
param_grid: Dict[str, Any]) -> Dict[str, Any]:
|
18 |
+
"""
|
19 |
+
Recherche les meilleurs hyperparamètres pour un 'trainer' donné,
|
20 |
+
selon la grille 'param_grid', en utilisant Optuna.
|
21 |
+
|
22 |
+
:param trainer: Instance d'une classe implémentant BaseTrainer.
|
23 |
+
:param param_grid: Dictionnaire définissant l'espace de recherche
|
24 |
+
pour chaque hyperparamètre.
|
25 |
+
:return: Un dictionnaire contenant les hyperparamètres optimaux trouvés.
|
26 |
+
"""
|
27 |
+
# L'implémentation réelle d'Optuna ne figure pas dans l'UML,
|
28 |
+
# donc nous restons minimalistes et précises,
|
29 |
+
# sans ajouter d'hypothèses externes.
|
30 |
+
# L'UML mentionne juste + optimize(trainer: BaseTrainer, param_grid: dict): dict
|
31 |
+
import optuna
|
32 |
+
from functools import partial
|
33 |
+
|
34 |
+
class OptunaOptimizer(HyperparameterOptimizer):
|
35 |
+
"""
|
36 |
+
Optimiseur d'hyperparamètres basé sur la librairie Optuna.
|
37 |
+
Implémente l'interface HyperparameterOptimizer.
|
38 |
+
"""
|
39 |
+
|
40 |
+
def optimize(self, trainer: BaseTrainer, param_grid: Dict[str, Any]) -> Dict[str, Any]:
|
41 |
+
"""
|
42 |
+
Recherche les meilleurs hyperparamètres pour un 'trainer' donné,
|
43 |
+
selon la grille 'param_grid', en utilisant Optuna.
|
44 |
+
|
45 |
+
:param trainer: Instance d'une classe implémentant BaseTrainer.
|
46 |
+
:param param_grid: Dictionnaire définissant l'espace de recherche
|
47 |
+
pour chaque hyperparamètre.
|
48 |
+
:return: Un dictionnaire contenant les hyperparamètres optimaux trouvés.
|
49 |
+
"""
|
50 |
+
def suggest_param(trial, param, values):
|
51 |
+
return (
|
52 |
+
trial.suggest_categorical(param, values) if isinstance(values, list) else
|
53 |
+
trial.suggest_float(param, values['low'], values['high'], log=values.get('log', False)) if isinstance(values, dict) and 'low' in values and 'high' in values else
|
54 |
+
trial.suggest_int(param, values['low'], values.get('high', 10)) if isinstance(values, dict) else
|
55 |
+
trial.suggest_float(param, 0.0, 1.0)
|
56 |
+
)
|
57 |
+
|
58 |
+
def objective(trial):
|
59 |
+
params = {param: suggest_param(trial, param, vals) for param, vals in param_grid.items()}
|
60 |
+
trainer.config.hyperparameters = params
|
61 |
+
trainer.train()
|
62 |
+
results = trainer.evaluate()
|
63 |
+
return results.get('validation_loss', float('inf')) # Supposant que le loss est à minimiser
|
64 |
+
|
65 |
+
study = optuna.create_study(direction='minimize')
|
66 |
+
n_trials = trainer.config.hyperparameters.get('n_trials', 100)
|
67 |
+
study.optimize(objective, n_trials=n_trials)
|
68 |
+
|
69 |
+
return study.best_params
|
src/optimizers/ray_tune_optimizer.py
ADDED
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# =======================================
|
2 |
+
# Fichier: optimizers/ray_tune_optimizer.py
|
3 |
+
# =======================================
|
4 |
+
|
5 |
+
from typing import Dict, Any
|
6 |
+
from interfaces.hyperparameter_optimizer import HyperparameterOptimizer
|
7 |
+
from base_trainer import BaseTrainer
|
8 |
+
import ray
|
9 |
+
from ray import tune
|
10 |
+
from ray.tune import CLIReporter
|
11 |
+
from ray.tune.schedulers import ASHAScheduler
|
12 |
+
from trainers.cuml.svm_trainer import SvmTrainer
|
13 |
+
from trainers.cuml.random_forest_trainer import RandomForestTrainer
|
14 |
+
from trainers.cuml.logistic_regression_trainer import LogisticRegressionTrainer
|
15 |
+
from trainers.cuml.linear_regression_trainer import LinearRegressionTrainer
|
16 |
+
|
17 |
+
|
18 |
+
class RayTuneOptimizer(HyperparameterOptimizer):
|
19 |
+
"""
|
20 |
+
Optimiseur d'hyperparamètres basé sur Ray Tune.
|
21 |
+
Implémente l'interface HyperparameterOptimizer.
|
22 |
+
"""
|
23 |
+
|
24 |
+
def optimize(self, trainer: BaseTrainer, param_grid: Dict[str, Any]) -> Dict[str, Any]:
|
25 |
+
"""
|
26 |
+
Recherche les meilleurs hyperparamètres pour un 'trainer' donné,
|
27 |
+
selon la grille 'param_grid', en utilisant Ray Tune.
|
28 |
+
|
29 |
+
:param trainer: Instance d'une classe implémentant BaseTrainer.
|
30 |
+
:param param_grid: Dictionnaire définissant l'espace de recherche
|
31 |
+
pour chaque hyperparamètre.
|
32 |
+
:return: Un dictionnaire contenant les hyperparamètres optimaux trouvés.
|
33 |
+
"""
|
34 |
+
full_config = trainer.config.dict() if hasattr(trainer.config, 'dict') else trainer.config
|
35 |
+
config = self._create_config(param_grid)
|
36 |
+
scheduler = self._create_scheduler()
|
37 |
+
reporter = self._create_reporter()
|
38 |
+
|
39 |
+
analysis = tune.run(
|
40 |
+
self._train_model,
|
41 |
+
config={"hyperparameters": config, **full_config},
|
42 |
+
num_samples=full_config['hyperparameters'].get('n_trials', 100),
|
43 |
+
scheduler=scheduler,
|
44 |
+
progress_reporter=reporter,
|
45 |
+
resources_per_trial={"cpu": 1, "gpu": 0}
|
46 |
+
)
|
47 |
+
|
48 |
+
return analysis.best_config
|
49 |
+
|
50 |
+
def _create_config(self, param_grid: Dict[str, Any]) -> Dict[str, Any]:
|
51 |
+
"""
|
52 |
+
Crée la configuration pour Ray Tune à partir du param_grid.
|
53 |
+
|
54 |
+
:param param_grid: Dictionnaire définissant l'espace de recherche
|
55 |
+
pour chaque hyperparamètre.
|
56 |
+
:return: Dictionnaire de configuration pour Ray Tune.
|
57 |
+
"""
|
58 |
+
return {param: self._define_search_space(param, vals) for param, vals in param_grid.items()}
|
59 |
+
|
60 |
+
def _define_search_space(self, param: str, vals: Any) -> Any:
|
61 |
+
"""
|
62 |
+
Définit l'espace de recherche pour un hyperparamètre donné.
|
63 |
+
|
64 |
+
:param param: Nom de l'hyperparamètre.
|
65 |
+
:param vals: Valeurs possibles ou dictionnaire définissant l'espace.
|
66 |
+
:return: Espace de recherche Ray Tune.
|
67 |
+
"""
|
68 |
+
if isinstance(vals, list):
|
69 |
+
return tune.choice(vals)
|
70 |
+
elif isinstance(vals, dict):
|
71 |
+
low = vals.get('low', 0)
|
72 |
+
high = vals.get('high', 10)
|
73 |
+
log = vals.get('log', False)
|
74 |
+
if 'low' in vals and 'high' in vals:
|
75 |
+
return tune.uniform(param, low, high) if log else tune.randint(param, low, high)
|
76 |
+
return tune.uniform(param, 0.0, 1.0)
|
77 |
+
|
78 |
+
def _create_scheduler(self) -> ASHAScheduler:
|
79 |
+
"""
|
80 |
+
Crée un scheduler ASHAScheduler pour Ray Tune.
|
81 |
+
|
82 |
+
:return: Instance d'ASHAScheduler.
|
83 |
+
"""
|
84 |
+
return ASHAScheduler(
|
85 |
+
max_t=100,
|
86 |
+
grace_period=10,
|
87 |
+
reduction_factor=2
|
88 |
+
)
|
89 |
+
|
90 |
+
def _create_reporter(self) -> CLIReporter:
|
91 |
+
"""
|
92 |
+
Crée un reporter CLIReporter pour Ray Tune.
|
93 |
+
|
94 |
+
:return: Instance de CLIReporter.
|
95 |
+
"""
|
96 |
+
return CLIReporter(
|
97 |
+
metric_columns=["validation_loss", "training_iteration"]
|
98 |
+
)
|
99 |
+
|
100 |
+
def _train_model(self, config: Dict[str, Any]):
|
101 |
+
"""
|
102 |
+
Fonction d'entraînement pour Ray Tune.
|
103 |
+
|
104 |
+
:param config: Configuration des hyperparamètres.
|
105 |
+
"""
|
106 |
+
merged_config = config.copy()
|
107 |
+
hyperparams = merged_config.pop('hyperparameters', {})
|
108 |
+
trainer_instance = self._get_trainer_instance(merged_config)
|
109 |
+
trainer_instance.config.hyperparameters = hyperparams
|
110 |
+
trainer_instance.train()
|
111 |
+
results = trainer_instance.evaluate()
|
112 |
+
tune.report(validation_loss=results.get('validation_loss', float('inf')))
|
113 |
+
|
114 |
+
def _get_trainer_instance(self, config: Dict[str, Any]) -> BaseTrainer:
|
115 |
+
"""
|
116 |
+
Obtient une instance de BaseTrainer basée sur la configuration.
|
117 |
+
|
118 |
+
:param config: Configuration globale incluant hyperparamètres.
|
119 |
+
:return: Instance de BaseTrainer.
|
120 |
+
"""
|
121 |
+
model_type = config['model']['type'].lower()
|
122 |
+
trainer_mapping = {
|
123 |
+
'svm': SvmTrainer,
|
124 |
+
'random_forest': RandomForestTrainer,
|
125 |
+
'logistic_regression': LogisticRegressionTrainer,
|
126 |
+
'linear_regression': LinearRegressionTrainer,
|
127 |
+
# Ajouter d'autres mappings ici si nécessaire
|
128 |
+
}
|
129 |
+
|
130 |
+
trainer_class = trainer_mapping.get(model_type)
|
131 |
+
if not trainer_class:
|
132 |
+
raise ValueError(f"Type de modèle non supporté : {model_type}")
|
133 |
+
|
134 |
+
return trainer_class(
|
135 |
+
config=config,
|
136 |
+
data_path=config['data']['path'],
|
137 |
+
target_column=config['data']['target_column']
|
138 |
+
)
|
src/parameter_logging.py
ADDED
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# ==================================
|
2 |
+
# Fichier: parameter_logging.py
|
3 |
+
# ==================================
|
4 |
+
|
5 |
+
from functools import singledispatch
|
6 |
+
from typing import Dict, Any
|
7 |
+
from base_trainer import BaseTrainer
|
8 |
+
from trainers.huggingface.huggingface_transformer_trainer import HuggingFaceTransformerTrainer
|
9 |
+
from trainers.cuml.svm_trainer import SvmTrainer
|
10 |
+
from trainers.cuml.random_forest_trainer import RandomForestTrainer
|
11 |
+
from trainers.cuml.logistic_regression_trainer import LogisticRegressionTrainer
|
12 |
+
from trainers.cuml.linear_regression_trainer import LinearRegressionTrainer
|
13 |
+
|
14 |
+
|
15 |
+
@singledispatch
|
16 |
+
def get_relevant_params_for_logging(trainer: BaseTrainer) -> Dict[str, Any]:
|
17 |
+
"""
|
18 |
+
Méthode générique, par défaut, pour récupérer les paramètres
|
19 |
+
à logger dans MLflow ou ailleurs.
|
20 |
+
|
21 |
+
:param trainer: Trainer dont on veut extraire les paramètres.
|
22 |
+
:return: Un dictionnaire de paramètres pertinents.
|
23 |
+
"""
|
24 |
+
# Par défaut, on récupère la section 'params' du config.model,
|
25 |
+
# mais l'UML ne précise pas la logique interne.
|
26 |
+
return trainer.config.model.params
|
27 |
+
|
28 |
+
|
29 |
+
@get_relevant_params_for_logging.register
|
30 |
+
def _(trainer: HuggingFaceTransformerTrainer) -> Dict[str, Any]:
|
31 |
+
"""
|
32 |
+
Cas particulier pour un HuggingFaceTransformerTrainer.
|
33 |
+
"""
|
34 |
+
# Extrait les paramètres spécifiques HuggingFace indiqués dans trainer.config.model.params.
|
35 |
+
return trainer.config.model.params
|
36 |
+
|
37 |
+
|
38 |
+
@get_relevant_params_for_logging.register
|
39 |
+
def _(trainer: SvmTrainer) -> Dict[str, Any]:
|
40 |
+
"""
|
41 |
+
Cas particulier pour un SvmTrainer.
|
42 |
+
"""
|
43 |
+
return trainer.config.model.params
|
44 |
+
|
45 |
+
|
46 |
+
@get_relevant_params_for_logging.register
|
47 |
+
def _(trainer: RandomForestTrainer) -> Dict[str, Any]:
|
48 |
+
"""
|
49 |
+
Cas particulier pour un RandomForestTrainer.
|
50 |
+
"""
|
51 |
+
return trainer.config.model.params
|
52 |
+
|
53 |
+
|
54 |
+
@get_relevant_params_for_logging.register
|
55 |
+
def _(trainer: LogisticRegressionTrainer) -> Dict[str, Any]:
|
56 |
+
"""
|
57 |
+
Cas particulier pour un LogisticRegressionTrainer.
|
58 |
+
"""
|
59 |
+
return trainer.config.model.params
|
60 |
+
|
61 |
+
|
62 |
+
@get_relevant_params_for_logging.register
|
63 |
+
def _(trainer: LinearRegressionTrainer) -> Dict[str, Any]:
|
64 |
+
"""
|
65 |
+
Cas particulier pour un LinearRegressionTrainer.
|
66 |
+
"""
|
67 |
+
return trainer.config.model.params
|
src/trainers/cuml/linear_regression_trainer.py
ADDED
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# ============================================
|
2 |
+
# Fichier: trainers/cuml/linear_regression_trainer.py
|
3 |
+
# ============================================
|
4 |
+
|
5 |
+
from cuml.linear_model import LinearRegression
|
6 |
+
|
7 |
+
from cuml_trainer import CuMLTrainer
|
8 |
+
from config import Config
|
9 |
+
from interfaces.cuml_tfidf_vectorizer import CuMLTfidfVectorizer
|
10 |
+
from interfaces.metrics_calculator import DefaultMetricsCalculator
|
11 |
+
|
12 |
+
class LinearRegressionTrainer(CuMLTrainer):
|
13 |
+
"""
|
14 |
+
Entraîneur spécifique utilisant un classifieur LinearRegression de la librairie cuML.
|
15 |
+
Hérite de CuMLTrainer, qui hérite lui-même de BaseTrainer.
|
16 |
+
|
17 |
+
Cette classe implémente les méthodes spécifiques à l'utilisation d'un modèle
|
18 |
+
de régression linéaire pour la prédiction de valeurs continues.
|
19 |
+
"""
|
20 |
+
|
21 |
+
def __init__(self, config: Config, data_path: str, target_column: str) -> None:
|
22 |
+
"""
|
23 |
+
Initialise un LinearRegressionTrainer avec la configuration et les paramètres
|
24 |
+
du parent CuMLTrainer.
|
25 |
+
|
26 |
+
:param config: Configuration globale du système.
|
27 |
+
:param data_path: Chemin vers le fichier de données.
|
28 |
+
:param target_column: Nom de la colonne cible.
|
29 |
+
"""
|
30 |
+
super().__init__(config, data_path, target_column)
|
31 |
+
|
32 |
+
def _build_classifier(self) -> None:
|
33 |
+
"""
|
34 |
+
Crée et configure un classifieur LinearRegression selon les paramètres
|
35 |
+
spécifiés dans la configuration.
|
36 |
+
|
37 |
+
Utilise directement les paramètres de la configuration pour initialiser
|
38 |
+
le modèle avec les hyperparamètres appropriés.
|
39 |
+
"""
|
40 |
+
params = self.config.model.params or {}
|
41 |
+
self.classifier = LinearRegression(**params)
|
42 |
+
|
43 |
+
def build_components(self) -> None:
|
44 |
+
"""
|
45 |
+
Instancie le vectorizer, le classifieur LinearRegression et le calculateur de métriques.
|
46 |
+
|
47 |
+
Cette méthode configure tous les composants nécessaires au fonctionnement
|
48 |
+
du trainer, en se basant sur les paramètres spécifiés dans la configuration.
|
49 |
+
"""
|
50 |
+
# Récupération de la méthode et des paramètres de vectorisation
|
51 |
+
vector_method = self.config.vectorization.method
|
52 |
+
vector_params = self.config.vectorization.dict().get(vector_method, {})
|
53 |
+
self.vectorizer = CuMLTfidfVectorizer(**vector_params)
|
54 |
+
|
55 |
+
# Construction du classifieur
|
56 |
+
self._build_classifier()
|
57 |
+
|
58 |
+
# Initialisation du calculateur de métriques
|
59 |
+
self.metrics_calculator = DefaultMetricsCalculator()
|
60 |
+
|
61 |
+
def train(self) -> None:
|
62 |
+
"""
|
63 |
+
Entraîne le classifieur LinearRegression sur les données vectorisées.
|
64 |
+
|
65 |
+
Utilise l'implémentation fournie par la classe parente CuMLTrainer,
|
66 |
+
qui s'occupe du chargement des données, de la vectorisation et de l'entraînement.
|
67 |
+
"""
|
68 |
+
# Inheriting from CuMLTrainer => loads data, vectorizes, fits
|
69 |
+
super().train()
|
70 |
+
|
71 |
+
def evaluate(self) -> None:
|
72 |
+
"""
|
73 |
+
Évalue le classifieur LinearRegression et calcule les métriques appropriées.
|
74 |
+
|
75 |
+
Utilise l'implémentation fournie par la classe parente CuMLTrainer,
|
76 |
+
qui s'occupe du chargement des données, de la vectorisation, de la prédiction
|
77 |
+
et du calcul des métriques via le metrics_calculator.
|
78 |
+
"""
|
79 |
+
# Inheriting from CuMLTrainer => loads data, vectorizes, calls the metrics calculator
|
80 |
+
super().evaluate()
|
src/trainers/cuml/logistic_regression_trainer.py
ADDED
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# ============================================
|
2 |
+
# Fichier: trainers/cuml/logistic_regression_trainer.py
|
3 |
+
# ============================================
|
4 |
+
|
5 |
+
from cuml.linear_model import LogisticRegression
|
6 |
+
|
7 |
+
from cuml_trainer import CuMLTrainer
|
8 |
+
from config import Config
|
9 |
+
from interfaces.cuml_tfidf_vectorizer import CuMLTfidfVectorizer
|
10 |
+
from interfaces.metrics_calculator import DefaultMetricsCalculator
|
11 |
+
|
12 |
+
class LogisticRegressionTrainer(CuMLTrainer):
|
13 |
+
"""
|
14 |
+
Entraîneur spécifique utilisant un classifieur LogisticRegression de la librairie cuML.
|
15 |
+
Hérite de CuMLTrainer, qui hérite lui-même de BaseTrainer.
|
16 |
+
|
17 |
+
Cette classe implémente les méthodes spécifiques à l'utilisation d'un modèle
|
18 |
+
de régression logistique pour la classification binaire ou multi-classes.
|
19 |
+
"""
|
20 |
+
|
21 |
+
def __init__(self, config: Config, data_path: str, target_column: str) -> None:
|
22 |
+
"""
|
23 |
+
Initialise un LogisticRegressionTrainer avec la configuration et les paramètres
|
24 |
+
du parent CuMLTrainer.
|
25 |
+
|
26 |
+
:param config: Configuration globale du système.
|
27 |
+
:param data_path: Chemin vers le fichier de données.
|
28 |
+
:param target_column: Nom de la colonne cible.
|
29 |
+
"""
|
30 |
+
super().__init__(config, data_path, target_column)
|
31 |
+
|
32 |
+
def _build_classifier(self) -> None:
|
33 |
+
"""
|
34 |
+
Crée et configure un classifieur LogisticRegression selon les paramètres
|
35 |
+
spécifiés dans la configuration.
|
36 |
+
|
37 |
+
Utilise directement les paramètres de la configuration pour initialiser
|
38 |
+
le modèle avec les hyperparamètres appropriés.
|
39 |
+
"""
|
40 |
+
params = self.config.model.params or {}
|
41 |
+
self.classifier = LogisticRegression(**params)
|
42 |
+
|
43 |
+
def build_components(self) -> None:
|
44 |
+
"""
|
45 |
+
Instancie le vectorizer, le classifieur LogisticRegression et le calculateur de métriques.
|
46 |
+
|
47 |
+
Cette méthode configure tous les composants nécessaires au fonctionnement
|
48 |
+
du trainer, en se basant sur les paramètres spécifiés dans la configuration.
|
49 |
+
"""
|
50 |
+
# Récupération de la méthode et des paramètres de vectorisation
|
51 |
+
vector_method = self.config.vectorization.method
|
52 |
+
vector_params = self.config.vectorization.dict().get(vector_method, {})
|
53 |
+
self.vectorizer = CuMLTfidfVectorizer(**vector_params)
|
54 |
+
|
55 |
+
# Construction du classifieur
|
56 |
+
self._build_classifier()
|
57 |
+
|
58 |
+
# Initialisation du calculateur de métriques
|
59 |
+
self.metrics_calculator = DefaultMetricsCalculator()
|
60 |
+
|
61 |
+
def train(self) -> None:
|
62 |
+
"""
|
63 |
+
Entraîne le classifieur LogisticRegression sur les données vectorisées.
|
64 |
+
|
65 |
+
Utilise l'implémentation fournie par la classe parente CuMLTrainer,
|
66 |
+
qui s'occupe du chargement des données, de la vectorisation et de l'entraînement.
|
67 |
+
"""
|
68 |
+
super().train()
|
69 |
+
|
70 |
+
def evaluate(self) -> None:
|
71 |
+
"""
|
72 |
+
Évalue le classifieur LogisticRegression et calcule les métriques appropriées.
|
73 |
+
|
74 |
+
Utilise l'implémentation fournie par la classe parente CuMLTrainer,
|
75 |
+
qui s'occupe du chargement des données, de la vectorisation, de la prédiction
|
76 |
+
et du calcul des métriques via le metrics_calculator.
|
77 |
+
"""
|
78 |
+
super().evaluate()
|
src/trainers/cuml/random_forest_trainer.py
ADDED
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# ============================================
|
2 |
+
# Fichier: trainers/cuml/random_forest_trainer.py
|
3 |
+
# ============================================
|
4 |
+
|
5 |
+
from typing import Optional
|
6 |
+
from cuml.ensemble import RandomForestClassifier
|
7 |
+
import cupy as cp
|
8 |
+
|
9 |
+
from cuml_trainer import CuMLTrainer
|
10 |
+
from config import Config
|
11 |
+
from interfaces.cuml_tfidf_vectorizer import CuMLTfidfVectorizer
|
12 |
+
from interfaces.metrics_calculator import DefaultMetricsCalculator
|
13 |
+
|
14 |
+
class RandomForestTrainer(CuMLTrainer):
|
15 |
+
"""
|
16 |
+
Entraîneur spécifique utilisant un classifieur RandomForestClassifier de la librairie cuML.
|
17 |
+
Hérite de CuMLTrainer, qui hérite lui-même de BaseTrainer.
|
18 |
+
|
19 |
+
Cette classe implémente les méthodes spécifiques à l'utilisation d'un modèle
|
20 |
+
de forêt aléatoire pour la classification, en exploitant les capacités GPU
|
21 |
+
offertes par la librairie cuML.
|
22 |
+
"""
|
23 |
+
|
24 |
+
def __init__(self, config: Config, data_path: str, target_column: str) -> None:
|
25 |
+
"""
|
26 |
+
Initialise un RandomForestTrainer avec la configuration et les paramètres
|
27 |
+
du parent CuMLTrainer.
|
28 |
+
|
29 |
+
:param config: Configuration globale du système.
|
30 |
+
:param data_path: Chemin vers le fichier de données.
|
31 |
+
:param target_column: Nom de la colonne cible.
|
32 |
+
"""
|
33 |
+
super().__init__(config, data_path, target_column)
|
34 |
+
|
35 |
+
def _build_classifier(self) -> None:
|
36 |
+
"""
|
37 |
+
Crée et configure un classifieur RandomForestClassifier selon les paramètres
|
38 |
+
spécifiés dans la configuration.
|
39 |
+
|
40 |
+
Utilise directement les paramètres de la configuration pour initialiser
|
41 |
+
le modèle avec les hyperparamètres appropriés (ex: n_estimators, max_depth, etc.).
|
42 |
+
"""
|
43 |
+
params = self.config.model.params or {}
|
44 |
+
self.classifier = RandomForestClassifier(**params)
|
45 |
+
|
46 |
+
def build_components(self) -> None:
|
47 |
+
"""
|
48 |
+
Instancie le vectorizer, le classifieur RandomForest et le calculateur de métriques.
|
49 |
+
|
50 |
+
Cette méthode configure tous les composants nécessaires au fonctionnement
|
51 |
+
du trainer, en se basant sur les paramètres spécifiés dans la configuration.
|
52 |
+
"""
|
53 |
+
# Récupération de la méthode et des paramètres de vectorisation
|
54 |
+
vector_method = self.config.vectorization.method # e.g. "tfidf"
|
55 |
+
vector_params = self.config.vectorization.dict().get(vector_method, {})
|
56 |
+
self.vectorizer = CuMLTfidfVectorizer(**vector_params)
|
57 |
+
|
58 |
+
# Construction du classifieur
|
59 |
+
self._build_classifier()
|
60 |
+
|
61 |
+
# Initialisation du calculateur de métriques
|
62 |
+
self.metrics_calculator = DefaultMetricsCalculator()
|
63 |
+
|
64 |
+
def train(self) -> None:
|
65 |
+
"""
|
66 |
+
Entraîne le classifieur RandomForest sur les données vectorisées.
|
67 |
+
|
68 |
+
Utilise l'implémentation fournie par la classe parente CuMLTrainer,
|
69 |
+
qui s'occupe du chargement des données, de la vectorisation et de l'entraînement.
|
70 |
+
"""
|
71 |
+
super().train()
|
72 |
+
|
73 |
+
def evaluate(self) -> None:
|
74 |
+
"""
|
75 |
+
Évalue le classifieur RandomForest et calcule les métriques appropriées.
|
76 |
+
|
77 |
+
Utilise l'implémentation fournie par la classe parente CuMLTrainer,
|
78 |
+
qui s'occupe du chargement des données, de la vectorisation, de la prédiction
|
79 |
+
et du calcul des métriques via le metrics_calculator.
|
80 |
+
"""
|
81 |
+
super().evaluate()
|
82 |
+
|
83 |
+
def _get_binary_predictions(self, X: cp.ndarray) -> cp.ndarray:
|
84 |
+
"""
|
85 |
+
Retourne les prédictions binaires du modèle RandomForest.
|
86 |
+
|
87 |
+
:param X: Matrice de caractéristiques au format cupy.ndarray.
|
88 |
+
:return: Vecteur de prédictions binaires.
|
89 |
+
"""
|
90 |
+
X_prepared = self._prepare_input_for_predict(X)
|
91 |
+
return self.classifier.predict(X_prepared)
|
92 |
+
|
93 |
+
def _get_positive_probabilities(self, X: cp.ndarray) -> Optional[cp.ndarray]:
|
94 |
+
"""
|
95 |
+
Retourne les probabilités de la classe positive.
|
96 |
+
Les forêts aléatoires peuvent toujours fournir des probabilités,
|
97 |
+
ce qui est l'un de leurs avantages.
|
98 |
+
|
99 |
+
:param X: Matrice de caractéristiques au format cupy.ndarray.
|
100 |
+
:return: Vecteur de probabilités pour la classe positive.
|
101 |
+
"""
|
102 |
+
X_prepared = self._prepare_input_for_predict(X)
|
103 |
+
return self.classifier.predict_proba(X_prepared)[:, 1]
|
src/trainers/cuml/svm_trainer.py
ADDED
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# ============================================
|
2 |
+
# Fichier: trainers/cuml/svm_trainer.py
|
3 |
+
# ============================================
|
4 |
+
|
5 |
+
from cuml.svm import SVC
|
6 |
+
import cupy as cp
|
7 |
+
from typing import Optional
|
8 |
+
|
9 |
+
from cuml_trainer import CuMLTrainer
|
10 |
+
from config import Config
|
11 |
+
from interfaces.cuml_tfidf_vectorizer import CuMLTfidfVectorizer
|
12 |
+
from interfaces.metrics_calculator import DefaultMetricsCalculator
|
13 |
+
|
14 |
+
class SvmTrainer(CuMLTrainer):
|
15 |
+
"""
|
16 |
+
Entraîneur spécifique utilisant un classifieur SVC (Support Vector Classifier) de la librairie cuML.
|
17 |
+
Hérite de CuMLTrainer, qui hérite lui-même de BaseTrainer.
|
18 |
+
|
19 |
+
Cette classe implémente les méthodes spécifiques à l'utilisation d'un modèle SVM
|
20 |
+
pour la classification binaire ou multi-classes, en exploitant les capacités GPU
|
21 |
+
offertes par la librairie cuML.
|
22 |
+
"""
|
23 |
+
|
24 |
+
def __init__(self, config: Config, data_path: str, target_column: str) -> None:
|
25 |
+
"""
|
26 |
+
Initialise un SvmTrainer avec la configuration et les paramètres
|
27 |
+
du parent CuMLTrainer.
|
28 |
+
|
29 |
+
:param config: Configuration globale du système.
|
30 |
+
:param data_path: Chemin vers le fichier de données.
|
31 |
+
:param target_column: Nom de la colonne cible.
|
32 |
+
"""
|
33 |
+
super().__init__(config, data_path, target_column)
|
34 |
+
|
35 |
+
def _build_classifier(self) -> None:
|
36 |
+
"""
|
37 |
+
Crée et configure un classifieur SVC selon les paramètres
|
38 |
+
spécifiés dans la configuration.
|
39 |
+
|
40 |
+
Utilise directement les paramètres de la configuration pour initialiser
|
41 |
+
le modèle avec les hyperparamètres appropriés (ex: kernel, C, gamma, etc.).
|
42 |
+
"""
|
43 |
+
params = self.config.model.params or {}
|
44 |
+
self.classifier = SVC(**params)
|
45 |
+
|
46 |
+
def build_components(self) -> None:
|
47 |
+
"""
|
48 |
+
Instancie le vectorizer, le classifieur SVC et le calculateur de métriques.
|
49 |
+
|
50 |
+
Cette méthode configure tous les composants nécessaires au fonctionnement
|
51 |
+
du trainer, en se basant sur les paramètres spécifiés dans la configuration.
|
52 |
+
"""
|
53 |
+
# Récupération de la méthode et des paramètres de vectorisation
|
54 |
+
vector_method = self.config.vectorization.method
|
55 |
+
vector_params = self.config.vectorization.dict().get(vector_method, {})
|
56 |
+
self.vectorizer = CuMLTfidfVectorizer(**vector_params)
|
57 |
+
|
58 |
+
# Construction du classifieur
|
59 |
+
self._build_classifier()
|
60 |
+
|
61 |
+
# Initialisation du calculateur de métriques
|
62 |
+
self.metrics_calculator = DefaultMetricsCalculator()
|
63 |
+
|
64 |
+
def train(self) -> None:
|
65 |
+
"""
|
66 |
+
Entraîne le classifieur SVC sur les données vectorisées.
|
67 |
+
|
68 |
+
Utilise l'implémentation fournie par la classe parente CuMLTrainer,
|
69 |
+
qui s'occupe du chargement des données, de la vectorisation et de l'entraînement.
|
70 |
+
"""
|
71 |
+
super().train()
|
72 |
+
|
73 |
+
def evaluate(self) -> None:
|
74 |
+
"""
|
75 |
+
Évalue le classifieur SVC et calcule les métriques appropriées.
|
76 |
+
|
77 |
+
Utilise l'implémentation fournie par la classe parente CuMLTrainer,
|
78 |
+
qui s'occupe du chargement des données, de la vectorisation, de la prédiction
|
79 |
+
et du calcul des métriques via le metrics_calculator.
|
80 |
+
"""
|
81 |
+
super().evaluate()
|
82 |
+
|
83 |
+
def _get_binary_predictions(self, X: cp.ndarray) -> cp.ndarray:
|
84 |
+
"""
|
85 |
+
Retourne les prédictions binaires du modèle SVC.
|
86 |
+
|
87 |
+
Cette méthode est spécifiquement utilisée pour obtenir des prédictions
|
88 |
+
binaires (0/1) à partir du modèle entraîné.
|
89 |
+
|
90 |
+
:param X: Matrice de caractéristiques au format cupy.ndarray.
|
91 |
+
:return: Vecteur de prédictions binaires.
|
92 |
+
"""
|
93 |
+
X_prepared = self._prepare_input_for_predict(X)
|
94 |
+
return self.classifier.predict(X_prepared)
|
95 |
+
|
96 |
+
def _get_positive_probabilities(self, X: cp.ndarray) -> Optional[cp.ndarray]:
|
97 |
+
"""
|
98 |
+
Retourne les probabilités de la classe positive si disponible.
|
99 |
+
|
100 |
+
Cette méthode est utilisée pour obtenir les probabilités d'appartenance
|
101 |
+
à la classe positive, utile notamment pour calculer l'AUC-ROC ou
|
102 |
+
pour des seuils de décision personnalisés.
|
103 |
+
|
104 |
+
:param X: Matrice de caractéristiques au format cupy.ndarray.
|
105 |
+
:return: Vecteur de probabilités ou None si non disponible.
|
106 |
+
"""
|
107 |
+
if hasattr(self.classifier, 'predict_proba'):
|
108 |
+
X_prepared = self._prepare_input_for_predict(X)
|
109 |
+
return self.classifier.predict_proba(X_prepared)[:, 1]
|
110 |
+
return None
|
src/trainers/huggingface/huggingface_transformer_trainer.py
ADDED
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# ============================================================
|
2 |
+
# Fichier: trainers/huggingface/huggingface_transformer_trainer.py
|
3 |
+
# ============================================================
|
4 |
+
|
5 |
+
from typing import Optional
|
6 |
+
import cupy as cp
|
7 |
+
import cudf
|
8 |
+
import torch
|
9 |
+
from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments
|
10 |
+
|
11 |
+
from base_trainer import BaseTrainer
|
12 |
+
from config import Config
|
13 |
+
|
14 |
+
|
15 |
+
class HuggingFaceTransformerTrainer(BaseTrainer):
|
16 |
+
"""
|
17 |
+
Entraîneur spécifique Hugging Face, utilisant un tokenizer,
|
18 |
+
un modèle AutoModelForSequenceClassification et un HF Trainer.
|
19 |
+
Ne dépend pas d'un vectorizer cuML d'après l'UML.
|
20 |
+
"""
|
21 |
+
|
22 |
+
def __init__(self, config: Config, data_path: str,
|
23 |
+
target_column: str) -> None:
|
24 |
+
"""
|
25 |
+
Initialise un HuggingFaceTransformerTrainer avec la configuration
|
26 |
+
et les paramètres du parent BaseTrainer.
|
27 |
+
|
28 |
+
:param config: Configuration globale du système.
|
29 |
+
(La config.vectorizer n'est pas utilisée ici.)
|
30 |
+
:param data_path: Chemin vers le fichier de données.
|
31 |
+
:param target_column: Nom de la colonne cible dans vos données.
|
32 |
+
"""
|
33 |
+
super().__init__(config, data_path, target_column)
|
34 |
+
self.tokenizer: Optional[AutoTokenizer] = None
|
35 |
+
self.model: Optional[AutoModelForSequenceClassification] = None
|
36 |
+
self.hf_trainer: Optional[Trainer] = None
|
37 |
+
|
38 |
+
def build_components(self) -> None:
|
39 |
+
"""
|
40 |
+
Instancie le tokenizer et le modèle Hugging Face
|
41 |
+
AutoModelForSequenceClassification, puis crée un Trainer
|
42 |
+
avec des TrainingArguments par défaut.
|
43 |
+
"""
|
44 |
+
model_name = self.config.model.params.get("pretrained_model_name",
|
45 |
+
"bert-base-uncased")
|
46 |
+
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
|
47 |
+
self.model = AutoModelForSequenceClassification.from_pretrained(
|
48 |
+
model_name)
|
49 |
+
training_args = self._prepare_training_args()
|
50 |
+
|
51 |
+
# Le HF Trainer a besoin de datasets, qui sont construits
|
52 |
+
# dans le code de train/evaluate ou un data loader.
|
53 |
+
self.hf_trainer = Trainer(model=self.model,
|
54 |
+
args=training_args,
|
55 |
+
train_dataset=None,
|
56 |
+
eval_dataset=None)
|
57 |
+
|
58 |
+
def train(self) -> None:
|
59 |
+
"""
|
60 |
+
Entraîne le modèle Hugging Face sur le jeu de données
|
61 |
+
(les datasets sont assignés au HF Trainer).
|
62 |
+
"""
|
63 |
+
pass
|
64 |
+
|
65 |
+
def evaluate(self) -> None:
|
66 |
+
"""
|
67 |
+
Évalue le modèle Hugging Face; la logique de calcul
|
68 |
+
des métriques est en partie assurée par le HF Trainer.
|
69 |
+
"""
|
70 |
+
pass
|
71 |
+
|
72 |
+
def _create_torch_dataset(self, texts: cudf.Series,
|
73 |
+
labels: cp.ndarray) -> torch.utils.data.Dataset:
|
74 |
+
"""
|
75 |
+
Convertit un cudf.Series de textes et un tableau cupy de labels
|
76 |
+
en un Dataset PyTorch.
|
77 |
+
|
78 |
+
:param texts: Série cudf contenant les textes.
|
79 |
+
:param labels: Vecteur cupy des labels (ex. classification binaire ou multiclasses).
|
80 |
+
:return: Un Dataset PyTorch utilisable par Trainer.
|
81 |
+
"""
|
82 |
+
# Implémentation possible : tokenization + construction d'un dataset custom.
|
83 |
+
raise NotImplementedError(
|
84 |
+
"La méthode _create_torch_dataset est à implémenter selon vos besoins."
|
85 |
+
)
|
86 |
+
|
87 |
+
def _prepare_training_args(self) -> TrainingArguments:
|
88 |
+
"""
|
89 |
+
Construit un objet TrainingArguments Hugging Face,
|
90 |
+
par exemple pour définir l'output_dir, le batch_size, etc.
|
91 |
+
|
92 |
+
:return: Instance de TrainingArguments configurée.
|
93 |
+
"""
|
94 |
+
return TrainingArguments(output_dir="./results",
|
95 |
+
num_train_epochs=1,
|
96 |
+
per_device_train_batch_size=8,
|
97 |
+
per_device_eval_batch_size=8)
|
src/utilities/cuml_pyfunc_wrapper.py
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# ===========================================
|
2 |
+
# Fichier: utilities/cuml_pyfunc_wrapper.py
|
3 |
+
# ===========================================
|
4 |
+
|
5 |
+
from typing import Any, Dict, Optional
|
6 |
+
import pandas as pd
|
7 |
+
import numpy as np
|
8 |
+
|
9 |
+
from interfaces.vectorizer import Vectorizer
|
10 |
+
|
11 |
+
|
12 |
+
class CuMLPyFuncWrapper:
|
13 |
+
"""
|
14 |
+
Classe wrapper pour intégration de modèles cuML dans MLflow PyFunc,
|
15 |
+
permettant le chargement et l'inférence.
|
16 |
+
"""
|
17 |
+
|
18 |
+
def __init__(self, vectorizer: Vectorizer, classifier: object) -> None:
|
19 |
+
"""
|
20 |
+
Initialise le wrapper avec un vectorizer cuDF/cupy et le classifieur cuML.
|
21 |
+
|
22 |
+
:param vectorizer: Instance implémentant l'interface Vectorizer.
|
23 |
+
:param classifier: Modèle cuML déjà entraîné (ex. SVC, RandomForest, etc.).
|
24 |
+
"""
|
25 |
+
self.vectorizer: Vectorizer = vectorizer
|
26 |
+
self.classifier: object = classifier
|
27 |
+
|
28 |
+
def load_context(self, context: Dict[str, Any]) -> None:
|
29 |
+
"""
|
30 |
+
Chargé par MLflow PyFunc lors du rechargement du modèle,
|
31 |
+
par exemple pour initialiser l'environnement.
|
32 |
+
|
33 |
+
:param context: Contexte de chargement contenant d'éventuelles informations
|
34 |
+
sur l'environnement ou l'emplacement d'artefacts.
|
35 |
+
"""
|
36 |
+
# Le diagramme UML n'indique pas de logique interne, donc on reste concis.
|
37 |
+
pass
|
38 |
+
|
39 |
+
def predict(self, context: Dict[str, Any],
|
40 |
+
model_input: pd.DataFrame) -> np.ndarray:
|
41 |
+
"""
|
42 |
+
Fonction de prédiction, appelée par MLflow PyFunc.
|
43 |
+
Convertit éventuellement model_input en cudf, vectorise,
|
44 |
+
puis appelle le modèle cuML.
|
45 |
+
|
46 |
+
:param context: Contexte d'exécution éventuel (non défini par l'UML).
|
47 |
+
:param model_input: Données d'entrée sous forme de DataFrame pandas.
|
48 |
+
:return: Un vecteur numpy des prédictions.
|
49 |
+
"""
|
50 |
+
# On ne fait aucune hypothèse supplémentaire sur la forme de model_input.
|
51 |
+
# On se contente de renvoyer un np.ndarray.
|
52 |
+
# Logique d'exemple : conversion en array et appel classifier.predict(...)
|
53 |
+
# A adapter selon la forme réelle du classifier.
|
54 |
+
raise NotImplementedError(
|
55 |
+
"La méthode 'predict' doit intégrer la logique de conversion et d'appel cuML."
|
56 |
+
)
|
uml.plantuml
ADDED
@@ -0,0 +1,333 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@startuml
|
2 |
+
!theme plain
|
3 |
+
|
4 |
+
skinparam classAttributeIconSize 0
|
5 |
+
skinparam defaultFontName Arial
|
6 |
+
skinparam class {
|
7 |
+
BackgroundColor PaleTurquoise
|
8 |
+
BorderColor DarkSlateGray
|
9 |
+
}
|
10 |
+
skinparam abstractClass {
|
11 |
+
BackgroundColor LightYellow
|
12 |
+
BorderColor DarkSlateGray
|
13 |
+
}
|
14 |
+
skinparam interface {
|
15 |
+
BackgroundColor White
|
16 |
+
BorderColor Black
|
17 |
+
}
|
18 |
+
|
19 |
+
' ----------- Type ParamGrid -----------
|
20 |
+
class ParamGrid {
|
21 |
+
note as N1
|
22 |
+
Exemples de clés et valeurs :
|
23 |
+
- "C": [0.1, 1, 10, 100]
|
24 |
+
- "kernel": ["linear", "rbf"]
|
25 |
+
- "gamma":
|
26 |
+
low: 0.001
|
27 |
+
high: 0.1
|
28 |
+
log: true
|
29 |
+
|
30 |
+
Ce type sert à décrire la structure attendue pour le param_grid
|
31 |
+
dans les Optimizers.
|
32 |
+
end note
|
33 |
+
}
|
34 |
+
|
35 |
+
' ----------- Interfaces -----------
|
36 |
+
package "Interfaces" {
|
37 |
+
interface MetricsCalculator {
|
38 |
+
+ calculate_and_log(y_true: cp.ndarray, y_pred: cp.ndarray, prefix: str): dict
|
39 |
+
+ calculate_and_log_multiclass(y_true: cp.ndarray, y_pred: cp.ndarray, prefix: str): dict
|
40 |
+
--
|
41 |
+
note bottom
|
42 |
+
Génère toujours : accuracy, f1, precision, recall, auc-roc
|
43 |
+
end note
|
44 |
+
}
|
45 |
+
|
46 |
+
interface Vectorizer {
|
47 |
+
+ fit_transform(texts: cudf.Series): cp.ndarray | csr_matrix
|
48 |
+
+ transform(texts: cudf.Series): cp.ndarray | csr_matrix
|
49 |
+
}
|
50 |
+
|
51 |
+
interface HyperparameterOptimizer {
|
52 |
+
+ optimize(trainer: BaseTrainer, param_grid: ParamGrid): dict
|
53 |
+
--
|
54 |
+
note bottom
|
55 |
+
Renvoie un dict correspondant \nà la best config trouvée
|
56 |
+
ex: {"C": 1, "kernel": "linear", "gamma": 0.01}
|
57 |
+
end note
|
58 |
+
}
|
59 |
+
}
|
60 |
+
|
61 |
+
|
62 |
+
' ----------- Base Classes -----------
|
63 |
+
package "Base Classes" {
|
64 |
+
abstract class BaseTrainer {
|
65 |
+
- config: Config
|
66 |
+
- classifier: object
|
67 |
+
- metrics_calculator: MetricsCalculator
|
68 |
+
--
|
69 |
+
+ __init__(config: Config, data_path: str, target_column: str)
|
70 |
+
+ build_components(): void
|
71 |
+
+ train(): void
|
72 |
+
+ evaluate(): void
|
73 |
+
+ log_parameters_to_mlflow(): void
|
74 |
+
+ optimize_if_needed(): void
|
75 |
+
-
|
76 |
+
_prepare_input_for_fit(X: cp.ndarray | csr_matrix): cp.ndarray | csr_matrix
|
77 |
+
-
|
78 |
+
_prepare_input_for_predict(X: cp.ndarray | csr_matrix): cp.ndarray | csr_matrix
|
79 |
+
-
|
80 |
+
_get_binary_predictions(X: cp.ndarray): cp.ndarray
|
81 |
+
-
|
82 |
+
_get_positive_probabilities(X: cp.ndarray): cp.ndarray | None
|
83 |
+
-
|
84 |
+
_get_label_dtype(): cp.dtype
|
85 |
+
|
86 |
+
note right
|
87 |
+
log_parameters_to_mlflow():
|
88 |
+
appelle la fonction singledispatch
|
89 |
+
get_relevant_params_for_logging(self).
|
90 |
+
|
91 |
+
optimize_if_needed():
|
92 |
+
Vérifie dans self.config \nsi un optimizer est défini, \npuis appelle optimize() \nsur ce dernier si besoin.
|
93 |
+
end note
|
94 |
+
}
|
95 |
+
|
96 |
+
abstract class CuMLTrainer extends BaseTrainer {
|
97 |
+
- vectorizer: Vectorizer
|
98 |
+
- classifier: cuML.Base
|
99 |
+
--
|
100 |
+
+ build_components(): void
|
101 |
+
+ train(): void
|
102 |
+
+ evaluate(): void
|
103 |
+
-
|
104 |
+
_prepare_input_for_fit(X: cp.ndarray | csr_matrix): cp.ndarray
|
105 |
+
-
|
106 |
+
_prepare_input_for_predict(X: cp.ndarray | csr_matrix): cp.ndarray
|
107 |
+
}
|
108 |
+
}
|
109 |
+
|
110 |
+
' ----------- Concrete Trainers (cuML) -----------
|
111 |
+
package "Concrete Trainers (cuML)" {
|
112 |
+
class SvmTrainer extends CuMLTrainer {
|
113 |
+
- classifier: SVC
|
114 |
+
--
|
115 |
+
+ _build_classifier(): void
|
116 |
+
note bottom
|
117 |
+
SvmTrainer est affecté \npar les paramètres C, kernel, \net gamma (pour RBF).
|
118 |
+
end note
|
119 |
+
}
|
120 |
+
|
121 |
+
class RandomForestTrainer extends CuMLTrainer {
|
122 |
+
- classifier: RandomForestClassifier
|
123 |
+
--
|
124 |
+
+ _build_classifier(): void
|
125 |
+
}
|
126 |
+
|
127 |
+
class LogisticRegressionTrainer extends CuMLTrainer {
|
128 |
+
- classifier: LogisticRegression
|
129 |
+
--
|
130 |
+
+ _build_classifier(): void
|
131 |
+
}
|
132 |
+
|
133 |
+
class LinearRegressionTrainer extends CuMLTrainer {
|
134 |
+
- classifier: LinearRegression
|
135 |
+
--
|
136 |
+
+ _build_classifier(): void
|
137 |
+
}
|
138 |
+
}
|
139 |
+
|
140 |
+
' ----------- Concrete Trainers (Hugging Face) -----------
|
141 |
+
package "Concrete Trainers (Hugging Face)" {
|
142 |
+
class HuggingFaceTransformerTrainer extends BaseTrainer {
|
143 |
+
- tokenizer: AutoTokenizer
|
144 |
+
- model: AutoModelForSequenceClassification
|
145 |
+
- hf_trainer: Trainer
|
146 |
+
--
|
147 |
+
+ build_components(): void
|
148 |
+
+ train(): void
|
149 |
+
+ evaluate(): void
|
150 |
+
-
|
151 |
+
_create_torch_dataset(texts: cudf.Series, labels: cp.ndarray): torch.utils.data.Dataset
|
152 |
+
-
|
153 |
+
_prepare_training_args(): TrainingArguments
|
154 |
+
|
155 |
+
note right
|
156 |
+
Ce trainer n'utilise pas
|
157 |
+
la config vectorizer
|
158 |
+
end note
|
159 |
+
}
|
160 |
+
}
|
161 |
+
|
162 |
+
' ----------- Hyperparameter Optimizers -----------
|
163 |
+
package "Hyperparameter Optimizers" {
|
164 |
+
class OptunaOptimizer {
|
165 |
+
- study: optuna.study.Study
|
166 |
+
- objective: function
|
167 |
+
--
|
168 |
+
+ optimize(trainer: BaseTrainer, param_grid: ParamGrid): dict
|
169 |
+
--
|
170 |
+
note bottom
|
171 |
+
Implementation:
|
172 |
+
1) Crée/récupère un study Optuna.
|
173 |
+
2) Définit l'objective (fonction de coût).
|
174 |
+
Ex: Utilise param_grid["C"] \npour suggérer \ntrial.suggest_float("C",...)
|
175 |
+
3) Applique les hyperparams au trainer \n(e.g. trainer.classifier = SVC(**params)).
|
176 |
+
4) study.optimize(..., n_trials=...)
|
177 |
+
5) Retourne la meilleure config sous forme d'un dict
|
178 |
+
end note
|
179 |
+
}
|
180 |
+
|
181 |
+
class RayTuneOptimizer {
|
182 |
+
- param_space: dict
|
183 |
+
- search_alg: object
|
184 |
+
- scheduler: object
|
185 |
+
--
|
186 |
+
+ optimize(trainer: BaseTrainer, param_grid: ParamGrid): dict
|
187 |
+
--
|
188 |
+
note bottom
|
189 |
+
Implementation:
|
190 |
+
1) Convertit param_grid \nen param_space pour Ray Tune.
|
191 |
+
(ex: "C": tune.grid_search([...]))
|
192 |
+
2) Lance tune.run(...).
|
193 |
+
3) Utilise search_alg/scheduler.
|
194 |
+
4) Retourne la meilleure config \nsous forme d'un dict
|
195 |
+
end note
|
196 |
+
}
|
197 |
+
|
198 |
+
OptunaOptimizer ..> HyperparameterOptimizer : «implements»
|
199 |
+
RayTuneOptimizer ..> HyperparameterOptimizer : «implements»
|
200 |
+
}
|
201 |
+
|
202 |
+
' ----------- MLflow Integration -----------
|
203 |
+
package "MLflow Integration" {
|
204 |
+
class MLflowDecorator {
|
205 |
+
- experiment_name: str
|
206 |
+
- tracking_uri: str
|
207 |
+
--
|
208 |
+
+ __init__(experiment_name: str, tracking_uri: str): void
|
209 |
+
+ __call__(func: function): function
|
210 |
+
+ _start_run(): void
|
211 |
+
+ _log_params(params: dict): void
|
212 |
+
+ _log_metrics(metrics: dict): void
|
213 |
+
+ _log_artifacts(artifacts: dict): void
|
214 |
+
+ _end_run(status: str): void
|
215 |
+
}
|
216 |
+
}
|
217 |
+
|
218 |
+
' ----------- Utilities -----------
|
219 |
+
package "Utilities" {
|
220 |
+
class CuMLPyFuncWrapper {
|
221 |
+
- vectorizer: Vectorizer
|
222 |
+
- classifier: object
|
223 |
+
--
|
224 |
+
+ load_context(context): void
|
225 |
+
+ predict(context, model_input: pd.DataFrame): np.ndarray
|
226 |
+
}
|
227 |
+
}
|
228 |
+
|
229 |
+
' ----------- Configuration -----------
|
230 |
+
package "Configuration" {
|
231 |
+
class Config <<PydanticModel>> {
|
232 |
+
+ model: ModelConfig
|
233 |
+
+ vectorization: VectorizationConfig
|
234 |
+
+ data: DataConfig
|
235 |
+
+ hyperparameters: HyperparameterConfig
|
236 |
+
}
|
237 |
+
|
238 |
+
class ModelConfig <<PydanticModel>> {
|
239 |
+
+ type: str
|
240 |
+
+ params: dict
|
241 |
+
}
|
242 |
+
|
243 |
+
class VectorizationConfig <<PydanticModel>> {
|
244 |
+
+ method: str
|
245 |
+
+ tfidf: dict
|
246 |
+
+ bow: dict
|
247 |
+
}
|
248 |
+
|
249 |
+
class DataConfig <<PydanticModel>> {
|
250 |
+
+ path: str
|
251 |
+
+ target_column: str
|
252 |
+
}
|
253 |
+
|
254 |
+
class HyperparameterConfig <<PydanticModel>> {
|
255 |
+
+ optimizer: str
|
256 |
+
+ param_grid: dict
|
257 |
+
+ n_trials: int
|
258 |
+
--
|
259 |
+
note bottom
|
260 |
+
Exemple de param_grid pour SVM:
|
261 |
+
\{
|
262 |
+
"C": [0.1, 1, 10, 100],
|
263 |
+
"kernel": ["linear", "rbf"],
|
264 |
+
"gamma": \{
|
265 |
+
"low": 0.001,
|
266 |
+
"high": 0.1,
|
267 |
+
"log": true
|
268 |
+
\}
|
269 |
+
\}
|
270 |
+
n_trials: 50
|
271 |
+
end note
|
272 |
+
}
|
273 |
+
Config <|-- ModelConfig
|
274 |
+
Config <|-- VectorizationConfig
|
275 |
+
Config <|-- DataConfig
|
276 |
+
Config <|-- HyperparameterConfig
|
277 |
+
|
278 |
+
note left of Config
|
279 |
+
Extrait YAML:
|
280 |
+
hyperparameters:
|
281 |
+
optimizer: "optuna"
|
282 |
+
param_grid:
|
283 |
+
C: [0.1, 1, 10, 100]
|
284 |
+
kernel:
|
285 |
+
- "linear"
|
286 |
+
- "rbf"
|
287 |
+
gamma:
|
288 |
+
low: 0.001
|
289 |
+
high: 0.1
|
290 |
+
log: true
|
291 |
+
n_trials: 50
|
292 |
+
=
|
293 |
+
Hydra -> DictConfig -> Config(Pydantic)
|
294 |
+
end note
|
295 |
+
}
|
296 |
+
|
297 |
+
' ----------- singledispatch function -----------
|
298 |
+
package "Parameter Logging (singledispatch)" {
|
299 |
+
object get_relevant_params_for_logging <<Function>>
|
300 |
+
note bottom
|
301 |
+
@singledispatch
|
302 |
+
def get_relevant_params_for_logging(trainer: BaseTrainer) -> dict:
|
303 |
+
...
|
304 |
+
|
305 |
+
@get_relevant_params_for_logging.register
|
306 |
+
def _(trainer: HuggingFaceTransformerTrainer) -> dict:
|
307 |
+
...
|
308 |
+
|
309 |
+
@get_relevant_params_for_logging.register
|
310 |
+
def _(trainer: SvmTrainer) -> dict:
|
311 |
+
...
|
312 |
+
|
313 |
+
etc.
|
314 |
+
end note
|
315 |
+
}
|
316 |
+
|
317 |
+
' ----------- Relations -----------
|
318 |
+
BaseTrainer ..> MetricsCalculator : «uses»
|
319 |
+
BaseTrainer ..> HyperparameterOptimizer : «may use»
|
320 |
+
BaseTrainer ..> MLflowDecorator : «may be decorated by»
|
321 |
+
BaseTrainer ..> get_relevant_params_for_logging : «calls singledispatch function»
|
322 |
+
|
323 |
+
CuMLTrainer ..> cuML.Base : «uses»
|
324 |
+
CuMLTrainer ..> CuMLPyFuncWrapper : «for saving model»
|
325 |
+
|
326 |
+
HuggingFaceTransformerTrainer ..> AutoTokenizer : «uses»
|
327 |
+
HuggingFaceTransformerTrainer ..> AutoModelForSequenceClassification : «uses»
|
328 |
+
HuggingFaceTransformerTrainer ..> Trainer : «uses»
|
329 |
+
HuggingFaceTransformerTrainer ..> TrainingArguments : «uses»
|
330 |
+
|
331 |
+
MLflowDecorator ..> mlflow : «uses»
|
332 |
+
|
333 |
+
@enduml
|