[PYTHON] Utiliser des décorateurs pour empêcher la ré-exécution du traitement des données

Aperçu

C'est un processus courant pour traiter des données, les enregistrer une fois sur un disque et les réutiliser (ignorer le traitement des données à partir de la deuxième fois), mais en tenant compte de la dépendance des paramètres au moment de la réutilisation, etc. Cela a tendance à être compliqué de manière inattendue. Par conséquent, considérez une implémentation qui ne répète pas le même processus en faisant un jugement de saut à l'aide d'un décorateur Python.

Une bibliothèque de ce processus peut être trouvée sur github.com/sotetsuk/memozo:

motivation

Par exemple, supposons que vous ayez maintenant une énorme quantité de données de déclaration (une phrase par ligne):

1. I have a pen.
2. I have an apple.
3. ah! Apple pen!

...

9999...

# PPAP (copyright belongs to Pikotaro)

Supposons maintenant que vous souhaitiez filtrer uniquement les phrases contenant un mot-clé spécifique à partir de ces données (par exemple, la phrase qui contient le mot-clé pen '').

L'une des implémentations naïves du filtre serait de créer un générateur qui cède à chaque fois qu'il trouve une instruction qui répond aux critères:

def filter_data(keyword):
    path_to_raw_data = './data/sentences.txt'
    with codecs.open(path_to_raw_data, 'r', 'utf-8') as f:
        for line in f:
            if keyword in line:
                yield line

gen = filter_data('pen')
for line in gen:
    print(line, end='')

Et si ces données traitées (données filtrées) sont réutilisées plusieurs fois, ce n'est pas toujours une bonne idée de scanner toutes les données à chaque fois. Vous souhaiterez peut-être mettre en cache les données filtrées sur le disque une fois, puis utiliser les données mises en cache. De plus, ce processus de traitement des données dépend du paramètre (mot-clé```), donc si ce processus est exécuté avec un mot-clé '' différent, toutes les données seront à nouveau vérifiées et placées sur le disque. Il y a aussi l'aspect de vouloir mettre en cache. Et j'ai le désir de réaliser ce processus simplement en enveloppant la fonction à l'aide d'un décorateur.

En résumé, le but est d'utiliser le décorateur awesome_decorator pour mettre en cache la sortie du générateur, et si cette fonction est exécutée avec les mêmes paramètres, utilisez le cache pour renvoyer la sortie: est:

@awesome_decorator
def filter_data(keyword):
    path_to_raw_data = './data/sentences.txt'
    with codecs.open(path_to_raw_data, 'r', 'utf-8') as f:
        for line in f:
            if keyword in line:
                yield line


#La première fois, il analyse toutes les données et renvoie le résultat.
#À ce stade, l'instruction filtrée'./data/pen.txt'Cachez sur.
gen_pen_sentences1 = filter_data('pen')
for line in gen_pen_sentences1:
    print(line, end='')

#Puisqu'il est exécuté avec les mêmes paramètres, le cache'./data/pen.txt'Renvoie les données de.
gen_pen_sentences2 = filter_data('pen')
for line in gen_pen_sentences2:
    print(line, end='')

#Puisqu'il s'agit d'un nouveau paramètre, nous le filtrerons à nouveau à partir des données brutes.
gen_apple_sentences = filter_data('apple')
for line in gen_apple_sentences:
    print(line, end='')

De plus, cet exemple est une fonction qui renvoie un générateur, mais il peut y avoir d'autres situations où vous souhaitez mettre en cache le résultat de l'exécution d'une fonction qui retourne un objet qui peut être sérialisé par pickle '' sur le disque (par exemple, prétraité). ndarray '' et modèles d'apprentissage automatique formés en fonction des paramètres).

la mise en oeuvre

awesome_decoratorEst facile à mettre en œuvre, déterminez s'il existe déjà des fichiers en cache,

  1. S'il y a un cache, créez un nouveau générateur qui renvoie la valeur du cache et retournez-le à la place du générateur d'origine
  2. S'il n'y a pas de cache, encapsulez le générateur d'origine et renvoyez un générateur pour écrire dans le cache à chaque fois qu'il renvoie une valeur

Juste (même si vous utilisez `` pickle '', etc.):

def awesome_decorator(func):

    @functools.wraps(func)
    def _wrapper(keyword):
        #Cette fois, par souci de simplicité, nous supposons que l'argument de la fonction n'est qu'un mot clé.
        #général(*args, **kwargs)Lors de l'utilisation, utilisez inspect etc. pour extraire les arguments et leurs valeurs.
        file_path = './data/{}.txt'.format(keyword)

        #S'il y a des données en cache, il renvoie un générateur qui lit les instructions à partir de celui-ci.
        if os.path.exists(file_path):
            def gen_cached_data():
                with codecs.open(file_path, 'r', 'utf-8') as f:
                    for line in f:
                        yield line
            return gen_cached_data()

        #S'il n'y a pas de données mises en cache, il générera un décorateur qui retournera une instruction à partir des données brutes comme d'habitude.
        gen = func(keyword)

        #Il met également en cache les valeurs renvoyées par les générateurs ci-dessus.
        def generator_with_cache(gen, file_path):
            with codecs.open(file_path, 'w', 'utf-8') as f:
                for e in gen:
                    f.write(e)
                    yield e

        return generator_with_cache(gen, file_path)

    return _wrapper

Pour une explication du décorateur lui-même, l'article 12 étapes pour comprendre les décorateurs Python est facile à comprendre.

Dans l'ensemble, cela ressemble à ceci (cela fonctionne très bien avec . / Data / phrase.txt ''):

awesome_generator.py


# -*- coding: utf-8 -*-

import os
import functools
import codecs


def awesome_decorator(func):

    @functools.wraps(func)
    def _wrapper(keyword):
        #Cette fois, par souci de simplicité, nous supposons que l'argument de la fonction n'est qu'un mot clé.
        #général(*args, **kwargs)Lors de l'utilisation, utilisez inspect etc. pour extraire les arguments et leurs valeurs.
        file_path = './data/{}.txt'.format(keyword)

        #S'il y a des données en cache, il renvoie un générateur qui lit les instructions à partir de celui-ci.
        if os.path.exists(file_path):
            def gen_cached_data():
                with codecs.open(file_path, 'r', 'utf-8') as f:
                    for line in f:
                        yield line
            return gen_cached_data()

        #S'il n'y a pas de données mises en cache, il générera un décorateur qui retournera une instruction à partir des données brutes comme d'habitude.
        gen = func(keyword)

        #Il met également en cache les valeurs renvoyées par les générateurs ci-dessus.
        def generator_with_cache(gen, file_path):
            with codecs.open(file_path, 'w', 'utf-8') as f:
                for e in gen:
                    f.write(e)
                    yield e

        return generator_with_cache(gen, file_path)

    return _wrapper


@awesome_decorator
def filter_data(keyword):
    path_to_raw_data = './data/sentences.txt'
    with codecs.open(path_to_raw_data, 'r', 'utf-8') as f:
        for line in f:
            if keyword in line:
                yield line


if __name__ == '__main__':
    #La première fois, il analyse toutes les données et renvoie le résultat.
    #À ce stade, l'instruction filtrée'./data/pen.txt'Cachez sur.
    gen_pen_sentences1 = filter_data('pen')
    for line in gen_pen_sentences1:
        print(line, end='')

    #Puisqu'il est exécuté avec les mêmes paramètres, le cache'./data/pen.txt'Renvoie les données de.
    gen_pen_sentences2 = filter_data('pen')
    for line in gen_pen_sentences2:
        print(line, end='')

    #Puisqu'il s'agit d'un nouveau paramètre, nous le filtrerons à nouveau à partir des données brutes.
    gen_apple_sentences = filter_data('apple')
    for line in gen_apple_sentences:
        print(line, end='')

memozo 今回の実装は,パラメータの形やファイル名等を固定された形で扱っていましたが,任意の形に少し拡張したものをパッケージとしてgithub.com/sotetsuk/memozoにまとめました. Avec cela, ce processus peut être écrit comme ceci:

from memozo import Memozo

m = Memozo('./data')

@m.generator(file_name='filtered_sentences', ext='txt')
def filter_data(keyword):
    path_to_raw_data = './data/sentences.txt'
    with codecs.open(path_to_raw_data, 'r', 'utf-8') as f:
        for line in f:
            if keyword in line:
                yield line

Le fichier cache est enregistré dans '' ./ data / filtered_sentences_1fec01f.txt'`, et l'historique des paramètres utilisés dans . / Data / .memozo``` est écrit. Le hachage est calculé à partir de (nom du fichier, nom de la fonction, paramètre), et si l'historique et le fichier cache utilisant le même hachage existent déjà, l'exécution de la fonction sera ignorée. En d'autres termes, si vous exécutez avec le même (nom de fichier, nom de fonction, paramètre), la valeur sera renvoyée depuis le cache, et si vous en changez une, le résultat sera différent.

En plus du générateur, il existe des versions de fonctions qui correspondent à pickle```, codecs``` et ordinaire open```.

Je pense que la mise en œuvre est encore incomplète, donc je vous serais reconnaissant de bien vouloir mentionner Problème / PR, etc.

Relation

タスク間に複雑な依存関係がある場合はDAGベースのワークフローツールを使った方がいいでしょう.一例として,github.com/spotify/luigiなどが挙げられます.

Les références

Recommended Posts

Utiliser des décorateurs pour empêcher la ré-exécution du traitement des données
Résumé de l'utilisation de pandas.DataFrame.loc
Résumé de l'utilisation de pyenv-virtualenv
Résumé de l'utilisation de csvkit
traitement pour utiliser les données notMNIST en Python (et essayé de les classer)
[Introduction à Data Scientist] Bases du calcul scientifique, du traitement des données et comment utiliser la bibliothèque de dessins graphiques graph Bases de Scipy
[Introduction à Data Scientist] Bases du calcul scientifique, du traitement des données et comment utiliser la bibliothèque de dessins graphiques graph Bases de Pandas
[Introduction à Data Scientist] Bases du calcul scientifique, du traitement des données et comment utiliser la bibliothèque de dessin de graphes ♬ Bases de Matplotlib
[Python] Résumé de l'utilisation des pandas
100 traitement du langage knock-91: Préparation des données d'analogie
Convertir les données de la grille en données contenant des lignes (?) À l'aide de pandas
[Python2.7] Résumé de l'utilisation d'unittest
Jupyter Notebook Principes d'utilisation
Comment utiliser "deque" pour les données Python
Bases de PyTorch (1) -Comment utiliser Tensor-
Résumé de l'utilisation de la liste Python
[Python2.7] Résumé de l'utilisation du sous-processus
Exemple de traitement efficace des données avec PANDAS
[Introduction au Data Scientist] Bases de Python ♬
[Question] Comment utiliser plot_surface de python
[Introduction to Data Scientist] Bases du calcul scientifique, du traitement des données et comment utiliser la bibliothèque de dessins graphiques ♬ Construction d'environnement
Comment utiliser Folium (visualisation des informations de localisation)
[Python] Comment utiliser deux types de type ()
Utilisez le mode de traitement des nouvelles tentatives ajouté à Boto3
Convertissez les données avec la forme (nombre de données, 1) en (nombre de données,) avec numpy.
Pas beaucoup de mention de la façon d'utiliser Pickle
Résumé de l'utilisation de MNIST avec Python
Comment utiliser les outils d'analyse de données pour les débutants
[Pandas] Principes de base du traitement des données de date à l'aide de dt
Préparation à l’essai de «Data Science 100 Knock (traitement des données structurées)»
Histoire d'essayer d'utiliser Tensorboard avec Pytorch
Je veux obtenir les données de League of Legends ③
Je veux obtenir les données de League of Legends ②
Utilisation des données météorologiques passées 1 (affichage des points Amedas)
[Introduction à cx_Oracle] (5e) Gestion des données japonaises
Comment utiliser la bibliothèque de traitement d'image basée sur PyTorch "Kornia"
Résumé de l'étude de Python pour utiliser AWS Lambda
Nettoyage des données 3 Utilisation d'OpenCV et prétraitement des données d'image
Un graphique sympa pour l'analyse des données de Wiire!
Je veux obtenir les données de League of Legends ①
Quoi utiliser pour les piles et les files d'attente Python (comparaison de vitesse de chaque structure de données)
Utilisez Pandas pour écrire uniquement les lignes spécifiées du bloc de données dans le fichier Excel
Créer un ensemble de données d'images à utiliser pour la formation
[Introduction à Python] Comment utiliser l'instruction while (traitement répétitif)
100 langage traitement knock-92 (utilisant Gensim): application aux données d'analogie
Résumé des outils nécessaires pour analyser les données en Python
Traitement pleine largeur et demi-largeur des données CSV en Python
Remarques sur l'utilisation d'AIST Spacon ABCI
J'ai essayé de résumer comment utiliser matplotlib de python
À propos du prétraitement des données des systèmes utilisant l'apprentissage automatique
Vous avez besoin d'amour pour éviter d'effacer accidentellement cron
[Chapitre 5] Introduction à Python avec 100 coups de traitement du langage
[Chapitre 6] Introduction à scicit-learn avec 100 coups de traitement du langage
Comment utiliser Python Kivy ① ~ Bases du langage Kv ~
Envoyer des données de Python au traitement via une communication socket
Vérification des performances du prétraitement des données dans le traitement du langage naturel
[Chapitre 3] Introduction à Python avec 100 coups de traitement du langage
DataNitro, implémentation de la fonction de lecture des données de feuille
Utilisons les données ouvertes de "Mamebus" en Python