C'est le 11e jour du Calendrier de l'Avent TensorFlow2.0 2019.
Je voudrais résumer comment prétraiter le texte à l'aide de l 'API tf.data.Dataset.
Dans cet article, nous vous expliquerons dans l'ordre suivant.
Puisque l'explication est longue (le code est aussi long ...), si vous voulez avoir une vue d'ensemble du code, [ici](https://github.com/tokusumi/nlp-dnn-baselines/blob/master/nlp-dnn- Vous pouvez vous y référer à partir de baselines / preprocess / tfdata.py).
(Notez que le contenu de cet article n'a pas été entièrement validé. Le code fonctionne, mais il y a certaines choses dont je ne suis pas sûr qu'il contribue à l'amélioration des performances. Mis à jour de temps à autre Je vais le faire, mais j'espère que vous le garderez à titre indicatif.)
Les articles suivants sont liés au calendrier de l'Avent. J'espère que cela est également utile.
--Jour 3: Une introduction de base à l'API tf.data.Dataset (L'histoire de la fonction de jeu de données fort qui peut être utilisée avec TensorFlow) ――Jour 7: L'API tf.data.Dataset introduit la procédure de partage à l'aide de Mecab (Séparer le corpus de news de Liveoor utilisant Mecab et tf.data. / masahikoofjoyto / items / b444262405ad7371c78a)))
--Jour 10: Nous essayons d'accélérer la carte en parallélisant avec joblib. Dans cet article, je présenterai la fonction de parallélisation que possède tf.data .map, mais j'aimerais vérifier laquelle est la plus rapide. (Au contraire, il semble qu'ils peuvent être combinés) ([[application TF2.0] Un cas où l'augmentation de données à usage général a été parallélisée et réalisée à grande vitesse avec la fonction de jeu de données fort de l'exemple TF](https://qiita.com/ Suguru_Toyohara / items / 528447a73fc6dd20ea57)))
Je pense que le processus d'apprentissage typique est le suivant.
Au fur et à mesure que l'ensemble de données augmente, si vous suivez les étapes 1 à 4 une par une, vous manquerez de ressources. (Surtout pour les images, c'est souvent plusieurs Go, donc 1. Il n'est pas possible de traiter simplement en lisant les données à la fois) Par conséquent, divisez-le en lots (par exemple, pour quelques images) et effectuez un traitement de 1 à 4 en même temps. Il est recommandé de répéter cela. C'est ce qu'on appelle le traitement par pipeline.
Avec un pipeline simple, cette série de processus peut entraîner une latence perdue dans la partie supérieure, comme illustré ci-dessous. https://www.tensorflow.org/guide/data_performance
L'API tf.data.Dataset dispose des fonctions suivantes pour répartir le traitement des frais généraux et réduire les temps d'attente inutiles.
--prefetch: traité en parallèle par CPU et GPU / TPU respectivement --map: traitement parallèle du prétraitement --read_file: Traitement parallèle de la lecture
Ceux-ci seront décrits plus tard. Tout d'abord, j'écrirai sur le prétraitement de texte pour savoir comment utiliser l'API tf.data.Dataset.
Prétraitons maintenant le texte à l'aide de l'API tf.data.Dataest. Je pense que l'ordre peut changer, mais je pense que le flux de prétraitement de texte standard est le suivant.
2.1. load Tout d'abord, créez un chargeur de jeu de données. Le flux de traitement est le suivant.
Étant donné que la taille de l'ensemble de données que nous traitons augmente ces jours-ci, je ne pense pas qu'il y ait beaucoup de cas où les données sont sur le disque local depuis le début. Par conséquent, les cas suivants peuvent être envisagés.
--Télécharger à partir du stockage externe --Télécharger à partir du stockage cloud --Obtenir de la base de données
Voici un exemple de simple récupération de données à partir d'un stockage externe (aucune authentification requise). Ci-dessous, vous pouvez télécharger les fichiers texte cowper.txt, derby.txt, butler.txt sur votre disque local. (Comme il est facile à télécharger, nous utiliserons ces données de texte anglais, mais en réalité, elles sont censées être prétraitées pour le japonais) De plus, c'est une fonction qui renvoie une liste des chemins du disque local téléchargé. Si vous remplacez la méthode de téléchargement comme il convient et organisez la sortie, vous pouvez utiliser la même procédure ci-dessous.
def download_file(directory_url: List[str], file_names: List[str]) -> List[str]:
file_paths = [
tf.keras.utils.get_file(file_name, directory_url + file_name)
for file_name in file_names
]
return file_paths
# download dataset in local disk
directory_url = 'https://storage.googleapis.com/download.tensorflow.org/data/illiad/'
file_names = ['cowper.txt', 'derby.txt', 'butler.txt']
file_paths = download_file(directory_url, file_names)
Le reste du processus est résumé comme suit. Vous disposez maintenant d'un ensemble de données qui itère le texte et l'étiquette.
def load_dataset(file_paths: List[str], file_names: List[str], BUFFER_SIZE=1000):
#Spécifiez plusieurs fichiers à charger
files = tf.data.Dataset.list_files(file_paths)
#Appliquer la fonction de carte pour chaque fichier(labeling_map_fn sera décrit plus tard(Lire les données&Étiquetage))
datasets = files.interleave(
labeling_map_fn(file_names),
)
#mélange de données
all_labeled_data = datasets.shuffle(
BUFFER_SIZE, reshuffle_each_iteration=False
)
return all_labeled_data
datasets = load_dataset(file_paths, file_names)
text, label = next(iter(datasets))
print(text)
# <tf.Tensor: id=99928, shape=(), dtype=string, numpy=b'Comes furious on, but speeds not, kept aloof'>
print(label)
# <tf.Tensor: id=99929, shape=(), dtype=int64, numpy=0>
Nous examinerons le traitement en détail.
Les fichiers créés par tf.data.Dataset.list_files sont des instances de jeu de données qui ont le chemin du disque local comme valeur comme indiqué ci-dessous. C'est un problème, mais l'instance du jeu de données doit être répétée pour voir ce qu'il contient. Encore plus ennuyeux, vous pouvez obtenir la valeur en utilisant la méthode `` .numpy () ''.
print(files)
# <DatasetV1Adapter shapes: (), types: tf.string>
next(iter(files))
# <tf.Tensor: id=99804, shape=(), dtype=string, numpy=b'/Users/username/.keras/datasets/cowper.txt'>
next(iter(files)).numpy()
# b'/Users/username/.keras/datasets/cowper.txt'
Après avoir appliqué la fonction de carte à l'ensemble de données, aplatissez les résultats et combinez-les. Dans cet usage, nous définissons d'abord une fonction de carte qui lit un fichier texte et renvoie un ensemble de données qui itère ligne par ligne. Et si vous le transmettez à `` .interleave () '', au lieu de créer un ensemble de données séparé pour chaque fichier, vous allez créer un ensemble de données plat qui est itéré ligne par ligne à partir de tous les fichiers.
Référence: Document officiel
Comme vous pouvez le voir d'après le nom, il mélange l'ensemble de données. Extrayez les données de manière aléatoire de buffer_size pendant l'itération. Lorsque l'itération est répétée et dépasse buffer_size, elle est extraite des données pour la prochaine buffer_size. Par conséquent, un buffer_size important garantit l'encombrement. Cependant, si buffer_size est grand, il consomme des ressources en conséquence, ce qui est un compromis.
De plus, si vous définissez
reshuffle_each_iteration = False```, il sera mélangé dans le même ordre, quel que soit le nombre de fois que vous démarrez l'itération. Puisque la valeur par défaut est True, chaque fois que vous écrivez
next (iter (dataset)) '' ou
pour les données de l'ensemble de données:
après avoir simplement appelé `` .shuffle () ''. Il sera itéré dans un ordre différent. Que ce soit bon ou mauvais, soyez prudent.
Je vais vous montrer comment lire un fichier .txt dont le nom de fichier est une étiquette et chaque ligne est une donnée texte. Je pense que c'est un processus standard, mais j'espère que vous pourrez le remplacer le cas échéant en fonction du format des données.
Ici, nous obtenons un ensemble de données avec du texte plat et des étiquettes en passant la fonction de carte suivante à `` .interleave () ''.
tf.data.TextLineDataset () '' et générez une instance Dataset.def labeling_map_fn(file_names):
def _get_label(datasets):
"""
valeur de l'ensemble de données(file path)Analyser le nom du fichier à partir de
file_Que le numéro d'index des noms soit l'ID d'étiquette
"""
filename = datasets.numpy().decode().rsplit('/', 1)[-1]
label = file_names.index(filename)
return label
def _labeler(example, label):
"""Ajouter une étiquette à l'ensemble de données"""
return tf.cast(example, tf.string), tf.cast(label, tf.int64)
def _labeling_map_fn(file_path: str):
"""main map function"""
#Lire ligne par ligne à partir d'un fichier texte
datasets = tf.data.TextLineDataset(file_path)
#Convertir le chemin du fichier en ID d'étiquette
label = tf.py_function(_get_label, inp=[file_path], Tout=tf.int64)
#Ajouter l'ID d'étiquette à l'ensemble de données
labeled_dataset = datasets.map(lambda ex: _labeler(ex, label))
return labeled_dataset
return _labeling_map_fn
En cours de route, j'utilise une fonction appelée
tf.py_function``` (doc). Cela est dû au fait que l'argument de la fonction de mappage de l'API Dataset reçoit un objet Tensor. Vous ne pouvez pas faire référence directement à la valeur de l'objet Tensor en python, mais si vous l'enveloppez avec tf.py_function, le même type de valeur que lorsque
next (iter (dataset)) '' est passé en argument. Vous pouvez donc vous référer à la valeur avec `` .numpy () '' et écrire un traitement python familier.
Cependant, il semble y avoir des difficultés de performances, je voudrais donc éviter de l'utiliser autant que possible.
2.2. standarize & 2.3. tokenize Ici, divers processus sont effectués à la fois. Il est supposé que vous utiliserez une bibliothèque python ou une bibliothèque solide. Il y a beaucoup de traitement pour le texte dans tensorflow, mais c'est assez difficile, donc je suppose que vous utiliserez celui écrit en python tel quel. Au moins, vous ne pouvez pas écrire séparément avec tensorflow, donc je pense que c'est un processus essentiel en japonais.
janome est une analyse morphologique implémentée en python et est pratique car elle ne peut être utilisée qu'avec pip install. Vous pouvez créer de manière flexible un pipeline standardisé appelé analyseur, comme indiqué ci-dessous.
from janome.tokenizer import Tokenizer
from janome.analyzer import Analyzer
from janome.charfilter import (
RegexReplaceCharFilter #Remplacement de la chaîne
)
from janome.tokenfilter import (
CompoundNounFilter, #Nomenclature composée
POSStopFilter, #Supprimer des mots de pièce spécifiques
LowerCaseFilter #Convertir en minuscules
)
def janome_tokenizer():
# standarize texts
char_filters = [RegexReplaceCharFilter(u'Œil de vipère', u'janome')]
tokenizer = Tokenizer()
token_filters = [CompoundNounFilter(), POSStopFilter(['symbole','Particule']), LowerCaseFilter()]
analyze = Analyzer(char_filters, tokenizer, token_filters).analyze
def _tokenizer(text, label):
tokenized_text = " ".join([wakati.surface for wakati in analyze(text.numpy().decode())])
return tokenized_text, label
return _tokenizer
Avec cela seul, il sera normalisé et divisé comme suit.
text, _ = janome_tokenizer()('La serpentine est un analyseur morphologique. Facile à utiliser.', 0)
print(text)
# 'analyseur morphologique janome facile à utiliser.'
Appelez la fonction ci-dessus à partir de l'API Dataset. Pour ce faire, utilisez à nouveau tf.py_function pour convertir. Vous devez spécifier le type de sortie. Ensuite, vous pouvez appeler cette fonction en la passant à l'ensemble de données avec `` .map () ''.
def tokenize_map_fn(tokenizer):
"""
convert python function for tf.data map
"""
def _tokenize_map_fn(text: str, label: int):
return tf.py_function(tokenizer, inp=[text, label], Tout=(tf.string, tf.int64))
return _tokenize_map_fn
datasets = datasets.map(tokenize_map_fn(janome_tokenizer()))
2.4. encode
Utilisez l 'API tensorflow_datasets.text pour encoder (convertir la chaîne en ID).
En particulier,
tfds.features.text.Tokenizer () '' et `` tfds.features.text.TokenTextEncoder '' sont utiles pour l'encodage.
Tout d'abord, vous devez créer un vocabulaire. Si vous le faites en premier, vous pouvez omettre ce qui suit.
Ici, nous allons créer un vocabulaire à partir des données d'entraînement. Utilisez
tfds.features.text.Tokenizer () '' pour obtenir le jeton et set () pour supprimer les doublons.
import tensorflow_datasets as tfds
def get_vocabulary(datasets) -> Set[str]:
tokenizer = tfds.features.text.Tokenizer().tokenize
def _tokenize_map_fn(text, label):
def _tokenize(text, label):
return tokenizer(text.numpy()), label
return tf.py_function(_tokenize, inp=[text, label], Tout=(tf.string, tf.int64))
dataset = datasets.map(_tokenize_map_fn)
vocab = {g.decode() for f, _ in dataset for g in f.numpy()}
return vocab
vocab_set = get_vocabulary(datasets)
print(vocab_set)
# {'indomitable', 'suspicion', 'wer', ... }
encode
Ici, nous utilisons tfds.features.text.TokenTextEncoder () '' pour convertir le jeton contenu dans le vocabulaire en un identifiant. Utilisez le suivant ```encode_map_fn ()
pour
datasets.map ()
`.
def encoder(vocabulary_set: Set[str]):
"""
encode text to numbers. must set vocabulary_set
"""
encoder = tfds.features.text.TokenTextEncoder(vocabulary_set).encode
def _encode(text: str, label: int):
encoded_text = encoder(text.numpy())
return encoded_text, label
return _encode
def encode_map_fn(encoder):
"""
convert python function for tf.data map
"""
def _encode_map_fn(text: str, label: int):
return tf.py_function(encoder, inp=[text, label], Tout=(tf.int64, tf.int64))
return _encode_map_fn
datasets = datasets.map(encode_map_fn(encoder(vocab_set)))
print(next(iter(datasets))[0].numpy())
# [111, 1211, 4, 10101]
2.5. split Divisez l'ensemble de données en train et test. Ce qui suit peut être omis s'il est séparé du début. Avec l'API Dataset, la division d'un ensemble de données est très facile à implémenter comme suit.
def split_train_test(data, TEST_SIZE: int, BUFFER_SIZE: int, SEED=123):
"""
TEST_SIZE =Nombre de données de test
note: because of reshuffle_each_iteration = True (default),
train_data is reshuffled if you reuse train_data.
"""
train_data = data.skip(TEST_SIZE).shuffle(BUFFER_SIZE, seed=SEED)
test_data = data.take(TEST_SIZE)
return train_data, test_data
2.6. padding & 2.7. batch Avec l'api tf.data.Dataset, le remplissage et le traitement par lots peuvent être effectués en même temps. Dans l'état actuel des choses, epochs est le nombre d'époques et BATCH_SIZE est la taille du lot. Voici quelques points à garder à l'esprit:
--Si vous définissez `` drop_remainder = True '', lorsque vous regroupez les données, les dernières données de l'itération qui n'ont pas atteint la taille du lot ne seront pas utilisées.
train_data = train_data.padded_batch(BATCH_SIZE, padded_shapes=([max_len], []), drop_remainder=True)
test_data = test_data.padded_batch(BATCH_SIZE, padded_shapes=([max_len], []), drop_remainder=False)
Ici, max_len peut être obtenu à partir du jeu de données comme indiqué ci-dessous, ou il peut être entré de manière fixe.
La plupart des modèles nécessitent une longueur de jeton maximale. Obtenez-le à partir de l'ensemble de données ici. Si vous décidez d'entrer, vous pouvez ignorer le traitement suivant.
def get_max_len(datasets) -> int:
tokenizer = tfds.features.text.Tokenizer().tokenize
def _get_len_map_fn(text: str, label: int):
def _get_len(text: str):
return len(tokenizer(text.numpy()))
return tf.py_function(_get_len, inp=[text, ], Tout=tf.int32)
dataset = datasets.map(_get_len_map_fn)
max_len = max({f.numpy() for f in dataset})
return max_len
J'ai examiné l'implémentation à l'aide de l'API tf.data.Dataset dans le flux suivant.
Au moment de l'apprentissage, passez-le simplement à la méthode `` .fit () '' comme indiqué ci-dessous.
model.fit(train_data,
epochs=epochs,
validation_data=test_data
)
Comme expliqué au début, la série de processus de prétraitement peut entraîner des temps d'attente inutiles dans la partie supérieure comme suit. https://www.tensorflow.org/guide/data_performance
L'API tf.data.Dataset dispose des fonctions suivantes pour répartir le traitement des frais généraux et réduire les temps d'attente inutiles.
--prefetch: traité en parallèle par CPU et GPU / TPU respectivement --map: traitement parallèle du prétraitement --read_file: Traitement parallèle de la lecture
Référence: Optimiser les pipelines d'entrée avec tf.data prefetch Les processus sont exécutés en parallèle sur le CPU et le GPU / TPU. Il est automatiquement ajusté par tf.experiments.AUTOTUNE. https://www.tensorflow.org/guide/data_performance
Sans tracas. Ajoutez simplement le traitement suivant à la fin. (Dans cet article, nous le ferons pour train_data et test_data)
dataset = dataset.prefetch(buffer_size=tf.data.experimental.AUTOTUNE)
map La fonction de carte peut également être distribuée. Ceci est également ajusté automatiquement par tf.experiments.AUTOTUNE. De plus, si cela devient trop lent, vous pouvez d'abord utiliser la méthode `` .batch () '', puis la passer. https://www.tensorflow.org/guide/data_performance
Ajoutez simplement un argument à la méthode `` .map () '' comme indiqué ci-dessous.
dataset = dataset.map(map_func, num_parallel_calls=tf.data.experimental.AUTOTUNE)
read file Même lors de la lecture de plusieurs fichiers, le traitement peut être distribué et lu en même temps. Les E / S sont probablement le goulot d'étranglement, en particulier lors de la lecture de données à partir d'un stockage distant. (Dans cet article, il est lu à partir du disque local, il peut donc ne pas être très efficace.)
https://www.tensorflow.org/guide/data_performanceVous devez ajouter un argument à la méthode `` .interleave () '' comme indiqué ci-dessous.
dataset = files.interleave(
tf.data.TFRecordDataset, cycle_length=FLAGS.num_parallel_reads,
num_parallel_calls=tf.data.experimental.AUTOTUNE)
cache Bien que le contexte change, `` .cache () '' est efficace pour améliorer les performances. Si vous écrivez comme suit, il sera mis en cache en mémoire.
dataset = dataset.cache()
Si vous passez la chaîne comme argument comme indiqué ci-dessous, elle sera enregistrée dans un fichier au lieu d'être en mémoire.
dataset = dataset.cache('tfdata')
Cela fait longtemps, mais je vous ai montré comment prétraiter du texte à l'aide de l'API tf.data.Dataset. Le code cohésif peut être trouvé ici [https://github.com/tokusumi/nlp-dnn-baselines/blob/master/nlp-dnn-baselines/preprocess/tfdata.py). En particulier, nous avons résumé l'introduction de l'API tf.data.Dataset, la procédure de prétraitement de texte et des conseils pour améliorer les performances. L'explication est devenue longue, mais merci d'avoir lu jusqu'au bout! J'espère que cela vous sera utile!
refs