Понимание различий между похожими методами сглаживания numpy

#python #image #numpy #reshape

#python #изображение #numpy #изменение формы

Вопрос:

Я работаю над домашним заданием на 2-ю неделю первого курса в deeplearning.ai сертификат на Coursera.

Одной из первых задач является сглаживание изображения (209, 64, 64, 3). Вы можете сделать это тремя способами (или я так думаю):

  1. X.изменить форму(X.shape[0],-1).T
  2. X.сгладить().изменить форму(12288, 209)
  3. X.изменение формы (12288, 209)

В этом упражнении я обнаружил, что только первый вариант правильно изменяет форму изображения, но я понятия не имею, почему. Любая помощь была бы высоко оценена.

Комментарии:

1. X.reshape(X.shape[0],-1).T == X.reshape(-1, X.shape[0]) и здесь X.flatten().reshape(12288, 209) вы расплющиваете, а затем придаете не плоскую форму, как и в последнем случае. какой из них дал желаемый результат?

2. Это одно изображение или 209 изображений? Только 1 учитывает порядок элементов в блоках (64,64).

Ответ №1:

Во-первых, мы отмечаем, что мы можем думать о reshape как о «вытягивании» массива в длинную строку элементов, а затем «переупаковке» их путем заполнения осей в определенном порядке. Порядок. Рассмотрим следующий массив:

 array = np.arange(48).reshape(6, 4, 2)
  

Этот массив будет содержать элементы от 0 до 47 и иметь форму (6, 4, 2) . Эту форму можно интерпретировать просто как порядок, в котором элементы были размещены на каждой оси.

Например:

 >>> print(array[0, :, :])
[[0 1]
 [2 3]
 [4 5]
 [6 7]]
  

Длина первой оси равна 48 / 4 / 2 = 8 , и поэтому этот фрагмент должен содержать 8 элементов. Поскольку это первая ось, она просто состоит из первых 8 элементов исходного кода в рабочем порядке.

Далее нам нужно решить, как эти 8 элементов будут заполнять 2 другие оси. Эти 8 элементов можно рассматривать как образующие собственный подмассив с формой (4, 2) . Поскольку первая ось (в подмассиве) должна быть заполнена первой, мы ожидаем, что она будет содержать пары элементов в рабочем порядке:

 >>> for i in range(array.shape[1]):
...    print(array[0, i, :])
[0 1]
[2 3]
[4 5]
[6 7]
  

Сравните это с последней осью:

 >>> for i in range(array.shape[2]):
...     print(array[0, :, i])
[0 2 4 6]
[1 3 5 7]
  

Затем второй фрагмент array[1, :, :] будет содержать следующие 8 элементов, или от 8 до 15, причем этот процесс повторяется до тех пор, пока не останется ни одного элемента.

Теперь обратите внимание, что этап «извлечения» похож на flatten() . Поэтому неудивительно, что 2 и 3 одинаковы:

 X = np.random.rand(209, 64, 64, 3)
print(X.flatten().reshape(12288, 209) == X.reshape(12288, 209)).all(axis=None)
  

Вывод:

 True
  

Таким образом, беглое сравнение с 1. покажет, что 1. является нечетным. Обратите внимание, что X.shape[0] равно 209 (длина X первой оси). Следовательно, 1. эквивалентно X.reshape(209, -1).T (-1 является сокращением для вывода последней оси и .T транспонирует массив).

Следовательно, они отличаются не своей формой, а порядком, в котором элементы были размещены по осям. 2. и 3. начатый с одной и той же точки, сглаженный массив, состоящий из элементов в первой строке, затем второй, затем третьей и так далее. Следовательно, (0, 0) содержит первый исходный элемент, а затем (0, 1) , (0, 2)

С другой стороны, при выполнении изменения формы в 1. и затем транспонировании этот линейный порядок элементов больше не соблюдается. Вместо этого сначала заполняются столбцы, которые (0, 0) содержат первый исходный элемент, а затем (1, 0) и так далее.

Ответ №2:

Конечный результат всех трех операций будет одинаковым. Эти три метода — это всего лишь три разных способа достижения одного и того же результата. Но должен же быть какой-то подвох, верно? Да, есть.

  1. X.изменение формы(X.shape[0], -1).T: Когда вы передаете -1 в качестве оси операции изменения формы, вы говорите Hey, here is my array. I am giving you the first dimension(X.shape[0] in this case), figure out yourself what the second dimesnion should be! . Поскольку reshape это просто другой способ упорядочивания элементов, numpy будет учитываться все остальные измерения, и для второго измерения потребуется произведение фигур.
  2. X.flatten().reshape(12288, 209): Вот вы говорите, что Yes, I know the shape of the ndarray I want но вместо того, чтобы напрямую изменять форму, вы сначала flattened ее вычеркнули, а затем переставили элементы.
  3. X. изменение формы (12288, 209): Это то же самое, что и второй вариант, но знайте, что вы не выполняете redundant flatten операцию по изменению формы вашего ndarray.

Что еще?: Затраченное время

 a = np.random.rand(2,3,4) 
%timeit d = a.reshape(a.shape[0], -1)
382 ns ± 8.94 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit b = a.flatten().reshape(2,12)
963 ns ± 11.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit c = a.reshape(2,12)
272 ns ± 4.61 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

print(b.shape, c.shape, d.shape)
(2, 12) (2, 12) (2, 12)

print((a.flatten()==b.flatten()).all())
True

print((a.flatten()==c.flatten()).all())
True

print((a.flatten()==d.flatten()).all())
True


  

Комментарии:

1. Это неправильно. Вы вызываете all для каждого из массивов, который вернет True , если все их элементы отличны от нуля, и, следовательно, вы печатаете результат (True == True == True == True) . Вы хотите (a == b).all() and (b == c).all() and (c == d).all() .

2. Почему вы сглаживали в тестах? Почему бы не использовать np.allclose ?

Ответ №3:

(209, 64, 64, 3) выглядит как массив изображений, по 209 изображений в каждом (64,64,3). Изменение формы должно поддерживать эти элементы изображения вместе и в порядке.

Иллюстрирую на примере меньшего размера:

 In [845]: arr = np.arange(24).reshape(4,2,3)                                    
In [846]: arr                                                                   
Out[846]: 
array([[[ 0,  1,  2],
        [ 3,  4,  5]],

       [[ 6,  7,  8],
        [ 9, 10, 11]],

       [[12, 13, 14],
        [15, 16, 17]],

       [[18, 19, 20],
        [21, 22, 23]]])
In [847]: arr[1]                                                                
Out[847]: 
array([[ 6,  7,  8],
       [ 9, 10, 11]])
  

Наивная перестройка:

 In [848]: x = arr.reshape(6,4)                                                  
In [849]: x                                                                     
Out[849]: 
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15],
       [16, 17, 18, 19],
       [20, 21, 22, 23]])
In [850]: x[:,1]                                                                
Out[850]: array([ 1,  5,  9, 13, 17, 21])
  

При выборе столбца получается другой набор чисел, чем в Out[847] . [6,7,8] теперь разделите между строками 2 и 3. И [1,5,9...] взяты отовсюду arr .

Изменение формы с последующим переносом: (4,2,3)=>(4,(2*3))=>(4,6)=>(6,4):

 In [851]: x = arr.reshape(4,6).T                                                
In [852]: x                                                                     
Out[852]: 
array([[ 0,  6, 12, 18],
       [ 1,  7, 13, 19],
       [ 2,  8, 14, 20],
       [ 3,  9, 15, 21],
       [ 4, 10, 16, 22],
       [ 5, 11, 17, 23]])
In [853]: x[:,1]                                                                
Out[853]: array([ 6,  7,  8,  9, 10, 11])
In [855]: x[:,1].reshape(2,3)                                                   
Out[855]: 
array([[ 6,  7,  8],
       [ 9, 10, 11]])
  

Формально reshape просто требуется, чтобы общее количество элементов не менялось. Но, как показано здесь, подгруппы измерений также должны оставаться неизменными, (4,2,3) => (4,6) или (8,3) , не (6,4) . В противном случае вы рискуете перегруппировать значения.

С just reshape и transpose, x по-прежнему view , совместно используется буфер данных с arr . Но order это другое. Дальнейшее изменение формы (например, ravel ), скорее всего, приведет к созданию копии.

 In [859]: arr.__array_interface__['data']                                       
Out[859]: (36072624, False)
In [860]: x.__array_interface__['data']                                         
Out[860]: (36072624, False)
In [861]: x.ravel()                                                             
Out[861]: 
array([ 0,  6, 12, 18,  1,  7,...])
In [862]: x.ravel(order='F')                                                    
Out[862]: 
array([ 0,  1,  2,  3,  4,  5, ...])