Compléter automatiquement le contenu YAML avec Python

Résumé de cet article

Vous pouvez écrire certains paramètres dans un fichier YAML et le charger à partir de Python pour référence. Surtout dans le domaine de l'apprentissage automatique, j'ai beaucoup d'impressions.

Cependant, le résultat du chargement est un objet dictionnaire, et son nom de clé et sa structure hiérarchique doivent être visuellement confirmés dans le fichier YAML. Je ne pouvais pas supporter le travail de vérification YAML pendant le codage.

Cet article est l'un des moyens de le faire pour les personnes qui souhaitent définir la configuration ** dans un fichier YAML et cet élément est automatiquement complété lors de l'implémentation de Python ** </ font>. Est introduit.

Le code peut être trouvé ici [https://github.com/Nkriskeeic/configer). Ce code est modulaire et peut être installé avec pip.

Décrivez la valeur du paramètre dans le fichier YAML

Tout d'abord, examinons un exemple d'écriture de valeurs de paramètre dans un fichier YAML.

config.yml


model:
    in_channels: 3
    n_blocks: 10
    block:
        channels: 64
        activation: relu
    out_channels: 1

Je pense que les valeurs de réglage sont souvent décrites hiérarchiquement dans le fichier YAML comme indiqué ci-dessus. Lorsque vous lisez ceci à partir d'un script Python, cela peut être écrit un peu différemment, mais je pense que cela ressemblera à quelque chose comme ça.

main.py


config = yaml.safe_load('./config.yml')
model = Model(
    in_channels = config['model']['in_channels'],
    out_channels = config['model']['out_channels'],
    n_blocks = config['model']['n_blobks'],
    block_channels = config['model']['block']['channels'],
    ...
)

Tout d'abord, je ne pouvais pas supporter la tâche de coder en dur ce ** nom de clé de dictionnaire **. Deuxièmement, pour la structure hiérarchique définie dans YAML, je ne pouvais pas supporter ** d'aller dans le fichier YAML pour me souvenir quelles valeurs étaient définies dans quelle hiérarchie **.

Idéalement, ** Si vous définissez la configuration dans le fichier YAML, les éléments définis dans YAML seront automatiquement complétés même pendant l'implémentation dans le script Python **.

main.py[idéal]


config = get_config('./config.yml')
model = Model(
    in_channels = config.model.in_channels,
    out_channels = config.model.out_channels, # <-L'accès aux points se termine automatiquement. Je veux que tu vérifies le type

Dans YACS, le modèle du fichier YAML est défini par le script Python, donc je pense qu'il est possible de sortir le contenu de YAML par complétion automatique dans le script Python. Cependant, il n'a pas été complété par mon PyCharm.

yacs


from yacs.config import CfgNode as CN

_C = CN()

_C.MODEL = CN()
_C.MODEL.IN_CHANNELS = 3

def get_cfg_defaults():
  return _C.clone()

config = get_cfg_defaults()
config.MODEL.IN_CHANNELS # <--Non auto-complété

J'ai donc décidé de créer mon propre framework ** si vous définissez la configuration dans le fichier YAML, les éléments définis dans YAML seront automatiquement complétés même pendant la mise en œuvre dans le script Python.

Générez le script Python correspondant à partir du fichier YAML

Tout d'abord, ** Si un fichier Python avec le même contenu peut être généré automatiquement à partir du fichier YAML, il sera possible de le compléter automatiquement en se référant au fichier Python lors de l'implémentation de ** </ font>. J'ai fait. C'est l'image.

config.yml


hoge: piyo

Conversion YAML-> Python

config.py


hoge: str = 'piyo'

Cependant, avec la simple conversion ci-dessus, j'ai trouvé difficile d'exprimer la structure hiérarchique en YAML sur Python.

Par conséquent, j'ai décidé de le rendre un peu plus compliqué et de le gérer en ** transformant toutes les structures hiérarchiques en classes de données **.

config.yml


model:
    in_channels: 3
    n_blocks: 10
    block:
        channels: 64
        activation: relu
    out_channels: 1

Conversion YAML-> Python

config.py


@dataclass
class ModelBlock:
    channels: int = 64
    activation: str = 'relu'

@dataclass
class Model:
    in_channels: int = 3
    n_blocks: int = 10
    block: ModelBlock = ModelBlock()
    out_channels: int = 1

@dataclass
class Config:
    model: Model = Model()

Config().model.block.channels  # <-Tout peut être complété

La raison de l'utilisation de la classe de données est de réduire la quantité de description due à la génération automatique de méthodes magiques, et d'empêcher les modifications accidentelles des variables membres en définissant gelé = True. J'essaye également de générer des annotations de type. Ceci est implémenté de manière récursive. Comme ça.

Avec cela seul, je viens de convertir le fichier YAML en un script Python, mais comme j'ai pu classer la configuration, je voudrais le générer après avoir ajouté des méthodes pratiques liées au paramètre.

Il semblait pénible d'écrire la génération uniquement avec la concaténation de chaînes, j'ai donc décidé d'utiliser un module de génération de script Python brut tiers, prestring. fait.

Je l'ai implémenté avec Like this. Générez un config.py en combinant plusieurs fichiers C'est une image à faire.

config.yml


model:
    in_channels: 3
    n_blocks: 10
    block:
        channels: 64
        activation: relu
    out_channels: 1

Conversion YAML-> Python

config.py


@dataclass
class ModelBlock:
    channels: int = 64
    activation: str = 'relu'

@dataclass
class Model:
    in_channels: int = 3
    n_blocks: int = 10
    block: ModelBlock = ModelBlock()
    out_channels: int = 1

@dataclass
class Config:
    model: Model = Model()

    def some_cool_method():
        ...

class ConfigGenerator:
    def generate():
        ...
        return Config()

La classe Config est chargée de maintenir le contenu de YAML. La raison de la création de la classe ConfigGenerator est que lors de la lecture de la valeur de réglage du fichier YAML, il était nécessaire de vérifier si elle était incompatible avec la classe Config actuelle et si le type était différent.

Avec cela, si vous définissez la configuration dans le fichier cible ** YAML, les éléments définis dans YAML seront automatiquement complétés même pendant l'implémentation dans le script Python (en convertissant le contenu du fichier YAML en classe Python) ** Les choses sont désormais possibles.

main.py


config = ConfigGenerator().generate()
model = Model(
    in_channels = config.model.in_channels,
    out_channels = config.model.out_channels, # <-L'accès aux points se termine automatiquement et examine également les annotations de type

Cependant, comme un problème, il existe une restriction ** si vous écrivez un fichier YAML, vous devez appuyer sur la commande pour le convertir en script python à partir du terminal **.

Si vous n'avez pas à accéder à la faute de frappe ou au fichier YAML en 1 seconde, vous êtes dans la plage acceptable et c'est OK.

Caractéristiques supplémentaires

Puisque config a été transformé en une classe de données, j'ai pensé qu'il serait pratique d'ajouter diverses méthodes de classe par génération automatique.

Fonction d'écrasement de la valeur de réglage

Lorsque la valeur de réglage est fréquemment modifiée dans l'expérience, il y a une scène telle que "Le réglage de la valeur par défaut est décrit dans default.yml, et certaines valeurs sont mises à jour avec ʻexp1.yml`". sortir. De plus, pour améliorer la visibilité, "Ecrivez le paramètre de valeur par défaut dans" default.yml ", remplacez le paramètre de modèle par" model.yml "et remplacez le paramètre de jeu de données par" dataset.yml "". Il y a aussi des scènes.

À ce moment-là

main.py


config = ConfigGenerator() \
    .update_by(['exp1.yml']) \
    .generate()
config = ConfigGenerator() \
    .update_by(['model.yml', 'dataset.yml']) \
    .generate()

J'ai décidé d'ajouter une telle fonction à Config Generator car ce serait pratique si possible. Cependant, pour le YAML passé par ʻupdate_by`, tout ce que vous avez à faire est de le charger comme dictionnaire, de vérifier le type et le nom de la variable, et d'écraser la valeur correspondante.

Pour le moment, j'ai rendu possible la réception de plusieurs fichiers YAML en parallèle, mais si j'essaie d'écraser la même valeur de réglage avec une valeur différente dans le fichier de l'autre, je donnerai une erreur.

fonction pprint

En affichant les valeurs des paramètres d'une manière facile à lire lors de l'exécution du script, vous pouvez éviter des accidents inattendus.

Je l'ai fait afficher comme ça.

python


config = ConfigGenerator() \
    .update_by(['model.yml']) \
    .update_by(['exp01.yml']) \
    .generate()

config.pprint(wait_yes=True)  # <-Empêcher le code de s'exécuter à moins que vous ne vérifiiez l'affichage et n'appuyiez sur OUI

Résultat de sortie

default from /config/default.yml model:     in_channels: 3     n_blocks: 20 (default 10, changed by /config/model.yml)     block:         channels: 32 (default 64, changed by /config/exp01.yml)         activation: relu     out_channels: 1

J'ai essayé d'émettre un message d'avertissement lors de la mise à jour de la valeur par défaut avec un fichier. Cela vous permet de remarquer immédiatement lorsque vous exécutez le code avec un paramètre différent du paramètre planifié.

Recommended Posts