偏差与方差、L1正则化、L2正则化、dropout正则化、神经网络调优、批标准化Batch Normalization(BN层)、Early Stopping、数据增强

日萌社

人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新)


3.2 深度学习正则化

3.2.1 偏差与方差

3.2.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%

以上这些比例可以根据数据集情况选择。

3.2.1.2 偏差与方差的意义

“偏差-方差分解”(bias-variance decomposition)是解释学习算法泛化性能的一种重要工具。

泛化误差可分解为偏差、方差与噪声,泛化性能是由学习算法的能力数据的充分性以及学习任务本身的难度所共同决定的。

  • 偏差:度量了学习算法的期望预测与真实结果的偏离程度,即刻画了学习算法本身的拟合能力
  • 方差:度量了同样大小的训练集的变动所导致的学习性能的变化,即刻画了数据扰动所造成的影响
  • 噪声:表达了在当前任务上任何学习算法所能够达到的期望泛化误差的下界,即刻画了学习问题本身的难度

那么偏差、方差与我们的数据集划分到底有什么关系呢?

  • 1、训练集的错误率较小,而验证集/测试集的错误率较大,说明模型存在较大方差,可能出现了过拟合
  • 2、训练集和测试集的错误率都较大,且两者相近,说明模型存在较大偏差,可能出现了欠拟合
  • 3、训练集和测试集的错误率都较小,且两者相近,说明方差和偏差都较小,这个模型效果比较好。

所以我们最终总结,方差一般指的是数据模型得出来了,能不能对未知数据的扰动预测准确。而偏差说明在训练集当中就已经误差较大了,基本上在测试集中没有好的效果。

所以如果我们的模型出现了较大的方差或者同时也有较大的偏差,该怎么去解决?

3.2.1.3 解决方法

对于高方差,有以下几种方式:

  • 获取更多的数据,使得训练能够包含所有可能出现的情况
  • 正则化(Regularization)
  • 寻找更合适的网络结构

对于高偏差,有以下几种方式:

  • 扩大网络规模,如添加隐藏层或者神经元数量
  • 寻找合适的网络架构,使用更大的网络结构,如AlexNet
  • 训练时间更长一些

不断尝试,直到找到低偏差、低方差的框架。

3.2.2 正则化(Regularization)

正则化即在成本函数中加入一个正则化项(惩罚项),惩罚模型的复杂度,防止网络过拟合

3.2.2.1 L1与L2正则化(复习)

3.2.2.2 正则化项的理解

在损失函数中增加一项,那么其实梯度下降是要减少损失函数的大小,对于L2或者L1来讲都是要去减少这个正则项的大小,那么也就是会减少W权重的大小。这是我们一个直观上的感受。


矩阵的2范数的平方的求导 

 

 


3.2.2.3 神经网络中的正则化

神经网络中的正则化与逻辑回归相似,只不过参数W变多了,每一层都有若干个权重,可以理解成一个矩阵

3.2.2.4 L1与L2正则化为什么能够防止过拟合

正则化因子设置的足够大的情况下,为了使成本函数最小化,权重矩阵 W 就会被设置为接近于 0 的值,直观上相当于消除了很多神经元的影响,那么大的神经网络就会变成一个较小的网络。

3.2.3 Droupout正则化

Droupout论文地址:http://jmlr.org/papers/volume15/srivastava14a.old/srivastava14a.pdf

翻译解释:

1、过拟合是一个严重的问题。大型网络的使用速度也较慢,这使得在测试时结合许多不同的大型神经网络的预测来处理过拟合问题变得非常棘手。Dropout是解决这个问题的一种技巧。关键的想法是在训练过程中,从神经网络中随机丢弃神经元(以及它们的连接)
2、在训练过程中,dropout技巧会从指数级的的不同的“稀疏”网络中抽取样本。在测试时,就可以很容易地估计出所有这些稀疏网络的预测结果的平均。这显著地减少了过拟合,并且比其他正则化方法有了很大的改进
3、drop改进了神经网络在视觉、语音识别、文档分类和计算生物学等监督学习任务上的性能,获得了许多基准数据集state-of-the-art结果。
  • 定义:Droupout是随机的对神经网络每一层进行丢弃部分神经元操作。

对于网络的每一层会进行设置保留概率,即keep_prob。假设keep_prob为0.8,那么也就是在每一层所有神经元有20% 的概率直接失效,可以理解为0.

3.2.3.1 Dropout 模型描述

下面我们来讲解dropout的工作过程

  • 1、训练过程

    • 1、神经元随机失效,概率为P
    • 2、并且在神经元存在且工作的状态下,权重才会更新,权重更新的越多理论上会变得更大
  • 2、测试过程

    • 1、神经元随机失效,概率为0
    • 2、所有的神经元都会参与计算,大于训练时候的任意一个模型的计算量
  • 3、模型过程伪代码过程

讲解:

  • 4、结构代码实现-(inverted dropout)

实现随机失活算法,最常用的一种是反向随机失活(inverted dropout) ,这种方式会对每层进行如下代码操作

def dropout(x, level):
    if level < 0. or level >= 1:
        raise Exception('dropout保持概率在0到1之间')
    sample = np.random.binomial(n=1, p=level, size=x.shape)
    print(sample)
    x *= sample
    x /= level
    return x
x = np.asarray([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], dtype=np.float32)
dropout(x, 0.8)

# 其中这步骤在计算的时候对余下的非0的进行扩大倍数,因为p<0。0/x=0,所以0不影响,训练的时候代码可以直接写上
x /= level
  • 利用inverted dropout,我们可以在训练的时候直接将dropout后留下的权重扩大1/p 倍,这样就可以使结果的scale保持不变,而在预测的时候也不用做额外的操作了,更方便一些。

问题:为什么需要去做rescale(重要)

  • 解释原理:

通俗解释:训练的时候只有占比为p 的隐藏层单元参与训练,那么在预测的时候,如果所有的隐藏层单元都需要参与进来,则得到的结果相比训练时平均要大1/p ,为了避免这种情况,就需要测试的时候将输出结果乘以 1/p 使下一层的输入规模保持不变。

数学解释:试的时候不去随机失活?所以为了保证训练和预测的时候期望一样,必须得做scale;我们设置dropout probability为p, 那么该层大约有比例为p的单元会被drop掉,因为每个神经元是否drop就是一次伯努利实验,这层的dropout概率服从伯努利分布,而分布的期望就是np。

1、使用dropout对特征的影响

左边没有dropout的情况下,每个单元并没有很明确地要去检测某个图像特征,这是由于单元之间的相互适应性太强,需要组合在一起才能比较好地工作,但是就右图有dropout的情况而言很明显可以看出,每个单元都在检测一种边缘、笔画或者点,也就是说每个单元之间没有那么强的相互适应性。泛化能力会强很多。

2、使用dropout对参数稀疏程度影响

dropout对隐层激活值稀疏性的影响,两种模型均使用ReLU。左:平均激活度的直方图显示大多数单位的平均激活度约为2.0。激活的直方图显示了一个远离零的巨大模式。显然,很大一部分单元具有很高的活化度。右图:平均激活度的直方图显示,大多数单位的平均激活度较小,约为0.7。可以看出加入了dropout以后激活值的分布向0靠近,稀疏性明显增加。

3、dropout 率大小影响

论文坐着做了两种尝试,如果保持隐藏层神经元个数n固定的时候,p的值影响如左图表示,取值0.5~0.8合适;如果是pn保持不变的时候,P也是在0.5到0.8之间比较合适。(在实验的时候做了pn=256第一层网络和pn=512第二层网络进行的测试)

3.2.3.2 Droupout为什么有效(如何理解)

  • 动机

    • 1、Dropout的动机来自于关于性别在进化中的作用的理论。
      • 有性生殖包括从一个亲本和另一个亲本中提取一半的基因,加入非常少量的随机突变,并将它们结合产生受精卵。
      • 无性繁殖是通过父母基因的拷贝中加入微小突变来创造后代。
      • 结果:中大型动物中,一般是有性繁殖,有性繁殖是指后代的基因从父母两方各继承一半。但是从直观上看,似乎无性繁殖更加合理,无性繁殖可以保留大段大段的优秀基因。
      • 原因:有性繁殖则将基因随机拆了又拆,破坏了大段基因的联合适应性。自然选择中毕竟没有选择无性繁殖,而选择了有性繁殖, 因此一些基因必须要自己学会做一些事而不只是跟很多其他基因合作,这种合作会减少个体适应性。类似地,随机的选择dropout可以增加隐层神经元的健壮性。
  • 解释

    • 、减少神经元之间复杂的共适应性。当隐藏层神经元被随机删除之后,使得全连接网络具有了一定的稀疏化,从而有效地减轻了不同特征的协同效应。也就是说,有些特征可能会依赖于固定关系的隐含节点的共同作用,而通过Dropout的话,它强迫一个神经单元,和随机挑选出来的其他神经单元共同工作,达到好的效果。消除减弱了神经元节点间的联合适应性,增强了泛化能力。
    • 2、Dropout可以看作是一种随机正则化技术,加入了 dropout 后,输入的特征都存在被随机清除的可能,所以该神经元不会再特别依赖于任何一个输入特征,也就是不会给任何一个输入特征设置太大的权重。通过传播过程,dropout 将产生和 L2 正则化相同的收缩权重的效果。
      • 对于不同的层,设置的keep_prob大小也不一致,神经元较少的层,会设keep_prob为 1.0,而神经元多的层则会设置比较小的keep_prob

3.2.3.3 Dropout实用指南

论文中给出了相关dropout时间的注意事项

  • 1、dropout会增加训练的时间,通常带有dropout的网络会比不带的标准网络需要2~3倍的训练时间。

    • 主要原因在于参数更新非常多,每次训练都要训练不同失去神经元的结构。因此,不是最后一次计算出的梯度需要在测试期间使用。使用的dropout率越高,所需要的训练时间越长,所以需要在训练时间和防止过拟合之间做出权衡。
    • 对于线性回归可以使用L2正则化等方法,对于更加复杂的结构,使用具体那种正则化方法不是很明确。
  • 2、因为dropout在梯度下降中引入了大量的噪声导致梯度相互抑制,因此学习速率要增加10-100倍。另外一个减少噪声的方法是用momentum,momentum对于标准网络一般采用0.9,对于dropout网络一般是0.95-0.99。两个方法可以同时采用
  • 3、防止学习过快导致网络增长太大,一般给隐藏层权重的norm一个上限c,c一般取值3-4
    • dropout和max-normalization、large decaying learning rates and high momentum等组合起来效果更好
  • 4、Dropout Rate:一般取值0.5-0.8之间,drop比例p越小,要求隐含层n越大,训练也会越慢
  • 5、调试时候使用技巧:
    • 先确定网络没问题,再打开dropout训练测试:dropout 的缺点是成本函数无法被明确定义,因为每次会随机消除一部分神经元,所以参数也无法确定具体哪一些,在反向传播的时候带来计算上的麻烦,也就无法保证当前网络是否损失函数下降的。如果要使用droupout,会先关闭这个参数,保证损失函数是单调下降的,确定网络没有问题,再次打开droupout才会有效。

3.2.4 案例:DropOut机制与L2正则化实现

1、目的

实现下面模型中引入DropOut机制以及L2正则化机制,提高模型效果。

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

引入DropOut机制需要做的事情有,前向传播的随机失活,在反向计算损失时候的处理以及在预测阶段的实现。

2、步骤分析

  • 1、数据读取、模型前向与反向传播代码封装
  • 2、在前向传播与反向传播中插入DropOut机制代码
  • 3、实现L2正则化机智的前向与反向传播

3、代码实现


utils.py

import numpy as np
import matplotlib.pyplot as plt
import sklearn
import sklearn.datasets
import sklearn.linear_model
import scipy.io


def sigmoid(x):
    """
    sigmoid
    """
    s = 1 / (1 + np.exp(-x))
    return s


def relu(x):
    """
    relu
    """
    s = np.maximum(0, x)

    return s


def initialize_parameters(layer_dims):
    """
    初始化网络参数
    """
    np.random.seed(3)
    parameters = {}
    L = len(layer_dims)

    for l in range(1, L):
        parameters['W' + str(l)] = np.random.randn(layer_dims[l], layer_dims[l-1]) / np.sqrt(layer_dims[l-1])
        parameters['b' + str(l)] = np.zeros((layer_dims[l], 1))

    return parameters


def forward_propagation(X, parameters):
    """
    正常前向传播
    """
        
    # 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):
    """
    正常反向传播
    """
    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)
    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 = 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


def update_parameters(parameters, grads, learning_rate):
    """
    更新参数
    """

    n = len(parameters) // 2 # number of layers in the neural networks

    # Update rule for each parameter
    for k in range(n):
        parameters["W" + str(k+1)] = parameters["W" + str(k+1)] - learning_rate * grads["dW" + str(k+1)]
        parameters["b" + str(k+1)] = parameters["b" + str(k+1)] - learning_rate * grads["db" + str(k+1)]
        
    return parameters


def predict(X, y, parameters):
    """
    在训练阶段进行rescale,预测过程不用做出变化
    """
    
    m = X.shape[1]
    p = np.zeros((1,m), dtype = np.int)
    
    # 不带处理的前向传播过程
    a3, caches = forward_propagation(X, parameters)
    
    # 根据预测概率转化目标值
    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 compute_cost(a3, Y):
    """
    计算损失
    """
    m = Y.shape[1]
    
    logprobs = np.dot(-np.log(a3), Y.T) + np.dot(-np.log(1 - a3), (1 - Y).T)
    cost = 1./m * np.nansum(logprobs)
    
    return cost


def load_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
    return train_X, train_Y, test_X, test_Y

regularization.py

import numpy as np

from utils import initialize_parameters, compute_cost, forward_propagation, backward_propagation, update_parameters
from utils import load_dataset, predict, sigmoid, relu


# -------
# 1、有正则化的损失函数计算
# 2、正则化后的反向传播计算
# -------
def compute_cost_with_regularization(A3, Y, parameters, lambd):
    """
    损失函数中增加L2正则化
    """
    m = Y.shape[1]
    W1 = parameters["W1"]
    W2 = parameters["W2"]
    W3 = parameters["W3"]

    # 1、计算交叉熵损失
    cross_entropy_cost = compute_cost(A3, Y)

    # 2、加入L2正则化的损失部分
    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


def backward_propagation_with_regularization(X, Y, cache, lambd):
    """
    对增加了L2正则化后的损失函数进行反向传播计算
    """

    m = X.shape[1]
    (Z1, A1, W1, b1, Z2, A2, W2, b2, Z3, A3, W3, b3) = cache

    # 含有正则化的反向传播公式
    # z = a - y
    # # dw = 1/ m * ((dz*A.T)+ lambd * w)
    dZ3 = A3 - Y
    dW3 = 1. / m * (np.dot(dZ3, A2.T) + lambd * 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 * 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 * 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


def forward_propagation_with_dropout(X, parameters, keep_prob=0.5):
    """
    带有dropout的前向传播
    """
    # 1、获取parameter的参数结果
    W1 = parameters["W1"]
    b1 = parameters["b1"]
    W2 = parameters["W2"]
    b2 = parameters["b2"]
    W3 = parameters["W3"]
    b3 = parameters["b3"]

    # 2、三层网络的输出计算
    # linear->relu->linear->relu->linear->sigmoid
    # 第一层
    Z1 = np.dot(W1, X) + b1
    A1 = relu(Z1)

    # 第一层到第二层的dropout
    D1 = np.random.binomial(n=1, p=keep_prob, size=A1.shape)
    A1 = np.multiply(A1, D1)
    A1 /= keep_prob

    # 第二层
    Z2 = np.dot(W2, A1) + b2
    A2 = relu(Z2)

    # 第二层到第三层的dropout
    D2 = np.random.binomial(n=1, p=keep_prob, size=A2.shape)
    A2 = np.multiply(A2, D2)
    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


def backward_propagation_with_dropout(X, Y, cache, keep_prob):
    """
    droupout的反向传播
    需要随机失活神经元,同样对输出做扩大
    """

    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 /= 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 /= 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


def model(X, Y, learning_rate=0.3, num_iterations=30000, lambd=0, keep_prob=1):
    """
    使用三层网络,激活函数为:LINEAR->RELU->LINEAR->RELU->LINEAR->SIGMOID.
    第一个隐层:20个神经元
    第二个隐层:3个神经元
    输出层:1个神经元
    """

    grads = {}
    costs = []
    m = X.shape[1]
    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.
        # 如果keep_prob=1,进行正常前向传播
        # 如果keep_prob<1,说明需要进行droupout计算
        if keep_prob == 1:
            a3, cache = forward_propagation(X, parameters)
        elif keep_prob < 1:
            a3, cache = forward_propagation_with_dropout(X, parameters, keep_prob)

        # 计算损失
        # 如果传入lambd不为0,判断加入正则化
        if lambd == 0:
            cost = compute_cost(a3, Y)
        else:
            cost = compute_cost_with_regularization(a3, Y, parameters, lambd)

        # 只允许选择一个,要么L2正则化,要么Droupout
        if lambd == 0 and keep_prob == 1:
            exit()

        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 i % 10000 == 0:
            print("迭代次数为 {}: 损失结果大小:{}".format(i, cost))
            costs.append(cost)

    return parameters


if __name__ == '__main__':

    train_X, train_Y, test_X, test_Y = load_dataset()

    # 正则化效果
    parameters = model(train_X, train_Y, lambd=0.1)

    print("训练集的准确率:")
    predictions_train = predict(train_X, train_Y, parameters)

    print("测试集的准确率:")
    predictions_test = predict(test_X, test_Y, parameters)

    # dropout效果
    parameters = model(train_X, train_Y, keep_prob=0.86)

    print("训练集的准确率:")
    predictions_train = predict(train_X, train_Y, parameters)

    print("测试集的准确率:")
    predictions_test = predict(test_X, test_Y, parameters)

运行效果

# dropout
迭代次数为 0: 损失结果大小:0.6616994233431039
迭代次数为 10000: 损失结果大小:0.20018054682333422
迭代次数为 20000: 损失结果大小:0.197457875306683
训练集的准确率:
Accuracy: 0.9383886255924171
测试集的准确率:
Accuracy: 0.95
# 正则化
迭代次数为 0: 损失结果大小:0.6543912405149825
迭代次数为 10000: 损失结果大小:0.0
迭代次数为 20000: 损失结果大小:0.0
训练集的准确率:
Accuracy: 0.9289099526066351
测试集的准确率:
Accuracy: 0.95

3.2.5 神经网络调优

我们经常会涉及到参数的调优,也称之为超参数调优。目前我们从第二部分中讲过的超参数有

3.2.5.1 调参技巧

对于调参,通常采用跟机器学习中介绍的网格搜索一致,让所有参数的可能组合在一起,得到N组结果。然后去测试每一组的效果去选择。

3.2.5.2 运行

通常我们有这么多参数组合,每一个组合运行训练都需要很长时间,但是如果资源允许的话,可以同时并行的训练多个参数模型,并观察效果。如果资源不允许的话,还是得一个模型一个模型的运行,并时刻观察损失的变化

所以对于这么多的超参数,调优是一件复杂的事情,怎么让这么多的超参数范围,工作效果还能达到更好,训练变得更容易呢?

3.2.6 批标准化(Batch Normalization)

Batch Normalization论文地址:https://arxiv.org/abs/1502.03167

其中最开头介绍是这样的:

  • 我们将这种现象称为内部协变量偏移 ,并通过 标准化层 输入来解决问题

  • 可以消除对Dropout的需求。

  • 应用于最先进的图像分类模型,批量标准化实现了相同的精度,培训步骤减少了14倍,并且显着地超过了原始模型。使用批量标准化网络的集合,我们改进了ImageNet分类的最佳发布结果:达到4.9%的前5个验证错误(和4.8%的测试错误),超出了人类评估者的准确性。

首先我们还是回到之前,我们对输入特征 X 使用了标准化处理。标准化化后的优化得到了加速。

对于深层网络呢?我们接下来看一下这个公式,这是向量的表示。表示每Mini-batch有m个样本。

  • m个样本的向量表示

3.2.6.1 批标准化公式

  • 为什么要使用这样两个参数

如果各隐藏层的输入均值在靠近0的区域,即处于激活函数的线性区域,不利于训练非线性神经网络,从而得到效果较差的模型。因此,需要用 γ 和 β 对标准化后的结果做进一步处理。

3.2.6.2 过程图

3.2.6.3 为什么批标准化能够是优化过程变得简单

我们之前在原文中标记了一个问题叫做叫做"internal covariate shift"。这个词翻译叫做协变量偏移,那么有一个解释叫做 在网络当中数据的分布会随着不同数据集改变 。这是网络中存在的问题。那我们一起来看一下数据本身分布是在这里会有什么问题。

也就是说如果我们在训练集中的数据分布如左图,那么网络当中学习到的分布状况也就是左图。那对于给定一个测试集中的数据,分布不一样。这个网络可能就不能准确去区分。这种情况下,一般要对模型进行重新训练。

3.2.6.4 BN总结

Batch Normalization 也起到微弱的正则化效果,但是不要将 Batch Normalization 作为正则化的手段,而是当作加速学习的方式。Batch Normalization主要解决的还是反向传播过程中的梯度问题(梯度消失和爆炸)。


 

 


3.2.7 其它方法

  • 早停止法(Early Stopping)
  • 数据增强

3.2.7.1 早停止法(Early Stopping)

通常我们在训练验证的时候,发现过拟合。可以得到下面这张损失图

通常不断训练之后,损失越来越小。但是到了一定之后,模型学到的过于复杂(过于拟合训练集上的数据的特征)造成测试集开始损失较小,后来又变大。模型的w参数会越来越大,那么可以在测试集损失减小一定程度之后停止训练。

但是这种方法治标不治本,得从根本上解决数据或者网络的问题。

3.2.7.2 数据增强

  • 1、数据增强定义

指通过剪切、旋转/反射/翻转变换、缩放变换、平移变换、尺度变换、对比度变换、噪声扰动、颜色变换等一种或多种组合数据增强变换的方式来增加数据集的大小

即使卷积神经网络被放在不同方向上,卷积神经网络对平移、视角、尺寸或照度(或以上组合)保持不变性,都会认为是一个物体。

为什么这样做?

假设数据集中的两个类。左边的代表品牌A(福特),右边的代表品牌B(雪佛兰)。

假设完成了训练,并且输入下面的图像(品牌A),但是你的神经网络输出认为它是品牌B的汽车!

为什么会发生这种现象? 因为算法可能会寻找区分一个类和另一个类的最明显特征。在这个例子中 ,这个特征就是所有品牌A的汽车朝向左边,所有品牌B的汽车朝向右边。神经网络的好坏取决于输入的数据。

怎么解决这个问题?

我们需要减少数据集中不相关特征的数量。对上面的汽车类型分类器来说,你只需要将现有的数据集中的照片水平翻转,使汽车朝向另一侧。现在,用新的数据集训练神经网络,通过过增强数据集,可以防止神经网络学习到不相关的模式,提升效果。(在没有采集更多的图片前提下)

那么我们应该在机器学习过程中的什么位置进行数据增强?在向模型输入数据之前增强数据集。

  • 2、数据增强类别:
    • 离线增强。预先进行所有必要的变换,从根本上增加数据集的规模(例如,通过翻转所有图像,保存后数据集数量会增加2倍)。
    • 在线增强,或称为动态增强。可通过对即将输入模型的小批量数据的执行相应的变化,这样同一张图片每次训练被随机执行一些变化操作,相当于不同的数据集了。

那么我们的代码中也是进行这种在线增强。

3.2.7.3 数据增强技术

  • 1、几何变换类
  • 2、颜色变换类

1、几何变换类

  • 几何变换类即对图像进行几何变换,包括翻转,旋转,裁剪,变形,缩放等各类操作

下面一些方法基础但功能强大的增强技术,目前被广泛应用。

  • 翻转:tf.image.random_flip_left_right
    • 你可以水平或垂直翻转图像。一些架构并不支持垂直翻转图像。但,垂直翻转等价于将图片旋转180再水平翻转。下面就是图像翻转的例子。

  • 旋转:rotate

剪裁:random_crop

  • 随机从原始图像中采样一部分,然后将这部分图像调整为原始图像大小。这个方法更流行的叫法是随机裁剪。

从左侧开始分别为:原始图像,从左上角裁剪出一个正方形部分,然后从右下角裁剪出一个正方形部分。剪裁的部分被调整为原始图像大小。
  • 平移、缩放等等方法

2、颜色变换

  • 常见的包括噪声、模糊、颜色变换、擦除、填充等等

  • 噪声:基于噪声的数据增强就是在原来的图片的基础上,随机叠加一些噪声,最常见的做法就是高斯噪声

3.2.7.5 效果

数据增强的效果是非常好的,比如下面的例子,绿色和粉色表示没有数据增强之前的损失和准确率效果,红色和蓝色表示数据增强之后的损失和准确率结果,可以看到学习效果也改善较快。

那么TensorFlow 官方源码都是基于 vgg与inception论文的图像增强介绍,全部通过tf.image相关API来预处理图像。并且提供了各种封装过tf.image之后的API。那么TensorFlow 官网也给我们提供了一些模型的数据增强过程。

3.2.7.6 数据增强库

  • 一些开源的库:Image augmentation for machine learning experiments.:https://github.com/aleju/imgaug
    • 里面有详细介绍以及效果动图
  • TensorFlow库:

TensorFlow给我们提供了很多相关的API,其中就有tf.image模块部分,如下:

random_brightness(...):以随机因素调整图像的亮度。
random_contrast(...):通过随机因素调整图像的对比度。
random_crop(...):将张量随机裁剪为给定大小。
random_flip_left_right(...):随机水平翻转图像(从左到右)。
random_flip_up_down(...):垂直(上下颠倒)随机翻转图像。
random_hue(...):通过随机因素调整RGB图像的色调。
random_jpeg_quality(...):随机更改jpeg编码质量,以产生jpeg噪声。
random_saturation(...):通过随机因子调整RGB图像的饱和度。
resize(...):调整大小images以size使用指定的method。
resize_with_crop_or_pad(...):裁剪和/或将图像填充到目标宽度和高度。
resize_with_pad(...):调整图像大小并将其填充到目标宽度和高度。
rgb_to_grayscale(...):将一个或多个图像从RGB转换为灰度。
rgb_to_hsv(...):将一个或多个图像从RGB转换为HSV。
rgb_to_yiq(...):将一个或多个图像从RGB转换为YIQ。
rgb_to_yuv(...):将一个或多个图像从RGB转换为YUV。

总结:对于大多数数据增强技术来说,我们会直接使用现成的API,在后续训练某些数据集用到具体哪些方法是我们在做方法上选择的讲解。

3.2.8 总结

  • 偏差与方差的意义
  • L2正则化与L1正则化的数学原理
    • 权重衰减
  • droupout原理以及方法
    • Inverted droupout
  • 正则化的作用
  • 数据增强的作用
  • 基本的超参数以及调参技巧
  • BN的原理以及作用
发布了372 篇原创文章 · 获赞 121 · 访问量 20万+

猜你喜欢

转载自blog.csdn.net/zimiao552147572/article/details/104877006