Supongamos que tengo la siguiente matriz 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]]) Me gustaría construir una nueva matriz, y , donde cada fila tenga los valores de un bloque de 2x2 desde x en el sentido de las agujas del reloj:
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]])Podría lograr eso usando Python para bucles de la siguiente manera:
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)Me pregunto si hay una forma más rápida que aproveche las funciones de Numpy y evite usar bucles de Python.
Primero, cree una vista de sliding_window_view en x con los cuadros de 2x2 que desea ver:
b = np.lib.stride_tricks.sliding_window_view(x, (2, 2)) Cada una de las matrices de 2x2 más internas contiene una versión desenredada de lo que desea, pero con la segunda parte de la matriz invertida. Hasta ahora no hemos copiado ningún dato. Ahora haz una copia deshilachando la última dimensión. La remodelación siempre hará una copia aquí porque b es altamente no contigua:
c = b.reshape(*b.shape[:2], 4)Cambia las dos últimas columnas:
c[..., 2:] = c[..., -1:1:-1]Ahora enreda las dimensiones principales:
y = c.reshape(-1, c.shape[-1]) Si tiene una versión de numpy anterior a la 1.20, puede reemplazar la definición de b con
b = np.lib.stride_tricks.as_strided(x, shape=(x.shape[0] - 1, x.shape[1] - 1, 2, 2), strides=x.strides * 2)Puede almacenar en caché su código ya que el ciclo itera principalmente la misma matriz una y otra vez (si desea mantener su mismo código con el ciclo). He hecho una comparación de velocidad para su código antes y después del almacenamiento en caché.
# 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)Y ahora con el almacenamiento en caché
# 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) He agregado datos simulados con una matriz (1000,5000) usando el range para mostrar la eficiencia del almacenamiento en caché.
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)