Как понять обалденные шаги для дилетанта? - PullRequest
0 голосов
/ 01 ноября 2018

Я сейчас прохожу через numpy, и в numpy есть тема, которая называется "Шаги". Я понимаю, что это. Но как это работает? Я не нашел никакой полезной информации в Интернете. Кто-нибудь может дать мне понять с точки зрения непрофессионала?

Ответы [ 2 ]

0 голосов
/ 09 ноября 2018

Просто, чтобы добавить отличный ответ @AndyK, я узнал о бесшабашных шагах от Numpy MedKit . Там они показывают использование с проблемой следующим образом:

С учетом ввода :

x = np.arange(20).reshape([4, 5])
>>> x
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19]])

Ожидаемый результат :

array([[[  0,  1,  2,  3,  4],
        [  5,  6,  7,  8,  9]],

       [[  5,  6,  7,  8,  9],
        [ 10, 11, 12, 13, 14]],

       [[ 10, 11, 12, 13, 14],
        [ 15, 16, 17, 18, 19]]])

Для этого нам нужно знать следующие термины:

shape - Размеры массива вдоль каждой оси.

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

>>> x.strides
(20, 4)

>>> np.int32().itemsize
4

Теперь, если мы посмотрим на Ожидаемый результат :

array([[[  0,  1,  2,  3,  4],
        [  5,  6,  7,  8,  9]],

       [[  5,  6,  7,  8,  9],
        [ 10, 11, 12, 13, 14]],

       [[ 10, 11, 12, 13, 14],
        [ 15, 16, 17, 18, 19]]])

Нам нужно манипулировать формой массива и шагами. Выходная форма должна быть (3, 2, 5), то есть 3 элемента, каждый из которых содержит две строки (m == 2), а каждая строка имеет 5 элементов.

Шаг должен быть изменен с (20, 4) на (20, 20, 4). Каждый элемент в новом выходном массиве начинается с новой строки, каждая строка состоит из 20 байтов (5 элементов по 4 байта в каждом), а каждый элемент занимает 4 байта (int32).

Итак:

>>> from numpy.lib import stride_tricks
>>> stride_tricks.as_strided(x, shape=(3, 2, 5),
                                strides=(20, 20, 4))
...
array([[[  0,  1,  2,  3,  4],
        [  5,  6,  7,  8,  9]],

       [[  5,  6,  7,  8,  9],
        [ 10, 11, 12, 13, 14]],

       [[ 10, 11, 12, 13, 14],
        [ 15, 16, 17, 18, 19]]])

Альтернативой будет:

>>> d = dict(x.__array_interface__)
>>> d['shape'] = (3, 2, 5)
>>> s['strides'] = (20, 20, 4)

>>> class Arr:
...     __array_interface__ = d
...     base = x

>>> np.array(Arr())
array([[[  0,  1,  2,  3,  4],
        [  5,  6,  7,  8,  9]],

       [[  5,  6,  7,  8,  9],
        [ 10, 11, 12, 13, 14]],

       [[ 10, 11, 12, 13, 14],
        [ 15, 16, 17, 18, 19]]])

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

Примечание:

При использовании очень больших массивов с этим трюком вычисление точных шагов не так тривиально. Я обычно делаю массив numpy.zeroes желаемой формы и получаю шаги, используя array.strides, и использую это в функции stride_tricks.as_strided.

Надеюсь, это поможет!

0 голосов
/ 01 ноября 2018

Фактические данные массива Numpy хранятся в однородном и непрерывном блоке памяти, называемом буфером данных. Для получения дополнительной информации см. NumPy innerals . При использовании (по умолчанию) row-major порядок, 2D массив выглядит так:

enter image description here

Чтобы отобразить индексы i, j, k, ... многомерного массива на позиции в буфере данных (смещение в байтах), NumPy использует понятие шагов . Шагов - это количество байтов, которые необходимо перепрыгнуть в памяти, чтобы перейти от одного элемента к следующему элементу / размеру массива следующий . Другими словами, это разделение байтов между последовательными элементами для каждого измерения.

Например:

>>> a = np.arange(1,10).reshape(3,3)
>>> a
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])

Этот двумерный массив имеет два направления: оси-0 (проходящие вертикально вниз по рядам) и ось-1 (проходящие горизонтально между столбцами), причем каждый элемент имеет размер:

>>> a.itemsize  # in bytes
4  

Таким образом, чтобы перейти от a[0, 0] -> a[0, 1] (перемещение по горизонтали вдоль 0-й строки, от 0-го столбца до 1-го столбца), шаг байта в буфере данных равен 4. То же самое для a[0, 1] -> a[0, 2], a[1, 0] -> a[1, 1] и т. Д. Это означает, что число шагов для горизонтального направления (ось-1) составляет 4 байта.

Однако, чтобы перейти от a[0, 0] -> a[1, 0] (двигаясь вертикально вдоль 0-го столбца, от 0-го ряда к 1-му ряду), вам нужно сначала пройти все оставшиеся элементы в 0-м ряду, чтобы добраться до 1-го ряда, и затем двигайтесь через 1-й ряд, чтобы добраться до предмета a[1, 0], то есть a[0, 0] -> a[0, 1] -> a[0, 2] -> a[1, 0]. Поэтому число шагов для вертикального направления (ось-0) составляет 3 * 4 = 12 байтов. Обратите внимание, что переход от a[0, 2] -> a[1, 0] и в целом от последнего элемента i-й строки к первому элементу (i + 1) -й строки также составляет 4 байта, поскольку массив a хранится в мажорный ряд.

Вот почему

>>> a.strides  # (strides[0], strides[1])
(12, 4)  

Вот еще один пример, показывающий, что шаги в горизонтальном направлении (ось-1), strides[1], для двумерного массива необязательно равны размеру элемента (например, массив с основным порядком столбцов):

>>> b = np.array([[1, 4, 7],
                  [2, 5, 8],
                  [3, 6, 9]]).T
>>> b
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])

>>> b.strides
(4, 12)

Здесь strides[1] кратно размеру предмета. Хотя массив b выглядит идентично массиву a, это другой массив: внутренне b хранится как |1|4|7|2|5|8|3|6|9| (поскольку транспонирование не влияет на буфер данных, а только меняет местами шаги и форму), тогда как a как |1|2|3|4|5|6|7|8|9|. Что делает их похожими, так это разные шаги. То есть шаг байта для b[0, 0] -> b[0, 1] составляет 3 * 4 = 12 байтов, а для b[0, 0] -> b[1, 0] - 4 байта, тогда как для a[0, 0] -> a[0, 1] - 4 байта и для a[0, 0] -> a[1, 0] - 12 байтов.

И последнее, но не менее важное: NumPy позволяет создавать виды существующих массивов с возможностью изменения шагов и формы, см. трюки шагов . Например:

>>> np.lib.stride_tricks.as_strided(a, shape=a.shape[::-1], strides=a.strides[::-1])
array([[1, 4, 7],
       [2, 5, 8],
       [3, 6, 9]])

, что эквивалентно транспонированию массива a.

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

>>> a = np.lib.stride_tricks.as_strided(np.array([1, 512, 0, 3], dtype=np.int16), 
                                        shape=(3,), strides=(3,))
>>> a
array([1, 2, 3], dtype=int16)

>>> a.strides[0]
3

>>> a.itemsize
2
...