6.梯度检验

王哲_UJN_MGG_AI / 2023-09-05 / 原文

import numpy as np
from testCases import *
from gc_utils import sigmoid, relu, dictionary_to_vector, vector_to_dictionary, gradients_to_vector

#########################################################

这段代码看起来导入了一些Python库和模块,以及一些自定义的测试用例和函数。

  1. import numpy as np: 这行代码导入了名为numpy的Python库,并将其重命名为np,以便在代码中更方便地使用。NumPy是一个用于科学计算的强大库,它提供了多维数组对象和许多用于操作这些数组的函数。

  2. from testCases import *: 这行代码导入了一个名为testCases的自定义模块中的所有内容。通常,testCases 模块可能包含一些测试数据和用于测试特定函数或算法的测试用例。

  3. from gc_utils import sigmoid, relu, dictionary_to_vector, vector_to_dictionary, gradients_to_vector: 这行代码从一个名为gc_utils的自定义模块中导入了一些特定的函数。这些函数包括:

    • sigmoid: 一个用于计算S形函数(sigmoid函数)的函数。
    • relu: 一个用于计算修正线性单元(ReLU)函数的函数。
    • dictionary_to_vector: 用于将字典中的参数转换为向量的函数。
    • vector_to_dictionary: 用于将向量转换为字典中的参数的函数。
    • gradients_to_vector: 用于将梯度值转换为向量的函数。

这段代码看起来是为了在后续的代码中使用这些函数和模块的功能。

#########################################################

def forward_propagation(x, theta):

J = np.dot(theta, x)

return J

#########################################################

这是一个名为 forward_propagation 的函数,用于执行前向传播(forward propagation)操作。

  • x: 这是一个输入向量或矩阵,表示神经网络的输入数据。通常,这是一个包含特征值的向量。

  • theta: 这是一个参数向量或矩阵,表示神经网络的权重参数。这些参数用于计算前向传播中的预测值。

  • J: 这是函数的返回值,它表示前向传播的输出或预测结果。具体来说,它是通过计算 thetax 的点积(dot product)得到的。

在神经网络中,前向传播是用于计算模型的预测输出的过程。这个函数简化了前向传播的实现,只计算了点积,而没有经过激活函数或其他复杂的运算。通常,在神经网络中,前向传播还会涉及到激活函数的应用,但这个函数似乎是一个简化版本,仅用于说明目的。

#########################################################

x, theta = 2, 4
J = forward_propagation(x, theta)
print ("J = " + str(J))

#########################################################

这段代码执行以下步骤:

  1. x, theta = 2, 4: 这一行代码定义了两个变量 xtheta,并为它们赋值。具体来说,x 被赋值为2,而 theta 被赋值为4。

  2. J = forward_propagation(x, theta): 这一行代码调用了 forward_propagation 函数,传递了 xtheta 作为参数。函数将根据传入的参数计算前向传播结果,并将结果赋值给变量 J

  3. print ("J = " + str(J)): 最后一行代码将计算得到的前向传播结果 J 打印到屏幕上。在这里, str(J) 是将 J 转换为字符串,以便与字符串 "J = " 连接在一起进行打印。

总结一下,这段代码的目的是计算给定的 xtheta 的前向传播结果,并将结果打印到屏幕上。在这个例子中,前向传播结果 J 将是 2 * 4 = 8,因此屏幕上将打印出 "J = 8"。

#########################################################

# 反向传播
def backward_propagation(x, theta):

dtheta = x
return dtheta

#########################################################

这是一个名为 backward_propagation 的函数,用于执行反向传播(backward propagation)操作。

  • x: 这是前向传播中使用的输入数据。

  • theta: 这是前向传播中使用的权重参数。

  • dtheta: 这是函数的返回值,表示反向传播的输出或梯度。具体来说,它是等于输入参数 x 的值。

在神经网络中,反向传播是用于计算梯度的过程,以便优化模型的权重参数。通常,反向传播会计算相对于损失函数的梯度,并使用梯度下降等优化算法来更新参数。

在这个函数中,梯度 dtheta 的计算非常简单,只是将输入 x 直接作为梯度的值返回。这是一个非常简化的示例,通常在实际的神经网络中,梯度计算会更加复杂,涉及到损失函数和激活函数的导数等。

#########################################################

x, theta = 2, 4
dtheta = backward_propagation(x, theta)
print ("dtheta = " + str(dtheta))

#########################################################

这段代码使用了先前定义的 backward_propagation 函数来计算反向传播的梯度,并打印输出。

  1. x, theta = 2, 4: 这一行代码定义了两个变量 xtheta,并为它们赋值。具体来说,x 被赋值为2,而 theta 被赋值为4。

  2. dtheta = backward_propagation(x, theta): 这一行代码调用了 backward_propagation 函数,传递了 xtheta 作为参数。函数将根据传入的参数计算反向传播的梯度,并将结果赋值给变量 dtheta

  3. print ("dtheta = " + str(dtheta)): 最后一行代码将计算得到的反向传播梯度 dtheta 打印到屏幕上。在这里, str(dtheta) 是将 dtheta 转换为字符串,以便与字符串 "dtheta = " 连接在一起进行打印。

在这个例子中,反向传播梯度 dtheta 将等于输入 x 的值,即 dtheta = 2。因此,在屏幕上将打印出 "dtheta = 2"。

总结一下,这段代码的目的是计算给定的 xtheta 的反向传播梯度,并将结果打印到屏幕上。

#########################################################

def gradient_check(x, theta, epsilon=1e-7):

# 利用前向传播计算出一个梯度
thetaplus = theta + epsilon
thetaminus = theta - epsilon
J_plus = forward_propagation(x, thetaplus)
J_minus = forward_propagation(x, thetaminus)
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 difference < 1e-7:
print("反向传播是正确的!")
else:
print("反向传播有问题!")

return difference

#########################################################

这是一个名为 gradient_check 的函数,用于进行梯度检查(gradient checking)。梯度检查是一种用于验证反向传播算法是否正确实现的方法。

  • x: 这是输入数据,用于进行前向传播和反向传播。

  • theta: 这是权重参数,用于计算前向传播和反向传播。

  • epsilon: 这是一个非常小的数,用于计算数值梯度的时候添加到 theta 中。它通常被设置为一个很小的正数,例如 1e-7

函数的主要步骤如下:

  1. 计算数值梯度 (gradapprox):

    • 使用 thetaepsilon 来计算 thetaplusthetaminus,分别是 theta 加上和减去 epsilon
    • 使用 thetaplusthetaminus 分别计算两个前向传播的结果 J_plusJ_minus
    • 计算数值梯度 gradapprox,它等于 (J_plus - J_minus) / (2 * epsilon)
  2. 使用反向传播计算真实梯度 (grad)。

  3. 计算两个梯度之间的差异 (difference),具体计算如下:

    • numerator 是两个梯度向量之间的欧几里德范数。
    • denominator 是两个梯度向量的欧几里德范数之和。
    • difference 等于 numerator / denominator
  4. 如果 difference 小于 1e-7,则认为反向传播实现正确,否则认为反向传播有问题。

最后,函数会返回 difference,表示数值梯度与真实梯度之间的差异。

这个函数的主要目的是帮助验证反向传播的实现是否正确,因为数值梯度是通过计算前向传播的微小变化来估算的,它应该与反向传播得到的梯度非常接近。

你可以使用这个函数来检查你的神经网络模型的梯度计算是否正确。如果 difference 很小(小于 1e-7),则反向传播实现正确。

#########################################################

x, theta = 2, 4
difference = gradient_check(x, theta)
print("difference = " + str(difference))

#########################################################

这段代码使用了先前定义的 gradient_check 函数来进行梯度检查,并打印输出梯度的差异。

  1. x, theta = 2, 4: 这一行代码定义了两个变量 xtheta,并为它们赋值。具体来说,x 被赋值为2,而 theta 被赋值为4。

  2. difference = gradient_check(x, theta): 这一行代码调用了 gradient_check 函数,传递了 xtheta 作为参数。函数将执行梯度检查,并将差异结果赋值给变量 difference

  3. print("difference = " + str(difference)): 最后一行代码将计算得到的差异 difference 打印到屏幕上。在这里, str(difference) 是将 difference 转换为字符串,以便与字符串 "difference = " 连接在一起进行打印。

梯度检查的结果将显示在屏幕上,告诉你数值梯度与真实梯度之间的差异有多大。如果差异很小(小于 1e-7),则说明反向传播的实现是正确的。否则,如果差异较大,则可能需要检查反向传播的实现是否存在问题。

总结一下,这段代码的目的是使用 gradient_check 函数来验证给定的 xtheta 的梯度计算是否正确,并打印出梯度的差异。

#########################################################

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"]

# LINEAR -> RELU -> LINEAR -> RELU -> LINEAR -> SIGMOID
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)

logprobs = np.multiply(-np.log(A3), Y) + np.multiply(-np.log(1 - A3), 1 - Y)
cost = 1. / m * np.sum(logprobs)

cache = (Z1, A1, W1, b1, Z2, A2, W2, b2, Z3, A3, W3, b3)

return cost, cache

#########################################################

这是一个名为 forward_propagation_n 的函数,用于执行神经网络的前向传播操作。

  • X: 这是输入特征的矩阵,每一列代表一个训练样本,每一行代表一个特征。

  • Y: 这是与输入样本对应的标签,用于训练神经网络。

  • parameters: 这是包含神经网络权重和偏差参数的字典,包括 W1, b1, W2, b2, W3, b3 等参数。

函数的主要步骤如下:

  1. 获取样本数量 m,以及各层的权重 W 和偏差 b

  2. 执行前向传播计算:

    • 计算第一层的线性组合 Z1,并通过激活函数 ReLU 得到 A1
    • 计算第二层的线性组合 Z2,并通过 ReLU 激活函数得到 A2
    • 计算第三层的线性组合 Z3,并通过 Sigmoid 激活函数得到 A3
  3. 计算成本(损失) cost

    • 通过计算交叉熵损失函数来计算 logprobs
    • 对所有样本求平均,得到总体损失 cost
  4. 将中间计算结果保存在 cache 中,以便在后续的反向传播中使用。

  5. 返回 costcache

这个函数实现了一个具有三层神经网络的前向传播过程,包括两个隐藏层和一个输出层。它计算出网络的预测结果 A3 和成本 cost,用于后续的反向传播和训练。

#########################################################

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) * 2 # ~~
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 = 4. / 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_n 的函数,用于执行神经网络的反向传播操作。

  • X: 这是输入特征的矩阵,每一列代表一个训练样本,每一行代表一个特征。

  • Y: 这是与输入样本对应的标签,用于训练神经网络。

  • cache: 这是在前向传播过程中计算得到的中间结果的缓存,包括 Z1, A1, W1, b1, Z2, A2, W2, b2, Z3, A3, W3, b3 等中间变量。

函数的主要步骤如下:

  1. 获取样本数量 m,以及在前向传播中计算得到的各层的中间变量 ZAWb

  2. 计算反向传播中的梯度:

    • 针对输出层,计算 dZ3A3 - YdW3 为输出层权重 W3 对应的梯度,db3 为输出层偏差 b3 对应的梯度。
    • 针对第二隐藏层,计算 dA2 为上一层梯度 dZ3 与权重 W3 的乘积,然后计算 dZ2dA2 乘以 ReLU 激活函数的导数(根据 A2 > 0 来计算),dW2 为权重 W2 对应的梯度,db2 为偏差 b2 对应的梯度。
    • 针对第一隐藏层,计算 dA1 为上一层梯度 dZ2 与权重 W2 的乘积,然后计算 dZ1dA1 乘以 ReLU 激活函数的导数(根据 A1 > 0 来计算),dW1 为权重 W1 对应的梯度,db1 为偏差 b1 对应的梯度。
  3. 将计算得到的梯度保存在字典 gradients 中,以便后续的参数更新。

  4. 返回梯度字典 gradients

这个函数执行了神经网络的反向传播过程,计算了各层的梯度,用于后续的参数更新。梯度计算涉及链式法则和激活函数的导数。这个函数返回一个包含各层梯度的字典。

#########################################################

def gradient_check_n(parameters, gradients, X, Y, epsilon=1e-7):

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))

# 计算gradapprox
for i in range(num_parameters):
thetaplus = np.copy(parameters_values)
thetaplus[i][0] = thetaplus[i][0] + epsilon
J_plus[i], _ = forward_propagation_n(X, Y, vector_to_dictionary(thetaplus))

thetaminus = np.copy(parameters_values)
thetaminus[i][0] = thetaminus[i][0] - epsilon
J_minus[i], _ = forward_propagation_n(X, Y, vector_to_dictionary(thetaminus))

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

if difference > 2e-7:
print("\033[93m" + "反向传播有问题! difference = " + str(difference) + "\033[0m")
else:
print("\033[92m" + "反向传播很完美! difference = " + str(difference) + "\033[0m")

return difference

#########################################################

这是一个名为 gradient_check_n 的函数,用于执行梯度检查以验证反向传播的正确性。这个函数的主要目的是比较数值梯度(通过微小变化计算)和实际梯度(通过反向传播计算)之间的差异。

  • parameters: 这是包含神经网络权重和偏差参数的字典。

  • gradients: 这是包含通过反向传播计算得到的梯度的字典。

  • X: 这是输入特征的矩阵,每一列代表一个训练样本,每一行代表一个特征。

  • Y: 这是与输入样本对应的标签,用于训练神经网络。

  • epsilon: 这是一个非常小的数,用于计算数值梯度的时候添加到参数中。通常被设置为一个很小的正数,例如 1e-7

函数的主要步骤如下:

  1. 将参数字典 parameters 转换为参数向量 parameters_values,将梯度字典 gradients 转换为梯度向量 grad。这是为了方便计算梯度向量的范数。

  2. 获取参数向量的维度 num_parameters

  3. 初始化一些数组来存储正向传播过程中计算的损失值 J_plusJ_minus,以及数值梯度 gradapprox

  4. 计算数值梯度 gradapprox

    • 对每个参数进行循环,分别增加和减少 epsilon 后,计算正向传播的损失值 J_plusJ_minus
    • 计算数值梯度 gradapprox[i](J_plus[i] - J_minus[i]) / (2 * epsilon)
  5. 计算梯度的差异:

    • 计算 numerator 为数值梯度 gradapprox 与实际梯度 grad 向量的欧几里德范数差。
    • 计算 denominator 为实际梯度 grad 向量的欧几里德范数与数值梯度 gradapprox 向量的欧几里德范数之和。
    • 计算 differencenumerator / denominator,表示两者之间的差异。
  6. 比较 difference 与一个阈值(2e-7):

    • 如果 difference 大于阈值,说明反向传播可能存在问题,打印警告信息。
    • 否则,如果 difference 很小,说明反向传播实现很好,打印成功信息。

最后,函数返回 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)

#########################################################

这段代码执行了梯度检查的过程。

  1. X, Y, parameters = gradient_check_n_test_case(): 这一行代码从名为 gradient_check_n_test_case 的测试用例中加载数据,包括输入特征 X、标签 Y 和神经网络的参数 parameters

  2. cost, cache = forward_propagation_n(X, Y, parameters): 这一行代码调用了 forward_propagation_n 函数,使用加载的数据 XY 和参数 parameters 执行前向传播,并计算损失 cost 和缓存 cache

  3. gradients = backward_propagation_n(X, Y, cache): 这一行代码调用了 backward_propagation_n 函数,使用加载的数据 XY 和前向传播的缓存 cache 执行反向传播,并计算梯度 gradients

  4. difference = gradient_check_n(parameters, gradients, X, Y): 这一行代码调用了 gradient_check_n 函数,使用加载的数据 XY、参数 parameters 和计算得到的梯度 gradients 执行梯度检查,计算数值梯度与实际梯度之间的差异。

最后,difference 变量将包含数值梯度与实际梯度之间的差异,你可以通过检查 difference 的值来判断反向传播是否正确实现。如果 difference 很小(小于 2e-7),则说明反向传播的实现是正确的。

这个过程是用于验证神经网络中反向传播的关键步骤是否正确实现的重要步骤之一。如果 difference 较大,可能需要检查反向传播的实现以修复问题。

#########################################################