[PYTHON] [TensorFlow 2] Il est recommandé de lire la quantité de fonction de TFRecord en unités de lots.

introduction

Lors de l'entraînement d'une grande quantité de données avec TensorFlow, il est pratique d'utiliser l'API Dataset pour lire les fonctionnalités stockées dans TFRecord. [\ Version compatible TensorFlow 2.x ] Comment entraîner une grande quantité de données à l'aide de TFRecord & DataSet avec TensorFlow (Keras) --Qiita

Vous pouvez trouver beaucoup d'exemples de code en effectuant une recherche, mais en fait, j'ai trouvé qu'en imaginant un moyen de le lire, il serait peut-être possible de le lire beaucoup plus rapidement que la méthode que vous voyez souvent.

Environnement de vérification

Méthode de lecture bien introduite

En plus de l'article ci-dessus, vous pouvez utiliser `` tf.io.parse_single_example () '' comme méthode de lecture souvent introduite dans les documents officiels et autres sites. [Utilisation de TFRecords et 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)))

Avec ce genre de sentiment, le processus de conversion de chaque enregistrement en une quantité de caractéristiques est placé dans Dataset avec `` map () ''. Probablement l'utilisation la plus importante.

Cependant, j'ai l'impression que le traitement est lent ... Même si j'apprends avec le GPU, l'utilisation du GPU ne s'en tient pas à près de 100%, mais l'utilisation du processeur n'augmente pas. Je pense que les E / S sont le goulot d'étranglement.

Vous ne pouvez pas lire dans les unités de lot?

Consulter la documentation officielle, en tant que théorie générale lors de la conversion d'un ensemble de données

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.

est ce qu'il lit. Better performance with the tf.data API | TensorFlow Core

En bref, "Il est recommandé de faire map () '' en utilisant des fonctions définies par l'utilisateur sur une base batch." Si tel est le cas, les performances s'amélioreraient-elles si les données pouvaient être lues et décodées par lots?

Je n'ai pas trouvé de matériel japonais du tout, mais il semble que la quantité de fonctionnalités puisse être décodée en unités de lot en utilisant `` tf.data.experimental.parse_example_dataset () ''. [^ 1] Le processus de décodage démarre après le traitement par lots comme indiqué ci-dessous.

[^ 1]: Il y a aussi tf.io.parse_example () '', et [exemple de code](https://stackoverflow.com/questions/37151895/tensorflow-read-all-examples-from-a- J'ai aussi trouvé tfrecords-at-once), mais je n'ai pas pu l'utiliser correctement car il semble être un vestige de la série 1.x (série 0.x?). (Quand j'ai essayé d'utiliser TFRecordReader``, j'étais en colère qu'il ne puisse pas être utilisé avec Eager Execution)

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)))

Chaque enregistrement est retourné au format dict '', vous devrez donc le convertir en un tuple séparé lors de l'entraînement avec keras.Model.fit () ''. Dans le cas de l'unité d'enregistrement, vous pouvez écrire la conversion en taple à la fois dans parse_example () '', mais ici vous devez ajouter le processus de conversion pour tapoter séparément avec map () ''.

Comparaison des performances

Je l'ai essayé. Ecrivez 10000 données de test MNIST et mesurez le temps de traitement de la pièce pour la lire. Je ne l'essayerai pas avant d'apprendre cette fois, mais comme on suppose qu'il sera utilisé pour l'apprentissage par la suite, dans le cas d'unités par lots, le processus de conversion des enregistrements en taples est également inclus.

Tout d'abord, écrivez les données dans le fichier TFRecord.

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"

#Écrire 10000 données d'évaluation de 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())

Ensuite, chargez-le de deux manières.

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)>

Notez que ds1 '' et ds2 '' sont créés différemment, mais à la fin ce sont exactement les mêmes données. La taille du lot et les données renvoyées seront les mêmes.

Démarrez le shell interactif avec `ʻi python -i read_tfrecord.py`` et mesurez le temps de traitement nécessaire pour décoder les 10000 enregistrements.

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

C'est une victoire écrasante dans la méthode de lecture par lots ...!

Que faire si la quantité d'entités est de longueur variable?

Dans l'exemple précédent, x '' avait une longueur fixe (784 dimensions), mais c'est un peu gênant quand il s'agit de longueur variable (en fonction de l'enregistrement). En général, il semble que la méthode principale soit de sérialiser des données de longueur variable et de les traiter comme tf.string ''.

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"
#Ecrire 1000 données de longueur variable
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())

Lors du décodage en unités d'enregistrement, lisez ce qui suit.

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)>

Dans les unités de lot, le nombre de colonnes de `` x '' est ajusté à la quantité de fonction la plus longue et la partie courte est remplie de 0.

ipython


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

Et si vous le faisiez par lots? Étant donné que le nombre de dimensions de x '' est différent pour chaque enregistrement, le traitement par lots de l'ensemble de données puis l'exécution de decode_raw '' avec `` map () '' échouera.

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

Cependant, si vous faites ```unbatch () puis decode_raw``, vous perdrez l'avantage d'accélérer.

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

C'est là que le sauveur entre en jeu. Uniquement disponible dans TensorFlow 2.1 et versions ultérieures, vous pouvez désormais spécifier un nouveau type de fonctionnalité appelé `` RaggedFeature '' lors du chargement des données. tf.io.RaggedFeature | TensorFlow Core v2.1.0

Avec cela, les fonctionnalités décodées seront RaggedTensor ''. Un Tensor '' ordinaire doit avoir le même nombre de colonnes par ligne, mais pas RaggedTensor ''. Vous pouvez représenter un Tensor '' avec différents nombres de colonnes pour chaque ligne. tf.RaggedTensor | TensorFlow Core v2.1.0

Tout d'abord, lors de l'écriture de données, créez des Fonctionnalités '' en utilisant les fonctionnalités de longueur variable comme liste de float32 ''.

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" #J'ai changé le nom
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())

Lors du chargement, spécifiez `` RaggedFeature '' comme quantité de fonctionnalité.

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)
          }))

Ici, chaque enregistrement de ds2 '' devient dictcomme dans le cas d'une longueur fixe, sauf que x '' devient RaggedTensor. Si vous coupez chaque ligne de RaggedTensor '', vous verrez Tensor '' de différentes tailles, comme indiqué ci-dessous.

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)>

Vous pouvez remplir la fin d'une fonction courte avec des zéros pour en faire un `` Tensor '' régulier en unités de lots. Cela vous donnera le même lot que si vous décodiez enregistrement par enregistrement.

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

Il a été réduit à près d'un dixième de celui du traitement enregistrement par enregistrement. Génial!

VarLenFeature

En fait, TensorFlow 1.x / 2.0 a également un moyen de lire les caractéristiques de longueur variable. Si le type de fonctionnalité est VarLenFeature '', vous pouvez lire la fonctionnalité comme SparseTensor ''. Comment faire TFRecord est le même que `` 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

Bien sûr, c'est beaucoup plus rapide que sur une base d'enregistrement par enregistrement, mais plus lent que le RaggedFeature ''. Si possible, j'aimerais utiliser RaggedFeature '' dans TensorFlow 2.1 ou version ultérieure.

Résumé

Recommended Posts

[TensorFlow 2] Il est recommandé de lire la quantité de fonction de TFRecord en unités de lots.
Livre recommandé lu dans 2 ans à partir du nouveau diplômé
L'arrière-plan des caractères de l'image texte est surexposé pour faciliter la lecture.
Lisez le binaire big endian en Python et convertissez-le en ndarray
TensorFlow / python> // grammaire> Cela semble être la division entière de python / En Python 2.X, décrire à partir de la division d'importation / division de plancher de __future__