Понимание обратного распространения транспонированных сверток в многослойных CNN (с кодом)

искусственный интеллект NumPy

[Введение] Транспонированная свертка всегда была трудна для понимания.Сегодня мы понимаем обратное распространение транспонированной свертки в простой двухслойной CNN с помощью подробных примеров вывода и кодов.

Компиляция | Экспертиза

Участвовать | Инин, Сяовэнь

Сегодня мы собираемся обучить простую CNN с двумя сверточными слоями, как показано ниже.

 

источник вдохновения



Кукуруза на тарелке напоминает мне принцип деконволюции в процессе обратного распространения CNN.

Красная рамка — выходное изображение 2*2.

Зеленая рамка — ядро ​​свертки 3*3.

Синее поле - это входное изображение 4 * 4.

«Поскольку мы получаем выходное изображение 2 * 2 после выполнения свертки для изображения 4 * 4, нам нужно выполнить некоторые операции с выходным изображением 2 * 2, чтобы получить изображение с 4 * 4 при выполнении обратного распространения».

Но кукуруза заставила меня понять, что цель не в том, чтобы восстановить исходное изображение. Вместо этого следует получить частоту ошибок для каждого веса в сети. Принимая во внимание, что в случае многослойной CNN нам необходимо распространить эту частоту ошибок обратно. Позвольте мне попытаться объяснить, что я имею в виду, на конкретном примере и коде.

сетевая структура



Как показано выше, структура сети очень проста, всего два слоя свертки и один полносвязный слой. Обратите внимание, что при выполнении свертки нам нужно транспонировать (повернуть) ядро ​​свертки на 180 градусов, обратите внимание на зеленую рамку на изображении выше.

Также обратите внимание, что я не нарисовал активационный слой для простоты. Но в коде я использовал tanh() или archtan() в качестве функции активации.

прямое распространение



ПРИМЕЧАНИЕ. Автор допустил ошибку в столбцах, и ему пришлось поменять местами два столбца, на которые указывает зеленая стрелка.

Итак, как видно выше, операцию свертки можно записать в одну строку. По причинам, которые я объясню позже, обратите внимание на переменные в красной рамке, которые являются входными данными для следующего слоя. Эта информация важна при выполнении обратного распространения.

Обратное распространение (с зелеными весами на изображении выше)

 

Желтое поле представляет скорость обучения, а все обратное распространение — стандартный процесс. Я также записал уравнение обновления градиента. . Наконец, обратите внимание на символ «k» в красной рамке, который я постоянно использую для обозначения (Out — Y).

Обратное распространение (с красными весами на изображении выше)

Красная коробка → (Out - Y)

Желтое поле → скорость обучения

Черный ящик → повернуть ядро ​​на 180 градусов (или транспонировать) перед операцией свертки (помните, что в операции свертки мы поворачиваем ядро ​​свертки).

Кроме фиолетовой коробки, все очень просто и понятно, для чего нужна фиолетовая коробка?

Фиолетовое поле → Поверните матрицу, чтобы она подошла для расчета производной каждой величины веса.

Теперь возникает вопрос, почему? Зачем мы это делаем?

Помните, я говорил вам, ребята, обращать внимание на ввод каждого слоя? Итак, вернемся снова.

Пожалуйста, внимательно посмотрите на цветные коробки.

Оранжевое поле → ввод W(2,2) умножается на красный W

Светло-зеленое поле → Умножение красного ввода W (2,1)

Синий прямоугольник → ввод W(1,2) умножается на красный W

Розовое поле → умножает красный ввод W() 1,1)

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

Цифры в темно-зеленых прямоугольниках -> зеленые веса.

Как видно, когда производная вычисляется для каждого красного веса, мы видим, что координаты XX меняются в зависимости от ввода. Нам нужно сопоставить эти координаты с каждым весом, поэтому мы поворачиваем матрицу на 180 градусов.

  • Обратное распространение синих весов, часть 1

корзина → вычислить свертку между (K * зеленый вес) и (заполнить красный вес)

Оранжевое поле → снова поверните матрицу, чтобы получить градиент каждого веса.

Черный ящик → повернуть ядро ​​свертки перед операцией свертки

Теперь возникает вопрос, а почему Padding (фиолетовая рамка)? Зачем нам нужно заполнять красные гири?

Мы объясним это позже.

 

  • Обратное распространение синих весов, часть 2

синий квадрат → матрица, рассчитанная в части 1

Черный ящик → транспонировать ядро ​​свертки перед операцией свертки

оранжевые, бирюзовые, синие, розовые квадраты → вычислить градиент каждого синего веса

Выше приведен более пристальный взгляд на повернутое ядро ​​свертки при выполнении операции свертки. Но теперь давайте снова посмотрим на ввод. 

Опять же, поскольку Out можно записать в одну строку, градиент синего веса выглядит так:

Зеленый ящик → зеленый вес

Оранжевое поле → градиент синего веса W(2,2)

Розовая коробка → градиент синего веса W(1,1)

Итак, мы снова поворачиваем (или транспонируем) матрицу, чтобы соответствовать градиенту каждого веса.

Кроме того, теперь, когда причина, по которой мы заполняем красные веса, очевидна, заключается в вычислении градиента для каждого веса, я снова покажу вам, что я имею в виду, заполняя красные веса (см. Раздел с фиолетовой звездочкой).

 

функция активации



Зеленый квадрат → производная функции активации, поскольку они имеют одинаковую размерность, мы можем выполнить поэлементное умножение

Красное поле → поверните ядро ​​свертки, чтобы оно соответствовало градиенту

Корзина → Заполните красные гири нулями (с именем W2)

код



import numpy as np,sys# Func: Only for 2D convolution from scipy.signal import convolve2dfrom sklearn.utils import shuffle# Func: For Back propagation on Max Poolingfrom scipy.ndimage.filters import maximum_filterimport skimage.measurenp.random.seed(12314)def ReLU(x):    mask  = (x >0) * 1.0     return mask * xdef d_ReLU(x):    mask  = (x >0) * 1.0     return mask def tanh(x):    return np.tanh(x)def d_tanh(x):    return 1 - np.tanh(x) ** 2def arctan(x):    return np.arctan(x)def d_arctan(x):    return 1 / ( 1 + x ** 2)def log(x):    return 1 / (1 + np.exp(-1 * x))def d_log(x):    return log(x) * ( 1 - log(x))# 1. Prepare Datanum_epoch = 1000learning_rate = 0.1total_error = 0x1 = np.array([    [0,0,0,-1],    [-1,0,-1,0],    [-1,0,-1,-1],    [1,0,-1,-1]    ])x2 = np.array([    [0,0,0,0],    [0,0,-1,0],    [0,0,0,0],    [1,0,0,-1]    ])x3 = np.array([    [0,0,0,-1],    [0,0,-1,0],    [-1,0,1,1],    [1,0,-1,1]    ])x4 = np.array([    [0,0,0,1],    [1,0,1,0],    [1,0,1,1],    [1,0,1,1]    ])image_label=np.array([    [-1.42889927219],    [-0.785398163397],    [0.0],    [1.46013910562]    ])image_matrix = np.array([x1,x2,x3,x4])w1 = (np.random.randn(2,2) * 4.2 )-0.1w2 = (np.random.randn(2,2)* 4.2)-0.1w3 = (np.random.randn(4,1)* 4.2)-0.1print('Prediction Before Training')predictions = np.array([])for image_index in range(len(image_matrix)):    current_image  = image_matrix[image_index]    l1 = convolve2d(current_image,w1,mode='valid')    l1A = tanh(l1)    l2 = convolve2d(l1A,w2,mode='valid')    l2A = arctan(l2)    l3IN = np.expand_dims(l2A.ravel(),0)    l3 = l3IN.dot(w3)    l3A = arctan(l3)    predictions = np.append(predictions,l3A)print('---Groud Truth----')print(image_label.T)print('--Prediction-----')print(predictions.T)print('--Prediction Rounded-----')print(np.round(predictions).T)print("\n")for iter in range(num_epoch):    for current_image_index in range(len(image_matrix)):                current_image = image_matrix[current_image_index]        current_image_label = image_label[current_image_index]        l1 = convolve2d(current_image,w1,mode='valid')        l1A = tanh(l1)        l2 = convolve2d(l1A,w2,mode='valid')        l2A = arctan(l2)        l3IN = np.expand_dims(l2A.ravel(),0)        l3 = l3IN.dot(w3)        l3A = arctan(l3)        cost = np.square(l3A - current_image_label).sum() * 0.5        total_error += cost        grad_3_part_1 = l3A - current_image_label        grad_3_part_2 = d_arctan(l3)        grad_3_part_3 =l3IN        grad_3 =  grad_3_part_3.T.dot( grad_3_part_1 * grad_3_part_2)        grad_2_part_IN = np.reshape((grad_3_part_1 * grad_3_part_2).        dot(w3.T),(2,2))        grad_2_part_1 = grad_2_part_IN        grad_2_part_2 = d_arctan(l2)        grad_2_part_3 = l1A        grad_2=  np.rot90( convolve2d(grad_2_part_3,np.rot90        (grad_2_part_1 * grad_2_part_2,2),mode='valid')     ,2)                grad_1_part_IN_pad_weight = np.pad(w2,1,mode='constant')        grad_1_part_IN = np.rot90(grad_2_part_1 * grad_2_part_2,2)        grad_1_part_1 = convolve2d(grad_1_part_IN_pad_weight,grad_1_part_IN,mode='valid')        grad_1_part_2 = d_tanh(l1)        grad_1_part_3 = current_image        grad_1 =  np.rot90( convolve2d(grad_1_part_3,np.        rot90(grad_1_part_1 * grad_1_part_2,2),mode='valid')     ,2)        w1 = w1 - learning_rate * grad_1        w2 = w2 - learning_rate * grad_2        w3 = w3 - learning_rate * grad_3            #print('Current iter:  ', iter, ' current cost: ', cost, end='\r')    total_error = 0        print('\n\n')print('Prediction After Training')predictions = np.array([])for image_index in range(len(image_matrix)):    current_image  = image_matrix[image_index]    l1 = convolve2d(current_image,w1,mode='valid')    l1A = tanh(l1)    l2 = convolve2d(l1A,w2,mode='valid')    l2A = arctan(l2)    l3IN = np.expand_dims(l2A.ravel(),0)    l3 = l3IN.dot(w3)    l3A = arctan(l3)    predictions = np.append(predictions,l3A)print('---Groud Truth----')print(image_label.T)print('--Prediction-----')print(predictions.T)print('--Prediction Rounded-----')print(np.round(predictions).T)print("\n")

Ссылка на полный код: https://repl.it/@Jae_DukDuk/transpose-conv

Оригинальная ссылка:

https://towardsdatascience.com/only-numpy-understanding-back-propagation-for-transpose-convolution-in-multi-layer-cnn-with-c0a07d191981