深度学习之正则化
学习目标:
- 了解偏差与方差的意义
- 知道L2正则化与L1正则化的数学意义
- 知道Droupout正则化的方法
- 了解早停止法、数据增强法的其它正则化方式
1. 方差与偏差
1.1 数据集划分
首先我们对机器学习当中涉及到的数据集划分进行一个简单的复习
- 训练集(train set):用训练集对算法或模型进行训练过程;
- 验证集(development set):利用验证集(又称为简单交叉验证集,hold-out cross validation set)进行交叉验证,选择出最好的模型;
- 测试集(test set):最后利用测试集对模型进行测试,对学习方法进行评估。
在小数据量的时代,如 100、1000、10000 的数据量大小,可以将数据集按照以下比例进行划分: - 无验证集的情况:70% / 30%
- 有验证集的情况:60% / 20% / 20%
而在如今的大数据时代,拥有的数据集的规模可能是百万级别的,所以验证集和测试集所占的比重会趋向于变得更小。
- 100 万数据量:98% / 1% / 1%
- 超百万数据量:99.5% / 0.25% / 0.25%
1.2 偏差与方差的意义
“偏差-方差分解”(bias-variance decomposition)是解释学习算法泛化性能的一种重要工具。
- 偏差:度量了学习算法的期望预测与真实结果的偏离程度,即刻画了学习算法本身的拟合能力
- 方差:度量了同样大小的训练集的变动所导致的学习性能的变化,即刻画了数据扰动所造成的影响
- 噪声:表达了在当前任务上任何学习算法所能够达到的期望泛化误差的下界,即刻画了学习问题本身的难度。
那么偏差、方差与我们的数据集划分到底有什么关系呢?
- 1.训练集的错误率较小,而验证集/测试集的错误率较大,说明模型存在较大方差,可能出现了过拟合.
- 2.训练集和测试集的错误率都较大,且两者相近,说明模型存在较大偏差,可能出现了欠拟合.
- 3.训练集和测试集的错误率都较小,且两者相近,说明方差和偏差都较小,这个模型效果比较好.
所以我们最终总结,方差一般指的是数据模型得出来了,能不能对未知数据的扰动预测准确。而偏差说明在训练集当中就已经误差较大了,基本上在测试集中没有好的效果。
所以如果我们的模型出现了较大的方差或者同时也有较大的偏差,该怎么去解决?
1.3 解决办法
对于高方差,有以下几种方式:
- 获取更多的数据,使得训练能够包含所有可能出现的情况
- 正则化(Regularization)
- 寻找更合适的网络结构
对于高偏差,有以下几种方式: - 扩大网络规模,如添加隐藏层或者神经元数量
- 寻找合适的网络架构,使用更大的网络结构,如AlexNet
- 训练时间更长一些
不断尝试,直到找到低偏差、低方差的框架。
2. 正则化
正则化,即在成本函数中加入一个正则化项(惩罚项),惩罚模型的复杂度,防止网络过拟合.
2.1 逻辑回归的L1与L2正则化
逻辑回归的参数W数量根据特征的数量而定,那么正则化如下:
注:其中,λ 为正则化因子,是超参数。 m是一个batch中数据个数, 除以2的原因是在求导的时候抵消掉。由于 L1 正则化最后得到 w 向量中将存在大量的 0,使模型变得稀疏化,因此 L2 正则化更加常用。
2.2 正则化项的理解
在损失函数中增加一项,那么其实梯度下降是要减少损失函数的大小,对于L2或者L1来讲都是要去减少这个正则项的大小,那么也就是会减少W权重的大小。这是我们一个直观上的感受。
- 接下来我们通过方向传播来理解这个其中的L2,对于损失函数我们要反向传播求参数梯度:
2.3 神经网络中的正则化
神经网络中的正则化与逻辑回归相似,只不过参数W变多了,每一层都有若干个权重,可以理解成一个矩阵:
对于矩阵的L2范数,有个专业名称叫弗罗贝尼乌斯范数(Frobenius Norm)
2.4 正则化为什么能够防止过拟合
正则化因子设置的足够大的情况下,为了使成本函数最小化,权重矩阵 W 就会被设置为接近于 0 的值,直观上相当于消除了很多神经元的影响,那么大的神经网络就会变成一个较小的网络。
3.Droupout正则化
Droupout:随机的对神经网络每一层进行丢弃部分神经元操作。
对于网络的每一层会进行设置保留概率,即keep_prob。假设keep_prob为0.8,那么也就是在每一层所有神经元有20% 的概率直接失效,可以理解为0.
3.1Inverted droupout
这种方式会对每层进行如下代码操作:
# 假设设置神经元保留概率
keep_prob = 0.8
# 随机建立一个标记1 or 0的矩阵,表示随机失活的单元,占比20%
dl = np.random.rand(al.shape[0], al.shape[1]) < keep_prob
# 让a1对应d1的为0地方结果为0
al = np.multiply(al, dl)
# 为了测试的时候,每一个单元都参与进来
al /= keep_prob
-
训练练的时候只有占比为p的隐藏层单元参与训练。
-
增加最后一行代码的原因,在预测的时候,所有的隐藏层单元都需要参与进来,就需要测试的时候将输出结果除以p使下一层的输入规模保持不变。
假设keep_prob=p=0.8
3.2 droupout为什么有效总结
加入了 dropout 后,输入的特征都存在被随机清除的可能,所以该神经元不会再特别依赖于任何一个输入特征,也就是不会给任何一个输入特征设置太大的权重。通过传播过程,dropout 将产生和 L2 正则化相同的收缩权重的效果。
-
对于不同的层,设置的keep_prob大小也不一致,神经元较少的层,会设keep_prob为 1.0,而神经元多的层则会设置比较小的keep_prob
-
通常被使用在计算机视觉领域,图像拥有更多的特征,场景容易过拟合,效果被实验人员证明是很不错的。
调试时候使用技巧:
- dropout 的缺点是成本函数无法被明确定义,因为每次会随机消除一部分神经元,所以参数也无法确定具体哪一些,在反向传播的时候带来计算上的麻烦,也就无法保证当前网络是否损失函数下降的。如果要使用droupout,会先关闭这个参数,保证损失函数是单调下降的,确定网络没有问题,再次打开droupout才会有效。
4. 其它正则化方法
- 早停止法(Early Stopping)
- 数据增强
4.1 早停止法(Early Stopping)
通常我们在训练验证的时候,发现过拟合。可以得到下面这张损失图.
通常不断训练之后,损失越来越小。但是到了一定之后,模型学到的过于复杂(过于拟合训练集上的数据的特征)造成测试集开始损失较小,后来又变大。模型的w参数会越来越大,那么可以在测试集损失减小一定程度之后停止训练。
但是这种方法治标不治本,得从根本上解决数据或者网络的问题。
4.2 数据增强
- 数据增强
指通过剪切、旋转/反射/翻转变换、缩放变换、平移变换、尺度变换、对比度变换、噪声扰动、颜色变换等一种或多种组合数据增强变换的方式来增加数据集的大小。
即使卷积神经网络被放在不同方向上,卷积神经网络对平移、视角、尺寸或照度(或以上组合)保持不变性,都会认为是一个物体。
-
数据增强类别
那么我们应该在机器学习过程中的什么位置进行数据增强?在向模型输入数据之前增强数据集。 -
离线增强。预先进行所有必要的变换,从根本上增加数据集的规模(例如,通过翻转所有图像,保存后数据集数量会增加2倍)。
-
在线增强,或称为动态增强。可通过对即将输入模型的小批量数据的执行相应的变化,这样同一张图片每次训练被随机执行一些变化操作,相当于不同的数据集了。
那么我们的代码中也是进行这种在线增强。
那么我们的代码中也是进行这种在线增强。 -
数据增强技术
下面一些方法基础但功能强大的增强技术,目前被广泛应用。
翻转:tf.image.random_flip_left_right
- 你可以水平或垂直翻转图像。一些架构并不支持垂直翻转图像。但,垂直翻转等价于将图片旋转180再水平翻转。下面就是图像翻转的例子。
从左侧开始分别是:原始图像,水平翻转图像,垂直翻转图像. - 旋转:rotate
- 剪裁:random_crop
- 随机从原始图像中采样一部分,然后将这部分图像调整为原始图像大小。这个方法更流行的叫法是随机裁剪。
- 随机从原始图像中采样一部分,然后将这部分图像调整为原始图像大小。这个方法更流行的叫法是随机裁剪。
- 平移、缩放等等方法
数据增强的效果是非常好的,比如下面的例子,绿色和粉色表示没有数据增强之前的损失和准确率效果,红色和蓝色表示数据增强之后的损失和准确率结果,可以看到学习效果也改善较快。
那么TensorFlow 官方源码都是基于 vgg与inception论文的图像增强介绍,全部通过tf.image相关API来预处理图像。并且提供了各种封装过tf.image之后的API。那么TensorFlow 官网也给我们提供了一些模型的数据增强过程。
5.完整代码及输出演示
代码中,将momentum正则化与adam正则化进行比较.
- optimization代码
import numpy as np
import matplotlib.pyplot as plt
import math
import sklearn
import sklearn.datasets
from utils import initialize_parameters, forward_propagation, compute_cost, backward_propagation
from utils import load_dataset, predict
def random_mini_batches(X, Y, mini_batch_size=64, seed=0):
"""
创建每批次固定数量特征值和目标值
"""
np.random.seed(seed)
m = X.shape[1]
mini_batches = []
# 对所有数据进行打乱
permutation = list(np.random.permutation(m))
shuffled_X = X[:, permutation]
shuffled_Y = Y[:, permutation].reshape((1, m))
# 循环将每批次数据按照固定格式装进列表当中
num_complete_minibatches = math.floor(
m / mini_batch_size)
# 所有训练数据分成多少组
for k in range(0, num_complete_minibatches):
mini_batch_X = shuffled_X[:, k * mini_batch_size: (k + 1) * mini_batch_size]
mini_batch_Y = shuffled_Y[:, k * mini_batch_size: (k + 1) * mini_batch_size]
mini_batch = (mini_batch_X, mini_batch_Y)
mini_batches.append(mini_batch)
# 最后剩下的样本数量mini-batch < mini_batch_size
if m % mini_batch_size != 0:
mini_batch_X = shuffled_X[:, num_complete_minibatches * mini_batch_size:]
mini_batch_Y = shuffled_Y[:, num_complete_minibatches * mini_batch_size:]
mini_batch = (mini_batch_X, mini_batch_Y)
mini_batches.append(mini_batch)
return mini_batches
def initialize_momentum(parameters):
"""
初始化网络中每一层的动量梯度下降的指数加权平均结果参数
parameters['W' + str(l)] = Wl
parameters['b' + str(l)] = bl
return:
v['dW' + str(l)] = velocity of dWl
v['db' + str(l)] = velocity of dbl
"""
# 得到网络的层数
L = len(parameters) // 2
v = {}
# 初始化动量参数
for l in range(L):
v["dW" + str(l + 1)] = np.zeros(parameters['W' + str(l + 1)].shape)
v["db" + str(l + 1)] = np.zeros(parameters['b' + str(l + 1)].shape)
return v
def update_parameters_with_momentum(parameters, gradients, v, beta, learning_rate):
"""
动量梯度下降算法实现
"""
# 得到网络的层数
L = len(parameters) // 2
# 动量梯度参数更新
for l in range(L):
# 开始
v["dW" + str(l + 1)] = beta * v['dW' + str(l + 1)] + (1 - beta) * (gradients['dW' + str(l + 1)])
v["db" + str(l + 1)] = beta * v['db' + str(l + 1)] + (1 - beta) * (gradients['db' + str(l + 1)])
parameters["W" + str(l + 1)] = parameters['W' + str(l + 1)] - learning_rate * v['dW' + str(l + 1)]
parameters["b" + str(l + 1)] = parameters['b' + str(l + 1)] - learning_rate * v['db' + str(l + 1)]
# 结束
return parameters, v
def initialize_adam(parameters):
"""
初始化Adam算法中的参数
"""
# 得到网络的参数
L = len(parameters) // 2
v = {}
s = {}
# 利用输入,初始化参数v,s
for l in range(L):
v["dW" + str(l + 1)] = np.zeros(parameters['W' + str(l + 1)].shape)
v["db" + str(l + 1)] = np.zeros(parameters['b' + str(l + 1)].shape)
s["dW" + str(l + 1)] = np.zeros(parameters['W' + str(l + 1)].shape)
s["db" + str(l + 1)] = np.zeros(parameters['b' + str(l + 1)].shape)
return v, s
def update_parameters_with_adam(parameters, gradients, v, s, t, learning_rate=0.01,
beta1=0.9, beta2=0.999, epsilon=1e-8):
"""
更新Adam算法网络的参数
"""
# 网络大小
L = len(parameters) // 2
v_corrected = {}
s_corrected = {}
# 更新所有参数
for l in range(L):
# 对梯度进行移动平均计算. 输入: "v, gradients, beta1". 输出: "v".
# 开始
v["dW" + str(l + 1)] = beta1 * v['dW' + str(l + 1)] + (1 - beta1) * gradients['dW' + str(l + 1)]
v["db" + str(l + 1)] = beta1 * v['db' + str(l + 1)] + (1 - beta1) * gradients['db' + str(l + 1)]
# 结束
# 计算修正结果. 输入: "v, beta1, t". 输出: "v_corrected".
# 开始
v_corrected["dW" + str(l + 1)] = v['dW' + str(l + 1)] / (1 - np.power(beta1, t))
v_corrected["db" + str(l + 1)] = v['db' + str(l + 1)] / (1 - np.power(beta1, t))
# 结束
# 平方梯度的移动平均值. 输入: "s, gradients, beta2". 输出: "s".
# 开始
s["dW" + str(l + 1)] = beta2 * s['dW' + str(l + 1)] + (1 - beta2) * np.power(gradients['dW' + str(l + 1)], 2)
s["db" + str(l + 1)] = beta2 * s['db' + str(l + 1)] + (1 - beta2) * np.power(gradients['db' + str(l + 1)], 2)
# 结束
# 计算修正的结果. 输入: "s, beta2, t". 输出: "s_corrected".
# 开始
s_corrected["dW" + str(l + 1)] = s['dW' + str(l + 1)] / (1 - np.power(beta2, t))
s_corrected["db" + str(l + 1)] = s['db' + str(l + 1)] / (1 - np.power(beta2, t))
# 结束
# 更新参数. 输入: "parameters, learning_rate, v_corrected, s_corrected, epsilon". 输出: "parameters".
# 开始
parameters["W" + str(l + 1)] = parameters['W' + str(l + 1)] - learning_rate * v_corrected[
'dW' + str(l + 1)] / np.sqrt(s_corrected['dW' + str(l + 1)] + epsilon)
parameters["b" + str(l + 1)] = parameters['b' + str(l + 1)] - learning_rate * v_corrected[
'db' + str(l + 1)] / np.sqrt(s_corrected['db' + str(l + 1)] + epsilon)
# 结束
return parameters, v, s
def model(X, Y, optimizer, learning_rate=0.0007, mini_batch_size=64, beta=0.9,
beta1=0.9, beta2=0.999, epsilon=1e-8, num_epochs=10000, print_cost=True):
"""
模型逻辑
定义一个三层网络(不包括输入层)
第一个隐层:5个神经元
第二个隐层:2个神经元
输出层:1个神经元
"""
# 计算网络的层数
layers_dims = [train_X.shape[0], 5, 2, 1]
L = len(layers_dims)
costs = []
t = 0
seed = 10
# 初始化网络结构
parameters = initialize_parameters(layers_dims)
# 初始化优化器参数
if optimizer == "momentum":
v = initialize_momentum(parameters)
elif optimizer == "adam":
v, s = initialize_adam(parameters)
# 优化逻辑
for i in range(num_epochs):
# 每次迭代所有样本顺序打乱不一样
seed = seed + 1
# 获取每批次数据
minibatches = random_mini_batches(X, Y, mini_batch_size, seed)
# 开始
for minibatch in minibatches:
# Mini-batch每批次的数据
(minibatch_X, minibatch_Y) = minibatch
# 前向传播minibatch_X, parameters,返回a3, caches
a3, caches = forward_propagation(minibatch_X, parameters)
# 计算损失,a3, minibatch_Y,返回cost
cost = compute_cost(a3, minibatch_Y)
# 反向传播,返回梯度
gradients = backward_propagation(minibatch_X, minibatch_Y, caches)
# 更新参数
if optimizer == "momentum":
parameters, v = update_parameters_with_momentum(parameters, gradients, v, beta, learning_rate)
elif optimizer == "adam":
t = t + 1
parameters, v, s = update_parameters_with_adam(parameters, gradients, v, s,
t, learning_rate, beta1, beta2, epsilon)
# 结束
# 每个1000批次打印损失
if print_cost and i % 1000 == 0:
print("第 %i 次迭代的损失值: %f" % (i, cost))
if print_cost and i % 100 == 0:
costs.append(cost)
# 画出损失的变化
plt.plot(costs)
plt.ylabel('cost')
plt.xlabel('epochs (per 100)')
plt.title("损失图")
plt.show()
return parameters
if __name__ == '__main__':
train_X, train_Y = load_dataset()
#parameters = model(train_X, train_Y, optimizer="momentum")
parameters = model(train_X, train_Y, optimizer="adam")
predictions = predict(train_X, train_Y, parameters)
- units代码
import numpy as np
import matplotlib.pyplot as plt
import h5py
import scipy.io
import sklearn
import sklearn.datasets
def sigmoid(x):
"""
Compute the sigmoid of x
Arguments:
x -- A scalar or numpy array of any size.
Return:
s -- sigmoid(x)
"""
s = 1/(1+np.exp(-x))
return s
def relu(x):
"""
Compute the relu of x
Arguments:
x -- A scalar or numpy array of any size.
Return:
s -- relu(x)
"""
s = np.maximum(0,x)
return s
def initialize_parameters(layer_dims):
"""
Arguments:
layer_dims -- python array (list) containing the dimensions of each layer in our network
Returns:
parameters -- python dictionary containing your parameters "W1", "b1", ..., "WL", "bL":
W1 -- weight matrix of shape (layer_dims[l], layer_dims[l-1])
b1 -- bias vector of shape (layer_dims[l], 1)
Wl -- weight matrix of shape (layer_dims[l-1], layer_dims[l])
bl -- bias vector of shape (1, layer_dims[l])
Tips:
- For example: the layer_dims for the "Planar Data classification model" would have been [2,2,1].
This means W1's shape was (2,2), b1 was (1,2), W2 was (2,1) and b2 was (1,1). Now you have to generalize it!
- In the for loop, use parameters['W' + str(l)] to access Wl, where l is the iterative integer.
"""
np.random.seed(3)
parameters = {}
L = len(layer_dims) # number of layers in the network
for l in range(1, L):
parameters['W' + str(l)] = np.random.randn(layer_dims[l], layer_dims[l-1])* np.sqrt(2 / layer_dims[l-1])
parameters['b' + str(l)] = np.zeros((layer_dims[l], 1))
assert(parameters['W' + str(l)].shape == layer_dims[l], layer_dims[l-1])
assert(parameters['W' + str(l)].shape == layer_dims[l], 1)
return parameters
def compute_cost(a3, Y):
"""
Implement the cost function
Arguments:
a3 -- post-activation, output of forward propagation
Y -- "true" labels vector, same shape as a3
Returns:
cost - value of the cost function
"""
m = Y.shape[1]
logprobs = np.multiply(-np.log(a3),Y) + np.multiply(-np.log(1 - a3), 1 - Y)
cost = 1./m * np.sum(logprobs)
return cost
def forward_propagation(X, parameters):
"""
Implements the forward propagation (and computes the loss) presented in Figure 2.
Arguments:
X -- input dataset, of shape (input size, number of examples)
parameters -- python dictionary containing your parameters "W1", "b1", "W2", "b2", "W3", "b3":
W1 -- weight matrix of shape ()
b1 -- bias vector of shape ()
W2 -- weight matrix of shape ()
b2 -- bias vector of shape ()
W3 -- weight matrix of shape ()
b3 -- bias vector of shape ()
Returns:
loss -- the loss function (vanilla logistic loss)
"""
# 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)
z2 = np.dot(W2, a1) + b2
a2 = relu(z2)
z3 = np.dot(W3, a2) + b3
a3 = sigmoid(z3)
cache = (z1, a1, W1, b1, z2, a2, W2, b2, z3, a3, W3, b3)
return a3, cache
def backward_propagation(X, Y, cache):
"""
Implement the backward propagation presented in figure 2.
Arguments:
X -- input dataset, of shape (input size, number of examples)
Y -- true "label" vector (containing 0 if cat, 1 if non-cat)
cache -- cache output from forward_propagation()
Returns:
gradients -- A dictionary with the gradients with respect to each parameter, activation and pre-activation variables
"""
m = X.shape[1]
(z1, a1, W1, b1, z2, a2, W2, b2, z3, a3, W3, b3) = cache
dz3 = 1./m * (a3 - Y)
dW3 = np.dot(dz3, a2.T)
db3 = np.sum(dz3, axis=1, keepdims = True)
da2 = np.dot(W3.T, dz3)
dz2 = np.multiply(da2, np.int64(a2 > 0))
dW2 = np.dot(dz2, a1.T)
db2 = np.sum(dz2, axis=1, keepdims = True)
da1 = np.dot(W2.T, dz2)
dz1 = np.multiply(da1, np.int64(a1 > 0))
dW1 = np.dot(dz1, X.T)
db1 = 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
def predict(X, y, parameters):
"""
This function is used to predict the results of a n-layer neural network.
Arguments:
X -- data set of examples you would like to label
parameters -- parameters of the trained model
Returns:
p -- predictions for the given dataset X
"""
m = X.shape[1]
p = np.zeros((1,m), dtype = np.int)
# Forward propagation
a3, caches = forward_propagation(X, parameters)
# convert probas to 0/1 predictions
for i in range(0, a3.shape[1]):
if a3[0,i] > 0.5:
p[0,i] = 1
else:
p[0,i] = 0
print("Accuracy: " + str(np.mean((p[0,:] == y[0,:]))))
return p
def load_dataset():
np.random.seed(3)
train_X, train_Y = sklearn.datasets.make_moons(n_samples=300, noise=.2)
train_X = train_X.T
train_Y = train_Y.reshape((1, train_Y.shape[0]))
return train_X, train_Y
输出结果:
- momentum正则化:
/home/yuyang/anaconda3/envs/tensor1-6/bin/python3.5 "/media/yuyang/Yinux/heima/Deep learning/梯度下降优化算法/optimization.py"
第 0 次迭代的损失值: 0.690741
第 1000 次迭代的损失值: 0.685341
第 2000 次迭代的损失值: 0.647145
第 3000 次迭代的损失值: 0.619594
第 4000 次迭代的损失值: 0.576665
第 5000 次迭代的损失值: 0.607324
第 6000 次迭代的损失值: 0.529476
第 7000 次迭代的损失值: 0.460936
第 8000 次迭代的损失值: 0.465780
第 9000 次迭代的损失值: 0.464740
Accuracy: 0.7966666666666666
Process finished with exit code 0
- adam正则化:
/home/yuyang/anaconda3/envs/tensor1-6/bin/python3.5 "/media/yuyang/Yinux/heima/Deep learning/梯度下降优化算法/optimization.py"
第 0 次迭代的损失值: 0.690552
第 1000 次迭代的损失值: 0.185501
第 2000 次迭代的损失值: 0.150830
第 3000 次迭代的损失值: 0.074454
第 4000 次迭代的损失值: 0.125959
第 5000 次迭代的损失值: 0.104344
第 6000 次迭代的损失值: 0.100676
第 7000 次迭代的损失值: 0.031652
第 8000 次迭代的损失值: 0.111973
第 9000 次迭代的损失值: 0.197940
Accuracy: 0.94
Process finished with exit code 0
总结:adam正则化效果姣好.