本文主要叙述神经网络的数据集、偏差与方差、正则化、随机失活、归一化输入、梯度消失与梯度爆炸、梯度检验等要点。
数据集
在建立神经网络模型前,我们要将数据集划分为三个部分:训练集、交叉验证集和测试集。欠拟合的情况下,出现高偏差;过拟合的情况下,出现高方差。出现高偏差时,可以使用增加隐藏层数目、加长训练时间等方法解决。出现高方差时,可以使用增加训练数据、正则化等方法解决。
正则化
正则化是指在成本函数中加入一项正则化项,以降低模型的复杂度。比较常用的正则化方法是L2正则化。当正则化因子$\lambda$设置的足够大时,权重矩阵W的值会接近于0,消除了很多神经元的权重,从而减小过拟合。
另外一个正则化方法是随机失活。在神经网络的某些层,为随机消除某些神经元,也可以达到降低复杂度的目的。在实际应用中,一般采用反向随机失活方法。随机失活的一个缺点是,其使得成本函数不能被明确定义,因为每次迭代都会随机消除一些神经元结点。
随机失活方法的核心代码如下,针对第一个隐藏层。
keep_prob = 0.5 # 随机失活概率
Z1 = np.dot(W1, X) + b1
A1 = relu(Z1)
# 前向传播,以下针对A1进行随机失活
D1 = np.random.rand(A1.shape[0], A1.shape[1])
D1 = D1 < keep_prob # 转为布尔矩阵
A1 = D1 * A1 # 随机将矩阵中的数置零
A1 = A1 / keep_prob # 保持期望值与未进行随机失活时相同,此方法称为反向随机失活
# 反向传播,以下针对dA1
dA1 = dA1 * D1 # 使用之前计算的D1关闭神经元,使之与前向传播相同
dA1 = dA1 / keep_prob # 放大剩余神经元的值,保持期望
其他正则化方法有数据扩增、早停法等。早停法是指在交叉验证集的误差上升前停止迭代,避免过拟合。其缺点是无法独立处理偏差与方差的优化。
归一化
归一化也称标准化,将数据限制在一定范围内,避免某些特征权重差距过大,从而加快梯度下降求最优解的速度。方法有线性归一化、标准差归一化和非线性归一化。
梯度
在计算梯度时,可能出现梯度指数级递增和递减的情况,分别称为梯度爆炸和梯度消失。这涉及到参数初始化的问题,可使用Xavier(或Glorot)初始化和He初始化等方法解决。He初始化的方法如下:
def initialize_parameters_he(layers_dims):
np.random.seed(3)
parameters = {}
L = len(layers_dims) - 1
for l in range(1, L + 1):
parameters['W' + str(l)] = np.random.randn(layers_dims[l], layers_dims[l - 1]) * np.sqrt(2./layers_dims[l-1]) # 参数W初始化后需要乘以额外系数
parameters['b' + str(l)] = np.zeros((layers_dims[l], 1))
return parameters
梯度检验则是计算数值梯度,得到梯度的近似值,再与反向传播得出的梯度相比较,看两个值是否接近。如果相近,则证明神经网络中计算梯度的算法是正确的。对于n层神经网络的梯度校验代码如下:
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))
# 参数theta迭代
for i in range(num_parameters):
# 每个参数theta加epsilon,再次计算J
thetaplus = np.copy(parameters_values)
thetaplus[i][0] = thetaplus[i][0] + epsilon
J_plus[i], _ = forward_propagation_n(X, Y, vector_to_dictionary(thetaplus))
# 每个参数theta减epsilon,再次计算J
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) - np.linalg.norm(gradapprox)
denominator = np.linalg.norm(grad) + np.linalg.norm(gradapprox)
difference = numerator / denominator
if difference > 1e-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