Es ist der 11. Tag von TensorFlow2.0 Adventskalender 2019.
Ich möchte zusammenfassen, wie Text mithilfe der tf.data.Dataset API vorverarbeitet wird.
In diesem Artikel werden wir in der folgenden Reihenfolge erklären.
Da die Erklärung lang ist (der Code ist auch lang ...), können Sie hier nur den Code aus der Vogelperspektive betrachten Sie können von baselines / preprocess / tfdata.py) darauf verweisen.
(Beachten Sie, dass der Inhalt dieses Artikels nicht vollständig validiert wurde. Der Code funktioniert, aber es gibt einige Dinge, bei denen ich nicht sicher bin, ob er zu einer Leistungsverbesserung beiträgt. Von Zeit zu Zeit aktualisiert Ich werde es tun, aber ich hoffe, Sie behalten es nur als Referenz.)
Die folgenden Artikel beziehen sich auf den Adventskalender. Ich hoffe das ist auch hilfreich.
Tag 3: Eine grundlegende Einführung in die tf.data.Dataset-API (Die Geschichte der starken Dataset-Funktion, die mit TensorFlow verwendet werden kann) ――Tag 7: Die tf.data.Dataset-API führt das Verfahren zum Teilen mit Mecab ein (Separates Live-News-Korpus mit Mecab und tf.data. / masahikoofjoyto / items / b444262405ad7371c78a))))
Tag 10: Wir versuchen, die Karte durch Parallelisierung mit joblib zu beschleunigen. In diesem Artikel werde ich die Parallelisierungsfunktion vorstellen, die tf.data .map selbst hat, aber ich möchte überprüfen, welche schneller ist. (Vielmehr scheint es, dass sie kombiniert werden können) ([[TF2.0-Anwendung] Ein Fall, in dem die allgemeine Datenerweiterung mit der starken Datensatzfunktion des TF-Beispiels mit hoher Geschwindigkeit parallelisiert und realisiert wurde](https://qiita.com/ Suguru_Toyohara / items / 528447a73fc6dd20ea57)))
Ich denke, der typische Lernprozess ist wie folgt.
Wenn der Datensatz wächst und Sie die Schritte 1 bis 4 nacheinander ausführen, gehen Ihnen die Ressourcen aus. (Insbesondere bei Bildern sind es oft mehrere GB, also 1. Es ist nicht möglich, nur durch gleichzeitiges Lesen von Daten zu verarbeiten.) Teilen Sie es daher in Stapel auf (z. B. alle paar Bilder) und führen Sie die Verarbeitung von 1 bis 4 gleichzeitig durch. Es wird empfohlen, dies zu wiederholen. Dies wird als Pipeline-Verarbeitung bezeichnet.
Mit einer einfachen Pipeline kann diese Reihe von Prozessen zu einer verschwendeten Latenz im Overhead-Teil führen, wie unten gezeigt. https://www.tensorflow.org/guide/data_performance
Die tf.data.Dataset-API verfügt über die folgenden Funktionen, um die Overhead-Verarbeitung zu verteilen und unnötige Wartezeiten zu reduzieren.
--prefetch: Wird parallel von CPU und GPU / TPU verarbeitet --map: Parallele Verarbeitung der Vorverarbeitung --read_file: Parallele Verarbeitung des Lesens
Diese werden später beschrieben. Zunächst werde ich über die Textvorverarbeitung schreiben, um zu wissen, wie die API tf.data.Dataset verwendet wird.
Lassen Sie uns nun den Text mit der API tf.data.Dataest vorverarbeiten. Ich denke, die Reihenfolge kann sich ändern, aber ich denke, der Standardablauf für die Textvorverarbeitung ist wie folgt.
2.1. load Erstellen Sie zunächst einen Dataset Loader. Der Verarbeitungsablauf ist wie folgt.
Da die Größe des Datensatzes, den wir verarbeiten, heutzutage immer größer wird, gibt es meines Erachtens nicht viele Fälle, in denen sich die Daten von Anfang an auf der lokalen CD befinden. Daher können die folgenden Fälle berücksichtigt werden.
--Download aus externem Speicher --Download aus dem Cloud-Speicher
Hier ist ein Beispiel für das einfache Abrufen von Daten aus einem externen Speicher (keine Authentifizierung erforderlich). Unten können Sie die Textdateien cowper.txt, derby.txt, butler.txt auf Ihre lokale CD herunterladen. (Da es einfach herunterzuladen ist, werden wir diese englischen Textdaten verwenden, aber in Wirklichkeit sollte es für Japanisch vorverarbeitet sein.) Darüber hinaus ist es eine Funktion, die eine Liste der Pfade der heruntergeladenen lokalen Disc zurückgibt. Wenn Sie die Download-Methode entsprechend ersetzen und die Ausgabe anordnen, können Sie wie folgt vorgehen.
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)
Der Rest des Prozesses wird wie folgt zusammengefasst. Jetzt haben Sie einen Datensatz, der Text und Beschriftung iteriert.
def load_dataset(file_paths: List[str], file_names: List[str], BUFFER_SIZE=1000):
#Geben Sie mehrere zu ladende Dateien an
files = tf.data.Dataset.list_files(file_paths)
#Wenden Sie die Kartenfunktion für jede Datei an(labeling_map_fn wird später beschrieben(Daten lesen&Beschriftung))
datasets = files.interleave(
labeling_map_fn(file_names),
)
#Daten mischen
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>
Wir werden uns die Verarbeitung im Detail ansehen.
Die von tf.data.Dataset.list_files erstellten Dateien sind Dataset-Instanzen, deren Wert den Pfad der lokalen Disc enthält (siehe unten). Es ist ein Ärger, aber die Dataset-Instanz muss iteriert werden, um zu sehen, was sich darin befindet. Noch ärgerlicher ist, dass Sie den Wert mit der Methode `.numpy ()`
abrufen können.
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'
Nachdem Sie die Kartenfunktion auf das Dataset angewendet haben, reduzieren Sie die Ergebnisse und kombinieren Sie sie. In dieser Verwendung definieren wir zuerst eine Kartenfunktion, die eine Textdatei liest und einen Datensatz zurückgibt, der Zeile für Zeile iteriert. Und wenn Sie es an .interleave ()
übergeben, anstatt für jede Datei einen eigenen Datensatz zu erstellen, erstellen Sie einen flachen Datensatz, der zeilenweise aus allen Dateien iteriert wird.
Referenz: Offizielles Dokument
Wie Sie dem Namen entnehmen können, wird der Datensatz gemischt. Extrahieren Sie während der Iteration zufällig Daten aus buffer_size. Wenn die Iteration wiederholt wird und buffer_size überschreitet, wird sie aus den Daten für die nächste buffer_size extrahiert. Daher garantiert eine große Puffergröße Unordnung. Wenn buffer_size jedoch groß ist, verbraucht es Ressourcen entsprechend, was ein Kompromiss ist.
Wenn Sie "reshuffle_each_iteration = False" setzen, wird es in derselben Reihenfolge gemischt, unabhängig davon, wie oft Sie mit der Iteration beginnen. Da der Standardwert True ist, schreiben Sie jedes Mal, wenn Sie `next (iter (Dataset))`
oder `für Daten im Dataset schreiben:`
nach einfachem Aufruf von .shuffle ()
` Es wird in einer anderen Reihenfolge wiederholt. Sei gut oder schlecht, sei vorsichtig.
Ich werde Ihnen zeigen, wie Sie eine TXT-Datei lesen, deren Dateiname eine Bezeichnung ist und jede Zeile eine Textdaten enthält. Ich denke, es ist ein Standardprozess, aber ich hoffe, Sie können ihn je nach Datenformat entsprechend ersetzen.
Hier können Sie einen Datensatz mit flachem Text und Beschriftung erhalten, indem Sie die folgende Zuordnungsfunktion an `.interleave ()`
übergeben.
`tf.data.TextLineDataset ()`
und generieren Sie eine Dataset-Instanz.`.map (labeler)`
, um die Label-ID zuzuweisen, die mit dem Dateinamen identisch ist.def labeling_map_fn(file_names):
def _get_label(datasets):
"""
Datensatzwert(file path)Analysieren Sie den Dateinamen von
file_Die Indexnummer der Namen sei die Label-ID
"""
filename = datasets.numpy().decode().rsplit('/', 1)[-1]
label = file_names.index(filename)
return label
def _labeler(example, label):
"""Etikett zum Datensatz hinzufügen"""
return tf.cast(example, tf.string), tf.cast(label, tf.int64)
def _labeling_map_fn(file_path: str):
"""main map function"""
#Zeile für Zeile aus der Textdatei lesen
datasets = tf.data.TextLineDataset(file_path)
#Konvertieren Sie den Dateipfad in die Label-ID
label = tf.py_function(_get_label, inp=[file_path], Tout=tf.int64)
#Fügen Sie dem Datensatz eine Etiketten-ID hinzu
labeled_dataset = datasets.map(lambda ex: _labeler(ex, label))
return labeled_dataset
return _labeling_map_fn
Unterwegs verwende ich eine Funktion namens "tf.py_function" (doc). Dies liegt daran, dass dem Dataset API Map-Funktionsargument ein Tensor-Objekt übergeben wird. Das Tensor-Objekt kann nicht direkt in Python auf den Wert verweisen. Wenn Sie ihn jedoch mit tf.py_function umschließen, wird derselbe Typwert wie beim Übergeben von `next (iter (Dataset))`
als Argument übergeben. Sie können also mit `` .numpy ()
`auf den Wert verweisen und vertraute Python-Verarbeitung schreiben.
Es scheint jedoch einige Schwierigkeiten bei der Leistung zu geben, daher möchte ich vermeiden, sie so oft wie möglich zu verwenden.
2.2. standarize & 2.3. tokenize Hier werden verschiedene Prozesse gleichzeitig ausgeführt. Es wird davon ausgegangen, dass Sie eine Python-Bibliothek oder eine solide Bibliothek verwenden. Es gibt eine Menge Verarbeitung für Text in Tensorflow, aber es ist ziemlich schwierig, daher gehe ich davon aus, dass Sie die in Python geschriebene so verwenden, wie sie ist. Zumindest kann man mit Tensorflow nicht separat schreiben, daher denke ich, dass dies ein wesentlicher Prozess auf Japanisch ist.
janome ist eine in Python implementierte morphologische Analyse und praktisch, da sie nur mit der Pip-Installation verwendet werden kann. Sie können flexibel eine standardisierte Pipeline namens Analyzer erstellen, wie unten gezeigt.
from janome.tokenizer import Tokenizer
from janome.analyzer import Analyzer
from janome.charfilter import (
RegexReplaceCharFilter #String-Ersatz
)
from janome.tokenfilter import (
CompoundNounFilter, #Zusammengesetzte Nomenklatur
POSStopFilter, #Entfernen Sie bestimmte Teilwörter
LowerCaseFilter #In Kleinbuchstaben konvertieren
)
def janome_tokenizer():
# standarize texts
char_filters = [RegexReplaceCharFilter(u'Schlangenaugen', u'janome')]
tokenizer = Tokenizer()
token_filters = [CompoundNounFilter(), POSStopFilter(['Symbol','Partikel']), 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
Allein damit wird es wie folgt standardisiert und aufgeteilt.
text, _ = janome_tokenizer()('Das Serpentin ist ein morphologischer Analysator. Einfach zu verwenden.', 0)
print(text)
# 'Janome Morphological Analyzer einfach zu bedienen.'
Rufen Sie die obige Funktion über die Dataset-API auf.
Verwenden Sie dazu erneut tf.py_function zum Konvertieren. Sie müssen den Ausgabetyp angeben. Anschließend können Sie diese Funktion aufrufen, indem Sie sie mit `.map ()`
an das Dataset übergeben.
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
Verwenden Sie die tensorflow_datasets.text API zum Codieren (Zeichenfolge in ID konvertieren).
Insbesondere sind `tfds.features.text.Tokenizer ()`
und `` tfds.features.text.TokenTextEncoder``` zum Codieren nützlich.
Zunächst müssen Sie ein Vokabular erstellen. Wenn Sie es zuerst schaffen, können Sie Folgendes weglassen.
Hier erstellen wir aus den Trainingsdaten ein Vokabular. Verwenden Sie `tfds.features.text.Tokenizer ()`
, um das Token abzurufen, und set (), um die Duplikate zu entfernen.
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
Hier verwenden wir `tfds.features.text.TokenTextEncoder ()`
, um das im Vokabular enthaltene Token in eine ID umzuwandeln. Verwenden Sie das folgende `encode_map_fn ()`
für `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 Teilen Sie den Datensatz in Zug und Test. Folgendes kann weggelassen werden, wenn es vom Anfang getrennt ist. Mit der Dataset-API ist das Teilen eines Datasets wie folgt sehr einfach zu implementieren.
def split_train_test(data, TEST_SIZE: int, BUFFER_SIZE: int, SEED=123):
"""
TEST_SIZE =Anzahl der Testdaten
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 Mit der API tf.data.Dataset können Auffüllen und Stapeln gleichzeitig durchgeführt werden. Epochen ist die Anzahl der Epochen und BATCH_SIZE die Stapelgröße. Hier sind einige Dinge zu beachten:
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)
Hier kann max_len wie unten gezeigt aus dem Datensatz abgerufen oder auf feste Weise eingegeben werden.
Die meisten Modelle erfordern eine maximale Tokenlänge. Holen Sie es sich hier aus dem Datensatz. Wenn Sie sich für eine Eingabe entscheiden, können Sie die folgende Verarbeitung überspringen.
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
Ich habe mir die Implementierung mit der API tf.data.Dataset im folgenden Ablauf angesehen.
Übergeben Sie es zum Zeitpunkt des Lernens einfach an die Methode `.fit ()`
, wie unten gezeigt.
model.fit(train_data,
epochs=epochs,
validation_data=test_data
)
Wie eingangs erläutert, kann die Reihe von Vorverarbeitungsprozessen im Overhead-Teil wie folgt unnötige Wartezeiten verursachen. https://www.tensorflow.org/guide/data_performance
Die tf.data.Dataset-API verfügt über die folgenden Funktionen, um die Overhead-Verarbeitung zu verteilen und unnötige Wartezeiten zu reduzieren.
--prefetch: Wird parallel von CPU und GPU / TPU verarbeitet --map: Parallele Verarbeitung der Vorverarbeitung --read_file: Parallele Verarbeitung des Lesens
Referenz: Eingabe-Pipelines mit tf.data optimieren prefetch Prozesse werden parallel auf der CPU und der GPU / TPU ausgeführt. Es wird automatisch durch tf.experiments.AUTOTUNE angepasst. https://www.tensorflow.org/guide/data_performance
Kein Stress. Fügen Sie am Ende einfach die folgende Verarbeitung hinzu. (In diesem Artikel machen wir das für train_data und test_data.)
dataset = dataset.prefetch(buffer_size=tf.data.experimental.AUTOTUNE)
map
Die Kartenfunktion kann auch verteilt werden.
Dies wird auch automatisch durch tf.experiments.AUTOTUNE angepasst.
Wenn es zu langsam wird, können Sie zuerst die Methode `.batch ()`
verwenden und dann übergeben.
https://www.tensorflow.org/guide/data_performance
Fügen Sie der `.map ()`
Methode einfach ein Argument hinzu, wie unten gezeigt.
dataset = dataset.map(map_func, num_parallel_calls=tf.data.experimental.AUTOTUNE)
read file Selbst beim Lesen mehrerer Dateien kann die Verarbeitung gleichzeitig verteilt und gelesen werden. E / A ist wahrscheinlich der Engpass, insbesondere beim Lesen von Daten aus dem Remotespeicher. (In diesem Artikel wird es von der lokalen CD gelesen, daher ist es möglicherweise nicht sehr effektiv.)
https://www.tensorflow.org/guide/data_performanceSie müssen der Methode .interleave ()
`ein Argument hinzufügen, wie unten gezeigt.
dataset = files.interleave(
tf.data.TFRecordDataset, cycle_length=FLAGS.num_parallel_reads,
num_parallel_calls=tf.data.experimental.AUTOTUNE)
cache
Obwohl sich der Kontext ändert, kann `.cache ()`
die Leistung verbessern.
Wenn Sie wie folgt schreiben, wird es im Speicher zwischengespeichert.
dataset = dataset.cache()
Wenn Sie eine Zeichenfolge wie unten gezeigt als Argument übergeben, wird sie in einer Datei anstatt im Speicher gespeichert.
dataset = dataset.cache('tfdata')
Es ist lange her, aber ich habe Ihnen gezeigt, wie Sie Text mithilfe der tf.data.Dataset-API vorverarbeiten. Den zusammenhängenden Code finden Sie hier [https://github.com/tokusumi/nlp-dnn-baselines/blob/master/nlp-dnn-baselines/preprocess/tfdata.py]. Insbesondere haben wir die Einführung der API tf.data.Dataset, das Verfahren zur Textvorverarbeitung und Tipps zur Verbesserung der Leistung zusammengefasst. Die Erklärung ist lang geworden, aber danke, dass Sie bis zum Ende gelesen haben! Ich hoffe es wird hilfreich für Sie sein!
refs
Recommended Posts