28. Configuration et CLI
argparseavancé,click,typer,pydantic-settings,.env, configuration hiérarchique, patterns de config pour le ML.
28.1 argparse avancé
Rappel des bases (leçon 13). Compléments :
Sous-commandes
import argparse
parser = argparse.ArgumentParser(description="Outil ML")
subparsers = parser.add_subparsers(dest="commande", required=True)
# Sous-commande "train"
train = subparsers.add_parser("train", help="Entraîner un modèle")
train.add_argument("--data", required=True)
train.add_argument("--epochs", type=int, default=10)
train.add_argument("--lr", type=float, default=0.001)
# Sous-commande "eval"
eval = subparsers.add_parser("eval", help="Évaluer un modèle")
eval.add_argument("--checkpoint", required=True)
eval.add_argument("--batch-size", type=int, default=32)
args = parser.parse_args()
if args.commande == "train":
entraîner(args.data, args.epochs, args.lr)
elif args.commande == "eval":
évaluer(args.checkpoint, args.batch_size)python script.py train --data dataset/ --epochs 50
python script.py eval --checkpoint model.ptTypes personnalisés
def intervalle(valeur: str) -> tuple[float, float]:
parts = [float(x) for x in valeur.split(",")]
if len(parts) != 2:
raise argparse.ArgumentTypeError("Format attendu: min,max")
return (parts[0], parts[1])
parser.add_argument("--range", type=intervalle, default=(0.0, 1.0))Actions
parser.add_argument("--verbose", action="store_true") # booléen
parser.add_argument("--mode", action="store", default="a") # valeur
parser.add_argument("--debug", action="store_const", const=True) # constante
parser.add_argument("--level", action="count", default=0) # -vvv → 3Groupes
# Groupe mutuellement exclusif
group = parser.add_mutually_exclusive_group()
group.add_argument("--train", action="store_true")
group.add_argument("--eval", action="store_true")28.2 click — décorateurs CLI
pip install clickimport click
@click.command()
@click.option("--data", required=True, help="Chemin du dataset")
@click.option("--epochs", default=10, type=int, help="Nombre d'epochs")
@click.option("--lr", default=0.001, type=float, help="Learning rate")
@click.option("--verbose", is_flag=True, help="Mode verbeux")
@click.argument("nom_experience")
def main(data, epochs, lr, verbose, nom_experience):
"""Entraîne un modèle sur DATA."""
if verbose:
click.echo(f"Configuration: epochs={epochs}, lr={lr}")
click.echo(f"Lancement de {nom_experience}...")
if __name__ == "__main__":
main()python train.py --data dataset/ --epochs 50 --lr 0.01 mon_expSous-commandes avec click.Group
@click.group()
def cli():
"""Outil de gestion d'expériences."""
@cli.command()
@click.option("--config", required=True)
def train(config):
"""Lancer l'entraînement."""
click.echo(f"Entraînement avec {config}")
@cli.command()
@click.option("--checkpoint", required=True)
def eval(checkpoint):
"""Évaluer un modèle."""
click.echo(f"Évaluation de {checkpoint}")
@cli.command()
@click.option("--name", required=True)
def init(name):
"""Initialiser une nouvelle expérience."""
click.echo(f"Expérience {name} créée")
if __name__ == "__main__":
cli()python exp.py train --config config.yaml
python exp.py eval --checkpoint model.pt
python exp.py init --name "exp-001"Prompts interactifs
@click.command()
@click.option("--nom", prompt="Nom de l'expérience")
@click.option("--epochs", prompt="Nombre d'epochs", type=int)
@click.password_option()
def config(nom, epochs):
"""Configuration interactive."""
click.echo(f"OK: {nom}, {epochs} epochs")28.3 typer — moderne, basé sur les types
pip install typerimport typer
app = typer.Typer()
@app.command()
def train(
data: str = typer.Option(..., "--data", "-d", help="Chemin du dataset"),
epochs: int = typer.Option(10, "--epochs", "-e"),
lr: float = typer.Option(0.001, "--lr"),
verbose: bool = typer.Option(False, "--verbose", "-v"),
):
"""Entraîner un modèle."""
if verbose:
typer.echo(f"Configuration: epochs={epochs}, lr={lr}")
typer.echo("Entraînement...")
@app.command()
def eval(
checkpoint: str = typer.Argument(..., help="Chemin du checkpoint"),
batch_size: int = typer.Option(32),
):
"""Évaluer un modèle."""
typer.echo(f"Évaluation de {checkpoint}")
if __name__ == "__main__":
app()Typer avec dataclasses
from dataclasses import dataclass
@dataclass
class ConfigEntraînement:
data: str
epochs: int = 10
lr: float = 0.001
batch_size: int = 32
app = typer.Typer()
@app.command()
def train(config: ConfigEntraînement = typer.Option(...)):
typer.echo(f"Entraînement: epochs={config.epochs}")Validation automatique
from typing import Annotated
def train(
lr: Annotated[float, typer.Option(min=1e-6, max=1.0)] = 0.001,
epochs: Annotated[int, typer.Option(min=1, max=1000)] = 10,
):
...28.4 pydantic-settings — configuration depuis l’environnement
pip install pydantic-settingsfrom pydantic_settings import BaseSettings
from typing import Optional
class Settings(BaseSettings):
# Valeurs depuis variables d'environnement ou .env
project_name: str = "ArtNotes-ML"
debug: bool = False
database_url: str = "sqlite:///data.db"
api_key: Optional[str] = None
max_workers: int = 4
experiment_dir: str = "./experiments"
class Config:
env_file = ".env"
env_prefix = "MYAPP_" # cherche MYAPP_DEBUG, etc.
settings = Settings()
# Utilisation
print(settings.database_url).env :
MYAPP_DEBUG=true
MYAPP_DATABASE_URL=postgres://localhost/db
MYAPP_API_KEY=secret123Héritage et profils
class DevSettings(Settings):
debug: bool = True
database_url: str = "sqlite:///dev.db"
class ProdSettings(Settings):
debug: bool = False
database_url: str = "postgres://prod/db"28.5 hydra — configuration hiérarchique (ML)
pip install hydra-core# config.yaml
defaults:
- model: resnet20
- training: default
- _self_# model/resnet20.yaml
name: resnet20
hidden_size: 256
dropout: 0.1# training/default.yaml
batch_size: 64
learning_rate: 0.001
epochs: 100
optimizer: adamimport hydra
from omegaconf import DictConfig
@hydra.main(version_base=None, config_path=".", config_name="config")
def main(cfg: DictConfig):
print(cfg.model.name) # resnet20
print(cfg.training.epochs) # 100
print(cfg.training.learning_rate) # 0.001
if __name__ == "__main__":
main()# Surcharge depuis la ligne de commande
python train.py model=resnet56 training.epochs=200
# Composer avec plusieurs configs
python train.py model=vit training=debug28.6 Tableau comparatif
| Outil | Approche | Typé | Validation | Sous-commandes | Config hiérarchique |
|---|---|---|---|---|---|
argparse | Impératif | Non | Manuel | Oui | Non |
click | Décorateur | Non | Via types | Oui | Non |
typer | Décorateur + types | Oui | Automatique | Oui | Non |
pydantic-settings | Classe | Oui | Automatique | Non | Héritage |
hydra | YAML + composition | OmegaConf | Non | Non | Oui |
28.7 Recommandations pour la thèse
- Petits scripts :
argparseoutyper(rapide, pas de dépendance) - CLI complète :
typer(typage, auto-doc, validation) - Configuration ML :
pydantic-settings+.env(secrets) + YAML (config) - Grid search / multi-expérience :
hydra(composition, overrides)
# Exemple typique thèse ML
from pydantic_settings import BaseSettings
from pathlib import Path
class ExpConfig(BaseSettings):
# Dataset
data_path: Path = Path("./data")
dataset: str = "cifar10"
num_classes: int = 10
# Modèle
model: str = "resnet20"
hidden_size: int = 256
dropout: float = 0.1
# Entraînement
epochs: int = 100
batch_size: int = 64
learning_rate: float = 0.001
weight_decay: float = 5e-4
optimizer: str = "adam"
# Byzantine
byzantine_ratio: float = 0.0
attack_type: str = "none"
aggregation_rule: str = "mean"
# Expérience
experiment_name: str = "default"
seed: int = 42
log_dir: Path = Path("./logs")
class Config:
env_file = ".env"
env_prefix = "EXP_"