Compare commits
3 Commits
f8549972d5
...
e6a05fbbc7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6a05fbbc7 | ||
|
|
d8c2e85083 | ||
|
|
0c0bd77156 |
2
.dvc/.gitignore
vendored
Normal file
2
.dvc/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/tmp
|
||||
/cache
|
||||
8
.dvc/config
Normal file
8
.dvc/config
Normal file
@@ -0,0 +1,8 @@
|
||||
[core]
|
||||
analytics = false
|
||||
remote = garage
|
||||
['remote "garage"']
|
||||
url = s3://dvc-store
|
||||
endpointurl = https://garage.192-168-122-143.nip.io
|
||||
region = garage
|
||||
ssl_verify = /etc/ssl/certs/ca-certificates.crt
|
||||
3
.dvcignore
Normal file
3
.dvcignore
Normal file
@@ -0,0 +1,3 @@
|
||||
# Add patterns of files dvc should ignore, which could improve
|
||||
# the performance. Learn more at
|
||||
# https://dvc.org/doc/user-guide/dvcignore
|
||||
15
.env.example
Normal file
15
.env.example
Normal file
@@ -0,0 +1,15 @@
|
||||
# Copier en .env (gitignore) et renseigner les secrets.
|
||||
# A sourcer avant de lancer les scripts : set -a; source .env; set +a
|
||||
|
||||
# --- MLflow (serveur de la VM, basic auth) ---
|
||||
MLFLOW_TRACKING_URI=https://mlflow.192-168-122-143.nip.io
|
||||
MLFLOW_TRACKING_USERNAME=admin
|
||||
MLFLOW_TRACKING_PASSWORD=change-me
|
||||
MLFLOW_EXPERIMENT_NAME=tp02_electricity_consumption
|
||||
|
||||
# --- CA interne ENI MLOps (les clients Python n'utilisent pas le store systeme par defaut) ---
|
||||
REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
|
||||
AWS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
|
||||
|
||||
# --- Import du package lab ---
|
||||
PYTHONPATH=/home/user/tp
|
||||
23
.gitignore
vendored
23
.gitignore
vendored
@@ -1,7 +1,17 @@
|
||||
# Donnees du fil rouge (volumineuses, gerees hors git / DVC plus tard)
|
||||
data/
|
||||
*.parquet
|
||||
*.csv
|
||||
# Donnees : gerees par DVC. Seuls les pointeurs .dvc et le .gitignore
|
||||
# genere par DVC sont versionnes ; les .parquet reels vont sur le remote S3 (Garage).
|
||||
/data/*
|
||||
!/data/*.dvc
|
||||
!/data/.gitignore
|
||||
|
||||
# Secrets / environnement
|
||||
.env
|
||||
*.key
|
||||
*.pem
|
||||
.dvc/config.local
|
||||
|
||||
# MLflow local eventuel (on utilise le serveur distant)
|
||||
mlruns/
|
||||
|
||||
# Jupyter
|
||||
.ipynb_checkpoints/
|
||||
@@ -14,8 +24,3 @@ __pycache__/
|
||||
*.pyc
|
||||
.venv/
|
||||
venv/
|
||||
|
||||
# Secrets / environnement
|
||||
.env
|
||||
*.key
|
||||
*.pem
|
||||
|
||||
58
README.md
Normal file
58
README.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# TP02 - Comparer et tracer des experimentations ML (DVC + MLflow)
|
||||
|
||||
Fil rouge : prediction de la consommation electrique. Ce module compare des strategies
|
||||
de features et de split, en versionnant les datasets avec **DVC** (remote S3 = Garage de la VM)
|
||||
et en tracant les experiences avec **MLflow** (serveur de la VM).
|
||||
|
||||
## Prerequis (sur la VM)
|
||||
|
||||
- venv : `/opt/venvs/mlops`
|
||||
- donnees source : `/data/modelling/features.parquet` + `target.parquet`
|
||||
- `.env` renseigne (voir `.env.example`), puis :
|
||||
|
||||
```bash
|
||||
cd /home/user/tp
|
||||
set -a; source .env; set +a
|
||||
alias py=/opt/venvs/mlops/bin/python
|
||||
alias dvc=/opt/venvs/mlops/bin/dvc
|
||||
```
|
||||
|
||||
## Package
|
||||
|
||||
- `lab/constants.py` : strategies de split (`full_history`, `recent_history`), strategies de
|
||||
features (`short_memory`, `seasonality`, `tendency`, `mixed`, `full`), `RIDGE_ALPHAS`.
|
||||
- `lab/split/cli.py` : lit `/data/modelling`, ecrit `data/{train,validation,test}.parquet`
|
||||
selon `CHOSEN_SPLIT_STRATEGY`.
|
||||
- `lab/modeling/cli.py` : `py -m lab.modeling.cli <strategy>` -> regression lineaire -> MLflow.
|
||||
- `lab/modeling_ridge/cli.py` : `py -m lab.modeling_ridge.cli [--strategy <s>]` -> Ridge sur `RIDGE_ALPHAS`.
|
||||
|
||||
## Versionner un dataset avec DVC
|
||||
|
||||
```bash
|
||||
py -m lab.split.cli
|
||||
dvc add data/train.parquet data/validation.parquet data/test.parquet
|
||||
git add data/*.dvc data/.gitignore lab .gitignore
|
||||
git commit -m "split <strategie>"
|
||||
git tag dataset-<version>
|
||||
dvc push
|
||||
```
|
||||
|
||||
Restaurer une version anterieure du dataset :
|
||||
|
||||
```bash
|
||||
git checkout dataset-v1-full-history -- data/train.parquet.dvc data/validation.parquet.dvc data/test.parquet.dvc
|
||||
dvc checkout
|
||||
```
|
||||
|
||||
## Entrainements
|
||||
|
||||
```bash
|
||||
for s in short_memory seasonality tendency mixed; do py -m lab.modeling.cli $s; done # Parties 1 et 2
|
||||
py -m lab.modeling_ridge.cli --strategy mixed # Partie 3
|
||||
```
|
||||
|
||||
Resultats et comparaisons : https://mlflow.192-168-122-143.nip.io (experience `tp02_electricity_consumption`).
|
||||
|
||||
## Livrable
|
||||
|
||||
Synthese des resultats et reponses aux questions : `SYNTHESE.md`.
|
||||
200
SYNTHESE.md
Normal file
200
SYNTHESE.md
Normal file
@@ -0,0 +1,200 @@
|
||||
# TP02 - Synthèse : comparer et tracer des expérimentations ML
|
||||
|
||||
Fil rouge : prédiction de la consommation électrique (kWh). Datasets versionnés avec **DVC**
|
||||
(remote S3 = Garage), expériences tracées avec **MLflow** (expérience `tp02_electricity_consumption`,
|
||||
13 runs : 10 régressions linéaires + 3 Ridge).
|
||||
|
||||
Deux versions de dataset produites et versionnées (tags git + DVC) :
|
||||
|
||||
| Version DVC | Tag git | Train | Validation | Test |
|
||||
|---|---|---|---|---|
|
||||
| v1 `full_history` | `dataset-v1-full-history` | 2011-2012 | 2013 | 2014 |
|
||||
| v2 `recent_history` | `dataset-v2-recent-history` | 2013 | jan-mai 2014 | juin-déc 2014 |
|
||||
|
||||
Métriques comparées : **RMSE** et **MAE** sur la **validation** (le test reste réservé).
|
||||
|
||||
---
|
||||
|
||||
## Partie 1 - Comparaison de stratégies de features (split `full_history`)
|
||||
|
||||
Stratégies testées (hypothèse -> features) :
|
||||
|
||||
| Stratégie | Hypothèse | Features |
|
||||
|---|---|---|
|
||||
| `short_memory` | la conso dépend surtout de la veille | lag_1d |
|
||||
| `seasonality` | conso plus saisonnière que journalière | lag_7d, lag_30d |
|
||||
| `tendency` | conso surtout tendancielle | rolling_mean_7d, rolling_mean_30d |
|
||||
| `mixed` | mémoire courte + tendance | lag_1d, lag_7d, lag_30d, rolling_mean_30d |
|
||||
| `full` | toutes les features | les 6 |
|
||||
|
||||
Résultats (validation, `full_history`) :
|
||||
|
||||
| Stratégie | RMSE | MAE |
|
||||
|---|---|---|
|
||||
| `full` | **7.41** | **4.07** |
|
||||
| `mixed` | 7.53 | 4.11 |
|
||||
| `short_memory` | 8.82 | 4.58 |
|
||||
| `seasonality` | 9.14 | 5.04 |
|
||||
| `tendency` | 20.79 | 14.66 |
|
||||
|
||||
**1.1 - Quelle stratégie obtient les meilleures performances ?**
|
||||
`full` (6 features) puis `mixed` (4 features), quasi à égalité. Toutes deux combinent lags courts
|
||||
et tendance. `tendency` (moyennes glissantes seules) est de loin la pire.
|
||||
|
||||
**1.2 - Ajouter davantage de features améliore-t-il systématiquement le modèle ?**
|
||||
Non. `short_memory` (1 feature) fait 8.82 alors que `tendency` (2 features) fait 20.79 : c'est la
|
||||
**pertinence** des features, pas leur nombre, qui compte. Et `full` (6) n'améliore `mixed` (4) que
|
||||
marginalement (7.41 vs 7.53) : rendements décroissants, les features supplémentaires apportent peu.
|
||||
|
||||
**1.3 - Quelles hypothèses semblent validées ?**
|
||||
- « La conso dépend de la veille » : validée, `lag_1d` est le prédicteur dominant (voir coefficients).
|
||||
- « Plutôt tendancielle » (moyennes glissantes seules) : **invalidée** (pire modèle).
|
||||
- La conso combine **mémoire court terme + tendance** : validée (mixed/full gagnants).
|
||||
|
||||
---
|
||||
|
||||
## Partie 2 - Comparaison des stratégies d'entraînement (split)
|
||||
|
||||
Chaque stratégie ré-entraînée sur `recent_history`. Matrice RMSE (validation) :
|
||||
|
||||
| Stratégie | `full_history` | `recent_history` |
|
||||
|---|---|---|
|
||||
| `short_memory` | 8.82 | 8.30 |
|
||||
| `seasonality` | 9.14 | 7.60 |
|
||||
| `tendency` | 20.79 | 18.56 |
|
||||
| `mixed` | 7.53 | 6.67 |
|
||||
| `full` | 7.41 | **6.58** |
|
||||
|
||||
La stratégie `full` a été relancée sur **la première version du dataset (v1) restaurée via DVC**
|
||||
(`git checkout dataset-v1-full-history -- ... && dvc checkout`), illustrant la reproductibilité.
|
||||
|
||||
**2.1 - Quel split obtient les meilleures performances ?**
|
||||
`recent_history` : RMSE plus bas pour **les 5 stratégies** (env. -12 %).
|
||||
|
||||
**2.2 - Davantage d'historique ou données plus récentes ?**
|
||||
Ici, **données plus récentes**. Nuance importante : la comparaison n'est pas strictement iso car les
|
||||
fenêtres de validation diffèrent (2013 pour `full_history` vs début 2014 pour `recent_history`).
|
||||
Prédire début 2014 à partir de 2013 (année adjacente) est plus « facile » que prédire 2013 à partir
|
||||
de 2011-2012. Enseignement : la **proximité temporelle train/validation** (moins de dérive) prime sur
|
||||
le simple volume d'historique ancien.
|
||||
|
||||
**2.3 - Avantages / inconvénients de chaque approche ?**
|
||||
- `full_history` : + capte les cycles longs (saisonnalité annuelle), robustesse au bruit ponctuel ;
|
||||
- inclut des données anciennes possiblement obsolètes (data/concept drift), plus coûteux, validation lointaine.
|
||||
- `recent_history` : + colle au régime actuel (moins de drift), moins coûteux ;
|
||||
- moins de données (variance accrue), saisonnalités longues moins fiables (`lag_365d`), sensible aux
|
||||
événements récents atypiques.
|
||||
|
||||
**2.4 - Bonus : coefficients du modèle** (linéaire `full`, `recent_history`), par influence décroissante :
|
||||
|
||||
| Feature | Coefficient |
|
||||
|---|---|
|
||||
| lag_1d | +0.460 |
|
||||
| lag_7d | +0.371 |
|
||||
| rolling_mean_7d | +0.288 |
|
||||
| rolling_mean_30d | -0.268 |
|
||||
| lag_365d | +0.090 |
|
||||
| lag_30d | +0.058 |
|
||||
|
||||
- Les plus influentes : `lag_1d` (la veille) et `lag_7d` (semaine dernière), puis la paire
|
||||
`rolling_mean_7d` (+) / `rolling_mean_30d` (-) qui agit en **différentiel de tendance** court/moyen terme.
|
||||
- Les peu utilisées : `lag_30d` et `lag_365d` (mensuel/annuel apportent peu une fois les autres présentes).
|
||||
- Cela **confirme les hypothèses de la Partie 1** : mémoire court terme + tendance courte dominent.
|
||||
|
||||
---
|
||||
|
||||
## Partie 3 - Influence des hyperparamètres avec Ridge
|
||||
|
||||
Sur le meilleur couple (features `full`, split `recent_history`), alphas `[1, 1e3, 1e9]` :
|
||||
|
||||
| alpha | RMSE (val) | MAE (val) | RMSE (train) |
|
||||
|---|---|---|---|
|
||||
| 1 | **6.576** | **3.677** | 7.397 |
|
||||
| 1e3 | **6.576** | **3.677** | 7.397 |
|
||||
| 1e9 | 7.059 | 4.185 | 8.115 |
|
||||
|
||||
Coefficients selon alpha :
|
||||
|
||||
| Feature | alpha=1 | alpha=1e3 | alpha=1e9 |
|
||||
|---|---|---|---|
|
||||
| lag_1d | 0.460 | 0.460 | 0.284 |
|
||||
| lag_7d | 0.371 | 0.371 | 0.263 |
|
||||
| lag_30d | 0.058 | 0.058 | 0.158 |
|
||||
| lag_365d | 0.090 | 0.090 | 0.174 |
|
||||
| rolling_mean_7d | 0.288 | 0.287 | 0.069 |
|
||||
| rolling_mean_30d | -0.268 | -0.268 | 0.043 |
|
||||
|
||||
**3.1 - Quelle valeur d'alpha obtient les meilleures performances ?**
|
||||
`alpha=1` (identique à `alpha=1e3`). `alpha=1e9` dégrade nettement (RMSE 7.06).
|
||||
|
||||
**3.2 - Que se passe-t-il sur les coefficients quand alpha augmente ?**
|
||||
Ils sont **contraints vers zéro** (régularisation L2). À `alpha=1` et `1e3` : quasi identiques, car la
|
||||
pénalité reste négligeable devant ~4,5 M d'échantillons. À `alpha=1e9` : forte contraction, les
|
||||
coefficients dominants s'écrasent (`lag_1d` 0.46 -> 0.28, `rolling_mean_7d` 0.29 -> 0.07,
|
||||
`rolling_mean_30d` -0.27 -> +0.04) ; le modèle devient plus « plat » (biais accru) -> RMSE augmente.
|
||||
|
||||
**3.3 - Pourquoi est-il indispensable de logger les hyperparamètres ?**
|
||||
Pour la **reproductibilité et la comparabilité** : une métrique n'a de sens qu'associée à ses HP (alpha,
|
||||
features, split). Sans, impossible de reproduire un run, d'expliquer un écart de performance ou de
|
||||
comparer objectivement. MLflow lie chaque métrique à ses paramètres -> traçabilité complète.
|
||||
|
||||
**3.4 - Bonus : que représente le bruit ? Faut-il l'apprendre ?**
|
||||
Le bruit = variations aléatoires non reproductibles (erreurs de mesure, aléas ponctuels). Il **ne faut
|
||||
pas** l'apprendre : le modèle mémoriserait des accidents particuliers (overfitting) au lieu de la relation
|
||||
générale, et généraliserait mal. Exemple : un pic de conso dû à une canicule exceptionnelle un 15 août
|
||||
ne doit pas devenir une règle.
|
||||
|
||||
**3.5 - L'ordre de grandeur des coefficients a-t-il un effet ? Et avec du bruit ?**
|
||||
Oui : de grands coefficients rendent la prédiction très sensible aux petites variations des features
|
||||
(amplification). En présence de bruit, ils **amplifient ce bruit** -> prédictions instables, variance
|
||||
élevée (overfitting). Des coefficients plus petits lissent la réponse.
|
||||
|
||||
**3.6 - Rôle de l'hyperparamètre alpha ?**
|
||||
alpha règle le **compromis biais/variance** en pénalisant la magnitude des coefficients : alpha faible ->
|
||||
modèle libre (variance élevée, risque d'overfitting) ; alpha élevé -> coefficients contraints (biais
|
||||
élevé, risque d'underfitting). C'est le levier de régularisation pour améliorer la généralisation face
|
||||
au bruit.
|
||||
|
||||
**3.7 - Comment utiliser un dataset de test dans le choix d'un hyperparamètre ?**
|
||||
On ne choisit **jamais** alpha sur le test. On règle alpha sur la **validation** (comparaison des alphas),
|
||||
puis on mesure **une seule fois** le modèle retenu sur le **test** (jamais vu) pour une estimation non
|
||||
biaisée de la généralisation. Utiliser le test pour régler alpha revient à le « fuiter » -> estimation
|
||||
trop optimiste.
|
||||
|
||||
---
|
||||
|
||||
## Partie 4 - Réflexion en production
|
||||
|
||||
**4.1 - Sur quelles données entraîner ?**
|
||||
Sur une **fenêtre glissante de données récentes** représentatives du régime actuel (ici `recent_history`
|
||||
l'emporte), en réintégrant régulièrement les dernières observations, tout en gardant assez d'historique
|
||||
pour capter les saisonnalités utiles (semaine, éventuellement année si stable). Compromis récence/volume.
|
||||
|
||||
**4.2 - À quelle fréquence réentraîner ?**
|
||||
Réentraînement **périodique** (hebdomadaire à mensuel selon le coût et la vitesse de dérive) **et**
|
||||
déclenché **par événement** (dégradation des métriques ou détection de drift). La forte saisonnalité de
|
||||
la conso justifie au minimum un rythme régulier accompagné d'une surveillance.
|
||||
|
||||
**4.3 - Quels indicateurs signalent un nouvel entraînement ?**
|
||||
- **Dégradation des métriques** en production (RMSE/MAE qui remontent vs baseline).
|
||||
- **Data drift** : la distribution des features d'entrée s'éloigne de celle d'entraînement.
|
||||
- **Concept drift** : la relation features -> cible change (nouveaux usages, réglementation, météo extrême).
|
||||
- Écart croissant entre distributions **train vs production** (surveillance type Evidently/Grafana).
|
||||
|
||||
---
|
||||
|
||||
## Livrables - synthèse finale
|
||||
|
||||
- **Stratégies de features testées** : `short_memory`, `seasonality`, `tendency`, `mixed`, `full`.
|
||||
- **Résultats** : voir matrices ci-dessus (validation RMSE/MAE).
|
||||
- **Meilleur run MLflow** : features `full` sur le split `recent_history` (RMSE **6.576**, MAE **3.677**) ;
|
||||
en linéaire comme en Ridge `alpha` ≤ 1e3 (résultats indistinguables ; run Ridge `alpha=1e3`
|
||||
`id 57cac1f5...`).
|
||||
- **Impact du changement de split** : `recent_history` améliore **toutes** les stratégies (~ -12 % RMSE),
|
||||
résultat à nuancer (fenêtres de validation différentes -> proximité temporelle avantageuse).
|
||||
- **Stratégie recommandée en production** : features **`mixed`** (parcimonie : quasi identique à `full`
|
||||
mais 4 features au lieu de 6, `lag_30d`/`lag_365d` contribuant peu), entraînée sur une **fenêtre de
|
||||
données récentes**, avec **Ridge `alpha` modéré (1 à 1e3)**.
|
||||
- **Raisons** : meilleure performance en validation, modèle plus simple donc plus robuste et
|
||||
maintenable, données récentes = moins de dérive, régularisation légère = filet de sécurité contre le
|
||||
bruit sans coût de performance.
|
||||
5
data/test.parquet.dvc
Normal file
5
data/test.parquet.dvc
Normal file
@@ -0,0 +1,5 @@
|
||||
outs:
|
||||
- md5: 6ecb52ceb1d8322a91454aca4d902646
|
||||
size: 68171008
|
||||
hash: md5
|
||||
path: test.parquet
|
||||
5
data/train.parquet.dvc
Normal file
5
data/train.parquet.dvc
Normal file
@@ -0,0 +1,5 @@
|
||||
outs:
|
||||
- md5: 24bf315461302605f8fd229eb263f499
|
||||
size: 115206905
|
||||
hash: md5
|
||||
path: train.parquet
|
||||
5
data/validation.parquet.dvc
Normal file
5
data/validation.parquet.dvc
Normal file
@@ -0,0 +1,5 @@
|
||||
outs:
|
||||
- md5: 5b3f656605f48a8aabeb5b21a6ae27ca
|
||||
size: 48074392
|
||||
hash: md5
|
||||
path: validation.parquet
|
||||
0
lab/__init__.py
Normal file
0
lab/__init__.py
Normal file
72
lab/constants.py
Normal file
72
lab/constants.py
Normal file
@@ -0,0 +1,72 @@
|
||||
import datetime
|
||||
from enum import StrEnum
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
|
||||
# Racine du depot de travail (/home/user/tp sur la VM) : lab/constants.py -> parents[1]
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
# Donnees source, deja preparees (hors git, volumineuses) : voir /data sur la VM
|
||||
SOURCE_DIR = Path("/data/modelling")
|
||||
|
||||
# Sorties de split versionnees par DVC dans le depot
|
||||
DATASET_DIR = REPO_ROOT / "data"
|
||||
|
||||
FEATURE_FILENAME = "features.parquet"
|
||||
TARGET_FILENAME = "target.parquet"
|
||||
|
||||
|
||||
class SplitStrategy(StrEnum):
|
||||
FULL_HISTORY = "full_history"
|
||||
RECENT_HISTORY = "recent_history"
|
||||
|
||||
|
||||
# Strategie de split active : pilote a la fois le decoupage produit par split/cli.py
|
||||
# et le parametre "split_strategy" logge dans MLflow. On la modifie (et on committe)
|
||||
# a chaque changement de version de dataset pour synchroniser DVC et Git.
|
||||
CHOSEN_SPLIT_STRATEGY = SplitStrategy.RECENT_HISTORY
|
||||
|
||||
DatasetPart = Literal["train", "test", "validation"]
|
||||
|
||||
DATASET_SPLIT_DATES: dict[SplitStrategy, dict[DatasetPart, tuple[datetime.date, datetime.date]]] = {
|
||||
# Partie 1 : tout l'historique disponible pour l'entrainement
|
||||
SplitStrategy.FULL_HISTORY: {
|
||||
"train": (datetime.date(2011, 1, 1), datetime.date(2012, 12, 31)),
|
||||
"validation": (datetime.date(2013, 1, 1), datetime.date(2013, 12, 31)),
|
||||
"test": (datetime.date(2014, 1, 1), datetime.date(2014, 12, 31)),
|
||||
},
|
||||
# Partie 2 : donnees plus recentes uniquement
|
||||
SplitStrategy.RECENT_HISTORY: {
|
||||
"train": (datetime.date(2013, 1, 1), datetime.date(2013, 12, 31)),
|
||||
"validation": (datetime.date(2014, 1, 1), datetime.date(2014, 5, 31)),
|
||||
"test": (datetime.date(2014, 6, 1), datetime.date(2014, 12, 31)),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class ModellingStrategy(StrEnum):
|
||||
SHORT_MEMORY = "short_memory"
|
||||
SEASONALITY = "seasonality"
|
||||
TENDENCY = "tendency"
|
||||
MIXED = "mixed"
|
||||
FULL = "full"
|
||||
|
||||
|
||||
Features = Literal["lag_1d", "lag_7d", "lag_30d", "lag_365d", "rolling_mean_7d", "rolling_mean_30d"]
|
||||
TARGET = "consumption_kwh"
|
||||
|
||||
MODELLING_FEATURES: dict[ModellingStrategy, list] = {
|
||||
# La conso depend surtout de la veille
|
||||
ModellingStrategy.SHORT_MEMORY: ["lag_1d"],
|
||||
# La conso est plus saisonniere que journaliere
|
||||
ModellingStrategy.SEASONALITY: ["lag_7d", "lag_30d"],
|
||||
# La conso suit surtout une tendance
|
||||
ModellingStrategy.TENDENCY: ["rolling_mean_7d", "rolling_mean_30d"],
|
||||
# Melange lags + tendance
|
||||
ModellingStrategy.MIXED: ["lag_1d", "lag_7d", "lag_30d", "rolling_mean_30d"],
|
||||
# Toutes les features disponibles (ajoutee en Partie 2 Etape 3)
|
||||
ModellingStrategy.FULL: ["lag_1d", "lag_7d", "lag_30d", "lag_365d", "rolling_mean_7d", "rolling_mean_30d"],
|
||||
}
|
||||
|
||||
# Valeurs d'alpha demandees par l'enonce (Partie 3)
|
||||
RIDGE_ALPHAS = [1, 1e3, 1e9]
|
||||
0
lab/modeling/__init__.py
Normal file
0
lab/modeling/__init__.py
Normal file
77
lab/modeling/cli.py
Normal file
77
lab/modeling/cli.py
Normal file
@@ -0,0 +1,77 @@
|
||||
import logging
|
||||
|
||||
import mlflow
|
||||
import pandas as pd
|
||||
import typer
|
||||
from sklearn import linear_model
|
||||
from sklearn import metrics
|
||||
|
||||
from .. import constants
|
||||
|
||||
app = typer.Typer()
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@app.command()
|
||||
def main(
|
||||
strategy: constants.ModellingStrategy,
|
||||
):
|
||||
training_file_path = constants.DATASET_DIR / "train.parquet"
|
||||
validation_file_path = constants.DATASET_DIR / "validation.parquet"
|
||||
|
||||
features = constants.MODELLING_FEATURES[strategy]
|
||||
|
||||
train_df = pd.read_parquet(training_file_path)
|
||||
validation_df = pd.read_parquet(validation_file_path)
|
||||
|
||||
train_df = train_df.dropna(
|
||||
subset=features + [constants.TARGET]
|
||||
)
|
||||
validation_df = validation_df.dropna(
|
||||
subset=features + [constants.TARGET]
|
||||
)
|
||||
|
||||
X_train = train_df[features]
|
||||
y_train = train_df[constants.TARGET]
|
||||
|
||||
X_validation = validation_df[features]
|
||||
y_validation = validation_df[constants.TARGET]
|
||||
|
||||
logger.info(f"Training model with strategy '{strategy}' and features {features}")
|
||||
|
||||
with mlflow.start_run(run_name=f"modelling_{strategy.value}"):
|
||||
mlflow.log_param("model_type", "linear")
|
||||
mlflow.log_param("strategy", strategy.value)
|
||||
mlflow.log_param("split_strategy", constants.CHOSEN_SPLIT_STRATEGY.value)
|
||||
|
||||
mlflow.log_param("features", ",".join(features))
|
||||
model = linear_model.LinearRegression()
|
||||
model.fit(X_train, y_train)
|
||||
|
||||
train_predictions = model.predict(X_train)
|
||||
validation_predictions = model.predict(X_validation)
|
||||
|
||||
train_rmse = metrics.root_mean_squared_error(y_train, train_predictions)
|
||||
validation_rmse = metrics.root_mean_squared_error(y_validation, validation_predictions)
|
||||
|
||||
train_mae = metrics.mean_absolute_error(y_train, train_predictions)
|
||||
validation_mae = metrics.mean_absolute_error(y_validation, validation_predictions)
|
||||
|
||||
mlflow.log_metric("train_rmse", train_rmse)
|
||||
mlflow.log_metric("validation_rmse", validation_rmse)
|
||||
|
||||
mlflow.log_metric("train_mae", train_mae)
|
||||
mlflow.log_metric("validation_mae", validation_mae)
|
||||
|
||||
for feature_name, coefficient in zip(
|
||||
features,
|
||||
model.coef_,
|
||||
strict=True,
|
||||
):
|
||||
mlflow.log_metric(f"coef_{feature_name}", float(coefficient))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app()
|
||||
0
lab/modeling_ridge/__init__.py
Normal file
0
lab/modeling_ridge/__init__.py
Normal file
79
lab/modeling_ridge/cli.py
Normal file
79
lab/modeling_ridge/cli.py
Normal file
@@ -0,0 +1,79 @@
|
||||
import logging
|
||||
|
||||
import mlflow
|
||||
import pandas as pd
|
||||
import typer
|
||||
from sklearn import linear_model
|
||||
from sklearn import metrics
|
||||
|
||||
from .. import constants
|
||||
|
||||
app = typer.Typer()
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@app.command()
|
||||
def main(
|
||||
strategy: constants.ModellingStrategy = constants.ModellingStrategy.MIXED,
|
||||
):
|
||||
training_file_path = constants.DATASET_DIR / "train.parquet"
|
||||
validation_file_path = constants.DATASET_DIR / "validation.parquet"
|
||||
|
||||
features = constants.MODELLING_FEATURES[strategy]
|
||||
|
||||
train_df = pd.read_parquet(training_file_path)
|
||||
validation_df = pd.read_parquet(validation_file_path)
|
||||
|
||||
train_df = train_df.dropna(
|
||||
subset=features + [constants.TARGET]
|
||||
)
|
||||
validation_df = validation_df.dropna(
|
||||
subset=features + [constants.TARGET]
|
||||
)
|
||||
|
||||
X_train = train_df[features]
|
||||
y_train = train_df[constants.TARGET]
|
||||
|
||||
X_validation = validation_df[features]
|
||||
y_validation = validation_df[constants.TARGET]
|
||||
|
||||
logger.info(f"Training Ridge with strategy '{strategy}' and features {features}")
|
||||
|
||||
for alpha in constants.RIDGE_ALPHAS:
|
||||
with mlflow.start_run(run_name=f"modelling_{strategy.value}_ridge_alpha_{alpha:g}"):
|
||||
mlflow.log_param("model_type", "ridge")
|
||||
mlflow.log_param("strategy", strategy.value)
|
||||
mlflow.log_param("split_strategy", constants.CHOSEN_SPLIT_STRATEGY.value)
|
||||
|
||||
mlflow.log_param("features", ",".join(features))
|
||||
mlflow.log_param("alpha", alpha)
|
||||
model = linear_model.Ridge(alpha=alpha)
|
||||
model.fit(X_train, y_train)
|
||||
|
||||
train_predictions = model.predict(X_train)
|
||||
validation_predictions = model.predict(X_validation)
|
||||
|
||||
train_rmse = metrics.root_mean_squared_error(y_train, train_predictions)
|
||||
validation_rmse = metrics.root_mean_squared_error(y_validation, validation_predictions)
|
||||
|
||||
train_mae = metrics.mean_absolute_error(y_train, train_predictions)
|
||||
validation_mae = metrics.mean_absolute_error(y_validation, validation_predictions)
|
||||
|
||||
mlflow.log_metric("train_rmse", train_rmse)
|
||||
mlflow.log_metric("validation_rmse", validation_rmse)
|
||||
|
||||
mlflow.log_metric("train_mae", train_mae)
|
||||
mlflow.log_metric("validation_mae", validation_mae)
|
||||
|
||||
for feature_name, coefficient in zip(
|
||||
features,
|
||||
model.coef_,
|
||||
strict=True,
|
||||
):
|
||||
mlflow.log_metric(f"coef_{feature_name}", float(coefficient))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app()
|
||||
0
lab/split/__init__.py
Normal file
0
lab/split/__init__.py
Normal file
35
lab/split/cli.py
Normal file
35
lab/split/cli.py
Normal file
@@ -0,0 +1,35 @@
|
||||
import logging
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from .. import constants
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def main():
|
||||
# Entrees : dataset source deja prepare (hors git)
|
||||
feature_file_path = constants.SOURCE_DIR / constants.FEATURE_FILENAME
|
||||
target_file_path = constants.SOURCE_DIR / constants.TARGET_FILENAME
|
||||
|
||||
logger.info(f"Read dataset from {feature_file_path} and {target_file_path}")
|
||||
df_features = pd.read_parquet(feature_file_path)
|
||||
df_target = pd.read_parquet(target_file_path)
|
||||
|
||||
df = df_features.join(df_target)
|
||||
timestamps = df.index.get_level_values("timestamp")
|
||||
|
||||
# Sorties : splits versionnes par DVC dans le depot
|
||||
constants.DATASET_DIR.mkdir(parents=True, exist_ok=True)
|
||||
logger.info(f"Split strategy: {constants.CHOSEN_SPLIT_STRATEGY.value}")
|
||||
for dataset_name, (start_date, end_date) in constants.DATASET_SPLIT_DATES[constants.CHOSEN_SPLIT_STRATEGY].items():
|
||||
file_path = constants.DATASET_DIR / f"{dataset_name}.parquet"
|
||||
mask = (timestamps.date >= start_date) & (timestamps.date <= end_date)
|
||||
df_split = df[mask]
|
||||
logger.info(f"Split dataset into {dataset_name} ({start_date} -> {end_date}) with shape: {df_split.shape}")
|
||||
df_split.to_parquet(file_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user