Я тренируюсь, чтобы понять, как использовать функцию as_strided в numpy. Я начал со следующего собственного примера, где я генерирую 5 изображений размером 3 x 3, где каждое изображение заполнено единицами, следующее - двойками и так далее до 5. Если я хочу преобразовать (5,3,3) Volume, чтобы поместить все изображения в одну строку, я могу сделать следующее, и это сработает:
import numpy as np
from numpy.lib.stride_tricks import as_strided
array_3d = np.empty((5, 3, 3))
for i in range(5):
array_3d[i] = np.full((3, 3), i+1)
print(array_3d.itemsize)
array_2d = as_strided(array_3d, shape=(5, 9), strides=(8*9, 8))
print(array_2d)
Это выводит следующее:
8
[[1. 1. 1. 1. 1. 1. 1. 1. 1.]
[2. 2. 2. 2. 2. 2. 2. 2. 2.]
[3. 3. 3. 3. 3. 3. 3. 3. 3.]
[4. 4. 4. 4. 4. 4. 4. 4. 4.]
[5. 5. 5. 5. 5. 5. 5. 5. 5.]]
Теперь я хотел попробовать более сложный пример размещения изображений (3, 3) рядом следующим образом с использованием функции as_strided:
[[1. 1. 1. 2. 2. 2. 3. 3. 3. 4. 4. 4. 5. 5. 5.]
[1. 1. 1. 2. 2. 2. 3. 3. 3. 4. 4. 4. 5. 5. 5.]
[1. 1. 1. 2. 2. 2. 3. 3. 3. 4. 4. 4. 5. 5. 5.]]
Однако я не могу придумать, как сделать это, просто используя функцию as_strided. Поэтому мне интересно, невозможно ли это только с помощью функции as_strided, и как я могу узнать, когда уместно использовать функцию as_strided помимо операций со скользящим окном.
🤔 А знаете ли вы, что...
С Python можно создавать роботов и автоматизированные системы с использованием библиотеки Raspberry Pi.
Предположим, что вам действительно нужен массив 3x15, содержащий 9 "1", 9 "2", ...
И совершаете ту же ошибку, что и в первом примере.
st1,st2,st3=array_3d.strides
np.lib.stride_tricks.as_strided(array_3d, shape=(3,15), strides=(st3,st2))
Оно работает. Но при том же оскорбительном предположении, которое вы сделали для себя в первом примере: при предположении, что array_3d
данные последовательны. То есть шаги array_3d
- это (8*9, 8*3, 8)
(шаги трехмерного массива формы (*,3,3) смежных данных с размером данных 8 байт).
array_3d = np.empty((5, 3, 3))
for i in range(5):
array_3d[i] = np.full((3, 3), i+1)
# Or np.repeat(np.arange(1,6),9).reshape(5,3,3)
st1,st2,st3=array_3d.strides # Following your method, I could have used 8 as st3, and 8*3 for st2.
# But I prefer this way, that works better when array_3d is not contiguous
# even if (see later) it is not sufficient here
as_strided(array_3d, shape=(3,15), strides=(st3,st2))
⇒
array([[1., 1., 1., 2., 2., 2., 3., 3., 3., 4., 4., 4., 5., 5., 5.],
[1., 1., 1., 2., 2., 2., 3., 3., 3., 4., 4., 4., 5., 5., 5.],
[1., 1., 1., 2., 2., 2., 3., 3., 3., 4., 4., 4., 5., 5., 5.]])
Но если данные не являются смежными, то есть если array_3d[x,y+1,0]
не стоит сразу после array_3d[x,y,2]
, то и ваш код, и мой не работают.
И может быть много причин, почему этого не может быть. Во-первых, потому что это array_3d
может быть и результатом другого as_strided
.
Или какие-то другие более простые операции. Например, reshape
, transpose
или даже простой подмассив.
Например, представьте, что вместо моего трюка np.repeat
с заменой цикла for на конструкцию array_3d
я предложил другой трюк
array_3d = np.block([np.arange(1,6)]*9).reshape(3,3,5).T
array_3d
это
array([[[1, 1, 1],
[1, 1, 1],
[1, 1, 1]],
[[2, 2, 2],
[2, 2, 2],
[2, 2, 2]],
[[3, 3, 3],
[3, 3, 3],
[3, 3, 3]],
[[4, 4, 4],
[4, 4, 4],
[4, 4, 4]],
[[5, 5, 5],
[5, 5, 5],
[5, 5, 5]]])
как у тебя или как у np.repeat
.
Но его успехи (8,40,120)
Итак, ваш код дает для первого примера
array([[1, 2, 3, 4, 5, 1, 2, 3, 4],
[5, 1, 2, 3, 4, 5, 1, 2, 3],
[4, 5, 1, 2, 3, 4, 5, 1, 2],
[3, 4, 5, 1, 2, 3, 4, 5, 1],
[2, 3, 4, 5, 1, 2, 3, 4, 5]])
И даже с моим более гибким способом (вычисление целевых шагов с фактическими шагами array_3d
, а не предполагаемыми шагами, как если бы array_3d
были смежными)
array([[ 1, 1, 1,
8961, 4426024058838412064, 2314885530453962528,
2336916743122085218, 8367723430002978409, 8318818597302461550],
[ 2, 2, 2,
1, 2842441403096198183, 7307499879225255280,
6933981515683226426, 7810489018405119336, 2338623232261300325],
[ 3, 3, 3,
11162752, 7883868307246424108, 8028827851415429235,
7958530579755660158, 7575167200708226665, 2334386829830418017],
[ 4, 4, 4,
8910, 7310589477035273328, 8386668381597627251,
7955973979015377710, 8390317583586653294, 7020095223739741549],
[ 5, 5, 5,
-1, 8243122375611719780, 7236828443234430825,
2332938645024170341, 8241988044566393376, 8030041785346585459]])
(Я знаю, это выглядит еще хуже. Но, по крайней мере, первые 3 цифры каждой строки я получил правильно. Тот факт, что остальные - чушь, происходит от того, что 4-я 5 не отделена от 3-й одинаковым объемом памяти чем 3-е отделено 2-м, что является ложным предположением, которое вы делаете).
Аналогично и для второго примера с моим кодом.
Я мог бы проделать ту же демонстрацию, но проще array_3d
. Например, если вы построили (5,4,4) big_array_3d
, используя свой метод, а затем извлекли из него (5,3,3) array_3d = big_array_3d[:,:3,:3]
. На самом деле это не какой-то хакерский хитроумный трюк, не так ли? Это вполне обычный кусок кода. И все же array_3d
не является непрерывным набором данных.
Итак, дело в том, что если вы начнете играть с шагами и, следовательно, создадите массив, шаги которого не совпадают с шагами старого доброго непрерывного массива порядка «C», вы должны быть первым, кто осознает, что не все массивы состоят из смежных данных, и первый, чтобы не предполагать, что это так.
Массив в numpy — это набор данных с типом, формой и некоторыми шагами, рассказывающими, как перевести индекс [i1,i2,i3,...]
в одни из данных. Использование формулы array[i1,i2,i3,...,ik]
— это dtype, найденный по адресу baseAddress + i1*stride1 + i2*stride2 + i3*stride3 + ... + ik*stridek
.
as_strided
позволяет вам воспользоваться тем фактом, что вы это понимаете, для изменения формы и шагов массива, чтобы показать различные правила индексации (как это делает transpose
. Или, в зависимости от условий, reshape
, indexing
, moveaxis
, ... as_strided
просто самая универсальная версия тех функций, которые не меняют данные, только форму и шаги)
Но это означает, что для того, чтобы проделать трюк с as_strided
, вам нужно найти набор шагов, который позволит baseAddress+i1*stride1+i2*stride2+...+ik*stridek
стать тем, кем вы хотите view[i1,i2,...,ik]
быть. А это значит, что такие шаги должны быть. И это не всегда так. Не любая перестановка данных может быть выражена в виде формулы линейного индексирования «α.i1+β.i2+...» (α, β, ... — это шаги)
Создадим немного другой массив — без fill/repeat
, чтобы мы могли сказать, как упорядочены массивы (3,3):
In [128]: array_3d = np.empty((4, 3, 3))
...: for i in range(4):
...: array_3d[i] = np.arange(9).reshape(3,3)+i*10
...:
In [129]: array_3d
Out[129]:
array([[[ 0., 1., 2.],
[ 3., 4., 5.],
[ 6., 7., 8.]],
[[10., 11., 12.],
[13., 14., 15.],
[16., 17., 18.]],
[[20., 21., 22.],
[23., 24., 25.],
[26., 27., 28.]],
[[30., 31., 32.],
[33., 34., 35.],
[36., 37., 38.]]])
Мы можем получить ваш первый макет, просто изменив форму:
In [130]: array_3d.reshape(4,-1)
Out[130]:
array([[ 0., 1., 2., 3., 4., 5., 6., 7., 8.],
[10., 11., 12., 13., 14., 15., 16., 17., 18.],
[20., 21., 22., 23., 24., 25., 26., 27., 28.],
[30., 31., 32., 33., 34., 35., 36., 37., 38.]])
Добавьте транспозицию, мы сможем объединить ваши блоки:
In [131]: array_3d.transpose(1,0,2).reshape(3,-1)
Out[131]:
array([[ 0., 1., 2., 10., 11., 12., 20., 21., 22., 30., 31., 32.],
[ 3., 4., 5., 13., 14., 15., 23., 24., 25., 33., 34., 35.],
[ 6., 7., 8., 16., 17., 18., 26., 27., 28., 36., 37., 38.]])
А при другом транспонировании расположить блоки по-другому. Это различие теряется, когда блоки «заполнены»:
In [132]: array_3d.transpose(2,0,1).reshape(3,-1)
Out[132]:
array([[ 0., 3., 6., 10., 13., 16., 20., 23., 26., 30., 33., 36.],
[ 1., 4., 7., 11., 14., 17., 21., 24., 27., 31., 34., 37.],
[ 2., 5., 8., 12., 15., 18., 22., 25., 28., 32., 35., 38.]])
[132] на самом деле является видом array_3d
(которое мы можем изменить, проверив его .base
).
[131] тоже view
, но с основанием (3,4,3), транспонированием. Изменение формы после транспонирования часто включает в себя своего рода копирование.
Мы также можем получить макет [131] с помощью concatenate
:
In [143]: np.concatenate(array_3d, axis=1)
Out[143]:
array([[ 0., 1., 2., 10., 11., 12., 20., 21., 22., 30., 31., 32.],
[ 3., 4., 5., 13., 14., 15., 23., 24., 25., 33., 34., 35.],
[ 6., 7., 8., 16., 17., 18., 26., 27., 28., 36., 37., 38.]])
as_strided
чаще всего используется для разбиения массива на более мелкие части, а не для уменьшения размеров. np.lib.stride_tricks.sliding_window_view
— более «безопасная» и простая в использовании версия. Помимо прочего, это не позволяет нам выполнять выборку за пределами исходного data_buffer.
Тем не менее, as_strided
можно использовать для получения 2D-макетов.
Как вы обнаружили, простое изменение формы можно воспроизвести с помощью:
In [145]: np.lib.stride_tricks.as_strided(array_3d,shape=(4,9), strides=(9*8,8))
Out[145]:
array([[ 0., 1., 2., 3., 4., 5., 6., 7., 8.],
[10., 11., 12., 13., 14., 15., 16., 17., 18.],
[20., 21., 22., 23., 24., 25., 26., 27., 28.],
[30., 31., 32., 33., 34., 35., 36., 37., 38.]])
И, используя разные шаги, получаем [132]:
In [149]: np.lib.stride_tricks.as_strided(array_3d,shape=(3,12), strides=(8,3*8))
Out[149]:
array([[ 0., 3., 6., 10., 13., 16., 20., 23., 26., 30., 33., 36.],
[ 1., 4., 7., 11., 14., 17., 21., 24., 27., 31., 34., 37.],
[ 2., 5., 8., 12., 15., 18., 22., 25., 28., 32., 35., 38.]])
Обратите внимание, как последовательные числа [0,1,2,3...] упорядочены по столбцам order='F'
. Это типично для случаев, когда отстающие шаги больше.
Я не думаю, что макета [131] можно добиться с помощью as_strided
. Как я уже заметил, это не представление исходного массива.
Чтобы проиллюстрировать, как as_strided
можно использовать для разбиения массива на небольшие блоки, мы можем использовать sliding_window_view
для создания блоков (3,3) из 2d-массива [131]:
In [152]: np.lib.stride_tricks.sliding_window_view(_131,(3,3)).shape
Out[152]: (1, 10, 3, 3)
Вы можете распечатать его, чтобы увидеть, как эти блоки «перекрываются/скользят», но, взяв каждый третий блок, мы восстанавливаем исходный трехмерный массив:
In [153]: np.lib.stride_tricks.sliding_window_view(_131,(3,3))[0,:3]
Out[153]:
array([[[ 0., 1., 2.],
[ 3., 4., 5.],
[ 6., 7., 8.]],
[[ 1., 2., 10.],
[ 4., 5., 13.],
[ 7., 8., 16.]],
[[ 2., 10., 11.],
[ 5., 13., 14.],
[ 8., 16., 17.]]])
И делая то же самое для [132], получаем оригинал, но с транспонированными блоками:
In [154]: np.lib.stride_tricks.sliding_window_view(_132,(3,3))[0,:3]
Out[154]:
array([[[ 0., 3., 6.],
[ 1., 4., 7.],
[ 2., 5., 8.]],
[[ 3., 6., 10.],
[ 4., 7., 11.],
[ 5., 8., 12.]],
[[ 6., 10., 13.],
[ 7., 11., 14.],
[ 8., 12., 15.]]])
В документации sliding_window_view
есть несколько хороших примеров использования и предостережения.
Построение заполнения/повтора можно выполнить с помощью as_strided
.
Начните с простого массива 1d:
In [163]: arr = np.arange(4)
Мы можем использовать другую функцию stride_tricks
для «реплицирования» значений:
In [164]: np.broadcast_to(arr,(3,3,4))
Out[164]:
array([[[0, 1, 2, 3],
[0, 1, 2, 3],
[0, 1, 2, 3]],
[[0, 1, 2, 3],
[0, 1, 2, 3],
[0, 1, 2, 3]],
[[0, 1, 2, 3],
[0, 1, 2, 3],
[0, 1, 2, 3]]])
а затем транспонируйте, чтобы получить тот же узор, который вы создали:
In [165]: np.broadcast_to(arr,(3,3,4)).transpose(2,0,1)
Out[165]:
array([[[0, 0, 0],
[0, 0, 0],
[0, 0, 0]],
[[1, 1, 1],
[1, 1, 1],
[1, 1, 1]],
[[2, 2, 2],
[2, 2, 2],
[2, 2, 2]],
[[3, 3, 3],
[3, 3, 3],
[3, 3, 3]]])
Обратите внимание на strides
этого массива:
In [166]: np.broadcast_to(arr,(3,3,4)).transpose(2,0,1).strides
Out[166]: (4, 0, 0)
Мы можем произвести то же самое с помощью as_strided
:
In [167]: np.lib.stride_tricks.as_strided(arr,(4,3,3),(4,0,0))
Out[167]:
array([[[0, 0, 0],
[0, 0, 0],
[0, 0, 0]],
[[1, 1, 1],
[1, 1, 1],
[1, 1, 1]],
[[2, 2, 2],
[2, 2, 2],
[2, 2, 2]],
[[3, 3, 3],
[3, 3, 3],
[3, 3, 3]]])
broadcast_to
использует другой инструмент, nditer
для достижения этой цели. Я подозреваю, что они делают это, чтобы избежать проблем с записью.
Нам не часто нужно использовать broadcast_to
; достаточно просто добавить размеры (плюс правила broadcasting
):
In [173]: arr[:,None,None]
Out[173]:
array([[[100]],
[[100]],
[[100]],
[[100]]])
In [174]: _.strides
Out[174]: (4, 0, 0)
У него те же ножки, но форма (4,1,1).
import numpy as np
# Create the 3D array (5 images of size 3x3)
array_3d = np.empty((5, 3, 3))
for i in range(5):
array_3d[i] = np.full((3, 3), i+1)
# Print the original 3D array
print("Original 3D array:")
print(array_3d)
'''
Output:
[[[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]]
[[2. 2. 2.]
[2. 2. 2.]
[2. 2. 2.]]
[[3. 3. 3.]
[3. 3. 3.]
[3. 3. 3.]]
[[4. 4. 4.]
[4. 4. 4.]
[4. 4. 4.]]
[[5. 5. 5.]
[5. 5. 5.]
[5. 5. 5.]]]
'''
print("Shape of the 3D array:", array_3d.shape) # Output: (5, 3, 3)
# Step 1: Reshape the 3D array into a 2D array where each 3x3 image is flattened into a single row
flattened_2d = array_3d.reshape(5, 3 * 3)
print("Flattened 2D array:")
print(flattened_2d)
'''
Output:
[[1. 1. 1. 1. 1. 1. 1. 1. 1.]
[2. 2. 2. 2. 2. 2. 2. 2. 2.]
[3. 3. 3. 3. 3. 3. 3. 3. 3.]
[4. 4. 4. 4. 4. 4. 4. 4. 4.]
[5. 5. 5. 5. 5. 5. 5. 5. 5.]]
'''
# Step 2: Transpose the 2D array to switch rows and columns
transposed_2d = flattened_2d.T
print("Transposed 2D array:")
print(transposed_2d)
'''
Output:
[[1. 2. 3. 4. 5.]
[1. 2. 3. 4. 5.]
[1. 2. 3. 4. 5.]
[1. 2. 3. 4. 5.]
[1. 2. 3. 4. 5.]
[1. 2. 3. 4. 5.]
[1. 2. 3. 4. 5.]
[1. 2. 3. 4. 5.]
[1. 2. 3. 4. 5.]]
'''
# Step 3: Reshape the transposed array to arrange the images side by side
side_by_side_2d = transposed_2d.reshape(3, 5 * 3)
print("Final 2D array with images side by side:")
print(side_by_side_2d)
'''
Output:
[[1. 2. 3. 4. 5. 1. 2. 3. 4. 5. 1. 2. 3. 4. 5.]
[1. 2. 3. 4. 5. 1. 2. 3. 4. 5. 1. 2. 3. 4. 5.]
[1. 2. 3. 4. 5. 1. 2. 3. 4. 5. 1. 2. 3. 4. 5.]]
'''