Configuration projet sous Python

Mettez un dictionnaire en boite avec la librairie Box pour gérer votre config projet

Nous avons, lors d'un récent article, passé en revue les dictionnaires python. Nous allons évoquer ici un cas d'application pratique, qui tire pleinement parti de la forme structurée des dictionnaires. Qui ne s'est pas retrouvé avec une ribambelle de variables, parsemées dans son code. Outre le fait que la lecture et la compréhension du code s'en trouvent affectée, cela s'avère également contre-productif pour toute opération de maintenance.
Les dictionnaires peuvent alors s'avérer un moyen simple et efficace d'organiser son code.

Des variables de config organisées

Prenons le cas concret d'un projet deep learning de classification d'images. L'idée est d'organiser en début de code les variables au sein d'un dictionnaire, dénommé config par exemple.
Dans l'exemple ci-dessous on retrouve quelques variables que les habitués du genre reconnaitront.


config = {'seed': 123,
          'traindir': '/input/train',
          'testdir': '/input/test',  
          'n_splits': 5,
          'epoch': 20,
          'optimizer':{
              'name': 'tf.keras.optimizers.Adam',
              'params':{
                  'lr': 1e-5
              },
          },
          'loss': 'mse',
}

Nous constatons dans le dictionnaire ci-dessus que les noms des variables et leur valeur d'initialisation respective font office de couples clés/valeurs du dictionnaire.
Par ailleurs, certaines variables peuvent elles-mêmes constituer un dictionnaire. On peut ainsi se retrouver avec plusieurs niveaux d'imbrication, c'est le cas de l'optimiseur dans notre exemple.

L'accès aux variables de config en mode dictionnaire

Bien que l'aspect dictionnaire ait déjà été traité dans un précèdent article, nous revenons brièvement sur l'accès aux données. Ainsi, l'accès aux variables se fera le plus simplement du monde, comme ceci :


# Acces au seed
print(config['seed'])

# Acces au learning rate de l'optimiseur
print(config['optimizer']['params']['lr'])

Tout ceci est déjà très bien, mais avouez que la notation est encore lourde, à la lecture comme à la rédaction du code. Ceci nous amène à la librairie box qui va nous permettre de basculer vers une notation type objet.

La librairie box

Commencons par installer puis importer la librairie.


pip install python-box[all]~=6.0 --upgrade
from box import Box

Une fois la librairie importée, il nous suffit d'instancier box en stipulant notre dictionnaire config, comme ceci :


config = Box(config)

Nous sommes désormais en mesure d'accéder à nos variables comme nous le ferions s'il s'agissait d'attributs d'une classe :


# Acces au seed
print(config.seed)

# Acces au learning rate de l'optimiseur
print(config.optimizer.params.lr)

Interpréter les constantes avec eval()

L'ensemble des variables déclarées dans notre dictionnaire config prennent pour valeurs des types simples (entiers, chaines de caractères, etc ...). Nous éviterons en effet d'instancier des classes pour rester sur un plan purement déclaratif.

Nous avons volontairement choisi dans l'exemple initial de stipuler une variable optimizer. Celle-ci declare, par exemple, le nom de l'optimiseur à appliquer au modèle, ainsi que le taux d'apprentissage associé. Le nom de l'optimiseur est indiqué sous forme de chaine de caractères, pourtant il faudra que Python l'interprète comme un objet, en l'occurrence de la classe torch.optim
C'est là qu'intervient l'instruction eval, puisque nous allons simplement instancier notre optimiseur avec du code ressemblant à celui-ci :


optimizer = eval(config.optimizer.name)(
  config.optimizer.params)

Retrouvez dans la rubrique "Nos datasets" toutes les données dont vous aurez besoin pour tester et pratiquer !