Python-векторизация скользящего окна

Я пытаюсь векторизовать операцию скользящего окна. Для 1-d случая полезный пример может идти по строкам:

x= vstack((np.array([range(10)]),np.array([range(10)])))

x[1,:]=np.where((x[0,:]<5)&(x[0,:]>0),x[1,x[0,:]+1],x[1,:])

значение n+1 для каждого текущего значения для индексов

x[1,:]=np.where((x[0,:]<2)&(x[0,:]>0),x[1,x[0,:]+1],x[1,:])
IndexError: index (10) out of range (0<=index<9) in dimension 1

Любопытно, что я не получил бы эту ошибку для значения n-1, которое означало бы индексы меньше 0. Он, кажется, не возражает:

x[1,:]=np.where((x[0,:]<5)&(x[0,:]>0),x[1,x[0,:]-1],x[1,:])

print(x)

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

есть ли вообще вокруг этого? мой подход абсолютно неправильный? любые комментарии будут оцененный.

EDIT:

это то, что я хотел бы достичь, я сплющиваю матрицу в массив numpy, на котором я хочу вычислить среднее значение окрестности 6x6 каждой ячейки:

matriz = np.array([[1,2,3,4,5],
   [6,5,4,3,2],
   [1,1,2,2,3],
   [3,3,2,2,1],
   [3,2,1,3,2],
   [1,2,3,1,2]])

# matrix to vector
vector2 = ndarray.flatten(matriz)

ncols = int(shape(matriz)[1])
nrows = int(shape(matriz)[0])

vector = np.zeros(nrows*ncols,dtype='float64')


# Interior pixels
if ( (i % ncols) != 0 and (i+1) % ncols != 0 and i>ncols and i<ncols*(nrows-1)):

    vector[i] = np.mean(np.array([vector2[i-ncols-1],vector2[i-ncols],vector2[i-ncols+1],vector2[i-1],vector2[i+1],vector2[i+ncols-1],vector2[i+ncols],vector2[i+ncols+1]]))

4 ответов


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

я исправил вашу функцию для работы, я считаю, что вы собирались для чего-то вроде этого:

def original(matriz):

    vector2 = np.ndarray.flatten(matriz)

    nrows, ncols= matriz.shape
    vector = np.zeros(nrows*ncols,dtype='float64')

    # Interior pixels
    for i in range(vector.shape[0]):
        if ( (i % ncols) != 0 and (i+1) % ncols != 0 and i>ncols and i<ncols*(nrows-1)):

            vector[i] = np.mean(np.array([vector2[i-ncols-1],vector2[i-ncols],\
                        vector2[i-ncols+1],vector2[i-1],vector2[i+1],\
                        vector2[i+ncols-1],vector2[i+ncols],vector2[i+ncols+1]]))

я переписал это, используя нарезку и просмотры:

def mean_around(arr):
    arr=arr.astype(np.float64)

    out= np.copy(arr[:-2,:-2])  #Top left corner
    out+= arr[:-2,2:]           #Top right corner
    out+= arr[:-2,1:-1]         #Top center
    out+= arr[2:,:-2]           #etc
    out+= arr[2:,2:]
    out+= arr[2:,1:-1]
    out+= arr[1:-1,2:]
    out+= arr[1:-1,:-2]

    out/=8.0    #Divide by # of elements to obtain mean

    cout=np.empty_like(arr)  #Create output array
    cout[1:-1,1:-1]=out      #Fill with out values
    cout[0,:]=0;cout[-1,:]=0;cout[:,0]=0;cout[:,-1]=0 #Set edges equal to zero

    return  cout

используя np.empty_like и затем заполнение краев казалось немного быстрее, чем np.zeros_like. Сначала позволяет дважды проверить, что они дают то же самое, используя ваш matriz матрица.

print np.allclose(mean_around(matriz),original(matriz))
True

print mean_around(matriz)
[[ 0.     0.     0.     0.     0.   ]
 [ 0.     2.5    2.75   3.125  0.   ]
 [ 0.     3.25   2.75   2.375  0.   ]
 [ 0.     1.875  2.     2.     0.   ]
 [ 0.     2.25   2.25   1.75   0.   ]
 [ 0.     0.     0.     0.     0.   ]]

некоторые тайминги:

a=np.random.rand(500,500)

print np.allclose(original(a),mean_around(a))
True

%timeit mean_around(a)
100 loops, best of 3: 4.4 ms per loop

%timeit original(a)
1 loops, best of 3: 6.6 s per loop

примерно ~1500x ускорение.

похоже, хорошее место для использования numba:

def mean_numba(arr):
    out=np.zeros_like(arr)
    col,rows=arr.shape

    for x in xrange(1,col-1):
        for y in xrange(1,rows-1):
            out[x,y]=(arr[x-1,y+1]+arr[x-1,y]+arr[x-1,y-1]+arr[x,y+1]+\
                      arr[x,y-1]+arr[x+1,y+1]+arr[x+1,y]+arr[x+1,y-1])/8.
    return out

nmean= autojit(mean_numba)

теперь давайте сравним со всеми представленными методами.

a=np.random.rand(5000,5000)

%timeit mean_around(a)
1 loops, best of 3: 729 ms per loop

%timeit nmean(a)
10 loops, best of 3: 169 ms per loop

#CT Zhu's answer
%timeit it_mean(a)
1 loops, best of 3: 36.7 s per loop

#Ali_m's answer
%timeit fast_local_mean(a,(3,3))
1 loops, best of 3: 4.7 s per loop

#lmjohns3's answer
%timeit scipy_conv(a)
1 loops, best of 3: 3.72 s per loop

скорость 4x с numba up довольно номинальная, что указывает на то, что код numpy примерно так же хорош, как и его получение. Я вытащил другие коды, как представлено, хотя мне пришлось изменить ответ @CTZhu, чтобы включить другой массив размеры.


похоже, вы пытаетесь вычислить 2D-свертку. Если вы можете использовать scipy, Я бы предложил попробовать scipy.сигнал.convolve2d:

matriz = np.random.randn(10, 10)

# to average a 3x3 neighborhood
kernel = np.ones((3, 3), float)

# to compute the mean, divide by size of neighborhood
kernel /= kernel.sum()

average = scipy.signal.convolve2d(matriz, kernel)

причина, по которой это вычисляет среднее значение всех окрестностей 3x3, можно увидеть, если вы "развернете" сверток2d в его составные петли. Эффективно (и игнорируя то, что происходит на краях исходных и ядровых массивов), он вычисляет :

X, Y = kernel.shape
for i in range(matriz.shape[0]):
    for j in range(matriz.shape[1]):
        for ii in range(X):
            for jj in range(Y):
                average[i, j] += kernel[ii, jj] * matriz[i+ii, j+jj]

поэтому, если каждое значение в вашем ядре 1/(1+1+1+1+1+1+1+1+1) == 1/9, вы можете переписать код выше как :

for i in range(matriz.shape[0]):
    for j in range(matriz.shape[1]):
        average[i, j] = 1./9 * matriz[i:i+X, j:j+Y].sum()

который точно такой же, как вычисление среднего значения в матрице, над областью 3x3, начиная с i, j.

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

kernel = np.ones((3, 3), float)
kernel[1, 1] = 2.
kernel /= kernel.sum()

и код свертки останется прежним, но вычисление даст другой тип среднего ("центровзвешенный"). Здесь есть много возможностей; надеюсь, это обеспечивает хорошую абстракцию для задачи, которую вы делаете.


в стандартной библиотеке Scipy есть функция, которая вычисляет среднее значение по скользящим окнам очень быстро. Это называется uniform_filter. Вы можете использовать его для реализации функции mean-of-neighbourhood следующим образом:

from scipy.ndimage.filters import uniform_filter
def neighbourhood_average(arr, win=3):
    sums = uniform_filter(arr, win, mode='constant') * (win*win)
    return ((sums - arr) / (win*win - 1))

это возвращает массив X здесь X[i,j] является средним из всех соседей i,j на arr кроме . Обратите внимание, что первый и последний столбец и первая и последняя строка подчиняются граничным условиям, и поэтому может быть недействительным для вашего приложения (вы можете использовать mode= для управления граничным правилом при необходимости).

, потому что uniform_filter использует высокоэффективный алгоритм линейного времени, реализованный в прямой C (линейный только в размере arr), он должен легко превзойти любые другие решения, особенно когда win большой.


проблема заключается в x[1,x[0,:]+1] индекс для 2-й оси: x[0,:]+1 is [1 2 3 4 5 6 7 8 9 10], в котором индекс 10 больше, чем размер x.

в случае x[1,x[0,:]-1] индекс 2-й оси составляет [-1 0 1 2 3 4 5 6 7 8 9], вы [9 0 1 2 3 4 5 6 7 8], as 9 является последним элементом и имеет индекс -1. Индекс второго элемента от конца равен -2 и так далее.

С np.where((x[0,:]<5)&(x[0,:]>0),x[1,x[0,:]-1],x[1,:]) и x[0,:]=[0 1 2 3 4 5 6 7 8 9], что по существу происходит, так это то, что первая ячейка взята форма x[1,:], потому что x[0,0] 0, а x[0,:]<5)&(x[0,:]>0 is False. Следующие четыре элемента взяты из x[1,x[0,:]-1]. Остальные из x[1,:]. Наконец, результат [0 0 1 2 3 4 5 6 7 8]

может показаться, что это нормально для раздвижного окна только 1 ячейки, но это удивит вас:

>>> np.where((x[0,:]<5)&(x[0,:]>0),x[1,x[0,:]-2],x[1,:])
array([0, 9, 0, 1, 2, 5, 6, 7, 8, 9])

при попытке переместить его с помощью окна из двух ячеек.

для этой конкретной проблемы, если мы хотим сохранить все в одной строке, это будет do:

>>> for i in [1, 2, 3, 4, 5, 6]:
    print hstack((np.where(x[1,x[0,:]-i]<x[0, -i], x[1,x[0,:]-i], 0)[:5], x[0,5:]))

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

изменить: Теперь я лучше понимаю ваш исходный вопрос, в основном вы хотите взять 2D-массив и вычислить среднее значение n*N ячеек вокруг каждой ячейки. Это довольно распространено. Сначала вы, вероятно, хотите ограничить N нечетными числами, иначе такую вещь, как среднее значение 2*2 вокруг ячейки, трудно определить. Предположим, мы хотим среднее значение 3*3:

#In this example, the shape is (10,10)
>>> a1=\
array([[3, 7, 0, 9, 0, 8, 1, 4, 3, 3],
   [5, 6, 5, 2, 9, 2, 3, 5, 2, 9],
   [0, 9, 8, 5, 3, 1, 8, 1, 9, 4],
   [7, 4, 0, 0, 9, 3, 3, 3, 5, 4],
   [3, 1, 2, 4, 8, 8, 2, 1, 9, 6],
   [0, 0, 3, 9, 3, 0, 9, 1, 3, 3],
   [1, 2, 7, 4, 6, 6, 2, 6, 2, 1],
   [3, 9, 8, 5, 0, 3, 1, 4, 0, 5],
   [0, 3, 1, 4, 9, 9, 7, 5, 4, 5],
   [4, 3, 8, 7, 8, 6, 8, 1, 1, 8]])
#move your original array 'a1' around, use range(-2,2) for 5*5 average and so on
>>> movea1=[a1[np.clip(np.arange(10)+i, 0, 9)][:,np.clip(np.arange(10)+j, 0, 9)] for i, j in itertools.product(*[range(-1,2),]*2)]
#then just take the average
>>> averagea1=np.mean(np.array(movea1), axis=0)
#trim the result array, because the cells among the edges do not have 3*3 average
>>> averagea1[1:10-1, 1:10-1]
array([[ 4.77777778,  5.66666667,  4.55555556,  4.33333333,  3.88888889,
     3.66666667,  4.        ,  4.44444444],
   [ 4.88888889,  4.33333333,  4.55555556,  3.77777778,  4.55555556,
     3.22222222,  4.33333333,  4.66666667],
   [ 3.77777778,  3.66666667,  4.33333333,  4.55555556,  5.        ,
     3.33333333,  4.55555556,  4.66666667],
   [ 2.22222222,  2.55555556,  4.22222222,  4.88888889,  5.        ,
     3.33333333,  4.        ,  3.88888889],
   [ 2.11111111,  3.55555556,  5.11111111,  5.33333333,  4.88888889,
     3.88888889,  3.88888889,  3.55555556],
   [ 3.66666667,  5.22222222,  5.        ,  4.        ,  3.33333333,
     3.55555556,  3.11111111,  2.77777778],
   [ 3.77777778,  4.77777778,  4.88888889,  5.11111111,  4.77777778,
     4.77777778,  3.44444444,  3.55555556],
   [ 4.33333333,  5.33333333,  5.55555556,  5.66666667,  5.66666667,
     4.88888889,  3.44444444,  3.66666667]])

я думаю, вам не нужно сглаживать 2D-массив, что вызывает путаницу. Кроме того, если вы хотите обработать элементы edge иначе, чем просто обрезать их, рассмотрите возможность создания маскированных массивов с помощью np.ma в "переместить исходный массив вокруг" шаг.