web-dev-qa-db-fra.com

La mise à niveau vers tf.dataset ne fonctionne pas correctement lors de l'analyse de csv

J'ai une expérience GCMLE et j'essaie de mettre à niveau mon input_fn pour utiliser la nouvelle fonctionnalité tf.data. J'ai créé le input_fn suivant à partir de ce sample

def input_fn(...):
    dataset = tf.data.Dataset.list_files(filenames).shuffle(num_shards) # shuffle up the list of input files
    dataset = dataset.interleave(lambda filename: # mix together records from cycle_length number of shards
                tf.data.TextLineDataset(filename).skip(1).map(lambda row: parse_csv(row, hparams)), cycle_length=5) 
    if shuffle:
      dataset = dataset.shuffle(buffer_size = 10000)
    dataset = dataset.repeat(num_epochs)
    dataset = dataset.batch(batch_size)
    iterator = dataset.make_one_shot_iterator()
    features = iterator.get_next()

    labels = features.pop(LABEL_COLUMN)

    return features, labels

mon parse_csv est identique à ce que j’avais utilisé précédemment, mais il ne fonctionne pas actuellement. Je peux résoudre certains problèmes, mais je ne comprends pas tout à fait pourquoi je rencontre ces problèmes. Voici le début de ma fonction parse_csv ()

def parse_csv(..):
    columns = tf.decode_csv(rows, record_defaults=CSV_COLUMN_DEFAULTS)
    raw_features = dict(Zip(FIELDNAMES, columns))

    words = tf.string_split(raw_features['sentences']) # splitting words
    vocab_table = tf.contrib.lookup.index_table_from_file(vocabulary_file = hparams.vocab_file,
                default_value = 0)

....
  1. Tout de suite cette tf.string_split() cesse de fonctionner et l'erreur est ValueError: Shape must be rank 1 but is rank 0 for 'csv_preprocessing/input_sequence_generation/StringSplit' (op: 'StringSplit') with input shapes: [], []. - ceci est facilement résolu en insérant raw_features['sentences'] dans un tenseur via [raw_features['sentences']] mais je ne comprends pas pourquoi cela est nécessaire avec l'approche this dataset? Comment se fait-il que dans l'ancienne version cela fonctionne bien? Pour que les formes correspondent au reste de mon modèle, il me faut supprimer cette dimension supplémentaire à la fin via words = tf.squeeze(words, 0) car j’ajoute cette dimension "non nécessaire" au tenseur. 

  2. Pour une raison quelconque, j'obtiens également une erreur indiquant que la table n'est pas initialisée tensorflow.python.framework.errors_impl.FailedPreconditionError: Table not initialized.; toutefois, ce code fonctionne parfaitement avec mon ancien input_fn() (voir ci-dessous). Par conséquent, je ne sais pas pourquoi je devrais maintenant initialiser les tables? Je n'ai pas trouvé de solution à cette partie. Est-ce qu'il me manque quelque chose pour pouvoir utiliser tf.contrib.lookup.index_table_from_file dans ma fonction parse_csv?

Pour référence, voici mon ancien input_fn () qui fonctionne toujours:

def input_fn(...):
    filename_queue = tf.train.string_input_producer(tf.train.match_filenames_once(filenames), 
                num_epochs=num_epochs, shuffle=shuffle, capacity=32)
    reader = tf.TextLineReader(skip_header_lines=skip_header_lines)

    _, rows = reader.read_up_to(filename_queue, num_records=batch_size)

    features = parse_csv(rows, hparams)


        if shuffle:
            features = tf.train.shuffle_batch(
                features,
                batch_size,
                min_after_dequeue=2 * batch_size + 1,
                capacity=batch_size * 10,
                num_threads=multiprocessing.cpu_count(), 
                enqueue_many=True,
                allow_smaller_final_batch=True
            )
        else:
            features = tf.train.batch(
                features,
                batch_size,
                capacity=batch_size * 10,
                num_threads=multiprocessing.cpu_count(),
                enqueue_many=True,
                allow_smaller_final_batch=True
            )

labels = features.pop(LABEL_COLUMN)

return features, labels

UPDATE TF 1.7

Je revisite ceci avec TF 1.7 (qui devrait avoir toutes les fonctionnalités de TF 1.6 mentionnées dans @mrry answer) mais je ne parviens toujours pas à reproduire le comportement. Pour mon ancien input_fn(), je suis capable de marcher environ 13 pas/s. La nouvelle fonction que j'utilise est la suivante:

def input_fn(...):
    files = tf.data.Dataset.list_files(filenames).shuffle(num_shards)
    dataset = files.apply(tf.contrib.data.parallel_interleave(lambda filename: tf.data.TextLineDataset(filename).skip(1), cycle_length=num_shards))
    dataset = dataset.apply(tf.contrib.data.map_and_batch(lambda row:
            parse_csv_dataset(row, hparams = hparams), 
            batch_size = batch_size, 
            num_parallel_batches = multiprocessing.cpu_count())) 
    dataset = dataset.prefetch(1)
    if shuffle:
        dataset = dataset.shuffle(buffer_size = 10000)
    dataset = dataset.repeat(num_epochs)
    iterator = dataset.make_initializable_iterator()
    features = iterator.get_next()
    tf.add_to_collection(tf.GraphKeys.TABLE_INITIALIZERS, iterator.initializer)

    labels = {key: features.pop(key) for key in LABEL_COLUMNS}

    return features, labels 

Je crois que je suis tous les guildines de performance tels que 1) utiliser prefetch 2) utiliser map_and_batch avec num_parallel_batches = cœurs 3) utiliser parallel_interleave 4) appliquer une lecture aléatoire avant la répétition. La seule étape que je n'utilise pas est la suggestion de cache, mais je m'attendrais à ce que cela ne soit réellement utile que pour les époques dépassant la première, ainsi que "l'application de l'entrelacement, de la prélecture et de la lecture aléatoire en premier". - Cependant, j’ai constaté qu’une accélération d’environ 10% avait été nécessaire après le prélecture et la lecture aléatoire après map_and_batch.

BUFFER ISSUE Le premier problème de performance que j’ai remarqué concerne mon ancien input_fn(). Il m’a fallu environ 13 minutes d’horloge pour franchir les 20 000 pas, et pourtant, avec la taille tampon de 10 000 Je suppose que nous attendons que 10 000 lots soient traités). J'attends toujours plus de 40 minutes pour que la mémoire tampon soit pleine. Cela a-t-il du sens de prendre ce temps? Si je sais que mes fichiers .csv partagés sur GCS sont déjà randomisés, est-il acceptable d'avoir une taille de mémoire tampon/tampon plus petite? J'essaie de reproduire le comportement de tf.train.shuffle_batch () - cependant, il semble qu'au pire il faudrait 13 minutes pour atteindre le pas de 10k pour remplir le tampon?

STEPS/SEC

Même une fois le tampon rempli, les pas globaux/s dépassent environ 3 pas/s (souvent aussi bas que 2 pas/s) sur le même modèle avec la valeur précédente input_fn () qui obtient ~ 13 pas/s. 

SLOPPY INTERLEAVE J'ai finalement essayé de remplacer parallel_interleave () par sloppy_interleave (), car il s'agit d'une autre suggestion de @ mrry. Quand je suis passé à sloppy_interleave, j'ai eu 14 pas/s! Je sais que cela signifie que ce n'est pas déterministe, mais cela devrait simplement signifier que ce n'est pas déterministe d'une période (ou d'une époque) à l'autre? Ou y a-t-il des implications plus importantes pour cela? Devrais-je m'inquiéter de toute différence réelle entre l'ancienne méthode shuffle_batch() et sloppy_interleave? Le fait que cela se traduise par une amélioration de 4-5x suggère-t-il le facteur de blocage précédent?

7
reese0106

Dans TF 1.4 (qui est actuellement la dernière version de TF qui fonctionne avec GCMLE), vous ne pourrez pas utiliser make_one_shot_iterator() avec les tables de recherche (voir la section pertinente post ), vous devrez utiliser Dataset.make_initializable_iterator(), puis initialiser iterator.initalizer. avec votre TABLES_INITIALIZER par défaut (à partir de ce post ). Voici à quoi devrait ressembler la input_fn():

def input_fn(...):
  dataset = tf.data.Dataset.list_files(filenames).shuffle(num_shards)

  # Define `vocab_table` outside the map function and use it in `parse_csv()`.
  vocab_table = tf.contrib.lookup.index_table_from_file(
      vocabulary_file=hparams.vocab_file, default_value=0)

  dataset = dataset.interleave(
      lambda filename: (tf.data.TextLineDataset(filename)
                        .skip(1)
                        .map(lambda row: parse_csv(row, hparams),
                             num_parallel_calls=multiprocessing.cpu_count())),
      cycle_length=5) 

  if shuffle:
    dataset = dataset.shuffle(buffer_size=10000)
  dataset = dataset.repeat(num_epochs)
  dataset = dataset.batch(batch_size)
  iterator = dataset.make_initializable_iterator()
  features = iterator.get_next()

  # add iterator.intializer to be handled by default table initializers
  tf.add_to_collection(tf.GraphKeys.TABLE_INITIALIZERS, iterator.initializer) 

  labels = features.pop(LABEL_COLUMN)

  return features, labels
2
reese0106
  1. Lorsque vous utilisez tf.data.TextLineDataset , chaque élément est une chaîne scalaire. À cet égard, cela ressemble plus à l'utilisation de tf.TextLineReader.read() que de la version de lot tf.TextLineReader.read_up_to(), qui renvoie un vecteur de chaînes. Malheureusement, la fonction tf.string_split() op requiert une entrée vectorielle (bien que celle-ci puisse éventuellement être modifiée à l'avenir), la manipulation de forme est donc actuellement nécessaire.

  2. Les tables de consultation interagissent un peu différemment avec les fonctions de tf.data. L'intuition est que vous devez déclarer la table de recherche une fois outside l'appel Dataset.map() (afin qu'elle soit initialisée une fois), puis la capturer dans la fonction parse_csv() pour appeler vocab_table.lookup(). Quelque chose comme ce qui suit devrait fonctionner:

    def input_fn(...):
      dataset = tf.data.Dataset.list_files(filenames).shuffle(num_shards)
    
      # Define `vocab_table` outside the map function and use it in `parse_csv()`.
      vocab_table = tf.contrib.lookup.index_table_from_file(
          vocabulary_file=hparams.vocab_file, default_value=0)
    
      def parse_csv(...):
        columns = tf.decode_csv(rows, record_defaults=CSV_COLUMN_DEFAULTS)
        raw_features = dict(Zip(FIELDNAMES, columns))
        words = tf.string_split([raw_features['sentences']]) # splitting words
    
        # Use the captured `vocab_table` here.
        Word_indices = vocab_table.lookup(words)
    
        # ...    
        features = ...
    
        # NOTE: Structure the output here so that you can simply return
        # the dataset from `input_fn()`.
        labels = features.pop(LABEL_COLUMN)
        return features, labels
    
      # NOTE: Consider using `tf.contrib.data.parallel_interleave()` to perform
      # the reads in parallel.
      dataset = dataset.interleave(
          lambda filename: (tf.data.TextLineDataset(filename)
                            .skip(1)
                            .map(lambda row: parse_csv(row, hparams),
                                 num_parallel_calls=multiprocessing.cpu_count())),
          cycle_length=5) 
    
      if shuffle:
        dataset = dataset.shuffle(buffer_size=10000)
      dataset = dataset.repeat(num_epochs)
      dataset = dataset.batch(batch_size)
    
      # NOTE: Add prefetching here to run the input pipeline in the background.
      dataset = dataset.prefetch(1)
    
      # NOTE: This requires TensorFlow 1.5 or later, but this change simplifies the
      # initialization of the lookup table.
      return dataset
    
2
mrry