Быстрый способ upsample массив numpy по ближайшей соседней плитки [дубликат]

этот вопрос уже есть ответ здесь:

У меня есть 2D-массив целых чисел, который является MxN, и я хотел бы расширить массив (BM)x (BN) здесь B длина квадратной стороны плитки таким образом каждое элемент входного массива повторяется как BxB блок в итоговом массиве. Ниже приведен пример с вложенным циклом for. Есть ли более быстрый / встроенный способ?

import numpy as np

a = np.arange(9).reshape([3,3])            # input array - 3x3
B=2.                                       # block size - 2  
A = np.zeros([a.shape[0]*B,a.shape[1]*B])  # output array - 6x6

# Loop, filling A with tiled values of a at each index
for i,l in enumerate(a):                   # lines in a
    for j,aij in enumerate(l):             # a[i,j]
        A[B*i:B*(i+1),B*j:B*(j+1)] = aij

результат ...

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

A =     [[ 0.  0.  1.  1.  2.  2.]
         [ 0.  0.  1.  1.  2.  2.]
         [ 3.  3.  4.  4.  5.  5.]
         [ 3.  3.  4.  4.  5.  5.]
         [ 6.  6.  7.  7.  8.  8.]
         [ 6.  6.  7.  7.  8.  8.]]

3 ответов


одним из вариантов является

>>> a.repeat(2, axis=0).repeat(2, axis=1)
array([[0, 0, 1, 1, 2, 2],
       [0, 0, 1, 1, 2, 2],
       [3, 3, 4, 4, 5, 5],
       [3, 3, 4, 4, 5, 5],
       [6, 6, 7, 7, 8, 8],
       [6, 6, 7, 7, 8, 8]])

Это немного расточительно из-за промежуточного массива, но он по крайней мере лаконичен.


вот потенциально быстрый способ использования трюков stride и перестройки:

from numpy.lib.stride_tricks import as_strided

def tile_array(a, b0, b1):
    r, c = a.shape                                    # number of rows/columns
    rs, cs = a.strides                                # row/column strides 
    x = as_strided(a, (r, b0, c, b1), (rs, 0, cs, 0)) # view a as larger 4D array
    return x.reshape(r*b0, c*b1)                      # create new 2D array

исходные данные a копируется, когда reshape вызывается, поэтому эта функция не возвращает представление. Однако, по сравнению с использованием repeat по нескольким осям требуется меньше операций копирования.

затем функцию можно использовать следующим образом:

>>> a = np.arange(9).reshape(3, 3)
>>> a
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])

>>> tile_array(a, 2, 2)
array([[0, 0, 1, 1, 2, 2],
       [0, 0, 1, 1, 2, 2],
       [3, 3, 4, 4, 5, 5],
       [3, 3, 4, 4, 5, 5],
       [6, 6, 7, 7, 8, 8],
       [6, 6, 7, 7, 8, 8]])

>>> tile_array(a, 3, 4)
array([[0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2],
       [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2],
       [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2],
       [3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5],
       [3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5],
       [3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5],
       [6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8],
       [6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8],
       [6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8]])

теперь маленький блоки, этот метод немного медленнее, чем при использовании repeat но быстрее, чем kron.

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

>>> %timeit tile_array(a, 20, 20)
100000 loops, best of 3: 18.7 µs per loop

>>> %timeit a.repeat(20, axis=0).repeat(20, axis=1)
10000 loops, best of 3: 26 µs per loop

>>> %timeit np.kron(a, np.ones((20,20), a.dtype))
10000 loops, best of 3: 106 µs per loop

разрыв между методами увеличивается по мере увеличения размера блока.

и если a большой массив, это может быть быстрее, чем альтернативы:

>>> a2 = np.arange(1000000).reshape(1000, 1000)
>>> %timeit tile_array(a2, 2, 2)
100 loops, best of 3: 11.4 ms per loop

>>> %timeit a2.repeat(2, axis=0).repeat(2, axis=1)
1 loops, best of 3: 30.9 ms per loop

наверное, не самый быстрый, но..

np.kron(a, np.ones((B,B), a.dtype))

Он же продукт Kronecker, поэтому он включает в себя умножение для каждого элемента на выходе.