技术栈

主页 > AI >

[kaggle系列 五] 通过mnist来研究神经网络的一些细节(3)

题目

https://www.kaggle.com/c/digit-recognizer

前言

上一篇用了个简单的神经网络来解决mnist的问题,介绍了一下权重初始化的技巧,防止训练梯度到最后一层的时候变为nan,还使用了bn算法,取得了一些成效。这一章里,我会介绍一下训练中使用的更新梯度的优化算法,还有对神经网络进行正则化和dropout的操作。

SGD的问题

使用随机梯度下降算法,虽然能够使得梯度不断下降,让模型收敛到一个较优解,但是也存在不少问题,看下面的图:

假设中间的笑脸是最优解,sgd的更新的轨迹会像图中显示的那样,不断波动。这是因为我们每次选择了一个batch去更新,每次的更新完全是根据当前计算的loss,就会出现这种情况:当前batch让你往东南方向走,下一个batch让你往西南方向走,虽然大体的方向是对的,但是会不断波动,导致更新速度比较慢。

Momentum算法

上面也讲了,sgd算法的问题是因为每次更新都完全依赖当前的batch。momentum算法来源于物理世界中动量的概念,更新模拟了物体的惯性,更新的时候使用一部分上次更新的方向,使用当前batch的loss对更新方向进行调整,得到最终更新的方向。利用这种方式,减少梯度震荡带来的影响,对更新进行加速:

这里$ ho$是一个超参数,表示上一个梯度更新方向衰减的系数,一般用0.9左右。$alpha$是学习率。
更新方式用图像表示就是这样,真正的更新方向是上一个更新的方向与当前方向的合成,有点类似与力的合成:

Nesterov Momentum算法

nesterov momentum其实和momentum差不多,只是修改了更新方向的合成方式:

公式:

和momentum差不多,就不多说了

AdaGrad算法

与上面的算法不同的是,AdaGrad算法关注的不是更新方向的问题,而是更新速率的问题,下面看一小段AdaGrad的代码:

可以看到代码里在更新的时候除了一个数,这个数是梯度的平方累积的开方,而1e-7是一个常数,这个只是为了防止计算的时候除0,可以发现,更新的速率是不断递减的,当更新到最后最后的时候,更新基本就会停止。
使用AdaGrad主要的目的是让学习率在学习过程中进行一些调节,因为开始的时候,学习率大一些可以加快训练速度,但是到了训练后期,可能需要进行一些细微方向的调整,但是学习率比较大的话就无法做到。

RMSProp算法

可以看到,AdaGrad算法中,学习率是不断递减的,这样就带来了一些问题。初始学习率要设置的比较合理,学习率设置较小的话,全程训练都会很慢,尤其到了后期,训练基本进行不下去,但是学习率过大,前期又有可能导致梯度爆炸之类的问题。
为了解决这个问题,RMSProp在AdaGrad算法的基础上,对分母做了一些处理,把前面累积的和削减,再加上新的值,这样既可以保证后期更新速率不至于太慢,又能较为灵活的调整学习率。

Adam算法

Adam其实就是把Momentum算法和RMSProp算法结合到一起,各取所长,双剑合璧,可以达到比较好的效果~

实验及效果

使用tensorflow的话,这几种算法的api都是有的,直接调用就好了:

# SGD
opt = tf.train.GradientDescentOptimizer(learning_rate=self.learning_rate)
# Momentum
opt = tf.train.MomentumOptimizer(learning_rate=self.learning_rate,momentum=0.9)
# Nesterov
opt = tf.train.MomentumOptimizer(learning_rate=self.learning_rate,momentum=0.9, use_nesterov=True)
# Adagrad
opt = tf.train.AdagradOptimizer(learning_rate=self.learning_rate)
# RMSProp
opt = tf.train.RMSPropOptimizer(learning_rate=self.learning_rate)
# Adam
opt = tf.train.AdamOptimizer(learning_rate=self.learning_rate)

我用了[784,256,64,10]这个网络,使用了bn算法,学习率用的0.01,50batch,训了5个epoch看效果,下面是测试结果:

Sgd:

效果一般般吧~

Momentum

要比sgd效果好一些,更新速度要更高一些。

Nesterov

和momentum没多大差距,毕竟原理差不多

AdaGrad

AdaGrad就表现得不尽如人意了,但是个人认为这是学习率设置不合理导致的,最开始的学习率设置的低了,导致更新速度一直很慢。

RMSProp

相比之下,RMSProp就好多了,因为做了一些decay操作,使得更新不会太僵硬。

Adam

我寄予厚望的Adam这次实验表现的没有RMSProp好啊……但这个也应该是学习率的问题,后面用了这个,效果还是很好的。

小结

虽然做了几个简单的实验比较,但是我发现,这个结果并不是那么有说服力,这些算法对与学习率和各种参数的要求是不同的,很难拉到同一起跑线上去比较,所以具体用哪个还是看自己的需求和实际情况。不过Adam应该是比较普适的一个方案。

正则化与Dropout

在神经网络训练的时候,如果模型比较复杂,很有可能出现过拟合的情况:在训练集上效果很好,但是在测试集里就表现的很惨,如下图:

虽然前面我们介绍了bn算法可以有效地缓解过拟合的问题,但是不妨碍我们研究一下别的方法,而且这些方法与bn算法也不冲突,也是有机会登场的~

之前也说过,模型的规模过大,很容易导致过拟合的问题,因此我们可以对模型做一个人为的限制,使得模型的复杂度不要过大,不要去过分拟合测试数据。

通常使用的正则化方法有两种,L1正则化与L2正则化,这两个方法的公式都比较类似:
L1:

L2:

这两种方法基本差不多,都是引入了权重作为loss的一部分,这使得梯度会向着权重变小的方向偏移,这么做有什么用呢?在神经网络比较复杂的时候,拟合训练数据的方式可能并不只有一种,引入了正则化项,相当于把拟合方向往某一个方向拉扯,不让参数自由生长,无形之中就限制了模型的复杂度。
那么L1和L2两种正则化方法有什么不同?这个在整体上来看,可能差不多,但是L1正则化会另外一个特性,通过推导公式可以证明,L1正则化会把一部分参数衰减到0,也就是某个w为0,这个特性可以在特征选择中起到作用,比如卷积神经网络就可以用这个特性来提取图片某个区域的特征。

使用tensorflow代码也比较好写:

## add_to_collection可以收集参数到一个集合中,这是为了方便计算
## 因为我们每一层都有一个w
## tf.contrib.layers.l1_regularizer是tensorflow提供计算正则项的函数
## 0.002相当于公式中的lambda,会乘到w的累积上,是个超参数
tf.add_to_collection('loss', tf.contrib.layers.l1_regularizer(0.002)(w))
# other code ....
# 计算交叉熵损失函数
self.cross_entropy = -tf.reduce_sum(self.label*tf.log(self.y))
# 原来用的就是交叉熵,我们把它和之前求得的正则化项加在一起
tf.add_to_collection('loss', self.cross_entropy)
self.loss = tf.add_n(tf.get_collection('loss'))

ok,那么Dropout呢?这又是什么操作?这个操作也比较简单,就是给训练的输入加入噪声,让模型拥有更好的泛化能力,看下面的图:

dropout在每批数据过网络的时候,会随机选择一些结点抛弃掉,图中的虚线。也就是这个输入不起作用,又或者看作把输入的值抹去置0。通过这种方法,从某种意义上来说,训练数据变多了,不同结点抛弃的组合,会产生许多不一样的样本,神经网络必须在这种情况下,抓住输入的主要的、关键的特征才能训练好。大体就是给神经网络制造更多的难题,让它学会提纲挈领,这样才会有更好的泛化能力,不去过分拟合训练数据。
dropout的代码就更简单了,对输入进行下面的处理就行了:

# 0.8是抛弃x的某个值的概率
x = tf.nn.dropout(x,0.8)

代码与结果

测试一下发现正则化和dropout的效果并不明显,而用adam效果非常好(可能也是之前训练的不够充分)。后来想了想,把网络扩得更大一些,然后加了dropout训了一下,发现acc有所提高。最终的参数:
layers = [784,512,256,10]
act:elu
batch_size:50
epoch:25
learning_rate: 0.02
use_bn: True use_
dropout:True
opt:AdamOptimizer
train_acc is: 0.995741, test_acc is 0.977619
这个模型在kaggle上的准确率达到了0.97814,效果可以说已经很好了。在不使用卷积神经网络的情况下,我认为还是比较高的吧。下一步打算上卷积神经网络,看看能达到什么效果。

import os
import numpy as np
import tensorflow as tf
import random
# layers = [784, 1024,2048,512, 10] act:elu batch_size:128 epoch:15 learning_rate: 0.01 train_acc is: 0.921534, test_acc is 0.920000
# layers = [784,30,10] act:elu batch_size:30 epoch:10 learning_rate: 0.2 train_acc is: 0.958571, test_acc is 0.947857
# layers = [784,256,64,10] act:elu batch_size:50 epoch:20 learning_rate: 0.02 use_bn: True train_acc is: 0.972566, test_acc is 0.956190
# layers = [784,256,64,10] act:elu batch_size:50 epoch:20 learning_rate: 0.02 use_bn: True opt:AdamOptimizer train_acc is: 0.998995, test_acc is 0.97595
# layers = [784,512,256,10] act:elu batch_size:50 epoch:25 learning_rate: 0.02 use_bn: True use_dropout:True opt:AdamOptimizer train_acc is: 0.995741, test_acc is 0.977619

class SimpleModel(object):
    def __init__(self):
        self.learning_rate = 0.02
        self.batch_size = 50
        self.epoch = 25
        self.use_bn = True
        self.use_regularizer = False
        self.use_dropout = True

    def setRegularizer(self,w):
        if self.use_regularizer:
            tf.add_to_collection('loss', tf.contrib.layers.l1_regularizer(0.002)(w))

    def hide_layer(self, x, layer_name, var_shape, is_train=True, decay=0.999):
        if self.use_dropout and is_train:
            x = tf.nn.dropout(x,0.8)
        n_input = np.prod(var_shape[:-1])
        w_initializer = tf.truncated_normal_initializer(mean=0,stddev=1,dtype=tf.float32)
        b_initializer = tf.constant_initializer(0.1, dtype=tf.float32)
        W = tf.get_variable(layer_name + '_w', var_shape, initializer=w_initializer,dtype=tf.float32, trainable=True)/np.sqrt(n_input/2)
        b = tf.get_variable(layer_name + '_b', [var_shape[-1]],initializer=b_initializer,dtype=tf.float32, trainable=True)
        self.setRegularizer(W)
        
        out = tf.matmul(x,W) + b
        if self.use_bn:
            zero_initializer = tf.constant_initializer(0.0, dtype=tf.float32)
            one_initializer = tf.constant_initializer(1.0, dtype=tf.float32)
            scale = tf.get_variable(layer_name + '_scale', var_shape[-1], initializer=one_initializer,dtype=tf.float32, trainable=True)
            beta = tf.get_variable(layer_name + '_beta', var_shape[-1], initializer=zero_initializer,dtype=tf.float32, trainable=True)
            ema_mean = tf.get_variable(layer_name + '_emamean', var_shape[-1],initializer=zero_initializer,dtype=tf.float32, trainable=False)
            ema_var = tf.get_variable(layer_name + '_emavar', var_shape[-1],initializer=one_initializer,dtype=tf.float32, trainable=False)
            if is_train:
                batch_mean, batch_var = tf.nn.moments(out,[0])
                self.train_mean = tf.assign(ema_mean, ema_mean * decay + batch_mean * (1 - decay))
                self.train_var = tf.assign(ema_var, ema_var * decay + batch_var * (1 - decay))
                with tf.control_dependencies([self.train_mean, self.train_var]):
                    out = tf.nn.batch_normalization(out, self.train_mean, self.train_var, beta, scale, variance_epsilon=0.00001)
            else:
                out = tf.nn.batch_normalization(out, ema_mean, ema_var,beta, scale, variance_epsilon=0.00001)
        return tf.nn.elu(out)

    def build_model(self, is_train=True):
        print 'build_model'
        self.x = tf.placeholder(tf.float32,[None, 784])
        layer_output = self.x
        layers = [784,30,10]
        layers = [784,256,64,10]
        layers = [784,512,256,10]
        n = len(layers) - 1
        for i in range(n):
            var_shape = [layers[i], layers[i + 1]]
            layer_output = self.hide_layer(layer_output, 'layer_' + str(i), var_shape, is_train)
            # layer_output = tf.nn.elu(tf.matmul(layer_output,W) + b)
        self.y = tf.nn.softmax(layer_output)

        self.label = tf.placeholder(tf.float32,[None,10])
        self.cross_entropy = -tf.reduce_sum(self.label*tf.log(self.y))
        # opt = tf.train.GradientDescentOptimizer(learning_rate=self.learning_rate)
        # opt = tf.train.MomentumOptimizer(learning_rate=self.learning_rate,momentum=0.9)
        # opt = tf.train.MomentumOptimizer(learning_rate=self.learning_rate,momentum=0.9, use_nesterov=True)
        # opt = tf.train.AdagradOptimizer(learning_rate=self.learning_rate)
        # opt = tf.train.RMSPropOptimizer(learning_rate=self.learning_rate)
        opt = tf.train.AdamOptimizer(learning_rate=self.learning_rate)

        self.loss = self.cross_entropy
        if self.use_regularizer:
            tf.add_to_collection('loss', self.cross_entropy)
            self.loss = tf.add_n(tf.get_collection('loss'))

        self.train_step = opt.minimize(self.loss)

        config = tf.ConfigProto(allow_soft_placement=True, log_device_placement=False)
        self.sess = tf.Session(config=config)
        init = tf.global_variables_initializer()
        self.sess.run(init)
        self.saver = tf.train.Saver(tf.global_variables())

    def randomBatch(self,size, epoch):
        self.data_tags = []
        for i in range(epoch):
            for j in range(size):
                self.data_tags.append(j)
        random.shuffle(self.data_tags)
        self.data_pos = 0

    def getNextBatch(self, x_inputs, y_labels):
        batch_x = []
        batch_y = []
        m = len(self.data_tags)
        for i in range(self.batch_size):
            p = self.data_tags[self.data_pos]
            self.data_pos = (self.data_pos + 1)%m
            batch_x.append(x_inputs[p])
            batch_y.append(y_labels[p])
        return np.array(batch_x),np.array(batch_y)

    def train(self,x_inputs, y_labels):
        pos = 0
        count = 0
        total = int(len(x_inputs)/self.batch_size)
        self.randomBatch(len(x_inputs),self.epoch)
        for i in range(self.epoch*total):
            x_batch,y_batch = self.getNextBatch(x_inputs,y_labels)
            y, loss,_ = self.sess.run([self.y, self.loss,self.train_step],feed_dict={self.x:x_batch,self.label:y_batch})
            # print 'y:' + str(y)
            # print 'loss :' + str(loss)

            count += 1
            if count % 50 == 0:
                print 'step %d: ,loss:%.6f' % (count, loss)

        self.saver.save(self.sess, './train_models/simple2.model.ckpt',global_step=0)

        print 'train over'

    def init_model(self,modelName):
        self.build_model(False)
        self.saver.restore(self.sess, os.path.join('./train_models/',modelName) )

    def test(self, x):
        predict = self.sess.run(self.y, feed_dict={self.x:x})
        #predict = np.array(predict).astype(float)
        res = np.argmax(predict, axis=1)
        # print res
        return res
        #return np.argmax(predict, axis=1)

责任编辑:admin  二维码分享:
本文标签: selftftrainratelearningbatch
点击我更换图片

评论列表