[PYTHON] [TensorFlow 2] Es wird empfohlen, die Funktionsmenge von TFRecord in Batch-Einheiten zu lesen.

Einführung

Wenn Sie eine große Datenmenge mit TensorFlow trainieren, ist es zweckmäßig, die Dataset-API zum Laden der in TFRecord gespeicherten Funktionen zu verwenden. [\ TensorFlow 2.x-kompatible Version ] So trainieren Sie eine große Datenmenge mit TFRecord & DataSet mit TensorFlow (Keras) - Qiita

Sie können eine Menge Beispielcode finden, indem Sie suchen, aber tatsächlich habe ich festgestellt, dass es durch die Entwicklung einer Methode zum Lesen möglich sein kann, ihn viel schneller zu lesen als die Methode, die Sie häufig sehen.

Überprüfungsumgebung

Gut eingeführte Lesemethode

Zusätzlich zu dem obigen Artikel können Sie "tf.io.parse_single_example ()" als Lesemethode verwenden, die häufig in offiziellen Dokumenten und anderen Websites eingeführt wird. [Verwendung von TFRecords und tf.Example | TensorFlow Core](https://www.tensorflow.org/tutorials/load_data/tfrecord?hl=ja#tfrecord_%E3%83%95%E3%82%A1%E3% 82% A4% E3% 83% AB% E3% 81% AE% E8% AA% AD% E3% 81% BF% E8% BE% BC% E3% 81% BF)

import tensorflow as tf
import numpy as np

feature_dim = 784
def parse_example(example):
    features = tf.io.parse_single_example(
        example,
        features={
            "x": tf.io.FixedLenFeature([feature_dim], dtype=tf.float32),
            "y": tf.io.FixedLenFeature([], dtype=tf.float32)
        })
    x = features["x"]
    y = features["y"]
    return x, y

ds1 = tf.data.TFRecordDataset(["test.tfrecords"]).map(parse_example).batch(512)
print(ds1)
print(next(iter(ds1)))

Mit dieser Art von Gefühl wird der Prozess des Konvertierens jedes Datensatzes in einen Feature-Betrag mit map () in den Datensatz gestellt. Wahrscheinlich die größte Verwendung.

Ich habe jedoch das Gefühl, dass die Verarbeitung langsam ist ... Selbst wenn ich mit GPU lerne, bleibt die GPU-Auslastung nicht bei nahezu 100%, aber die CPU-Auslastung steigt nicht an. Ich habe das Gefühl, dass I / O der Engpass ist.

Kann nicht in Batch-Einheiten lesen?

Betrachten Sie die offizielle Dokumentation als allgemeine Theorie bei der Konvertierung von Datensätzen

Invoking a user-defined function passed into the map transformation has overhead related to scheduling and executing the user-defined function. We recommend vectorizing the user-defined function (that is, have it operate over a batch of inputs at once) and apply the batch transformation before the map transformation.

ist was es liest. Better performance with the tf.data API | TensorFlow Core

Kurz gesagt, es wird empfohlen, map () mit benutzerdefinierten Funktionen stapelweise auszuführen. Wenn ja, würde sich die Leistung verbessern, wenn Daten in Batch-Einheiten gelesen und dekodiert werden könnten?

Ich konnte überhaupt kein japanisches Material finden, aber es scheint, dass Features mithilfe von tf.data.experimental.parse_example_dataset () in Batch-Einheiten dekodiert werden können. [^ 1] Der Dekodierungsprozess beginnt nach dem Stapeln wie unten gezeigt.

[^ 1]: Es gibt auch tf.io.parse_example () und [Beispielcode](https://stackoverflow.com/questions/37151895/tensorflow-read-all-examples-from-a- Ich habe auch tfrecords-auf einmal gefunden), aber ich konnte es nicht gut verwenden, da es ein Rest der 1.x-Serie (0.x-Serie?) Zu sein scheint. (Als ich versuchte, "TFRecordReader" zu verwenden, war ich wütend, dass es nicht mit Eager Execution verwendet werden konnte.)

feature_dim = 784
ds2 = tf.data.TFRecordDataset(["test.tfrecords"]) \
          .batch(512) \
          .apply(tf.data.experimental.parse_example_dataset({
              "x": tf.io.FixedLenFeature([feature_dim], dtype=tf.float32),
              "y": tf.io.FixedLenFeature([], dtype=tf.float32)
          }))
print(ds2)
print(next(iter(ds2)))

Jeder Datensatz wird im dict -Format zurückgegeben, sodass Sie ihn beim Training mit keras.Model.fit () in ein separates Tupel konvertieren müssen. Bei einer Datensatzeinheit können Sie die Konvertierung in taple sofort in parse_example () schreiben, aber hier müssen Sie den Konvertierungsprozess hinzufügen, um separat mit map () zu taple.

Leistungsvergleich

Ich habe es tatsächlich versucht. Schreiben Sie 10000 MNIST-Testdaten und messen Sie die Verarbeitungszeit des zu lesenden Teils. Ich werde es erst versuchen, wenn ich diesmal gelernt habe, aber da davon ausgegangen wird, dass es danach zum Lernen verwendet wird, ist im Fall von Batch-Einheiten auch der Prozess des Konvertierens von Datensätzen in Taples enthalten.

Schreiben Sie zuerst die Daten in die TFRecord-Datei.

data2tfrecord.py


import numpy as np
import tensorflow as tf
from tensorflow.keras.datasets import mnist

def feature_float_list(l):
    return tf.train.Feature(float_list=tf.train.FloatList(value=l))

def record2example(r_x, r_y):
    return tf.train.Example(features=tf.train.Features(feature={
        "x": feature_float_list(r_x),
        "y": feature_float_list(r_y)
    }))

filename_test  = "test.tfrecords"

#Schreiben Sie 10000 Bewertungsdaten von MNIST
_, (x_test, y_test) = mnist.load_data()
print("x_test    : ", x_test.shape)  # x_test    :  (10000, 28, 28)
print("y_test    : ", y_test.shape)  # y_test    :  (10000,)
x_test  = x_test.reshape((-1, 28*28)).astype("float32") / 255.0
y_test  = y_test.reshape((-1, 1)).astype("float32")
with tf.io.TFRecordWriter(filename_test) as writer:
    for r_x, r_y in zip(x_test, y_test):
        ex = record2example(r_x, r_y)
        writer.write(ex.SerializeToString())

Laden Sie es dann auf zwei Arten.

read_tfrecord.py


import tensorflow as tf
import numpy as np

feature_dim = 784

def parse_example(example):
    features = tf.io.parse_single_example(example, features={
        "x": tf.io.FixedLenFeature([feature_dim], dtype=tf.float32),
        "y": tf.io.FixedLenFeature([], dtype=tf.float32)
    })
    x = features["x"]
    y = features["y"]
    return x, y

ds1 = tf.data.TFRecordDataset(["test.tfrecords"]).map(parse_example).batch(512)
print(ds1) # <BatchDataset shapes: ((None, 784), (None,)), types: (tf.float32, tf.float32)>

def dict2tuple(feat):
    return feat["x"], feat["y"]

ds2 = tf.data.TFRecordDataset(["test.tfrecords"]) \
          .batch(512) \
          .apply(tf.data.experimental.parse_example_dataset({
              "x": tf.io.FixedLenFeature([feature_dim], dtype=tf.float32),
              "y": tf.io.FixedLenFeature([], dtype=tf.float32)
          })) \
          .map(dict2tuple)
print(ds2) # <MapDataset shapes: ((None, 784), (None,)), types: (tf.float32, tf.float32)>

Beachten Sie, dass ds1 und ds2 unterschiedlich erstellt werden, aber am Ende sind es genau die gleichen Daten. Die Stapelgröße und die zurückgegebenen Daten sind gleich.

Starten Sie die interaktive Shell mit "ipython -i read_tfrecord.py" und messen Sie die Verarbeitungszeit, die zum Dekodieren aller 10000 Datensätze erforderlich ist.

ipython


In [1]: %timeit [1 for _ in iter(ds1)]
1 loop, best of 3: 1.4 s per loop

In [2]: %timeit [1 for _ in iter(ds2)]
10 loops, best of 3: 56.3 ms per loop

Es ist ein überwältigender Sieg in der Methode des Lesens in Batch-Einheiten ...!

Was ist, wenn die Merkmalsmenge eine variable Länge hat?

Im vorherigen Beispiel hatte x eine feste Länge (784 Dimensionen), aber es ist etwas ärgerlich, wenn es um variable Länge geht (abhängig vom Datensatz). Im Allgemeinen scheint die Hauptmethode darin zu bestehen, Daten variabler Länge zu serialisieren und als "tf.string" zu behandeln.

data2tfrecord_var.py


import numpy as np
import tensorflow as tf

def feature_bytes_list(l):
    return tf.train.Feature(bytes_list=tf.train.BytesList(value=l))

def feature_float_list(l):
    return tf.train.Feature(float_list=tf.train.FloatList(value=l))

def record2example(r_x, r_y):
    return tf.train.Example(features=tf.train.Features(feature={
        "x": feature_bytes_list(r_x),
        "y": feature_float_list(r_y)
    }))

filename  = "random.tfrecords"
#Schreiben Sie 1000 Daten variabler Länge
with tf.io.TFRecordWriter(filename) as writer:
    for i in range(1000):
        r_x = np.random.random(i+1).astype("float32")
        r_y = np.random.random(1)
        ex = record2example([r_x.tostring()], r_y)
        writer.write(ex.SerializeToString())

Lesen Sie beim Decodieren in Datensatzeinheiten wie folgt.

read_tfrecord_var.py


import tensorflow as tf
import numpy as np

def parse_example(example):
    features = tf.io.parse_single_example(
        example,
        features={
            "x": tf.io.FixedLenFeature([], dtype=tf.string),
            "y": tf.io.FixedLenFeature([], dtype=tf.float32)
        })
    x = tf.io.decode_raw(features["x"], tf.float32)
    y = [features["y"]]
    return x, y

ds1 = tf.data.TFRecordDataset(["random.tfrecords"]).map(parse_example).padded_batch(512, ([None], [1]))
print(ds1) # <PaddedBatchDataset shapes: ((None, None), (None, 1)), types: (tf.float32, tf.float32)>

In Batch-Einheiten wird die Anzahl der Spalten von "x" an die längste Merkmalsmenge angepasst und der kurze Teil mit 0 gefüllt.

ipython


In [1]: %timeit [1 for _ in iter(ds1)]
10 loops, best of 3: 153 ms per loop

Was ist, wenn Sie es in Chargen tun? Da die Anzahl der Dimensionen von "x" für jeden Datensatz unterschiedlich ist, schlägt das Stapeln des Datensatzes und das anschließende Ausführen von "decode_raw" mit "map ()" fehl.

def dict2tuple(feature):
    return tf.io.decode_raw(feature["x"], tf.float32), [feature["y"]]

ds2 = tf.data.TFRecordDataset(["random.tfrecords"]) \
           .batch(512) \
           .apply(tf.data.experimental.parse_example_dataset({
               "x": tf.io.FixedLenFeature([], dtype=tf.string),
               "y": tf.io.FixedLenFeature([], dtype=tf.float32)
           })) \
           .map(dict2tuple)

print(next(iter(ds2)))
# InvalidArgumentError: DecodeRaw requires input strings to all be the same size, but element 1 has size 4 != 8

Wenn Sie jedoch unbatch () und dann decode_raw ausführen, verlieren Sie den Vorteil der Beschleunigung.

ds2 = tf.data.TFRecordDataset(["random.tfrecords"]) \
          .batch(512) \
          .apply(tf.data.experimental.parse_example_dataset({
              "x": tf.io.FixedLenFeature([], dtype=tf.string),
              "y": tf.io.FixedLenFeature([], dtype=tf.float32)
          })).unbatch().map(dict2tuple).padded_batch(512, ([None], [1]))

ipython


In [2]: %timeit [1 for _ in iter(ds2)]
10 loops, best of 3: 136 ms per loop

RaggedFeature

Hier kommt der Retter ins Spiel. Nur in TensorFlow 2.1 und höher verfügbar, können Sie jetzt beim Laden von Daten einen neuen Funktionstyp namens "RaggedFeature" angeben. tf.io.RaggedFeature | TensorFlow Core v2.1.0

Damit sind die dekodierten Funktionen "RaggedTensor". Gewöhnlicher Tensor sollte die gleiche Anzahl von Spalten pro Zeile haben, RaggedTensor jedoch nicht. Sie können einen Tensor mit einer unterschiedlichen Anzahl von Spalten für jede Zeile darstellen. tf.RaggedTensor | TensorFlow Core v2.1.0

Erstellen Sie beim Schreiben von Daten zunächst "Features", indem Sie die Features variabler Länge als Liste von "float32" verwenden.

def feature_float_list(l):
    return tf.train.Feature(float_list=tf.train.FloatList(value=l))

def record2example(r_x, r_y):
    return tf.train.Example(features=tf.train.Features(feature={
        "x": feature_float_list(r_x),
        "y": feature_float_list(r_y)
    }))

filename = "random2.tfrecords" #Ich habe den Namen geändert
with tf.io.TFRecordWriter(filename) as writer:
    for i in range(1000):
        r_x = np.random.random(i+1).astype("float32")
        r_y = np.random.random(1)
        ex = record2example(r_x, r_y)
        writer.write(ex.SerializeToString())

Geben Sie beim Laden "RaggedFeature" als Feature-Menge an.

ds2 = tf.data.TFRecordDataset(["random2.tfrecords"]) \
          .batch(512) \
          .apply(tf.data.experimental.parse_example_dataset({
              "x": tf.io.RaggedFeature(tf.float32),
              "y": tf.io.FixedLenFeature([], dtype=tf.float32)
          }))

Hier wird jeder Datensatz von ds2 wie im Fall einer festen Länge zu dict, außer dass x zu RaggedTensor wird. Wenn Sie jede Zeile von "RaggedTensor" in Scheiben schneiden, sehen Sie "Tensor" in verschiedenen Größen, wie unten gezeigt.

ipython


In [1]: next(iter(ds2))["x"][0]
Out[1]: <tf.Tensor: shape=(1,), dtype=float32, numpy=array([0.8635351], dtype=float32)>

In [2]: next(iter(ds2))["x"][1]
Out[2]: <tf.Tensor: shape=(2,), dtype=float32, numpy=array([0.66411597, 0.8526721 ], dtype=float32)>

In [3]: next(iter(ds2))["x"][2]
Out[3]: <tf.Tensor: shape=(3,), dtype=float32, numpy=array([0.7902446 , 0.13108689, 0.05331135], dtype=float32)>

Sie können das Ende eines kurzen Features mit Nullen auffüllen, um es stapelweise zu einem regulären "Tensor" zu machen. Auf diese Weise erhalten Sie den gleichen Stapel, als würden Sie Datensatz für Datensatz dekodieren.

def dict2tuple(feature):
    return feature["x"].to_tensor(), [feature["y"]]

ds2 = tf.data.TFRecordDataset(["random2.tfrecords"]) \
          .batch(512) \
          .apply(tf.data.experimental.parse_example_dataset({
              "x": tf.io.RaggedFeature(tf.float32),
              "y": tf.io.FixedLenFeature([], dtype=tf.float32)
          })).map(dict2tuple)

ipython


In [4]: %timeit [1 for _ in iter(ds2)]
100 loops, best of 3: 18.6 ms per loop

Es wurde auf fast ein Zehntel der rekordverdächtigen Verarbeitung reduziert. Toll!

VarLenFeature

Tatsächlich bietet TensorFlow 1.x / 2.0 auch die Möglichkeit, Features mit variabler Länge zu lesen. Wenn der Feature-Typ "VarLenFeature" ist, können Sie das Feature als "SparseTensor" lesen. Das Erstellen eines TFRecord ist dasselbe wie "RaggedFeature".

def dict2tuple(feature):
    return tf.sparse.to_dense(feature["x"]), [feature["y"]]

ds3 = tf.data.TFRecordDataset(["random2.tfrecords"]) \
          .batch(512) \
          .apply(tf.data.experimental.parse_example_dataset({
              "x": tf.io.VarLenFeature(tf.float32),
              "y": tf.io.FixedLenFeature([], dtype=tf.float32)
          })) \
          .map(dict2tuple)

ipython


In [5]: %timeit [1 for _ in iter(ds3)]
10 loops, best of 3: 39.9 ms per loop

Sicher, es ist viel schneller als auf Rekordbasis, aber langsamer als das "RaggedFeature". Wenn möglich, möchte ich RaggedFeature in TensorFlow 2.1 oder höher verwenden.

Zusammenfassung

Recommended Posts

[TensorFlow 2] Es wird empfohlen, die Funktionsmenge von TFRecord in Batch-Einheiten zu lesen.
Empfohlenes Buch in 2 Jahren von neuen Absolventen gelesen
Der Hintergrund der Zeichen im Textbild ist überbelichtet, um das Lesen zu erleichtern.
Lesen Sie die Big-Endian-Binärdatei in Python und konvertieren Sie sie in ndarray
TensorFlow / python> // grammar> Es scheint sich um die Ganzzahldivision von Python zu handeln