深度学习模型中的正则化实例
深度学习模型具有很大的灵活性,如果机器学习训练数据集不够大,过度拟合可能成为一个严重的问题。让我们来看一个例子。
问题陈述
你刚刚被法国足球公司聘为人工智能专家。他们希望您推荐法国守门员应将足球踢到的位置,以便法国队的球员可以用头撞球。
他们为您提供了法国过去10场比赛的以下2D机器学习数据集(https://www.kaggle.com/mriganksingh/football-header)。
每个点对应于足球场上的位置,在该位置上,法国守门员从足球场左侧射出球后,足球运动员用他的头将球击中。
- 如果圆点是蓝色的,则表示法国队队员成功地用头击球
- 如果该点为红色,则表示另一支球队的球员用头将球击中
您的目标:使用深度学习模型查找守门员应将球踢到球场上的位置。
方法
该机器学习数据集有噪声,但它看起来像一条对角线,将左上角(蓝色)和右下角(红色)分隔开。
我们将首先尝试一个非正则化模型。然后,我们将学习如何正则化它,并决定选择哪种模式来解决法国足球公司的问题。
非正则模型
我们将实现一个三层神经网络:
LINEAR-> RELU-> LINEAR-> RELU-> LINEAR-> SIGMOID。
这里,在本文最后提供的最终Python代码中以实用程序脚本的形式描述了各个函数。
def model(X, Y, learning_rate = 0.3, num_iterations = 30000, print_cost = True, lambd = 0, keep_prob = 1): """ Implements a three-layer neural network: LINEAR->RELU->LINEAR->RELU->LINEAR->SIGMOID. Arguments: X -- input data, of shape (input size, number of examples) Y -- true "label" vector (1 for blue dot / 0 for red dot), of shape (output size, number of examples) learning_rate -- learning rate of the optimization num_iterations -- number of iterations of the optimization loop print_cost -- If True, print the cost every 10000 iterations lambd -- regularization hyperparameter, scalar keep_prob - probability of keeping a neuron active during drop-out, scalar. Returns: parameters -- parameters learned by the model. They can then be used to predict. """ grads = {} costs = [] # to keep track of the cost m = X.shape[1] # number of examples layers_dims = [X.shape[0], 20, 3, 1] # Initialize parameters dictionary. parameters = initialize_parameters(layers_dims) # Loop (gradient descent) for i in range(0, num_iterations): # Forward propagation: LINEAR -> RELU -> LINEAR -> RELU -> LINEAR -> SIGMOID. if keep_prob == 1: a3, cache = forward_propagation(X, parameters) elif keep_prob < 1: a3, cache = forward_propagation_with_dropout(X, parameters, keep_prob) # Cost function if lambd == 0: cost = compute_cost(a3, Y) else: cost = compute_cost_with_regularization(a3, Y, parameters, lambd) # Backward propagation. assert(lambd==0 or keep_prob==1) # it is possible to use both L2 regularization and dropout, # but this assignment will only explore one at a time if lambd == 0 and keep_prob == 1: grads = backward_propagation(X, Y, cache) elif lambd != 0: grads = backward_propagation_with_regularization(X, Y, cache, lambd) elif keep_prob < 1: grads = backward_propagation_with_dropout(X, Y, cache, keep_prob) # Update parameters. parameters = update_parameters(parameters, grads, learning_rate) # Print the loss every 10000 iterations if print_cost and i % 10000 == 0: print("Cost after iteration {}: {}".format(i, cost)) if print_cost and i % 1000 == 0: costs.append(cost) # plot the cost plt.plot(costs) plt.ylabel('cost') plt.xlabel('iterations (x1,000)') plt.title("Learning rate =" + str(learning_rate)) plt.show() return parameters
训练以上机器学习模型
parameters = model(train_X, train_Y) print ("On the training set:") predictions_train = predict(train_X, train_Y, parameters) print ("On the test set:") predictions_test = predict(test_X, test_Y, parameters)
输出
训练准确度为94.7%,而测试准确度为91.5%。这是基准模型(我们将观察正则化对该机器学习模型的影响)。
让我们绘制该模型的决策边界。
plt.title("Model without regularization") axes = plt.gca() axes.set_xlim([-0.75,0.40]) axes.set_ylim([-0.75,0.65]) plot_decision_boundary(lambda x: predict_dec(parameters, x.T), train_X, train_Y)
你可以清楚地看到,非正则化模型显然是对训练集的过度拟合。
现在,我们将尝试使用一些正则化技术来解决这个过拟合问题。
什么是正则化?
让我们把它想成一个逻辑回归设置,你想最小化一个损失函数来得到w和b的最优值。
现在,我们从逻辑回归迁移到一个神经网络,在该网络中,我们使用非常相似的标准(而不是L-2norm)进行了稍微的修改,称为Frobenius Norm。
现在,使用上述修改后的成本函数,您需要执行反向传播并相应地更新权重以获得全局最小值。
因此,让我们对我们的模型实施L-2正则化。
L-2正则化模型
现在让我们运行模型和L2正规化(λ= 0.7)。model()函数将调用:
- compute_cost_with_regularization 代替 compute_cost
- backward_propagation_with_regularization 代替 backward_propagation
测试集的准确度提高到93%。您不会再拟合数据了。让我们绘制决策边界。
L2正则化使您的决策边界更加平滑。如果λ太大,则也可能“过度平滑”,从而导致模型具有较高的偏差。
L2正则化基于以下假设:权重较小的模型比权重较大的模型更简单。因此,通过对成本函数中权重的平方值进行惩罚,可以将所有权重转换为较小的值。权重越大,成本越高!这会导致一个更平滑的模型,其中输出随着输入的变化而变化得更慢。
现在让我们探索另一种称为Dropout的正则化技术。
Dropout正则化
我们要做的是遍历网络的每一层并设定消除神经网络中某个节点的概率。这样一来,你就拥有了一个小得多的网络。然后使用一个实例(观察)在一个更小的网络中进行反向传播。然后在下一个实例中,您将随机删除其他一些节点,并在缩小的网络上训练机器学习模型。
为了实现此Dropout,我们使用一种称为“Inverted Dropout”的技术。让我们用layer l = 3的例子来说明这一点。
Inverted dropout技术有助于扩大下一个激活层的期望值。
在测试时,我们不希望dropout,因为我们不想随机化我们的输出。
因此,除了使用较小的神经网络(具有较高的正则化效果)外,对于drop-out的另一种直觉是,您不能依赖于某个特征。因此,随机剔除节点会将权重分散到各处,并缩小权重的平方范数。
这里的超参数之一是“keep-prob”,表示保留节点的概率。它可以随层变化。基本的原则是,如果任何层有许多隐藏节点,那么“keep-prob”应该是低的,这意味着您应该去掉该层中的更多节点,这样模型就不会过度拟合,反之亦然。这也意味着,我们认为不会过度拟合的层,keep_prob可以是1(这意味着您要保留每个单元,而不是在该层dropout)。
Dropouts在计算机视觉领域非常常见,因为输入大小很大,因为它会将所有这些像素作为输入。
dropout的缺点之一是,成本函数J的定义不再像在每次迭代中随机剔除某些节点的迭代那样好。因此,监视梯度下降是困难的。因此,最好将其“off”,并检查“梯度下降”是否单调减小,然后将dropout 设置为“on”以减少过度拟合。
def forward_propagation_with_dropout(X, parameters, keep_prob = 0.5): """ Implements the forward propagation: LINEAR -> RELU + DROPOUT -> LINEAR -> RELU + DROPOUT -> LINEAR -> SIGMOID. Arguments: X -- input dataset, of shape (2, number of examples) parameters -- python dictionary containing your parameters "W1", "b1", "W2", "b2", "W3", "b3": W1 -- weight matrix of shape (20, 2) b1 -- bias vector of shape (20, 1) W2 -- weight matrix of shape (3, 20) b2 -- bias vector of shape (3, 1) W3 -- weight matrix of shape (1, 3) b3 -- bias vector of shape (1, 1) keep_prob - probability of keeping a neuron active during drop-out, scalar Returns: A3 -- last activation value, output of the forward propagation, of shape (1,1) cache -- tuple, information stored for computing the backward propagation """ np.random.seed(1) # retrieve parameters 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) D1 = np.random.rand(A1.shape[0], A1.shape[1]) # Step 1: initialize matrix D1 = np.random.rand(..., ...) D1 = (D1 < keep_prob) # Step 2: convert entries of D1 to 0 or 1 (using keep_prob as the threshold) A1 = A1*D1 # Step 3: shut down some neurons of A1 A1 = A1 / keep_prob # Step 4: scale the value of neurons that haven't been shut down Z2 = np.dot(W2, A1) + b2 A2 = relu(Z2) D2 = np.random.rand(A2.shape[0], A2.shape[1]) # Step 1: initialize matrix D2 = np.random.rand(..., ...) D2 = (D2 < keep_prob) # Step 2: convert entries of D2 to 0 or 1 (using keep_prob as the threshold) A2 = A2*D2 # Step 3: shut down some neurons of A2 A2 = A2 / keep_prob # Step 4: scale the value of neurons that haven't been shut down Z3 = np.dot(W3, A2) + b3 A3 = sigmoid(Z3) cache = (Z1, D1, A1, W1, b1, Z2, D2, A2, W2, b2, Z3, A3, W3, b3) return A3, cache
def backward_propagation_with_dropout(X, Y, cache, keep_prob): """ Implements the backward propagation of our baseline model to which we added dropout. Arguments: X -- input dataset, of shape (2, number of examples) Y -- "true" labels vector, of shape (output size, number of examples) cache -- cache output from forward_propagation_with_dropout() keep_prob - probability of keeping a neuron active during drop-out, scalar Returns: gradients -- A dictionary with the gradients with respect to each parameter, activation and pre-activation variables """ m = X.shape[1] (Z1, D1, A1, W1, b1, Z2, D2, 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) dA2 = dA2*D2 # Step 1: Apply mask D2 to shut down the same neurons as during the forward propagation dA2 = dA2/keep_prob # Step 2: Scale the value of neurons that haven't been shut down 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) dA1 = dA1*D1 # Step 1: Apply mask D1 to shut down the same neurons as during the forward propagation dA1 = dA1/keep_prob # Step 2: Scale the value of neurons that haven't been shut down 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
现在,我们使用dropout(keep_prob = 0.86)运行模型。这意味着在每次迭代中,您以14%的概率关闭第1层和第2层的每个神经元。函数model()现在将调用:
- forward_propagation_with_dropout代替forward_propagation。
- backward_propagation_with_dropout代替backward_propagation。
parameters = model(train_X, train_Y, keep_prob = 0.86, learning_rate = 0.3) print ("On the train set:") predictions_train = predict(train_X, train_Y, parameters) print ("On the test set:") predictions_test = predict(test_X, test_Y, parameters)
Dropout效果很好!测试准确度再次提高(达到95%)!您的模型并未过度拟合训练集,并且在测试集上做得很好。
plt.title("Model with dropout") axes = plt.gca() axes.set_xlim([-0.75,0.40]) axes.set_ylim([-0.75,0.65]) plot_decision_boundary(lambda x: predict_dec(parameters, x.T), train_X, train_Y)
使用Dropout时的一个常见错误是在训练和测试中都使用它。您仅应在训练中使用Dropout(随机删除节点)。
注意:tensorflow,keras或caffe之类的深度学习框架都带有Dropout层实现。
小结
我们可以清楚地看到,Dropout正则化效果最佳。请注意,正则化会损害训练集的性能!这是因为它限制了网络过度拟合训练集的能力。但是由于最终可以提供更好的测试准确性,因此可以为您的系统和法国足球队提供帮助。
参考代码:https://www.kaggle.com/baners/ai-in-french-football-corporation
其他正则化技术
- 数据增强—假设您正在训练猫分类器。您可以做的是水平翻转图像并将数据添加到训练集中。这也,现在您的训练集增加了一倍。
您还可以对这些图像进行随机裁剪,例如
对于光学字符识别(OCR),也可以使数字变形。
Early Stopping -这是另一种正则化技术。使用梯度下降训练模型时,成本函数(J)应该单调减少。通常,此误差会在一段时间后减少,然后增加。在“ Early Stoping”中,您将在神经网络做得非常好的过程中中途停止训练。它之所以有效是因为当你没有对这个神经网络进行足够的迭代时,向量“w”将接近于0,因为它们是用较小的值随机初始化的。随着你不断的训练(例如迭代的次数增加),它的值会越来越大。
“Early Stoping”的中等大小的值W,与L-2范数非常相似。缺点是您还需要优化这个超参数。您已经有许多要优化的超参数,再添加一个会使它变得更加复杂。在这种情况下,有一个概念叫做“正交化”,你可以一次专注于一个任务,也就是说,你可以用一种方法来最小化损失或者最小化过度拟合。Early Stoping在这两个方面都做尝试,会使事情变得过于复杂。因此,更好的方法是使用L-2正则化并尽可能长时间地训练您的神经网络。