[Введение] Транспонированная свертка всегда была трудна для понимания.Сегодня мы понимаем обратное распространение транспонированной свертки в простой двухслойной 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