python jak dopełnić tablicę numpy zerami

99

Chcę wiedzieć, jak mogę uzupełnić tablicę numpy 2D zerami za pomocą Pythona 2.6.6 z Numpy w wersji 1.5.0. Przepraszam! Ale to są moje ograniczenia. Dlatego nie mogę użyć np.pad. Na przykład chcę dopełnić azerami tak, aby pasował do kształtu b. Powód, dla którego chcę to zrobić, jest taki, że mogę:

b-a

takie że

>>> a
array([[ 1.,  1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.,  1.]])
>>> b
array([[ 3.,  3.,  3.,  3.,  3.,  3.],
       [ 3.,  3.,  3.,  3.,  3.,  3.],
       [ 3.,  3.,  3.,  3.,  3.,  3.],
       [ 3.,  3.,  3.,  3.,  3.,  3.]])
>>> c
array([[1, 1, 1, 1, 1, 0],
       [1, 1, 1, 1, 1, 0],
       [1, 1, 1, 1, 1, 0],
       [0, 0, 0, 0, 0, 0]])

Jedyny sposób, w jaki mogę o tym pomyśleć, to dołączanie, jednak wydaje się to dość brzydkie. czy jest możliwe użycie czystszego rozwiązania b.shape?

Edytuj, dziękuję za odpowiedź MSeiferts. Musiałem to trochę posprzątać i oto co dostałem:

def pad(array, reference_shape, offsets):
    """
    array: Array to be padded
    reference_shape: tuple of size of ndarray to create
    offsets: list of offsets (number of elements must be equal to the dimension of the array)
    will throw a ValueError if offsets is too big and the reference_shape cannot handle the offsets
    """

    # Create an array of zeros with the reference shape
    result = np.zeros(reference_shape)
    # Create a list of slices from offset to offset + shape in each dimension
    insertHere = [slice(offsets[dim], offsets[dim] + array.shape[dim]) for dim in range(array.ndim)]
    # Insert the array in the result at the specified offsets
    result[insertHere] = array
    return result
user2015487
źródło

Odpowiedzi:

161

Bardzo prosto, tworzysz tablicę zawierającą zera za pomocą kształtu odniesienia:

result = np.zeros(b.shape)
# actually you can also use result = np.zeros_like(b) 
# but that also copies the dtype not only the shape

a następnie wstaw tablicę tam, gdzie jej potrzebujesz:

result[:a.shape[0],:a.shape[1]] = a

i voila, masz to wypełnione:

print(result)
array([[ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.]])

Możesz również uczynić to nieco bardziej ogólnym, jeśli określisz, gdzie ma zostać wstawiony lewy górny element

result = np.zeros_like(b)
x_offset = 1  # 0 would be what you wanted
y_offset = 1  # 0 in your case
result[x_offset:a.shape[0]+x_offset,y_offset:a.shape[1]+y_offset] = a
result

array([[ 0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  1.,  1.,  1.,  1.],
       [ 0.,  1.,  1.,  1.,  1.,  1.],
       [ 0.,  1.,  1.,  1.,  1.,  1.]])

ale wtedy uważaj, aby nie mieć przesunięć większych niż dozwolone. Na x_offset = 2przykład to się nie powiedzie.


Jeśli masz dowolną liczbę wymiarów, możesz zdefiniować listę wycinków, aby wstawić oryginalną tablicę. Uważam, że interesujące jest trochę zabawy i stworzyłem funkcję dopełniania, która może wypełnić (z przesunięciem) tablicę o dowolnym kształcie, o ile tablica i odniesienie mają tę samą liczbę wymiarów, a przesunięcia nie są zbyt duże.

def pad(array, reference, offsets):
    """
    array: Array to be padded
    reference: Reference array with the desired shape
    offsets: list of offsets (number of elements must be equal to the dimension of the array)
    """
    # Create an array of zeros with the reference shape
    result = np.zeros(reference.shape)
    # Create a list of slices from offset to offset + shape in each dimension
    insertHere = [slice(offset[dim], offset[dim] + array.shape[dim]) for dim in range(a.ndim)]
    # Insert the array in the result at the specified offsets
    result[insertHere] = a
    return result

I kilka przypadków testowych:

import numpy as np

# 1 Dimension
a = np.ones(2)
b = np.ones(5)
offset = [3]
pad(a, b, offset)

# 3 Dimensions

a = np.ones((3,3,3))
b = np.ones((5,4,3))
offset = [1,0,0]
pad(a, b, offset)
MSeifert
źródło
Podsumowując przypadek, którego potrzebowałem: jeśli padded = np.zeros(b.shape) padded[tuple(slice(0,n) for n in a.shape)] = a
wstawiam
170

NumPy 1.7.0 (kiedy numpy.padzostał dodany) jest teraz dość stary (został wydany w 2013 roku), więc mimo że pytanie dotyczyło sposobu bez użycia tej funkcji, pomyślałem, że warto wiedzieć, jak można to osiągnąć za pomocą numpy.pad.

To całkiem proste:

>>> import numpy as np
>>> a = np.array([[ 1.,  1.,  1.,  1.,  1.],
...               [ 1.,  1.,  1.,  1.,  1.],
...               [ 1.,  1.,  1.,  1.,  1.]])
>>> np.pad(a, [(0, 1), (0, 1)], mode='constant')
array([[ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.]])

W tym przypadku użyłem, że 0jest to domyślna wartość mode='constant'. Ale można go również określić, przekazując go jawnie:

>>> np.pad(a, [(0, 1), (0, 1)], mode='constant', constant_values=0)
array([[ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.]])

Na wszelki wypadek drugi argument ( [(0, 1), (0, 1)]) wydaje się zagmatwany: każdy element listy (w tym przypadku krotka) odpowiada wymiarowi, a element w nim reprezentuje wypełnienie przed (pierwszy element) i po (drugi element). Więc:

[(0, 1), (0, 1)]
         ^^^^^^------ padding for second dimension
 ^^^^^^-------------- padding for first dimension

  ^------------------ no padding at the beginning of the first axis
     ^--------------- pad with one "value" at the end of the first axis.

W tym przypadku dopełnienie dla pierwszej i drugiej osi jest identyczne, więc można też po prostu podać 2-krotkę:

>>> np.pad(a, (0, 1), mode='constant')
array([[ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 1.,  1.,  1.,  1.,  1.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.]])

W przypadku, gdy wypełnienie przed i po jest identyczne, można nawet pominąć krotkę (jednak nie ma to zastosowania w tym przypadku):

>>> np.pad(a, 1, mode='constant')
array([[ 0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  1.,  1.,  1.,  1.,  0.],
       [ 0.,  1.,  1.,  1.,  1.,  1.,  0.],
       [ 0.,  1.,  1.,  1.,  1.,  1.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.]])

Lub jeśli dopełnienie przed i po jest identyczne, ale różne dla osi, możesz również pominąć drugi argument w krotkach wewnętrznych:

>>> np.pad(a, [(1, ), (2, )], mode='constant')
array([[ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  1.,  1.,  1.,  1.,  1.,  0.,  0.],
       [ 0.,  0.,  1.,  1.,  1.,  1.,  1.,  0.,  0.],
       [ 0.,  0.,  1.,  1.,  1.,  1.,  1.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.]])

Jednak wolę zawsze używać tego wyraźnego, ponieważ łatwo jest popełniać błędy (gdy oczekiwania NumPys różnią się od twoich intencji):

>>> np.pad(a, [1, 2], mode='constant')
array([[ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  1.,  1.,  1.,  1.,  0.,  0.],
       [ 0.,  1.,  1.,  1.,  1.,  1.,  0.,  0.],
       [ 0.,  1.,  1.,  1.,  1.,  1.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.]])

Tutaj NumPy myśli, że chcesz wyłożyć wszystkie osie 1 elementem przed i 2 elementami po każdej osi! Nawet jeśli zamierzałeś wypełnić 1 element na osi 1 i 2 elementy na osi 2.

Użyłem list krotek do wypełnienia, zauważ, że to tylko „moja konwencja”, możesz też użyć list list lub krotek krotek, a nawet krotek tablic. NumPy po prostu sprawdza długość argumentu (lub jeśli nie ma on długości) i długość każdego elementu (lub jeśli ma długość)!

MSeifert
źródło
5
To naprawdę dobrze wyjaśnione. Znacznie lepsze niż oryginalna dokumentacja. Dzięki.
M.Innat
mode='constant'jest rozsądną wartością domyślną, więc wypełnienie zerami można osiągnąć bez potrzeby stosowania jakichkolwiek opcjonalnych słów kluczowych, co prowadzi do nieco bardziej czytelnego kodu.
divenex
jak dodać dopełnienie tylko do trzeciego wymiaru tablicy numpy 3D?
Ramsha Siddiqui
@RamshaSiddiqui możesz użyć zer dla wymiarów, które nie powinny być wypełnione.
MSeifert
9

Rozumiem, że głównym problemem jest to, że musisz obliczyć, d=b-aale twoje tablice mają różne rozmiary. Nie ma potrzeby stosowania pośredniej wyściółkic

Możesz rozwiązać ten problem bez dopełnienia:

import numpy as np

a = np.array([[ 1.,  1.,  1.,  1.,  1.],
              [ 1.,  1.,  1.,  1.,  1.],
              [ 1.,  1.,  1.,  1.,  1.]])

b = np.array([[ 3.,  3.,  3.,  3.,  3.,  3.],
              [ 3.,  3.,  3.,  3.,  3.,  3.],
              [ 3.,  3.,  3.,  3.,  3.,  3.],
              [ 3.,  3.,  3.,  3.,  3.,  3.]])

d = b.copy()
d[:a.shape[0],:a.shape[1]] -=  a

print d

Wynik:

[[ 2.  2.  2.  2.  2.  3.]
 [ 2.  2.  2.  2.  2.  3.]
 [ 2.  2.  2.  2.  2.  3.]
 [ 3.  3.  3.  3.  3.  3.]]
Juan Leni
źródło
To prawda, że ​​w jego konkretnym przypadku niekoniecznie musi wypełniać, ale jest to jedna z niewielu operacji arytmetycznych, w których dopełnianie i twoje podejście są równoważne. Niemniej jednak miła odpowiedź!
MSeifert
1
Nie tylko to. Może to być również bardziej wydajne w pamięci niż wypełnianie zerami.
norok2
0

W przypadku, gdy musisz dodać ogrodzenie 1s do tablicy:

>>> mat = np.zeros((4,4), np.int32)
>>> mat
array([[0, 0, 0, 0],
       [0, 0, 0, 0],
       [0, 0, 0, 0],
       [0, 0, 0, 0]])
>>> mat[0,:] = mat[:,0] = mat[:,-1] =  mat[-1,:] = 1
>>> mat
array([[1, 1, 1, 1],
       [1, 0, 0, 1],
       [1, 0, 0, 1],
       [1, 1, 1, 1]])
MSeifert
źródło
0

Wiem, że trochę się spóźniłem, ale na wypadek, gdybyś chciał wykonać względne wypełnienie (inaczej dopełnianie krawędzi), oto jak możesz to zaimplementować. Zwróć uwagę, że pierwsze wystąpienie przypisania powoduje wypełnienie zerami, więc możesz go użyć zarówno do wypełnienia zerami, jak i do wypełnienia względnego (w tym miejscu kopiujesz wartości krawędzi oryginalnej tablicy do wypełnionej tablicy).

def replicate_padding(arr):
    """Perform replicate padding on a numpy array."""
    new_pad_shape = tuple(np.array(arr.shape) + 2) # 2 indicates the width + height to change, a (512, 512) image --> (514, 514) padded image.
    padded_array = np.zeros(new_pad_shape) #create an array of zeros with new dimensions
    
    # perform replication
    padded_array[1:-1,1:-1] = arr        # result will be zero-pad
    padded_array[0,1:-1] = arr[0]        # perform edge pad for top row
    padded_array[-1, 1:-1] = arr[-1]     # edge pad for bottom row
    padded_array.T[0, 1:-1] = arr.T[0]   # edge pad for first column
    padded_array.T[-1, 1:-1] = arr.T[-1] # edge pad for last column
    
    #at this point, all values except for the 4 corners should have been replicated
    padded_array[0][0] = arr[0][0]     # top left corner
    padded_array[-1][0] = arr[-1][0]   # bottom left corner
    padded_array[0][-1] = arr[0][-1]   # top right corner 
    padded_array[-1][-1] = arr[-1][-1] # bottom right corner

    return padded_array

Analiza złożoności:

Optymalnym rozwiązaniem jest metoda numpy'ego. Po uśrednieniu dla 5 przebiegów np.pad ze względnym wypełnieniem jest tylko 8%lepszy niż funkcja zdefiniowana powyżej. To pokazuje, że jest to dość optymalna metoda wypełniania względnego i zerowego.


#My method, replicate_padding
start = time.time()
padded = replicate_padding(input_image)
end = time.time()
delta0 = end - start

#np.pad with edge padding
start = time.time()
padded = np.pad(input_image, 1, mode='edge')
end = time.time()
delta = end - start


print(delta0) # np Output: 0.0008790493011474609 
print(delta)  # My Output: 0.0008130073547363281
print(100*((delta0-delta)/delta)) # Percent difference: 8.12316715542522%
Qasim Wani
źródło
0

Tensorflow zaimplementował również funkcje zmiany rozmiaru / wypełniania obrazów tf.image.pad tf.pad .

padded_image = tf.image.pad_to_bounding_box(image, top_padding, left_padding, target_height, target_width)

padded_image = tf.pad(image, paddings, "CONSTANT")

Te funkcje działają tak samo jak inne funkcje potoku wprowadzania danych w tensorflow i będą działać znacznie lepiej w aplikacjach uczenia maszynowego.

krenerd
źródło