Предположим, у меня есть следующий 2D-массив:
x = np.array([[10,20,30,40], [50,60,70,80],[90,100,110,120]])
print(x)
array([[ 10, 20, 30, 40],
[ 50, 60, 70, 80],
[ 90, 100, 110, 120]])
Я хотел бы построить новый массив y
, где каждая строка имеет значения блока 2x2 из x
в порядке по часовой стрелке:
print(y)
array([[ 10, 20, 60, 50],
[ 20, 30, 70, 60],
[ 30, 40, 80, 70],
[ 50, 60, 100, 90],
[ 60, 70, 110, 100],
[ 70, 80, 120, 110]])
Я мог бы добиться этого, используя циклы Python for следующим образом:
n_rows, n_cols = x.shape
y = []
for i in range(n_rows-1):
for j in range(n_cols-1):
row = [x[i,j],x[i,j+1],x[i+1, j+1],x[i+1,j]]
y.append(row)
y = np.array(y)
Интересно, есть ли более быстрый способ, использующий функции Numpy и избегающий использования циклов Python.
@joanis: я хочу извлечь каждый блок 2x2 и поместить его в ряд.
@джоанис. Вы немного утрируете: здесь нет суммирования, а извлечение происходит по часовой стрелке, а не просто вразброс.
@MadPhysicist Вы правы, скользящее окно - правильный инструмент, это не имеет ничего общего с извилинами. Мне нравится твой ответ. Я просто не нашел вопрос очень ясным в первую очередь.
Там я просто отредактировал вопрос, чтобы прояснить связь между x и y.
@джоанис. Проза вообще сбивает с толку. Обычно я смотрю на эталонную реализацию, предоставленную OP, и она совершенно недвусмысленна.
@MadPhysicist Я согласен только наполовину. Да, код однозначен, но немного хорошо написанной прозы поможет вам понять код гораздо быстрее.
Вы можете кэшировать свой код, так как цикл в основном повторяет одну и ту же матрицу снова и снова (если вы хотите сохранить свой код с циклом). Я сделал сравнение скорости вашего кода до и после кэширования.
# Before caching
def loop_before_cache():
n_rows, n_cols = x.shape
y = []
for i in range(n_rows-1):
for j in range(n_cols-1):
row = [x[i,j],x[i,j+1],x[i+1, j+1],x[i+1,j]]
y.append(row)
return np.array(y)
%timeit loop_before_cache()
11.6 µs ± 318 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
А теперь с кэшированием
# After caching
from functools import lru_cache
@lru_cache()
def loop_after_cache():
n_rows, n_cols = x.shape
y = []
for i in range(n_rows-1):
for j in range(n_cols-1):
row = [x[i,j],x[i,j+1],x[i+1, j+1],x[i+1,j]]
y.append(row)
return np.array(y)
%timeit loop_after_cache()
83.6 ns ± 2.42 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
Я добавил смоделированные данные с массивом (1000,5000) с помощью range
, чтобы показать эффективность кэширования.
x = np.array([i for i in range(1,5000001)])
x = np.reshape(x, (1000,5000))
# Before caching
def loop_before_cache():
n_rows, n_cols = x.shape
y = []
for i in range(n_rows-1):
for j in range(n_cols-1):
row = [x[i,j],x[i,j+1],x[i+1, j+1],x[i+1,j]]
y.append(row)
return np.array(y)
%timeit loop_before_cache()
8.58 s ± 113 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
# After caching
@lru_cache(maxsize = 256)
def loop_after_cache():
n_rows, n_cols = x.shape
y = []
for i in range(n_rows-1):
for j in range(n_cols-1):
row = [x[i,j],x[i,j+1],x[i+1, j+1],x[i+1,j]]
y.append(row)
return np.array(y)
%timeit loop_after_cache()
82.2 ns ± 5.58 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
Ницца! Тем не менее, я все еще хочу увидеть ответ, который, если возможно, использует функции numpy.
Я надеюсь, что смогу помочь с этим, но извините, я не очень хорош в этом numpy
. Что ж, вы тоже можете использовать это кеширование после того, как появится лучший пост с решением numpy: D
Теперь попробуйте это на массиве 100x500 или около того. Сравнительные тесты на крошечных наборах данных довольно бесполезны, поскольку в них преобладают накладные расходы, особенно в таких случаях, как numpy, который требует огромного объема подготовительной работы, прежде чем данные будут переданы фактической функции C.
Спасибо за предложение, я включил обновление в ответ
Сначала создайте sliding_window_view
в x
с прямоугольниками 2x2, которые вы хотите видеть:
b = np.lib.stride_tricks.sliding_window_view(x, (2, 2))
Каждый из самых внутренних массивов 2x2 содержит развернутую версию того, что вы хотите, но с перевернутой второй частью массива. Пока мы не копировали никаких данных. Теперь сделайте копию, распутав последнее измерение. Изменение формы всегда будет создавать здесь копию, потому что b
очень несмежное:
c = b.reshape(*b.shape[:2], 4)
Поменяйте местами последние два столбца:
c[..., 2:] = c[..., -1:1:-1]
Теперь распутайте ведущие размеры:
y = c.reshape(-1, c.shape[-1])
Если у вас есть версия numpy старше 1.20, вы можете заменить определение b
на
b = np.lib.stride_tricks.as_strided(x, shape=(x.shape[0] - 1, x.shape[1] - 1, 2, 2), strides=x.strides * 2)
Если я правильно понимаю, вы пытаетесь реализовать какую-то операцию, подобную свертке, где каждая строка в
y
имеет значения из блока 2x2 вx
, верно?