Что делает `tf.strided_slice()` do?

Мне интересно, что действительно делает оператор tf.strided_slice().
doc говорит,

В первый порядок эта операция извлекает срез конца конца - начинается с тензорного ввода, начиная с места, указанного в начале. Срез продолжается добавлением шага к индексу begin, пока все размеры не будут меньше конца. Обратите внимание, что компоненты шага могут быть отрицательными, что вызывает обратный срез.

И в образце

# 'input' is [[[1, 1, 1], [2, 2, 2]],
#             [[3, 3, 3], [4, 4, 4]],
#             [[5, 5, 5], [6, 6, 6]]]
tf.slice(input, [1, 0, 0], [2, 1, 3], [1, 1, 1]) ==> [[[3, 3, 3]]]
tf.slice(input, [1, 0, 0], [2, 2, 3], [1, 1, 1]) ==> [[[3, 3, 3],
                                                       [4, 4, 4]]]
tf.slice(input, [1, 1, 0], [2, -1, 3], [1, -1, 1]) ==>[[[4, 4, 4],
                                                        [3, 3, 3]]]

Итак, в моем понимании документа первый образец (tf.slice(input, begin=[1, 0, 0], end=[2, 1, 3], strides=[1, 1, 1])),

  • итоговый размер end - begin = [1, 1, 3]. Результат образца показывает [[[3, 3, 3,]]], что форма [1, 1, 3], кажется ОК.
  • первый элемент результата находится в begin = [1, 0, 0]. Первый элемент результата выборки 3, который равен input[1,0,0], кажется ОК.
  • фрагмент продолжается добавлением шага к индексу begin. Таким образом, второй элемент результата должен быть input[begin + strides] = input[2, 1, 1] = 6, но образец показывает, что второй элемент 3.

Что strided_slice() делает?

(Примечание: имена методов в образцах, а последний пример неверен.)

Ответы

Ответ 1

Ошибка в вашем аргументе заключается в том, что вы непосредственно добавляете элементы strides и begin по элементам. Это сделает функцию намного менее полезной. Вместо этого он увеличивает список begin по одному измерению за раз, начиная с последнего измерения.

Позвольте решить часть первого примера по частям. begin = [1, 0, 0] и end = [2, 1, 3]. Кроме того, все strides являются 1. Работайте в обратном направлении, начиная с последнего измерения.

Начните с элемента [1,0,0]. Теперь увеличивайте последнее измерение только на его количество шагов, давая вам [1,0,1]. Продолжайте делать это, пока не достигнете предела. Что-то вроде [1,0,2], [1,0,3] (конец цикла). Теперь в вашей следующей итерации начните с возрастания второго до последнего измерения и сброса последнего измерения, [1,1,0]. Здесь второе до последнего измерения равно end[1], поэтому переходим к первому измерению (от третьего к последнему) и reset остальные, предоставляя вам [2,0,0]. Опять же, вы находитесь в первом пределе измерения, поэтому выходите из цикла.

Следующий код представляет собой рекурсивную реализацию того, что я описал выше,

# Assume global `begin`, `end` and `stride`
def iterate(active, dim):
    if dim == len(begin):
        # last dimension incremented, work on the new matrix
        # Note that `active` and `begin` are lists
        new_matrix[active - begin] = old_matrix[active]
    else:
        for i in range(begin[dim], end[dim], stride[dim]):
            new_active = copy(active)
            new_active[dim] = i
            iterate(new_active, dim + 1)

iterate(begin, 0)

Ответ 2

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

     a = np.array([[[1, 1.2, 1.3], [2, 2.2, 2.3], [7, 7.2, 7.3]],
                       [[3, 3.2, 3.3], [4, 4.2, 4.3], [8, 8.2, 8.3]],
                       [[5, 5.2, 5.3], [6, 6.2, 6.3], [9, 9.2, 9.3]]]) 
                                                  shape = (3,3,3)

strided_slice() требуется 4 требуемых аргумента input_, begin, end, strides, в которых мы приводим наш a как аргумент input_.  Как и в случае с методом tf.slice(), аргумент begin основан на нулевом значении и остальном аргументе на основе аргументов. Однако в документах begin и end оба относятся к нулевому.

Функциональность метода довольно проста:
Он работает как итерация по петле, где begin - это местоположение элемента в тензоре, от которого начинается цикл, и end находится там, где он останавливается.

    tf.strided_slice(a, [0, 0, 0], [3, 3, 3], [1, 1, 1])

    output =  the tensor itself

    tf.strided_slice(a, [0, 0, 0], [3, 3, 3], [2, 2, 2])

    output = [[[ 1.   1.3]
               [ 7.   7.3]]

               [[ 5.   5.3]
                [ 9.   9.3]]]

strides являются шагами, по которым цикл повторяется, здесь [2,2,2] делает метод для получения значений, начиная с (0,0,0), (0,0,2), (0,2,0), (0,2,2), (2,0,0), (2,0,2)..... в тензоре a.

    tf.strided_slice(input3, [1, 1, 0], [2, -1, 3], [1, 1, 1]) 

будет производить выход, аналогичный tf.strided_slice(input3, [1, 1, 0], [2, 2, 3], [1, 1, 1]), поскольку тензор a имеет shape = (3,3,3).

Ответ 3

Концептуализация, которая действительно помогла мне понять это, заключалась в том, что эта функция эмулирует поведение индексации массивов numpy.

Если вы знакомы с массивами numpy, вы узнаете, что вы можете делать фрагменты с помощью input[start1:end1:step1, start2:end2:step2, ... startN:endN:stepN]. В принципе, очень сжатый способ записи циклов for для получения определенных элементов массива.

(Если вы знакомы с индексацией python, вы знаете, что вы можете захватить срез массива через input[start:end:step]. Массивы Numpy, которые могут быть вложенными, используют вышеуказанный набор объектов среза.)

Ну, strided_slice просто позволяет вам делать эту причудливую индексацию без синтаксического сахара. Пример numpy сверху просто становится

# input[start1:end1:step1, start2:end2:step2, ... startN:endN:stepN]
tf.strided_slice(input, [start1, start2, ..., startN],
    [end1, end2, ..., endN], [step1, step2, ..., stepN])

Документация немного сбивает с толку об этом в том смысле, что:

a) begin - end не является строго формой возвращаемого значения:

Документация утверждает иначе, но это справедливо только в том случае, если ваши шаги - все. Примеры:

rank1 = tf.constant(list(range(10)))
# The below op is basically:
# rank1[1:10:2] => [1, 3, 5, 7, 9]
tf.strided_slice(rank1, [1], [10], [2])

# [10,10] grid of the numbers from 0 to 99
rank2 = tf.constant([[i+j*10 for i in range(10)] for j in range(10)])
# The below op is basically:
# rank2[3:7:1, 5:10:2] => numbers 30 - 69, ending in 5, 7, or 9
sliced = tf.strided_slice(rank2, [3, 5], [7, 10], [1, 2])
# The below op is basically:
# rank2[3:7:1] => numbers 30 - 69
sliced = tf.strided_slice(rank2, [3], [7], [1]) 

b) он утверждает, что "begin, end и strides будет всей длиной n, где n вообще не совпадает с размерностью input"

Похоже, что размерность означает ранг здесь, но input должен быть тензором, по крайней мере, ранга-n; он не может быть ниже (см. выше пример ранга 2).

N.B. Я ничего не сказал/не изучил функцию маскировки, но это выходит за рамки вопроса.

Ответ 4

tf.strided_slice() используется для нарезки стиля numpy тензорной переменной. Он имеет 4 параметра в целом: ввод, начало, конец, шаг. Срез продолжается добавлением шага к индексу начала, пока все размеры не будут меньше конца. Например: Возьмем тензорную константу с названием "образец" размеров: [3,2,3]

import tensorflow as tf 

sample = tf.constant(
    [[[11, 12, 13], [21, 22, 23]],
    [[31, 32, 33], [41, 42, 43]],
    [[51, 52, 53], [61, 62, 63]]])

slice = tf.strided_slice(sample, begin=[0,0,0], end=[3,2,3], strides=[2,2,2])

with tf.Session() as sess:
    print(sess.run(slice))

Теперь выход будет:

[[[11 13]]

 [[51 53]]]

Это связано с тем, что шаг начинается с [0,0,0] и переходит в [2,1,2], отбрасывая любые несуществующие данные, такие как:

[[0,0,0], [0,0,2], [0,2,0], [0,2,2],
[2,0,0], [2,0,2], [2,2,0], [2,2,2]]

Если вы используете [1,1,1] в качестве шага, тогда он просто распечатает все значения.