Почему Tensorflow Numpy может быть будущим дифференцируемого программирования

Почему Tensorflow Numpy может быть будущим дифференцируемого программирования

25 июля 2025 г.

Обзор контента

  • TF Numpy и Tensorflow

  • Tf.tensor и ND Array

  • Tensorflow Feelsectionbility

  • Градиенты и якобийцы: tf.gradienttape

  • Следование компиляции: tf.function

  • Векторизация: TF.Vectorized_map

  • Размещение устройства

  • Сравнение производительности

  • Дальнейшее чтение

TF Numpy и Tensorflow

Tensorflow Numpy строится на вершине Tensorflow и, следовательно, беспрепятственно взаимодействует с Tensorflow.

tf.Tensorи nd массив

Nd массив - псевдоним дляtf.Tensor, так что, очевидно, они могут быть смешаны, не запуская фактические копии данных.

x = tf.constant([1, 2])
print(x)

# `asarray` and `convert_to_tensor` here are no-ops.
tnp_x = tnp.asarray(x)
print(tnp_x)
print(tf.convert_to_tensor(tnp_x))

# Note that tf.Tensor.numpy() will continue to return `np.ndarray`.
print(x.numpy(), x.numpy().__class__)

tf.Tensor([1 2], shape=(2,), dtype=int32)
tf.Tensor([1 2], shape=(2,), dtype=int32)
tf.Tensor([1 2], shape=(2,), dtype=int32)
[1 2] <class 'numpy.ndarray'>

Tensorflow Feelsectionbility

Nd массив может быть передана API -интерфейсам Tensorflow, так как nd массив - просто псевдоним дляtf.TensorПолем Как упоминалось ранее, такое взаимодействие не выполняет копии данных, даже для данных, размещенных на ускорителях или удаленных устройствах.

Наоборот,tf.TensorОбъекты могут быть переданы вtf.experimental.numpyAPI, не выполняя копии данных.

# ND array passed into TensorFlow function.
tf_sum = tf.reduce_sum(tnp.ones([2, 3], tnp.float32))
print("Output = %s" % tf_sum)

# `tf.Tensor` passed into TensorFlow NumPy function.
tnp_sum = tnp.sum(tf.ones([2, 3]))
print("Output = %s" % tnp_sum)

Output = tf.Tensor(6.0, shape=(), dtype=float32)
Output = tf.Tensor(6.0, shape=(), dtype=float32)

Градиенты и якобийцы: tf.gradienttape

GradientTape Tensorflow может использоваться для обратного распространения через Tensorflow и Tensorflow Numpy Code.

Используйте модель, созданную вПример модельраздел, и вычислить градиенты и якобийцы.

def create_batch(batch_size=32):
  """Creates a batch of input and labels."""
  return (tnp.random.randn(batch_size, 32).astype(tnp.float32),
          tnp.random.randn(batch_size, 2).astype(tnp.float32))

def compute_gradients(model, inputs, labels):
  """Computes gradients of squared loss between model prediction and labels."""
  with tf.GradientTape() as tape:
    assert model.weights is not None
    # Note that `model.weights` need to be explicitly watched since they
    # are not tf.Variables.
    tape.watch(model.weights)
    # Compute prediction and loss
    prediction = model.predict(inputs)
    loss = tnp.sum(tnp.square(prediction - labels))
  # This call computes the gradient through the computation above.
  return tape.gradient(loss, model.weights)

inputs, labels = create_batch()
gradients = compute_gradients(model, inputs, labels)

# Inspect the shapes of returned gradients to verify they match the
# parameter shapes.
print("Parameter shapes:", [w.shape for w in model.weights])
print("Gradient shapes:", [g.shape for g in gradients])
# Verify that gradients are of type ND array.
assert isinstance(gradients[0], tnp.ndarray)

Parameter shapes: [TensorShape([32, 64]), TensorShape([64]), TensorShape([64, 2])]
Gradient shapes: [TensorShape([32, 64]), TensorShape([64]), TensorShape([64, 2])]

# Computes a batch of jacobians. Each row is the jacobian of an element in the
# batch of outputs w.r.t. the corresponding input batch element.
def prediction_batch_jacobian(inputs):
  with tf.GradientTape() as tape:
    tape.watch(inputs)
    prediction = model.predict(inputs)
  return prediction, tape.batch_jacobian(prediction, inputs)

inp_batch = tnp.ones([16, 32], tnp.float32)
output, batch_jacobian = prediction_batch_jacobian(inp_batch)
# Note how the batch jacobian shape relates to the input and output shapes.
print("Output shape: %s, input shape: %s" % (output.shape, inp_batch.shape))
print("Batch jacobian shape:", batch_jacobian.shape)

Output shape: (16, 2), input shape: (16, 32)
Batch jacobian shape: (16, 2, 32)

Следование компиляции: tf.function

Tensorflow'stf.functionРаботает по «следом» кода, а затем оптимизирует эти следы для гораздо более высокой производительности. УвидетьВведение в графики и функцииПолем

tf.functionМожет также использоваться для оптимизации кода Tensorflow Numpy. Вот простой пример, чтобы продемонстрировать ускорение. Обратите внимание, что телоtf.functionКод включает в себя вызовы Tensorflow Numpy API.

inputs, labels = create_batch(512)
print("Eager performance")
compute_gradients(model, inputs, labels)
print(timeit.timeit(lambda: compute_gradients(model, inputs, labels),
                    number=10) * 100, "ms")

print("\ntf.function compiled performance")
compiled_compute_gradients = tf.function(compute_gradients)
compiled_compute_gradients(model, inputs, labels)  # warmup
print(timeit.timeit(lambda: compiled_compute_gradients(model, inputs, labels),
                    number=10) * 100, "ms")

Eager performance
2.710705300000882 ms

tf.function compiled performance
0.7041131000050882 ms

Векторизация: TF.Vectorized_map

Tensorflow обладает встроенной поддержкой векторизационных параллельных петлей, что позволяет ускорить от одного до двух порядков. Эти ускорения доступны черезtf.vectorized_mapAPI и примените также к коду TensorFlow Numpy.

Иногда полезно вычислить градиент каждого вывода в партии W.R.T. Соответствующий элемент входной партии. Такие вычисления могут быть выполнены эффективно, используяtf.vectorized_mapКак показано ниже.

@tf.function
def vectorized_per_example_gradients(inputs, labels):
  def single_example_gradient(arg):
    inp, label = arg
    return compute_gradients(model,
                             tnp.expand_dims(inp, 0),
                             tnp.expand_dims(label, 0))
  # Note that a call to `tf.vectorized_map` semantically maps
  # `single_example_gradient` over each row of `inputs` and `labels`.
  # The interface is similar to `tf.map_fn`.
  # The underlying machinery vectorizes away this map loop which gives
  # nice speedups.
  return tf.vectorized_map(single_example_gradient, (inputs, labels))

batch_size = 128
inputs, labels = create_batch(batch_size)

per_example_gradients = vectorized_per_example_gradients(inputs, labels)
for w, p in zip(model.weights, per_example_gradients):
  print("Weight shape: %s, batch size: %s, per example gradient shape: %s " % (
      w.shape, batch_size, p.shape))

Weight shape: (32, 64), batch size: 128, per example gradient shape: (128, 32, 64) 
Weight shape: (64,), batch size: 128, per example gradient shape: (128, 64) 
Weight shape: (64, 2), batch size: 128, per example gradient shape: (128, 64, 2)

# Benchmark the vectorized computation above and compare with
# unvectorized sequential computation using `tf.map_fn`.
@tf.function
def unvectorized_per_example_gradients(inputs, labels):
  def single_example_gradient(arg):
    inp, label = arg
    return compute_gradients(model,
                             tnp.expand_dims(inp, 0),
                             tnp.expand_dims(label, 0))

  return tf.map_fn(single_example_gradient, (inputs, labels),
                   fn_output_signature=(tf.float32, tf.float32, tf.float32))

print("Running vectorized computation")
print(timeit.timeit(lambda: vectorized_per_example_gradients(inputs, labels),
                    number=10) * 100, "ms")

print("\nRunning unvectorized computation")
per_example_gradients = unvectorized_per_example_gradients(inputs, labels)
print(timeit.timeit(lambda: unvectorized_per_example_gradients(inputs, labels),
                    number=10) * 100, "ms")

Running vectorized computation
0.659675699989748 ms

Running unvectorized computation
29.259711299982882 ms

Размещение устройства

Tensorflow Numpy может поставить операции на процессоров, графических процессоров, TPU и удаленных устройств. Он использует стандартные механизмы Tensorflow для размещения устройств. Ниже простой пример показан, как перечислить все устройства, а затем разместить некоторые вычисления на конкретное устройство.

Tensorflow также имеет API для репликации вычислений на разных устройствах и выполнения коллективных сокращений, которые здесь не будут покрыты.

Список устройств

tf.config.list_logical_devicesиtf.config.list_physical_devicesможно использовать, чтобы найти, какие устройства использовать.

print("All logical devices:", tf.config.list_logical_devices())
print("All physical devices:", tf.config.list_physical_devices())

# Try to get the GPU device. If unavailable, fallback to CPU.
try:
  device = tf.config.list_logical_devices(device_type="GPU")[0]
except IndexError:
  device = "/device:CPU:0"

All logical devices: [LogicalDevice(name='/device:CPU:0', device_type='CPU'), LogicalDevice(name='/device:GPU:0', device_type='GPU'), LogicalDevice(name='/device:GPU:1', device_type='GPU'), LogicalDevice(name='/device:GPU:2', device_type='GPU'), LogicalDevice(name='/device:GPU:3', device_type='GPU')]
All physical devices: [PhysicalDevice(name='/physical_device:CPU:0', device_type='CPU'), PhysicalDevice(name='/physical_device:GPU:0', device_type='GPU'), PhysicalDevice(name='/physical_device:GPU:1', device_type='GPU'), PhysicalDevice(name='/physical_device:GPU:2', device_type='GPU'), PhysicalDevice(name='/physical_device:GPU:3', device_type='GPU')]

Расположение операций:tf.device

Операции могут быть размещены на устройстве, позвонив вtf.deviceобъем.

print("Using device: %s" % str(device))
# Run operations in the `tf.device` scope.
# If a GPU is available, these operations execute on the GPU and outputs are
# placed on the GPU memory.
with tf.device(device):
  prediction = model.predict(create_batch(5)[0])

print("prediction is placed on %s" % prediction.device)

Using device: LogicalDevice(name='/device:GPU:0', device_type='GPU')
prediction is placed on /job:localhost/replica:0/task:0/device:GPU:0

Копирование ND массивов на устройствах:tnp.copy

Призыв кtnp.copy, помещенное в определенную область устройства, скопирует данные на это устройство, если данные уже не на этом устройстве.

with tf.device("/device:CPU:0"):
  prediction_cpu = tnp.copy(prediction)
print(prediction.device)
print(prediction_cpu.device)

/job:localhost/replica:0/task:0/device:GPU:0
/job:localhost/replica:0/task:0/device:CPU:0

Сравнение производительности

Tensorflow Numpy использует высоко оптимизированные ядра Tensorflow, которые могут быть отправлены на процессоров, GPU и TPU. Tensorflow также выполняет множество оптимизаций компилятора, таких как операция Fusion, которые приводят к улучшению производительности и памяти. ВидетьОптимизация графика tensorflow с GrapplerЧтобы узнать больше.

Однако Tensorflow имеет более высокие накладные расходы на отправку операций по сравнению с Numpy. Для рабочих нагрузок, состоящих из небольших операций (менее чем около 10 микросекунд), эти накладные расходы могут доминировать во время выполнения, а Numpy может обеспечить лучшую производительность. Для других случаев TensorFlow, как правило, должен обеспечивать лучшую производительность.

Запустите эталон ниже, чтобы сравнить производительность Numpy и Tensorflow Numpy для различных размеров ввода.

def benchmark(f, inputs, number=30, force_gpu_sync=False):
  """Utility to benchmark `f` on each value in `inputs`."""
  times = []
  for inp in inputs:
    def _g():
      if force_gpu_sync:
        one = tnp.asarray(1)
      f(inp)
      if force_gpu_sync:
        with tf.device("CPU:0"):
          tnp.copy(one)  # Force a sync for GPU case

    _g()  # warmup
    t = timeit.timeit(_g, number=number)
    times.append(t * 1000. / number)
  return times


def plot(np_times, tnp_times, compiled_tnp_times, has_gpu, tnp_times_gpu):
  """Plot the different runtimes."""
  plt.xlabel("size")
  plt.ylabel("time (ms)")
  plt.title("Sigmoid benchmark: TF NumPy vs NumPy")
  plt.plot(sizes, np_times, label="NumPy")
  plt.plot(sizes, tnp_times, label="TF NumPy (CPU)")
  plt.plot(sizes, compiled_tnp_times, label="Compiled TF NumPy (CPU)")
  if has_gpu:
    plt.plot(sizes, tnp_times_gpu, label="TF NumPy (GPU)")
  plt.legend()

# Define a simple implementation of `sigmoid`, and benchmark it using
# NumPy and TensorFlow NumPy for different input sizes.

def np_sigmoid(y):
  return 1. / (1. + np.exp(-y))

def tnp_sigmoid(y):
  return 1. / (1. + tnp.exp(-y))

@tf.function
def compiled_tnp_sigmoid(y):
  return tnp_sigmoid(y)

sizes = (2 ** 0, 2 ** 5, 2 ** 10, 2 ** 15, 2 ** 20)
np_inputs = [np.random.randn(size).astype(np.float32) for size in sizes]
np_times = benchmark(np_sigmoid, np_inputs)

with tf.device("/device:CPU:0"):
  tnp_inputs = [tnp.random.randn(size).astype(np.float32) for size in sizes]
  tnp_times = benchmark(tnp_sigmoid, tnp_inputs)
  compiled_tnp_times = benchmark(compiled_tnp_sigmoid, tnp_inputs)

has_gpu = len(tf.config.list_logical_devices("GPU"))
if has_gpu:
  with tf.device("/device:GPU:0"):
    tnp_inputs = [tnp.random.randn(size).astype(np.float32) for size in sizes]
    tnp_times_gpu = benchmark(compiled_tnp_sigmoid, tnp_inputs, 100, True)
else:
  tnp_times_gpu = None
plot(np_times, tnp_times, compiled_tnp_times, has_gpu, tnp_times_gpu)

Дальнейшее чтение

  • Tensorflow Numpy: учебник по классификации распределенных изображений
  • Tensorflow Numpy: кера и стратегия распределения
  • Анализ настроений с Trax и Tensorflow Numpy

Первоначально опубликовано наTensorflowВеб -сайт, эта статья появляется здесь под новым заголовком и имеет лицензию в CC на 4.0. Образцы кода, разделенные по лицензии Apache 2.0.


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE