В предыдущем посте Кодирование нейронной сети - прямое и обратное распространение мы реализовали как прямое, так и обратное распространение в numpy
. Однако реализация обратного распространения ошибки с нуля обычно более склонна к ошибкам / ошибкам. Следовательно, перед запуском нейронной сети на обучающих данных необходимо проверить правильность нашей реализации обратного распространения ошибки. Прежде чем мы начнем, давайте еще раз посмотрим, что такое обратное распространение: мы перебираем узлы в обратном топологическом порядке, начиная с последнего узла, чтобы вычислить производную стоимости по отношению к хвосту каждого ребра. Другими словами, мы вычисляем производную функции стоимости по всем параметрам, то есть ∂J / ∂θ, где θ представляет параметры модели.
Способ тестирования нашей реализации - вычисление числовых градиентов и сравнение их с градиентами обратного распространения (аналитическое). Есть два способа вычисления числовых градиентов:
- Правая форма:
- Двусторонняя форма (см. Рисунок 1):
Двусторонняя форма аппроксимации производной ближе, чем правая форма. Давайте проиллюстрируем это на следующем примере, используя функцию f (x) = x², взяв ее производную в x = 3.
- Аналитическая производная:
- Двусторонняя числовая производная:
- Правая числовая производная:
Как мы видим выше, разница между аналитической производной и двусторонним числовым градиентом практически равна нулю; однако разница между аналитической производной и правой производной составляет 0,01. Поэтому мы будем использовать двусторонний эпсилон-метод для вычисления числовых градиентов.
Кроме того, мы нормализуем разницу между числовыми значениями. градиенты и аналитические градиенты по следующей формуле: Если разница ≤ 10e-7, то наша реализация в порядке; в противном случае у нас где-то есть ошибка, и нам придется вернуться и вернуться к коду обратного распространения ошибки.
Ниже приведены шаги, необходимые для реализации проверки градиента:
- Выберите случайное количество примеров из обучающих данных, чтобы использовать их при вычислении как числовых, так и аналитических градиентов.
- Не используйте все примеры в обучающих данных, потому что проверка градиента выполняется очень медленно.
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
Поздравляю! Наша реализация верна :)
Заключение
Ниже приведены некоторые ключевые выводы:
- Двусторонний числовой градиент лучше аппроксимирует аналитические градиенты, чем правая форма.
2. Поскольку проверка градиента выполняется очень медленно:
- Примените его к одному или нескольким обучающим примерам.
- Отключите его при обучении нейронной сети, убедившись, что реализация обратного распространения ошибок верна.
3. Не работает проверка градиента при использовании метода выпадения. Используйте keep-prob = 1, чтобы проверить проверку градиента, а затем изменить его при обучении нейронной сети.
4. Эпсилон = 10e-7 - обычное значение, используемое для разницы между аналитическим градиентом и числовым градиентом. Если разница меньше 10e-7, то реализация обратного распространения ошибки верна.
5. Благодаря фреймворкам Deep Learning, таким как Tensorflow и Pytorch, мы можем редко реализовывать обратное распространение, потому что такие фреймворки вычисляют это за нас; тем не менее, чтобы стать хорошим специалистом в области глубокого обучения, полезно понимать, что происходит внутри.
Исходный код, создавший этот пост, можно найти здесь.
Первоначально опубликовано на сайте imaddabbura.github.io 8 апреля 2018 г.