• 神经网络过拟合问题


    在训练数据不够多,网络结构很复杂,或者overtraining时,可能会产生过拟合问题。

    一般我们会将整个数据集分为训练集training data、validation data,testing data。这个validation data是什么?它其实就是用来避免过拟合的,在训练过程中,我们通常用它来确定一些超参数(比如根据validation data上的accuracy来确定提前终止的epoch大小、根据validation data确定learning rate等等)。那为啥不直接在testing data上做这些呢?因为如果在testing data做这些,那么随着训练的进行,我们的网络实际上就是在一点一点地overfitting我们的testing data,导致最后得到的testing accuracy没有任何参考意义。因此,training data的作用是计算梯度更新权重,validation data如上所述,testing data则给出一个accuracy以判断网络的好坏。

    那么过拟合的直观解释为,随着训练过程的进行,模型复杂度增加,在training data上的error渐渐减小,但是在验证集上的error却反而渐渐增大——因为训练出来的网络过拟合了训练集,对训练集外的数据效果不好。也就是如过产生了过拟合问题,那么用training data得到的准确率同testing data得到的准确率相差非常大。

                                                                    

    那么为了防止过拟合问题,可用的方法有:得到更大的数据集,正则化方法,在网络层dropout一下。下面主要对dropout和正则化方法做讨论。

    1.dropout

                                        

    dropout的实质就是随机的让每层的一些神经元不工作以减少模型的复杂度。

    它为什么有助于防止过拟合呢?可以简单地这样解释,运用了dropout的训练过程,相当于训练了很多个只有半数隐层单元的神经网络(后面简称为“半数网络”),每一个这样的半数网络,都可以给出一个分类结果,这些结果有的是正确的,有的是错误的。随着训练的进行,大部分半数网络都可以给出正确的分类结果,那么少数的错误分类结果就不会对最终结果造成大的影响。

    2.正则化

    L2正则化就是在代价函数后面再加上一个正则化项:

    C0代表原始的代价函数,后面那一项就是L2正则化项,它是这样来的:所有参数w的平方的和,除以训练集的样本大小n。λ就是正则项系数,权衡正则项与C0项的比重。另外还有一个系数1/2,1/2经常会看到,主要是为了后面求导的结果方便,后面那一项求导会产生一个2,与1/2相乘刚好凑整。

    L2正则化项是怎么避免overfitting的呢?我们推导一下看看,先求导:

    可以发现L2正则化项对b的更新没有影响,但是对于w的更新有影响:

    在不使用L2正则化时,求导结果中w前系数为1,现在w前面系数为 1−ηλ/n ,因为η、λ、n都是正的,所以 1−ηλ/n小于1,它的效果是减小w,这也就是权重衰减(weight decay)的由来当然考虑到后面的导数项,w最终的值可能增大也可能减小

    另外,需要提一下,对于基于mini-batch的随机梯度下降,w和b更新的公式跟上面给出的有点不同:

    对比上面w的更新公式,可以发现后面那一项变了,变成所有导数加和,乘以η再除以m,m是一个mini-batch中样本的个数。

    到目前为止,我们只是解释了L2正则化项有让w“变小”的效果,但是还没解释为什么w“变小”可以防止overfitting?一个所谓“显而易见”的解释就是:更小的权值w,从某种意义上说,表示网络的复杂度更低,对数据的拟合刚刚好(这个法则也叫做奥卡姆剃刀),而在实际应用中,也验证了这一点,L2正则化的效果往往好于未经正则化的效果。当然,对于很多人(包括我)来说,这个解释似乎不那么显而易见,所以这里添加一个稍微数学一点的解释(引自知乎):

    过拟合的时候,拟合函数的系数往往非常大,为什么?如下图所示,过拟合,就是拟合函数需要顾忌每一个点,最终形成的拟合函数波动很大。在某些很小的区间里,函数值的变化很剧烈。这就意味着函数在某些小区间里的导数值(绝对值)非常大,由于自变量值可大可小,所以只有系数足够大,才能保证导数值很大。

    而正则化是通过约束参数的范数使其不要太大,所以可以在一定程度上减少过拟合情况。

     L1正则化:

    在原始的代价函数后面加上一个L1正则化项,即所有权重w的绝对值的和,乘以λ/n(这里不像L2正则化项那样,需要再乘以1/2,具体原因上面已经说过。)

    同样先计算导数:

    上式中sgn(w)表示w的符号。那么权重w的更新规则为:

    比原始的更新规则多出了η * λ * sgn(w)/n这一项。当w为正时,更新后的w变小。当w为负时,更新后的w变大——因此它的效果就是让w往0靠,使网络中的权重尽可能为0,也就相当于减小了网络复杂度,防止过拟合。

    另外,上面没有提到一个问题,当w为0时怎么办?当w等于0时,|W|是不可导的,所以我们只能按照原始的未经正则化的方法去更新w,这就相当于去掉η*λ*sgn(w)/n这一项,所以我们可以规定sgn(0)=0,这样就把w=0的情况也统一进来了。(在编程的时候,令sgn(0)=0,sgn(w>0)=1,sgn(w<0)=-1)

    下一篇将讨论优化器(optimizer)问题。

    '''
    ##加入正则项
    '''
    import tensorflow as tf
    from numpy.random import RandomState
    from tensorflow.examples.tutorials.mnist import input_data
    from matplotlib import pyplot as plt
    import numpy as np
     
    #获取一层神经网络的权重,并将权重的L2正则化损失加入到集合中
    def get_weight(shape,lamda):
        #定义变量
        var = tf.Variable(tf.random_normal(shape=shape),dtype=tf.float32)
        #将变量的L2正则化损失添加到集合中
        tf.add_to_collection("losses",tf.contrib.layers.l2_regularizer(lamda)(var))
        return var
     
     
    if __name__=="__main__":
        #获取数据
        mnist = input_data.read_data_sets('MNIST_data',one_hot = True)
        batch_size = 100
        n_batch = mnist.train.num_examples // batch_size
        #定义输入输出节点
        x= tf.placeholder(tf.float32,[None,784])
        y= tf.placeholder(tf.float32,[None,10])
        #定义每次迭代数据的大小
        #定义两层神经网络,并设置每一层神经网络的节点数目
        layer_dimension = [784,10]
        #获取神经网络的层数
        n_layers = len(layer_dimension)
        #定义神经网络第一层的输入
        cur_layer = x
        #当前层的节点个数
        in_dimension = layer_dimension[0]
        #通过循环来生成5层全连接的神经网络结构
        for i in range(1,n_layers):
            #定义神经网络上一层的输出,下一层的输入
            out_dimension = layer_dimension[i]
            #定义当前层中权重的变量,并将变量的L2损失添加到计算图的集合中
            weight = get_weight([in_dimension,out_dimension],0.001)
            #定义偏置项
            bias = tf.Variable(tf.constant(0.1,shape=[out_dimension]))
            #使用RELU激活函数
            cur_layer = tf.nn.softmax(tf.matmul(cur_layer,weight) + bias)
            #定义下一层神经网络的输入节点数
            in_dimension = layer_dimension[i]
        #定义均方差的损失函数
        loss_mse = tf.reduce_mean(tf.square(y - cur_layer))
        #将均方差孙函数添加到集合
        tf.add_to_collection("losses",loss_mse)
        #获取整个模型的损失函数,tf.get_collection("losses")返回集合中定义的损失
        #将整个集合中的损失相加得到整个模型的损失函数
        loss = tf.add_n(tf.get_collection("losses"))
        
        train = tf.train.MomentumOptimizer(0.1,0.9).minimize(loss)

        init = tf.global_variables_initializer()

        correct = tf.equal(tf.argmax(cur_layer,1),tf.argmax(y,1))
        accuracy = tf.reduce_mean(tf.cast(correct,tf.float32))

        loss_mse_list = []
        loss_list = []
        with tf.Session() as sess:
            sess.run(init)
            for epoch in range(31):
                for batch in range(n_batch):
                    batch_xs,batch_ys = mnist.train.next_batch(batch_size)
                    sess.run(train,feed_dict={x:batch_xs,y:batch_ys})
                    if(batch == n_batch-1):
                        loss_mse_list.append(sess.run(loss_mse,feed_dict={x:batch_xs,y:batch_ys}))
                        loss_list.append(sess.run(loss,feed_dict={x:batch_xs,y:batch_ys}))
                    
                acc = sess.run(accuracy,feed_dict={x:mnist.test.images,y:mnist.test.labels})
                print('iteration ', str(epoch),' accuracy: ',acc)
            
        plt.plot(np.array(loss_list) - np.array(loss_mse_list),'b-')
        plt.show()
  • 相关阅读:
    mysql的导出与导入命令的使用
    kendo ui 左侧弹出分享框
    Pytorch离线安装方法
    Python单词接龙小程序
    Shell结束指定名称的进程
    Shell脚本sed命令修改文件的某一行
    Shell中单双引号的区别
    矩阵问题
    泛型通配符详解
    合并链表
  • 原文地址:https://www.cnblogs.com/54hys/p/10199359.html
Copyright © 2020-2023  润新知