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
в "переместить исходный массив вокруг" шаг.