前言
在机器学习的应用层面中,已经了解了神经网络中的一些有关实践层面的正则化方法,本篇文章将会尝试利用各种正则化方法优化一个过拟合严重的神经网络。
问题引入:
假设,您刚刚被法国足球公司聘为AI专家。 他们希望您推荐法国守门员应将球踢到的位置,以便法国队的球员可以用头撞球。
为了实现这一问题,首先导入需要用到的python
库
导入相关库
import numpy as np
import matplotlib.pyplot as plt
import sklearn
import sklearn.datasets
import scipy.io
# 数据可视化图形设置
%matplotlib inline
plt.rcParams['figure.figsize'] = (7.0, 4.0) # 设置图篇默认大小
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'
加载数据并可视化显示
加载数据集并可视化的代码如下所示:
def load_2D_dataset():
data = scipy.io.loadmat('datasets/data.mat')
train_X = data['X'].T
train_Y = data['y'].T
test_X = data['Xval'].T
test_Y = data['yval'].T
plt.scatter(train_X[0, :], train_X[1, :], c=np.squeeze(train_Y), s=40, cmap=plt.cm.Spectral);
return train_X, train_Y, test_X, test_Y
train_X, train_Y, test_X, test_Y = load_2D_dataset()
可视化显示如下所示:
如上图所示,每个点对应于足球场上的位置,在该位置上,法国守门员从足球场左侧射出球后,足球运动员用他的头将球击中。
- 如果这个点使用蓝色表示的,则意味着法国队员能够用他的头部击球。
- 如果这个点是用蓝色表示的,则意味着其他队的球员能用他的头部击球。
本次任务的目标就是利用神经网络模型,为法国足球队的守门员提供可能的位置。通过数据分析,可以看出,尽管有一点噪音,但是似乎可以利用一条反对角线分割数据。本篇文章,将会使用不同的正则化模型,评估那种模型具有更好的性能。
不使用正则化的模型
神经网络模型的搭建,可以参考一步步构建一个神经网络的文章,构建一个神经网络的代码如下所示:
def model(X, Y, learning_rate = 0.3, num_iterations = 30000, print_cost = True, lambd = 0, keep_prob = 1):
"""
神经网络的结构: LINEAR->RELU->LINEAR->RELU->LINEAR->SIGMOID.
参数:
X -- 输入数据的维数 (特征数量, 样本数量)
Y -- 标签向量(1代表蓝点,0代表红点,数据维数(输出向量大小,样本数量))
learning_rate --优化的学习率
num_iterations -- 跌打次数
print_cost -- 如果设置为正,输出每1000次迭代的损失值
lambd -- 正则化超参数
keep_prob - dropout正则化中,保留神经元的概率
返回值:
parameters -- 返回模型可以被预测的参数
"""
grads = {}
costs = [] # to keep track of the cost
m = X.shape[1] # number of examples
layers_dims = [X.shape[0], 20, 3, 1]
# 初始化参数字典
parameters = initialize_parameters(layers_dims)
# 梯度下降的迭代次数
for i in range(0, num_iterations):
# 前向传播: 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)
# 损失函数
if lambd == 0:
cost = compute_cost(a3, Y)
else:
cost = compute_cost_with_regularization(a3, Y, parameters, lambd)
# 反向传播
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)
# 参数更新
parameters = update_parameters(parameters, grads, learning_rate)
# 打印出没10000次迭代的损失
if print_cost and i % 10000 == 0:
print("Cost after iteration {}: {}".format(i, cost))
if print_cost and i % 1000 == 0:
costs.append(cost)
以上代码中,设置正则化参数,也就是不使用正则化参数,可以看出梯度下降的过程和训练集和测试集的精度如下所示:
绘制出决策边界之后,如下图所示:
可以看出,决策边界包含了一些噪声点,模型有可能会造成过拟合现象。
L2正则化
L2正则化的前向传播函数
L2正则化方式是一种能够有效避免过拟合的标准方式,使用L2正则化之后,原来的损失函数:
将会变成如下所示:
其中,代表神经网络的层数,而代表神经网络中层的神经元数量,代表神经网络中第层的神经元数量
综上,带L2正则项的损失函数计算公式如下所示:
def compute_cost_with_regularization(A3, Y, parameters, lambd):
"""
参数:
A3 --前向传播的输出 维数为(输出大小,样本数量)
Y -- 输出标签,维数为(输出大小,样本数量)
parameters -- 以字典形式存储的一系列模型参数
返回值::
cost - 正则化之后的损失
"""
m = Y.shape[1]
W1 = parameters["W1"]
W2 = parameters["W2"]
W3 = parameters["W3"]
cross_entropy_cost = compute_cost(A3, Y)
L2_regularization_cost = (1/m)*(lambd/2)*(np.sum(np.square(W1))+np.sum(np.square(W2))+np.sum(np.square(W3)))
cost = cross_entropy_cost + L2_regularization_cost
return cost
A3, Y_assess, parameters = compute_cost_with_regularization_test_case()
print("cost = " + str(compute_cost_with_regularization(A3, Y_assess, parameters, lambd = 0.1)))
L2正则化之后的反向传播函数
实现L2正则化之后,神经网络的损失函数发生了变化,所以其反向传播的实现方式也发生了变化,变化的具体地方就是需要对正则化也需要求导,对正则化进行求导的公式如下所示:
根据以上公式,正则化之后的反向传播函数的实现代码如下所示:
def backward_propagation_with_regularization(X, Y, cache, lambd):
"""
带有正则化的项的神经网络反向传播函数
参数:
X -- 输入数据集(样本特征数量,样本大小)
Y --输出标签向量(输出向量大小,样本大小)
cache -- 前向传播过程中得到的一系列参数
lambd -- 正则化参数
返回值:
gradients --包含各个参数的梯度字典
"""
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) + lambd/m*W3
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) + lambd/m*W2
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) + lambd/m*W1
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
运行以上代码之后,可以看出,训练集精确度和测试集精确度比较接近,都达到可93%的精度,但是损失值较不使用正则化的模型有所上升。
绘制决策边界,可以看出,较之不使用正则化的模型,有效地减少了过拟合,使得决策边界更为“平滑”,如果正则化参数变得更大一些,决策边界就会变得更加平滑,但是也会导致模型精度下降,误差变大。
dropout正则化
dropout正则化,也被称之为随机失活正则化,即是在每次迭代的过程中,随机失活一些神经元,也就是将一些神经元的输出变为0,其中,参数keep-prob代表着保留该神经元的概率。
dropout正则化的前向传播
在一个3层的神经网络中,需要对第一个和第二个隐藏层使用dropout正则化,输入层和输出层并不会使用dropout正则化。dropout正则化的前向传播过程可以分为四个步骤实现:
创建一个随机向量,其维数大小和矩阵一样,并且使其随机值在之间分布。
对于向量的每一项而言,使用1-keep_prob的概率设置为0,keep_prob的概率设置为1
令,即也就是利用乘法的特性,将的神经元消除。
令,确保与没有实现dropout正则化网络有相同的损失值。
综上所述,dropout正则化的前向传播实现,如下代码所示:
def forward_propagation_with_dropout(X, parameters, keep_prob = 0.5):
np.random.seed(1)
W1 = parameters["W1"]
b1 = parameters["b1"]
W2 = parameters["W2"]
b2 = parameters["b2"]
W3 = parameters["W3"]
b3 = parameters["b3"]
A1 = relu(Z1)
D1 = np.random.rand(A1.shape[0],A1.shape[1])
D1 = (D1<keep_prob)
A1 = np.multiply(A1,D1)
A1 = A1/keep_prob
Z2 = np.dot(W2, A1) + b2
A2 = relu(Z2)
D2 = np.random.rand(A2.shape[0],A2.shape[1])
D2 = (D2<keep_prob)
A2 = np.multiply(A2,D2)
A2 = A2/keep_prob
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
dropout正则化的反向传播过程
dropout正则化的反向传播方法比正向传播的的实现更为简单,可以分为以下两个步骤:
反向传播与正向传播一样,也需要对相同的神经元随机失活,只不过是利用向量改变的值。
与前向传播一样,也需要在每一层除以,只不过是将变成
综上,反向传播的正则化的实现代码如下所示:
def backward_propagation_with_dropout(X, Y, cache, keep_prob):
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 = np.multiply(dA2,D2)
dA2 = dA2/keep_prob
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 = np.multiply(dA1,D1)
dA1 = dA1/keep_prob
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
运行以上代码,经过多次迭代之后的损失值和性能如下所示:
绘制此模型的决策边界,如下所示:
与不使用正则化的模型,使用L2正则化的模型相比,dropout正则化的模型经过多次迭代之后,损失值最低,在训练集上的精度有所下降,但是在测试集精度上升,决策边界在能够保持平滑的同时,分类也更加准确,模型总体上性能更好。
使用dropout正则化的注意事项:
- 仅仅在训练集上使用dropout正则化,在测试集上不使用。
- 模型的前向传播和反向传播都要使用dropout正则化。