Перенесите индексацию python/numpy в Tensorflow и улучшите производительность

В более раннем вопросе здесь я попросил совета по более быстрому присвоению элементов массиву. С тех пор я добился некоторого прогресса, например, я расширил рекомендуемую версию, чтобы позаботиться о трехмерных массивах, которые должны напоминать размер пакета более поздних обучающих данных для нейронной сети:

import numpy as np
import time

batch_dim = 2
first_dim = 5
second_dim = 7
depth_dim = 10

upper_count = 5000

toy_dict = {k:np.random.random_sample(size = depth_dim) for k in range(upper_count)}
a = np.array(list(toy_dict.values()))

def create_input_3d(orig_arr):
  print("Input shape:", orig_arr.shape)
  goal_arr = np.full(shape=(batch_dim, orig_arr.shape[1], orig_arr.shape[2], depth_dim), fill_value=1234, dtype=float)

  print("Goal shape:", goal_arr.shape)

  idx = np.indices(orig_arr.shape)
  print("Idx shape", idx.shape)
  goal_arr[idx[0], idx[1], idx[2]] = a[orig_arr[idx[0], idx[1], idx[2]]]

  return goal_arr

orig_arr_three_dim = np.random.randint(0, upper_count, size=(batch_dim, first_dim, second_dim))
orig_arr_three_dim.shape # (2,5,7)

reshaped = create_input_3d(orig_arr_three_dim)

Затем я решил создать собственный слой, чтобы повысить производительность и выполнить преобразование на лету (уменьшает объем памяти):

import tensorflow as tf
from tensorflow import keras
import numpy as np

#custom layer
class CustLayer(keras.layers.Layer):
    def __init__(self, info_matrix, first_dim, second_dim, info_dim, batch_size):
        super(CustLayer, self).__init__()
        self.w = tf.Variable(
            initial_value=info_matrix,
            trainable=False,
            dtype=tf.dtypes.float32
        )
        self.info_dim = info_dim
        self.first_dim = first_dim
        self.second_dim = second_dim
        self.batch_size = batch_size

    def call(self, orig_arr):

        goal_arr = tf.Variable(tf.zeros(shape=(self.batch_size, self.first_dim, self.second_dim, self.info_dim), dtype=float))

        #loop-approach (slower)
        for example in tf.range(self.batch_size):
          for row in tf.range(self.first_dim):
            for col in tf.range(self.second_dim):
              goal_arr[example,row,col].assign(self.w[orig_arr[example, row, col]])
        
        return goal_arr

upper_count = 50
info_length = 10
batch_size = 4

first_dim = 5
second_dim = 7
info_dim = 10

info_dict = {k:np.random.random_sample(size = info_length) for k in range(upper_count)} #toy dict that stores information about
info_matrix = np.array(list(info_dict.values()))


linear_layer = CustLayer(info_matrix, first_dim=first_dim, second_dim=second_dim, info_dim=info_dim, batch_size=batch_size)

test = []
for i in range(batch_size):
  test.append(np.random.randint(1,upper_count, size=(first_dim,second_dim)))

test = np.asarray(test)
test.shape # (4, 5, 7)

y= linear_layer(test)
y.shape # TensorShape([4, 5, 7, 10])

Поскольку расширенная индексация (как в моем первом опубликованном коде) не работала, я вернулся к наивным циклам for, которые слишком медленны.

То, что я ищу, - это способ использовать расширенную индексацию, как показано в первом фрагменте кода, и перепрограммировать ее для совместимости с tf. Позже это позволит мне использовать GPU для обучения.

Вкратце: ввод имеет форму (batch_size, first_dim, second_dim), форма возврата — (batch_size, first_dim, second_dim, info_dim), избавляясь от медленных циклов for. Заранее спасибо.

Другие ответы, которые я просмотрел: с 2016 года, тоже старый tf


person emil    schedule 11.09.2020    source источник


Ответы (1)


Для других, ищущих ответ, вот что я наконец придумал:

import tensorflow as tf
from tensorflow import keras
import numpy as np
import time

class CustLayer(keras.layers.Layer):
    def __init__(self, info_matrix, first_dim, second_dim, info_dim, batch_size):
        super(CustLayer, self).__init__()
        self.w = tf.Variable(
            initial_value=info_matrix,
            trainable=False,
            dtype=tf.dtypes.float32
        )
        self.info_matrix = info_matrix
        self.info_dim = info_dim
        self.first_dim = first_dim
        self.second_dim = second_dim
        self.batch_size = batch_size
   
    def my_numpy_func(self, x):
      # x will be a numpy array with the contents of the input to the
      # tf.function
      shape = x.shape
      goal_arr = np.zeros(shape=(shape[0], shape[1], shape[2], self.info_dim), dtype=np.float32)

      # indices to expand
      idx = np.indices(shape)
      goal_arr[idx[0], idx[1], idx[2]] = self.info_matrix[x[idx[0], idx[1], idx[2]]]

      shape_arr = np.array([shape[0], shape[1], shape[2]], dtype=np.int8)
      #tf.print("Shape:", shape)
      #tf.print("Shape_arr:", shape_arr)
      #tf.print("Type:",type(shape_arr))
      return goal_arr, shape_arr

    @tf.function(input_signature=[tf.TensorSpec((None, 39, 25), tf.int64)])
    def tf_function(self, input):
      
      y, shape_arr = tf.numpy_function(self.my_numpy_func, [input], [tf.float32, tf.int8], "Nameless")
      #tf.print("shape_arr", shape_arr)
      y = tf.reshape(y, shape=(shape_arr[0], shape_arr[1], shape_arr[2], self.info_dim))
      return y

    def call(self, orig_arr):
      return self.tf_function(orig_arr)
      

Предостережения: работает на GPU, но не на TPU.

person emil    schedule 22.09.2020