Различия между tf.nest.map_structure и tf.map_fn в скорости и результатах - PullRequest
0 голосов
/ 17 июня 2020

Мой вопрос можно резюмировать следующим образом:

  1. Почему tf.map_fn дает несколько другие результаты по сравнению с tf.nest.map_structure?
  2. Почему tf.map_fn намного медленнее, чем tf.nest.map_structure?
  3. Чтобы применить определенную функцию c к каждому примеру большого пакета, какой из них является более предпочтительным?

Теперь позвольте мне более подробно объяснить возникшую у меня проблему.

Мне нужно применить определенную функцию к каждому примеру в пакете. Сначала я попробовал метод tf.map_fn, описанный в: https://www.tensorflow.org/api_docs/python/tf/map_fn.

После реализации с использованием tf.map_fn я понял, что код ужасно медленный. Я немного поискал и, похоже, многие люди сталкивались с подобными проблемами. (например, https://github.com/tensorflow/tensorflow/issues/24774)

Когда я заменил tf.map_fn на tf.nest.map_structure, скорость была намного лучше. Однако результаты были немного разными.

Я сделал следующий игрушечный пример, чтобы проверить этот случай.

#!/usr/bin/python

import tensorflow as tf

def func(x):
    return tf.math.sqrt(x) 

x = tf.reshape(tf.range(24, dtype=tf.float32), (4, 6)) 

y1 = tf.nest.map_structure(func, x) 
print (y1)

y2 = tf.map_fn(func, x)
print (y2)

y3 = tf.math.sqrt(x)
print (y3)

print (tf.math.reduce_all(tf.equal(y1, y2)))
print (tf.math.reduce_all(tf.equal(y1, y3)))

y1 и y3 точно такие же, но y2 и y3 немного отличаются. y2 и y3 не совсем разные, но все же похожи.

Результат следующий:

tf.Tensor(
[[0.         0.99999994 1.4142134  1.7320508  1.9999999  2.236068  ]
 [2.4494896  2.6457512  2.8284268  2.9999998  3.1622777  3.3166249 ]
 [3.4641016  3.6055508  3.7416573  3.8729832  3.9999998  4.1231055 ]
 [4.2426405  4.3588986  4.472136   4.5825753  4.6904154  4.7958307 ]], shape=(4, 6), dtype=float32)
tf.Tensor(
[[0.        1.        1.4142135 1.7320508 2.        2.236068 ]
 [2.4494898 2.6457512 2.828427  3.        3.1622777 3.3166249]
 [3.4641016 3.6055512 3.7416575 3.8729835 4.        4.1231055]
 [4.2426405 4.358899  4.472136  4.582576  4.690416  4.7958317]], shape=(4, 6), dtype=float32)
tf.Tensor(
[[0.         0.99999994 1.4142134  1.7320508  1.9999999  2.236068  ]
 [2.4494896  2.6457512  2.8284268  2.9999998  3.1622777  3.3166249 ]
 [3.4641016  3.6055508  3.7416573  3.8729832  3.9999998  4.1231055 ]
 [4.2426405  4.3588986  4.472136   4.5825753  4.6904154  4.7958307 ]], shape=(4, 6), dtype=float32)
tf.Tensor(False, shape=(), dtype=bool)
tf.Tensor(True, shape=(), dtype=bool)

Ответы [ 2 ]

0 голосов
/ 30 июля 2020

tf.map_fn(func, elems):

Отображает по оси 0. Например:

tf.map_fn(lambda x: x*2, tf.constant([1, 2, 3])) # => [2, 4, 6]
tf.map_fn(lambda x: x[0]*x[1], tf.constant([[1, 0], [2, 4], [3, 5]])) # => [0, 8, 15]

tf.nest.map_structure(func, *structure)

Когда len(structure)==1

Концептуально :

tf.map_fn(func, flatten(structure[0]))
# then reapplies the structure[0] to the return value

Например:

tf.nest.map_structure(lambda x: x*2, tf.constant((1, 2, 3))) # => (2, 4, 6)
tf.nest.map_structure(lambda x: x*2, (1, (2, 3))) # => (2, (4, 6))

Когда len(structure) > 1, func накладывается на застежку-молнию всех элементов конструкции

Концептуально:

tf.map_fn(func, zip(*[flatten(_) _ in structure]))
# then reapplies the structure[0] to the return value 
# (all elements in structure must have the same structure,
#  i.e., structure[0] structureEqualsTo structure[1], etc.,)

Например:

tf.nest.map_structure(lambda *x: sum(x), [1, [0]], [2, [4]], [3, [5]]) 
# => [sum([1,2,3]),[sum([0,4,5])]] == [6, [9]]
0 голосов
/ 08 июля 2020

Меня тоже сбили с толку эти две функции, и sh есть документация получше. Но я замечаю одно отличие: tf.nest.structure(func, *structure) применяет func к каждой структуре structure, тогда как tf.map_fn(func, elems) сначала распаковывает elems для измерения 0, а затем применяет func к полученной последовательности.

Рассмотрим следующий пример:

example = [np.array([1, 2, 3]), np.array([-1, 1, -1])]


result_1 = tf.map_fn(lambda x: x[0] * x[1], example, dtype=tf.int64)
print(f"{result_1=}")

result_2 = tf.nest.map_structure(lambda x: x[0] * x[1], example)
print(f"{result_2=}")

Вывод:

result_1=<tf.Tensor: shape=(3,), dtype=int64, numpy=array([-1,  2, -3])>
result_2=[2, -1]

Возвращаясь к исходному вопросу, я тоже не смог ответить на 1 и 2. Что касается 3, я могу придумать два случая:

  1. Если ваш ввод - tf.Tensor, первое измерение которого - размер партии, тогда используйте map_fn
  2. Если ваш ввод - это список, длина которого равна размеру пакета, тогда используйте map_structure.

Опять же, было бы здорово, если бы кто-нибудь мог прояснить мое понимание.

...