Gradient Checking에 대해서 알아보자. 이는 back propagation의 검증을 위해 꼭 필요한 단계이다.
import numpy as np
from testCases import *
from public_tests import *
from gc_utils import sigmoid, relu, dictionary_to_vector, vector_to_dictionary, gradients_to_vector
%load_ext autoreload
%autoreload 2
먼저 1D Gradient checking부터 확인해보자.
알고있는 1D 선형 함수를 생각해보자.
세타라는 parameter가 있고, x를 input으로 하는 선형함수이다. 여기서 반대로 J와 J의 미분값을 계산해야 할 때, gradient checking을 이용해 J가 맞는지 아닌지 확인할 수 있다.
위 그림은 1D 선형 함수 모델의 진행 순서를 보여준다.
순서대로 함수를 빌드해보자.
J를 구하기 위한 Forward propagation함수를 만들어보자.
def forward_propagation(x, theta):
J = x*theta
return J
x, theta = 2, 4
J = forward_propagation(x, theta)
print ("J = " + str(J))
dtheta
값은 아래처럼 구할 수 있다.
def backward_propagation(x, theta):
dtheta = forward_propagation(x, theta)/theta
return dtheta
x, theta = 2, 4
dtheta = backward_propagation(x, theta)
print ("dtheta = " + str(dtheta))
이제 이 부분에서 backward_propagation()
함수가 정확히 dtheta값을 계산했는지 알아보기 위해 gradient checking을 도입한다.
gradapprox를 계산하기 위해서는
위 식을 대입하자.
그리고 backward_propagation
을 이용해서 gradient를 계산하고 그 값을 grad
에 넣는다.
마지막으로 gradapprox 와 grad의 difference를 아래의 식을 이용해 계산한다.
위 식을 계산하기 위해서 np.linalg.norm
을 이용한다.
함수를 완성해보자.
def gradient_check(x, theta, epsilon=1e-7, print_msg=False):
theta_plus = theta + epsilon
theta_minus = theta - epsilon
J_plus = forward_propagation(x, theta_plus)
J_minus = forward_propagation(x, theta_minus)
gradapprox = (J_plus - J_minus)/(2*epsilon)
grad = backward_propagation(x, theta)
numerator = np.linalg.norm(grad-gradapprox)
denominator = np.linalg.norm(grad)+np.linalg.norm(gradapprox)
difference = numerator/denominator
if print_msg:
if difference > 2e-7:
print ("\033[93m" + "There is a mistake in the backward propagation! difference = " + str(difference) + "\033[0m")
else:
print ("\033[92m" + "Your backward propagation works perfectly fine! difference = " + str(difference) + "\033[0m")
return difference
x, theta = 2, 4
difference = gradient_check(2,4, print_msg=True)
difference가 10^-7보다 작으므로 backward_propagation에서 계산이 맞았다.
이제 N-D에서 Gradient checking을 확인해보자.
모델의 순서도다. 1D에 비해 parameter들이 많아지긴 했지만 전체적인 함수부분은 똑같다. 자세히 살펴보자.
def forward_propagation_n(X, Y, parameters):
m = X.shape[1]
W1 = parameters["W1"]
b1 = parameters["b1"]
W2 = parameters["W2"]
b2 = parameters["b2"]
W3 = parameters["W3"]
b3 = parameters["b3"]
Z1 = np.dot(W1, X) + b1
A1 = relu(Z1)
Z2 = np.dot(W2, A1) + b2
A2 = relu(Z2)
Z3 = np.dot(W3, A2) + b3
A3 = sigmoid(Z3)
# Cost
log_probs = np.multiply(-np.log(A3),Y) + np.multiply(-np.log(1 - A3), 1 - Y)
cost = 1. / m * np.sum(log_probs)
cache = (Z1, A1, W1, b1, Z2, A2, W2, b2, Z3, A3, W3, b3)
return cost, cache
def backward_propagation_n(X, Y, cache):
m = X.shape[1]
(Z1, A1, W1, b1, Z2, A2, W2, b2, Z3, A3, W3, b3) = cache
dZ3 = A3 - Y
dW3 = 1. / m * np.dot(dZ3, A2.T)
db3 = 1. / m * np.sum(dZ3, axis=1, keepdims=True)
dA2 = np.dot(W3.T, dZ3)
dZ2 = np.multiply(dA2, np.int64(A2 > 0))
dW2 = 1. / m * np.dot(dZ2, A1.T)
db2 = 1. / m * np.sum(dZ2, axis=1, keepdims=True)
dA1 = np.dot(W2.T, dZ2)
dZ1 = np.multiply(dA1, np.int64(A1 > 0))
dW1 = 1. / m * np.dot(dZ1, X.T)
db1 = 1. / m * np.sum(dZ1, axis=1, keepdims=True)
gradients = {"dZ3": dZ3, "dW3": dW3, "db3": db3,
"dA2": dA2, "dZ2": dZ2, "dW2": dW2, "db2": db2,
"dA1": dA1, "dZ1": dZ1, "dW1": dW1, "db1": db1}
return gradients
여기서 만약에 숫자를 잘못 입력하게되면 아래에서 오류가 나게된다.
위에서 작성한 backward propagation()
함수는 오류를 수정한 버전이다.
아래 gradient check로 맞나 확인해보자.
def gradient_check_n(parameters, gradients, X, Y, epsilon=1e-7, print_msg=False):
parameters_values, _ = dictionary_to_vector(parameters)
grad = gradients_to_vector(gradients)
num_parameters = parameters_values.shape[0]
J_plus = np.zeros((num_parameters, 1))
J_minus = np.zeros((num_parameters, 1))
gradapprox = np.zeros((num_parameters, 1))
for i in range(num_parameters):
theta_plus = np.copy(parameters_values)
theta_plus[i] = theta_plus[i] + epsilon
J_plus[i],_ = forward_propagation_n(X, Y, vector_to_dictionary(theta_plus))
theta_minus = np.copy(parameters_values)
theta_minus[i] = theta_minus[i] - epsilon
J_minus[i], _ = forward_propagation_n(X, Y, vector_to_dictionary(theta_minus))
gradapprox[i] = (J_plus[i]-J_minus[i])/(2*epsilon)
numerator = np.linalg.norm(grad-gradapprox)
denominator = np.linalg.norm(grad)+np.linalg.norm(gradapprox)
difference = numerator/denominator
# YOUR CODE ENDS HERE
if print_msg:
if difference > 2e-7:
print ("\033[93m" + "There is a mistake in the backward propagation! difference = " + str(difference) + "\033[0m")
else:
print ("\033[92m" + "Your backward propagation works perfectly fine! difference = " + str(difference) + "\033[0m")
return difference
X, Y, parameters = gradient_check_n_test_case()
cost, cache = forward_propagation_n(X, Y, parameters)
gradients = backward_propagation_n(X, Y, cache)
difference = gradient_check_n(parameters, gradients, X, Y, 1e-7, True)
expected_values = [0.2850931567761623, 1.1890913024229996e-07]
assert not(type(difference) == np.ndarray), "You are not using np.linalg.norm for numerator or denominator"
assert np.any(np.isclose(difference, expected_values)), "Wrong value. It is not one of the expected values"
만약 위에 backward propagation()
에서 실수를 했다면 위 함수를 돌렸을 때
이런 비슷한 결과가 출력될 것이다.
그렇다면 다시backward propagation()
함수로 돌아가 오류를 수정하면 된다.
수정하고 다시 돌려보면
오류가 없다는 얘기이다.
주의할 점은 Gradient checking은 느리기 때문에 모든 iteration에서 사용할 필요가 없다. 일반적으로 코드가 올바른지 확인할 때만 쓰이며, 확인 후에는 코드를 끄고 수정한 backprop만 사용한다.