В предыдущем посте Кодирование нейронной сети - прямое и обратное распространение мы реализовали как прямое, так и обратное распространение в numpy. Однако реализация обратного распространения ошибки с нуля обычно более склонна к ошибкам / ошибкам. Следовательно, перед запуском нейронной сети на обучающих данных необходимо проверить правильность нашей реализации обратного распространения ошибки. Прежде чем мы начнем, давайте еще раз посмотрим, что такое обратное распространение: мы перебираем узлы в обратном топологическом порядке, начиная с последнего узла, чтобы вычислить производную стоимости по отношению к хвосту каждого ребра. Другими словами, мы вычисляем производную функции стоимости по всем параметрам, то есть ∂J / ∂θ, где θ представляет параметры модели.

Способ тестирования нашей реализации - вычисление числовых градиентов и сравнение их с градиентами обратного распространения (аналитическое). Есть два способа вычисления числовых градиентов:

  • Правая форма:

  • Двусторонняя форма (см. Рисунок 1):

Двусторонняя форма аппроксимации производной ближе, чем правая форма. Давайте проиллюстрируем это на следующем примере, используя функцию f (x) = x², взяв ее производную в x = 3.

  • Аналитическая производная:

  • Двусторонняя числовая производная:

  • Правая числовая производная:

Как мы видим выше, разница между аналитической производной и двусторонним числовым градиентом практически равна нулю; однако разница между аналитической производной и правой производной составляет 0,01. Поэтому мы будем использовать двусторонний эпсилон-метод для вычисления числовых градиентов.

Кроме того, мы нормализуем разницу между числовыми значениями. градиенты и аналитические градиенты по следующей формуле: Если разница ≤ 10e-7, то наша реализация в порядке; в противном случае у нас где-то есть ошибка, и нам придется вернуться и вернуться к коду обратного распространения ошибки.

Ниже приведены шаги, необходимые для реализации проверки градиента:

  1. Выберите случайное количество примеров из обучающих данных, чтобы использовать их при вычислении как числовых, так и аналитических градиентов.
  • Не используйте все примеры в обучающих данных, потому что проверка градиента выполняется очень медленно.

2. Инициализировать параметры.

3. Вычислить прямое распространение и стоимость кросс-энтропии.

4. Вычислите градиенты, используя нашу реализацию обратного распространения.

5. Вычислите числовые градиенты, используя метод двустороннего эпсилон.

6. Вычислите разницу между числовыми и аналитическими градиентами.

Мы будем использовать функции, которые мы написали в статье «Кодирование нейронной сети - прямое и обратное распространение», для инициализации параметров, вычисления прямого и обратного распространения, а также стоимости кросс-энтропии.

Давайте сначала импортируем данные.

# Loading packages
import sys
import h5py
import matplotlib.pyplot as plt
import numpy as np
from numpy.linalg import norm
import seaborn as sns sys.path.append("../scripts/")
from coding_neural_network_from_scratch import (initialize_parameters, L_model_forward, L_model_backward, compute_cost)
# Import the data
train_dataset = h5py.File("../data/train_catvnoncat.h5")
X_train = np.array(train_dataset["train_set_x"]).T
y_train = np.array(train_dataset["train_set_y"]).T
X_train = X_train.reshape(-1, 209)
y_train = y_train.reshape(-1, 209)
X_train.shape, y_train.shape
((12288, 209), (1, 209))

Затем мы напишем вспомогательные функции, которые упростят преобразование словарей параметров и градиентов в векторы, а затем повторно конвертируют их обратно в словари.

def dictionary_to_vector(params_dict):
L = len(layers_dims)
parameters = {}
k = 0
for l in range(1, L):
# Create temp variable to store dimension used on each layer
w_dim = layers_dims[l] * layers_dims[l - 1]
b_dim = layers_dims[l]
# Create temp var to be used in slicing parameters vector
temp_dim = k + w_dim
# add parameters to the dictionary
parameters["W" + str(l)] = vector[ k:temp_dim].reshape(layers_dims[l], layers_dims[l - 1]) 
parameters["b" + str(l)] = vector[ temp_dim:temp_dim + b_dim].reshape(b_dim, 1)
k += w_dim + b_dim
return parameters
def gradients_to_vector(gradients):
# Get the number of indices for the gradients to iterate over valid_grads = [key for key in gradients.keys() if not key.startswith("dA")]
L = len(valid_grads)// 2
count = 0
# Iterate over all gradients and append them to new_grads list
for l in range(1, L + 1):
if count == 0:
new_grads = gradients["dW" + str(l)].reshape(-1, 1)
new_grads = np.concatenate((new_grads, gradients["db" + str(l)].reshape(-1, 1)))
else:
new_grads = np.concatenate((new_grads, gradients["dW" + str(l)].reshape(-1, 1)))
new_grads = np.concatenate( (new_grads, gradients["db" + str(l)].reshape(-1, 1)))
count += 1
return new_grads

Наконец, мы напишем функцию проверки градиента, которая вычислит разницу между аналитическим и численным градиентами и сообщит нам, правильна ли наша реализация обратного распространения. Мы случайным образом выберем 1 пример, чтобы вычислить разницу.

def forward_prop_cost(X, parameters, Y, hidden_layers_activation_fn="tanh"):
# Compute forward prop
AL, _ = L_model_forward(X, parameters, hidden_layers_activation_fn) # Compute cost
cost = compute_cost(AL, Y)
return cost 
def gradient_check( parameters, gradients, X, Y, layers_dims, epsilon=1e-7, hidden_layers_activation_fn="tanh"):
# Roll out parameters and gradients dictionaries
parameters_vector = dictionary_to_vector(parameters) gradients_vector = gradients_to_vector(gradients)
# Create vector of zeros to be used with epsilon
grads_approx = np.zeros_like(parameters_vector)
for i in range(len(parameters_vector)):
# Compute cost of theta + epsilon
theta_plus = np.copy(parameters_vector)
theta_plus[i] = theta_plus[i] + epsilon
j_plus = forward_prop_cost( X, vector_to_dictionary(theta_plus, layers_dims), Y, hidden_layers_activation_fn)
# Compute cost of theta - epsilon
theta_minus = np.copy(parameters_vector)
theta_minus[i] = theta_minus[i] - epsilon
j_minus = forward_prop_cost( X, vector_to_dictionary(theta_minus, layers_dims), Y, hidden_layers_activation_fn)
# Compute numerical gradients
grads_approx[i] = (j_plus - j_minus) / (2 * epsilon)
# Compute the difference of numerical and analytical gradients numerator = norm(gradients_vector - grads_approx)
denominator = norm(grads_approx) + norm(gradients_vector)
difference = numerator / denominator
if difference > 10e-7:
print ("\033[31mThere is a mistake in back-propagation " +\ "implementation. The difference is: {}".format(difference))
else:
print ("\033[32mThere implementation of back-propagation is fine! "+\ "The difference is: {}".format(difference))
return difference
# Set up neural network architecture
layers_dims = [X_train.shape[0], 5, 5, 1]
# Initialize parameters parameters = initialize_parameters(layers_dims)
# Randomly selecting 1 example from training data
perms = np.random.permutation(X_train.shape[1])
index = perms[:1]
# Compute forward propagation
AL, caches = L_model_forward(X_train[:, index], parameters, "tanh") 
# Compute analytical gradients
gradients = L_model_backward(AL, y_train[:, index], caches, "tanh") 
# Compute difference of numerical and analytical gradients difference = gradient_check(parameters, gradients, X_train[:, index], y_train[:, index], layers_dims)
There implementation of back-propagation is fine! The difference is: 3.0220555297630148e-09

Поздравляю! Наша реализация верна :)

Заключение

Ниже приведены некоторые ключевые выводы:

  1. Двусторонний числовой градиент лучше аппроксимирует аналитические градиенты, чем правая форма.

2. Поскольку проверка градиента выполняется очень медленно:

  • Примените его к одному или нескольким обучающим примерам.
  • Отключите его при обучении нейронной сети, убедившись, что реализация обратного распространения ошибок верна.

3. Не работает проверка градиента при использовании метода выпадения. Используйте keep-prob = 1, чтобы проверить проверку градиента, а затем изменить его при обучении нейронной сети.

4. Эпсилон = 10e-7 - обычное значение, используемое для разницы между аналитическим градиентом и числовым градиентом. Если разница меньше 10e-7, то реализация обратного распространения ошибки верна.

5. Благодаря фреймворкам Deep Learning, таким как Tensorflow и Pytorch, мы можем редко реализовывать обратное распространение, потому что такие фреймворки вычисляют это за нас; тем не менее, чтобы стать хорошим специалистом в области глубокого обучения, полезно понимать, что происходит внутри.

Исходный код, создавший этот пост, можно найти здесь.

Первоначально опубликовано на сайте imaddabbura.github.io 8 апреля 2018 г.