Построение эффективного массива Numpy 2D из массива 1D - PullRequest
33 голосов
/ 07 февраля 2011

У меня есть такой массив:

A = array([1,2,3,4,5,6,7,8,9,10])

И я пытаюсь получить такой массив:

B = array([[1,2,3],
          [2,3,4],
          [3,4,5],
          [4,5,6]])

Где каждая строка (с фиксированной произвольной шириной) сдвинута на единицу. Массив A длиной 10 тыс. Записей, и я пытаюсь найти эффективный способ сделать это в Numpy В настоящее время я использую vstack и цикл for, который работает медленно. Есть ли более быстрый способ?

Edit:

width = 3 # fixed arbitrary width
length = 10000 # length of A which I wish to use
B = A[0:length + 1]
for i in range (1, length):
    B = np.vstack((B, A[i, i + width + 1]))

Ответы [ 7 ]

52 голосов
/ 07 февраля 2011

На самом деле, есть еще более эффективный способ сделать это ... Недостатком использования vstack и т. Д. Является то, что вы делаете копию массива.

Кстати, это фактически совпадает с ответом @ Paul, но я публикую это только для того, чтобы объяснить что-то более подробно ...

Есть способ сделать это с помощью просто просмотровтак что нет памяти дублируется.

Я напрямую заимствую это из пост Эрика Ригторпа в numpy-обсуждение , который, в свою очередь, позаимствовал его у Кита Гудмана1012 * Узкое место (что весьма полезно!).

Основная хитрость заключается в том, чтобы напрямую манипулировать шагами массива (для одномерных массивов):

import numpy as np

def rolling(a, window):
    shape = (a.size - window + 1, window)
    strides = (a.itemsize, a.itemsize)
    return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)

a = np.arange(10)
print rolling(a, 3)

Где a - ваш входной массив, а window - длина окна, которое вы хотите (3, в вашем случае).

Это дает:

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

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

Например (с использованием a = np.arange(100000) и window=3):

%timeit np.vstack([a[i:i-window] for i in xrange(window)]).T
1000 loops, best of 3: 256 us per loop

%timeit rolling(a, window)
100000 loops, best of 3: 12 us per loop

Если мы обобщаемэто в «скользящее окно» вдоль последней оси для N-мерного массива, мы получаем функцию «скользящего окна» Эрика Ригторпа:

import numpy as np

def rolling_window(a, window):
   """
   Make an ndarray with a rolling window of the last dimension

   Parameters
   ----------
   a : array_like
       Array to add rolling window to
   window : int
       Size of rolling window

   Returns
   -------
   Array that is a view of the original array with a added dimension
   of size w.

   Examples
   --------
   >>> x=np.arange(10).reshape((2,5))
   >>> rolling_window(x, 3)
   array([[[0, 1, 2], [1, 2, 3], [2, 3, 4]],
          [[5, 6, 7], [6, 7, 8], [7, 8, 9]]])

   Calculate rolling mean of last dimension:
   >>> np.mean(rolling_window(x, 3), -1)
   array([[ 1.,  2.,  3.],
          [ 6.,  7.,  8.]])

   """
   if window < 1:
       raise ValueError, "`window` must be at least 1."
   if window > a.shape[-1]:
       raise ValueError, "`window` is too long."
   shape = a.shape[:-1] + (a.shape[-1] - window + 1, window)
   strides = a.strides + (a.strides[-1],)
   return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)

Итак, давайте посмотрим, что здесь происходит ... Манипулированиемассив strides может показаться немного волшебным, но как только вы понимаете, что происходит, это совсем не так.Шаги в массиве numpy описывают размер в байтах шагов, которые необходимо предпринять, чтобы увеличить одно значение вдоль заданной оси.Таким образом, в случае одномерного массива с 64-разрядными числами с плавающей запятой длина каждого элемента составляет 8 байтов, а x.strides равно (8,).

x = np.arange(9)
print x.strides

Теперь, если мы изменим этов двумерном массиве 3x3 шаг будет равен (3 * 8, 8), так как нам нужно было бы прыгнуть на 24 байта для увеличения на один шаг по первой оси и на 8 байтов для увеличения на один шаг вдоль второй оси.

y = x.reshape(3,3)
print y.strides

Аналогично, транспонирование - это то же самое, что и обратное движение шагов массива:

print y
y.strides = y.strides[::-1]
print y

Очевидно, что шаги массива и форма массива тесно связаны между собой.Если мы изменим один, мы должны изменить другой соответственно, иначе у нас не будет правильного описания буфера памяти, который фактически содержит значения массива.

Поэтому, если вы хотите изменить одновременно форма и размер массива одновременно, вы не можете сделать это, просто установив x.strides и x.shape, даже если новые шаги и форма совместимы.

Вот тут и появляется numpy.lib.as_strided. На самом деле это очень простая функция, которая одновременно устанавливает шаги и форму массива одновременно.

Он проверяет, совместимы ли эти два, но не совместимы ли старые шаги и новая форма, как это было бы, если бы вы установили два независимо.(На самом деле это делается с помощью numpy's __array_interface__, что позволяет произвольным классам описывать буфер памяти как массив numpy.)

Итак, все, что мы сделали, это сделали так, чтобышаг вперед на один элемент (8 байт в случае 64-битного массива) вдоль одной оси, но также только шаг вперед на 8 байт вдоль другой оси .

Другими словами, в случае размера "окна" 3, массив имеет форму (whatever, 3), но вместо шага полного 3 * x.itemsize для второго измерения, он только шаг вперед на один пункт , что делает строки нового массива представлением «движущегося окна» в исходном массиве.

(Это также означает, что x.shape[0] * x.shape[1] не будет совпадать с x.size для вашего нового массива.)

В любом случае, надеюсь, это немного прояснит ситуацию.

10 голосов
/ 07 февраля 2011

Это решение неэффективно реализовано в цикле python, поскольку оно поставляется со всеми видами проверки типов, которых лучше избегать при работе с массивами numpy.Если ваш массив исключительно высокий, вы заметите большую скорость с этим:

newshape = (4,3)
newstrides = (A.itemsize, A.itemsize)
B = numpy.lib.stride_tricks.as_strided(A, shape=newshape, strides=newstrides)

Это дает представление массива А. Если вы хотите новый массив, вы можете редактировать, сделайте то же самое, но с .copy() в конце.

Подробности о шагах:

В этом случае кортеж newstrides будет равен (4,4), потому что массив имеет 4-элементы байта, и вы хотите продолжить пошаговое выполнение ваших данных в пошаговых пошаговых операциях в i-измерении.Второе значение «4» относится к шагам в j-измерении (в обычном массиве 4x4 это будет 16).Потому что в этом случае вы также хотите увеличить при чтении из буфера 4-байтовые шаги в j-измерении.

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

2 голосов
/ 19 сентября 2012

Просто чтобы продолжить с ответом @Joe general

import numpy as np
def rolling(a, window):
    step = 2 
    shape = ( (a.size-window)/step + 1   , window)


    strides = (a.itemsize*step, a.itemsize)

    return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)

a = np.arange(10)

print rolling(a, 3)

который выводит:

[[0 1 2]
 [2 3 4]
 [4 5 6]
 [6 7 8]]

Для дальнейшего обобщения для двумерного случая, т.е. используйте его для извлечения патчей из изображения

def rolling2d(a,win_h,win_w,step_h,step_w):

    h,w = a.shape
    shape = ( ((h-win_h)/step_h + 1)  * ((w-win_w)/step_w + 1) , win_h , win_w)

    strides = (step_w*a.itemsize, h*a.itemsize,a.itemsize)


    return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)

a = np.arange(36).reshape(6,6)
print a
print rolling2d (a,3,3,2,2)

который выводит:

[[ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]
 [12 13 14 15 16 17]
 [18 19 20 21 22 23]
 [24 25 26 27 28 29]
 [30 31 32 33 34 35]]
[[[ 0  1  2]
  [ 6  7  8]
  [12 13 14]]

 [[ 2  3  4]
  [ 8  9 10]
  [14 15 16]]

 [[ 4  5  6]
  [10 11 12]
  [16 17 18]]

 [[ 6  7  8]
  [12 13 14]
  [18 19 20]]]
2 голосов
/ 07 февраля 2011

Какой подход вы используете?

import numpy as np
A = np.array([1,2,3,4,5,6,7,8,9,10])
width = 3

np.vstack([A[i:i-len(A)+width] for i in xrange(len(A)-width)])
# needs 26.3µs

np.vstack([A[i:i-width] for i in xrange(width)]).T
# needs 13.2µs

Если ваша ширина относительно мала (3), а у вас большой A (10000 элементов), тогда разница еще более важна: 32,4 мсдля первого и 44 мкс для второго.

1 голос
/ 25 октября 2016

Посмотрите на: view_as_windows .

import numpy as np
from skimage.util.shape import view_as_windows
window_shape = (4, )
aa = np.arange(1000000000) # 1 billion
bb = view_as_windows(aa, window_shape)

Примерно за 1 секунду.

1 голос
/ 25 июля 2016

Я использую более обобщенную функцию, аналогичную функции @JustInTime, но применимую к ndarray

def sliding_window(x, size, overlap=0):
    step = size - overlap # in npts
    nwin = (x.shape[-1]-size)//step + 1
    shape = x.shape[:-1] + (nwin, size)
    strides = x.strides[:-1] + (step*x.strides[-1], x.strides[-1])
    return stride_tricks.as_strided(x, shape=shape, strides=strides)

Например,

x = np.arange(10)
M.sliding_window(x, 5, 3)
Out[1]: 
array([[0, 1, 2, 3, 4],
       [2, 3, 4, 5, 6],
       [4, 5, 6, 7, 8]])


x = np.arange(10).reshape((2,5))
M.sliding_window(x, 3, 1)
Out[2]: 
array([[[0, 1, 2],
        [2, 3, 4]],

       [[5, 6, 7],
        [7, 8, 9]]])
1 голос
/ 07 февраля 2011

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

import numpy
a = numpy.array([1,2,3,4,5,6])
b = numpy.reshape(a, (numpy.shape(a)[0],1))
b = numpy.concatenate((b, numpy.roll(b,-1,0), numpy.roll(b,-2,0)), 1)
b = b[0:(numpy.shape(a)[0]/2) + 1,:]

РЕДАКТИРОВАТЬ Очевидно, что решения, использующие шаги, превосходят это, с единственным серьезным недостаткомпри том, что они еще не хорошо документированы ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...