Tengo dos problemas de procesamiento de matriz diferentes que me gustaría resolver AQAP (Q = rápidamente) para asegurar que las soluciones no limiten la velocidad en mi proceso (usando NEAT para entrenar un bot de videojuegos). En un caso, quiero construir una función de penalización para hacer mayores alturas de columna, y en el otro quiero recompensar la construcción de "islas de un valor común".

Las operaciones comienzan en una matriz numpy de 26 filas x 6 columnas de valores de escala de grises con un fondo negro / 0.

Tengo soluciones de trabajo para cada problema que ya implementan algunos problemas, pero me gustaría impulsar un enfoque completamente vectorizado para ambos.

    import numpy as np,
    from scipy.ndimage.measurements import label as sp_label
    from math import ceil

Ambos problemas comienzan desde una matriz como esta:

    img= np.array([[ 0.,  0.,  0., 12.,  0.,  0.],
                   [ 0.,  0.,  0., 14.,  0.,  0.],               
                   [ 0.,  0.,  0., 14.,  0.,  0.],
                   [ 0.,  0.,  0., 14.,  0.,  0.],               
                   [16.,  0.,  0., 14.,  0.,  0.],
                   [16.,  0.,  0., 12.,  0.,  0.],               
                   [12.,  0., 11.,  0.,  0.,  0.],
                   [12.,  0., 11.,  0.,  0.,  0.],               
                   [16.,  0., 15.,  0., 15.,  0.],
                   [16.,  0., 15.,  0., 15.,  0.],               
                   [14.,  0., 12.,  0., 11.,  0.],
                   [14.,  0., 12.,  0., 11.,  0.],               
                   [14., 15., 11.,  0., 11.,  0.],
                   [14., 15., 11.,  0., 11.,  0.],               
                   [13., 16., 12.,  0., 13.,  0.],
                   [13., 16., 12.,  0., 13.,  0.],               
                   [13., 14., 16.,  0., 16.,  0.],
                   [13., 14., 16.,  0., 16.,  0.],               
                   [16., 14., 15.,  0., 14.,  0.],
                   [16., 14., 15.,  0., 14.,  0.],               
                   [14., 16., 14.,  0., 11.,  0.],
                   [14., 16., 14.,  0., 11.,  0.],               
                   [11., 13., 14., 16., 12., 13.],
                   [11., 13., 14., 16., 12., 13.],               
                   [12., 12., 15., 14., 15., 11.], 
                   [12., 12., 15., 14., 15., 11.]])

El primer problema (altura de columna) se está resolviendo actualmente con:


    # define valid connection directions for sp_label
    c_valid_conns = np.array((0,1,0,0,1,0,0,1,0,), dtype=np.int).reshape((3,3))

    # run the island labeling function sp_label    
    # c_ncomponents is a simple count of the conected columns in labeled
    columns, c_ncomponents = sp_label(img, c_valid_conns)

    # calculate out the column lengths
    col_lengths = np.array([(columns[columns == n]/n).sum() for n in range(1, c_ncomponents+1)])
    col_lengths

Para darme esta matriz: [6. 22. 20. 18. 14. 4. 4.]

(bonificación si el código ignora constantemente la región etiquetada que no "contiene" la parte inferior de la matriz (índice de fila 25 / -1))

El segundo problema consiste en enmascarar cada valor único y calcular los cuerpos contiguos en cada matriz enmascarada para obtener el tamaño de los cuerpos contiguos:

    # initial values to start the ball rolling
    values = [11, 12, 13, 14, 15, 16]
    isle_avgs_i = [1.25, 2, 0, 1,5, 2.25, 1]

    # apply filter masks to img to isolate each value 
    # Could these masks be pushed out into a third array dimension instead?
    masks = [(img == g) for g in np.unique(values)]

    # define the valid connectivities (8-way) for the sp_label function
    m_valid_conns = np.ones((3,3), dtype=np.int)

    # initialize islanding lists 
    # I'd love to do away with these when I no longer need the .append() method)
    mask_isle_avgs, isle_avgs = [],[]

    # for each mask in the image:         
    for i, mask in enumerate(masks):

        # run the island labeling function sp_label
        # m_labeled is the array containing the sequentially labeled islands
        # m_ncomponents is a simple count of the islands in m_labeled
        m_labeled, m_ncomponents = sp_label(mask, m_valid_conns)

        # collect the average (island size-1)s (halving to account for... 
        # ... y resolution) for each island into mask_isle_avgs list 
        # I'd like to vectorize this step
        mask_isle_avgs.append((sum([ceil((m_labeled[m_labeled == n]/n).sum()/2)-1 
                                    for n in range(1, m_ncomponents+1)]))/(m_ncomponents+1))

        # add up the mask isle averages for all the islands... 
        # ... and collect into isle_avgs list
        # I'd like to vectorize this step
        isle_avgs.append(sum(mask_isle_avgs))

    # initialize a difference list for the isle averages (I also want to do away with this step)
    d_avgs = []

    # evaluate whether isle_avgs is greater for the current frame or the...
    # ... previous frame (isle_avgs_i) and append either the current...
    # ... element or 0, depending on whether the delta is non-negative
    # I want this command vectorized
    [d_avgs.append(isle_avgs[j]) 
     if (isle_avgs[j]-isle_avgs_i[j])>=0 
     else d_avgs.append(0) for j in range(len(isle_avgs))]
    d_avgs

Para darme esta matriz d_avgs: [0, 0, 0.46785714285714286, 1.8678571428571429, 0, 0]

(bono de nuevo si el código ignora constantemente la región etiquetada que no "contiene" la parte inferior de la matriz (índice de fila 25 / -1) para dar esta matriz:

[0, 0, 0,43452380952380953, 1,6345238095238095, 0, 0])

Estoy buscando eliminar cualquier operación de lista y comprensión y moverla a una implementación numpy / scipy completamente vectorizada con los mismos resultados.

Cualquier ayuda para eliminar cualquiera de estos pasos sería muy apreciada.

0
chrised209 17 oct. 2019 a las 20:07

1 respuesta

La mejor respuesta

Así es como finalmente resolví este problema:

######## column height penalty calculation ########
            # c_ncomponents is a simple count of the conected columns in labeled
            columns, c_ncomponents = sp_label(unit_img, c_valid_conns)
#                 print(columns)
            # throw out the falling block with .isin(x,x[-1]) combined with... 
            # the mask nonzero(x) 
            drop_falling = np.isin(columns, columns[-1][np.nonzero(columns[-1])])
            col_hts = drop_falling.sum(axis=0)
#                 print(f'col_hts {col_hts}')
            # calculate differentials for the (grounded) column heights
            d_col_hts = np.sum(col_hts - col_hts_i)
#                 print(f'col_hts {col_hts} - col_hts_i {col_hts_i} ===> d_col_hts {d_col_hts}')
            # set col_hts_i to current col_hts for next evaluation
            col_hts_i = col_hts
            # calculate penalty/bonus function
#                 col_pen = (col_hts**4 - 3**4).sum()
            col_pen = np.where(d_col_hts > 0, (col_hts**4 - 3**4), 0).sum()
#                 
#             if col_pen !=0:
#                 print(f'col_pen: {col_pen}')
######## end column height penalty calculation ########

######## color island bonus calculation ########
            # mask the unit_img to remove the falling block
            isle_img = drop_falling * unit_img
#             print(isle_img)
            # broadcast the game board to add a layer for each color
            isle_imgs = np.broadcast_to(isle_img,(7,*isle_img.shape))
            # define a mask to discriminate on color in each layer
            isle_masked = isle_imgs*[isle_imgs==ind_grid[0]]
            # reshape the array to return to 3 dimensions
            isle_masked = isle_masked.reshape(isle_imgs.shape)
            # generate the isle labels
            isle_labels, isle_ncomps = sp_label(isle_masked, i_valid_conns)
            # determine the island sizes (via return_counts) for all the unique labels
            isle_inds, isle_sizes = np.unique(isle_labels, return_counts=True)
            # zero out isle_sizes[0] to remove spike for background (500+ for near empty board)
            isle_sizes[0] = 0
            # evaluate difference to determine whether bonus applies
            if isle_sizes_i.sum() != isle_sizes.sum():
            # calculate bonus for all island sizes ater throwing away the 0 count
                isle_bonus = (isle_sizes**3).sum()
            else:
                isle_bonus = 0
0
chrised209 21 oct. 2019 a las 16:43