J'ai un pipeline d'entrée non trivial qui from_generator
est parfait pour ...
dataset = tf.data.Dataset.from_generator(complex_img_label_generator,
(tf.int32, tf.string))
dataset = dataset.batch(64)
iter = dataset.make_one_shot_iterator()
imgs, labels = iter.get_next()
Où complex_img_label_generator
génère dynamiquement des images et renvoie un tableau numpy représentant un (H, W, 3)
image et une simple étiquette string
. Le traitement n'est pas quelque chose que je peux représenter comme la lecture de fichiers et tf.image
opérations.
Ma question concerne la mise en parallèle du générateur? Comment puis-je avoir N de ces générateurs en cours d'exécution dans leurs propres threads.
Une idée était d’utiliser dataset.map
avec num_parallel_calls
pour gérer le filetage; mais la carte fonctionne sur des tenseurs ... Une autre idée était de créer plusieurs générateurs chacun avec son propre prefetch
et de les joindre en quelque sorte, mais je ne vois pas comment je rejoindrais N flux de générateurs?
Des exemples canoniques que je pourrais suivre?
Il s'avère que je peux utiliser Dataset.map
si je rend le générateur super léger (ne générant que des métadonnées) et que je déplace ensuite l'éclairage lourd réel dans une fonction sans état. De cette façon, je peux paralléliser uniquement la partie de levage lourde avec .map
utilisant un py_func
.
Travaux; mais se sent un peu maladroit ... Ce serait génial de pouvoir simplement ajouter num_parallel_calls
à from_generator
:)
def pure_numpy_and_pil_complex_calculation(metadata, label):
# some complex pil and numpy work nothing to do with tf
...
dataset = tf.data.Dataset.from_generator(lightweight_generator,
output_types=(tf.string, # metadata
tf.string)) # label
def wrapped_complex_calulation(metadata, label):
return tf.py_func(func = pure_numpy_and_pil_complex_calculation,
inp = (metadata, label),
Tout = (tf.uint8, # (H,W,3) img
tf.string)) # label
dataset = dataset.map(wrapped_complex_calulation,
num_parallel_calls=8)
dataset = dataset.batch(64)
iter = dataset.make_one_shot_iterator()
imgs, labels = iter.get_next()
Je travaille sur un from_indexable
Pour tf.data.Dataset
https://github.com/tensorflow/tensorflow/issues/14448
L'avantage de from_indexable
Est qu'il peut être parallélisé, tandis qu'un générateur python ne peut pas être parallélisé.
La fonction from_indexable
Crée un tf.data.range
, Encapsule l'indexable dans un tf.py_func
Généralisé et appelle la carte.
Pour ceux qui veulent maintenant un from_indexable
, Voici le code lib
import tensorflow as tf
import numpy as np
from tensorflow.python.framework import tensor_shape
from tensorflow.python.util import nest
def py_func_decorator(output_types=None, output_shapes=None, stateful=True, name=None):
def decorator(func):
def call(*args):
nonlocal output_shapes
flat_output_types = nest.flatten(output_types)
flat_values = tf.py_func(
func,
inp=args,
Tout=flat_output_types,
stateful=stateful, name=name
)
if output_shapes is not None:
# I am not sure if this is nessesary
output_shapes = nest.map_structure_up_to(
output_types, tensor_shape.as_shape, output_shapes)
flattened_shapes = nest.flatten_up_to(output_types, output_shapes)
for ret_t, shape in Zip(flat_values, flattened_shapes):
ret_t.set_shape(shape)
return nest.pack_sequence_as(output_types, flat_values)
return call
return decorator
def from_indexable(iterator, output_types, output_shapes=None, num_parallel_calls=None, stateful=True, name=None):
ds = tf.data.Dataset.range(len(iterator))
@py_func_decorator(output_types, output_shapes, stateful=stateful, name=name)
def index_to_entry(index):
return iterator[index]
return ds.map(index_to_entry, num_parallel_calls=num_parallel_calls)
et voici un exemple (Remarque: from_indexable
a un num_parallel_calls argument
)
class PyDataSet:
def __len__(self):
return 20
def __getitem__(self, item):
return np.random.normal(size=(item+1, 10))
ds = from_indexable(PyDataSet(), output_types=tf.float64, output_shapes=[None, 10])
it = ds.make_one_shot_iterator()
entry = it.get_next()
with tf.Session() as sess:
print(sess.run(entry).shape)
print(sess.run(entry).shape)
Mise à jour 10 juin 2018: depuis https://github.com/tensorflow/tensorflow/pull/15121 est fusionné, le code de from_indexable
se simplifie pour:
import tensorflow as tf
def py_func_decorator(output_types=None, output_shapes=None, stateful=True, name=None):
def decorator(func):
def call(*args, **kwargs):
return tf.contrib.framework.py_func(
func=func,
args=args, kwargs=kwargs,
output_types=output_types, output_shapes=output_shapes,
stateful=stateful, name=name
)
return call
return decorator
def from_indexable(iterator, output_types, output_shapes=None, num_parallel_calls=None, stateful=True, name=None):
ds = tf.data.Dataset.range(len(iterator))
@py_func_decorator(output_types, output_shapes, stateful=stateful, name=name)
def index_to_entry(index):
return iterator[index]
return ds.map(index_to_entry, num_parallel_calls=num_parallel_calls)
Il est judicieux de limiter au minimum le travail effectué dans le generator
et de paralléliser le traitement coûteux à l'aide d'un map
.
Vous pouvez également "joindre" plusieurs générateurs à l'aide de parallel_interleave
comme suit:
def générateur (n): # retourne la nième fonction de générateur def jeu de données (n): return tf.data.Dataset .from_generator (générateur (n)) ds = tf.data.Dataset.range (N) .apply (tf.contrib.data.parallel_interleave (jeu de données, cycle_lenght = N)) # où N est le nombre de générateurs que vous utilisez