[kaggle系列 五] 通過mnist來研究神經(jīng)網(wǎng)絡(luò)的一些細節(jié)(3)

題目

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

前言

上一篇用了個簡單的神經(jīng)網(wǎng)絡(luò)來解決mnist的問題,介紹了一下權(quán)重初始化的技巧妆毕,防止訓練梯度到最后一層的時候變?yōu)閚an流强,還使用了bn算法痹届,取得了一些成效。這一章里打月,我會介紹一下訓練中使用的更新梯度的優(yōu)化算法队腐,還有對神經(jīng)網(wǎng)絡(luò)進行正則化和dropout的操作。

SGD的問題

使用隨機梯度下降算法奏篙,雖然能夠使得梯度不斷下降柴淘,讓模型收斂到一個較優(yōu)解,但是也存在不少問題,看下面的圖:

假設(shè)中間的笑臉是最優(yōu)解悠就,sgd的更新的軌跡會像圖中顯示的那樣千绪,不斷波動。這是因為我們每次選擇了一個batch去更新梗脾,每次的更新完全是根據(jù)當前計算的loss荸型,就會出現(xiàn)這種情況:當前batch讓你往東南方向走,下一個batch讓你往西南方向走炸茧,雖然大體的方向是對的瑞妇,但是會不斷波動,導致更新速度比較慢梭冠。

Momentum算法

上面也講了辕狰,sgd算法的問題是因為每次更新都完全依賴當前的batch。momentum算法來源于物理世界中動量的概念控漠,更新模擬了物體的慣性蔓倍,更新的時候使用一部分上次更新的方向,使用當前batch的loss對更新方向進行調(diào)整盐捷,得到最終更新的方向偶翅。利用這種方式,減少梯度震蕩帶來的影響碉渡,對更新進行加速:

這里$\rho$是一個超參數(shù)聚谁,表示上一個梯度更新方向衰減的系數(shù),一般用0.9左右滞诺。$\alpha$是學習率形导。
更新方式用圖像表示就是這樣,真正的更新方向是上一個更新的方向與當前方向的合成习霹,有點類似與力的合成:

Nesterov Momentum算法

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

公式:

和momentum差不多,就不多說了

AdaGrad算法

與上面的算法不同的是淋叶,AdaGrad算法關(guān)注的不是更新方向的問題憔披,而是更新速率的問題,下面看一小段AdaGrad的代碼:

可以看到代碼里在更新的時候除了一個數(shù)爸吮,這個數(shù)是梯度的平方累積的開方芬膝,而1e-7是一個常數(shù),這個只是為了防止計算的時候除0形娇,可以發(fā)現(xiàn)锰霜,更新的速率是不斷遞減的,當更新到最后最后的時候桐早,更新基本就會停止癣缅。
使用AdaGrad主要的目的是讓學習率在學習過程中進行一些調(diào)節(jié)厨剪,因為開始的時候,學習率大一些可以加快訓練速度友存,但是到了訓練后期祷膳,可能需要進行一些細微方向的調(diào)整,但是學習率比較大的話就無法做到屡立。

RMSProp算法

可以看到直晨,AdaGrad算法中,學習率是不斷遞減的膨俐,這樣就帶來了一些問題勇皇。初始學習率要設(shè)置的比較合理,學習率設(shè)置較小的話焚刺,全程訓練都會很慢敛摘,尤其到了后期,訓練基本進行不下去乳愉,但是學習率過大兄淫,前期又有可能導致梯度爆炸之類的問題。
為了解決這個問題蔓姚,RMSProp在AdaGrad算法的基礎(chǔ)上捕虽,對分母做了一些處理,把前面累積的和削減赂乐,再加上新的值薯鳍,這樣既可以保證后期更新速率不至于太慢咖气,又能較為靈活的調(diào)整學習率挨措。

Adam算法

Adam其實就是把Momentum算法和RMSProp算法結(jié)合到一起,各取所長崩溪,雙劍合璧浅役,可以達到比較好的效果~

實驗及效果

使用tensorflow的話,這幾種算法的api都是有的伶唯,直接調(diào)用就好了:

# 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]這個網(wǎng)絡(luò)觉既,使用了bn算法,學習率用的0.01乳幸,50batch瞪讼,訓了5個epoch看效果,下面是測試結(jié)果:

Sgd:

效果一般般吧~

Momentum

要比sgd效果好一些粹断,更新速度要更高一些符欠。

Nesterov

和momentum沒多大差距,畢竟原理差不多

AdaGrad

AdaGrad就表現(xiàn)得不盡如人意了瓶埋,但是個人認為這是學習率設(shè)置不合理導致的希柿,最開始的學習率設(shè)置的低了诊沪,導致更新速度一直很慢。

RMSProp

相比之下曾撤,RMSProp就好多了端姚,因為做了一些decay操作,使得更新不會太僵硬挤悉。

Adam

我寄予厚望的Adam這次實驗表現(xiàn)的沒有RMSProp好啊……但這個也應該是學習率的問題渐裸,后面用了這個,效果還是很好的尖啡。

小結(jié)

雖然做了幾個簡單的實驗比較橄仆,但是我發(fā)現(xiàn),這個結(jié)果并不是那么有說服力衅斩,這些算法對與學習率和各種參數(shù)的要求是不同的盆顾,很難拉到同一起跑線上去比較,所以具體用哪個還是看自己的需求和實際情況畏梆。不過Adam應該是比較普適的一個方案您宪。

正則化與Dropout

在神經(jīng)網(wǎng)絡(luò)訓練的時候,如果模型比較復雜奠涌,很有可能出現(xiàn)過擬合的情況:在訓練集上效果很好宪巨,但是在測試集里就表現(xiàn)的很慘,如下圖:

雖然前面我們介紹了bn算法可以有效地緩解過擬合的問題溜畅,但是不妨礙我們研究一下別的方法捏卓,而且這些方法與bn算法也不沖突,也是有機會登場的~

之前也說過慈格,模型的規(guī)模過大怠晴,很容易導致過擬合的問題,因此我們可以對模型做一個人為的限制浴捆,使得模型的復雜度不要過大蒜田,不要去過分擬合測試數(shù)據(jù)。

通常使用的正則化方法有兩種选泻,L1正則化與L2正則化冲粤,這兩個方法的公式都比較類似:
L1:

L2:

這兩種方法基本差不多,都是引入了權(quán)重作為loss的一部分页眯,這使得梯度會向著權(quán)重變小的方向偏移梯捕,這么做有什么用呢?在神經(jīng)網(wǎng)絡(luò)比較復雜的時候窝撵,擬合訓練數(shù)據(jù)的方式可能并不只有一種傀顾,引入了正則化項,相當于把擬合方向往某一個方向拉扯忿族,不讓參數(shù)自由生長锣笨,無形之中就限制了模型的復雜度蝌矛。
那么L1和L2兩種正則化方法有什么不同?這個在整體上來看错英,可能差不多入撒,但是L1正則化會另外一個特性,通過推導公式可以證明椭岩,L1正則化會把一部分參數(shù)衰減到0茅逮,也就是某個w為0,這個特性可以在特征選擇中起到作用判哥,比如卷積神經(jīng)網(wǎng)絡(luò)就可以用這個特性來提取圖片某個區(qū)域的特征献雅。

使用tensorflow代碼也比較好寫:

## add_to_collection可以收集參數(shù)到一個集合中,這是為了方便計算
## 因為我們每一層都有一個w
## tf.contrib.layers.l1_regularizer是tensorflow提供計算正則項的函數(shù)
## 0.002相當于公式中的lambda塌计,會乘到w的累積上挺身,是個超參數(shù)
tf.add_to_collection('loss', tf.contrib.layers.l1_regularizer(0.002)(w))
# other code ....
# 計算交叉熵損失函數(shù)
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在每批數(shù)據(jù)過網(wǎng)絡(luò)的時候伊脓,會隨機選擇一些結(jié)點拋棄掉府寒,圖中的虛線。也就是這個輸入不起作用报腔,又或者看作把輸入的值抹去置0株搔。通過這種方法,從某種意義上來說榄笙,訓練數(shù)據(jù)變多了邪狞,不同結(jié)點拋棄的組合祷蝌,會產(chǎn)生許多不一樣的樣本茅撞,神經(jīng)網(wǎng)絡(luò)必須在這種情況下,抓住輸入的主要的巨朦、關(guān)鍵的特征才能訓練好米丘。大體就是給神經(jīng)網(wǎng)絡(luò)制造更多的難題,讓它學會提綱挈領(lǐng)糊啡,這樣才會有更好的泛化能力拄查,不去過分擬合訓練數(shù)據(jù)。
dropout的代碼就更簡單了棚蓄,對輸入進行下面的處理就行了:

# 0.8是拋棄x的某個值的概率
x = tf.nn.dropout(x,0.8)

代碼與結(jié)果

測試一下發(fā)現(xiàn)正則化和dropout的效果并不明顯堕扶,而用adam效果非常好(可能也是之前訓練的不夠充分)碍脏。后來想了想,把網(wǎng)絡(luò)擴得更大一些稍算,然后加了dropout訓了一下典尾,發(fā)現(xiàn)acc有所提高。最終的參數(shù):
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糊探,效果可以說已經(jīng)很好了钾埂。在不使用卷積神經(jīng)網(wǎng)絡(luò)的情況下,我認為還是比較高的吧科平。下一步打算上卷積神經(jīng)網(wǎng)絡(luò)褥紫,看看能達到什么效果。

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)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瞪慧,一起剝皮案震驚了整個濱河市髓考,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌弃酌,老刑警劉巖绳军,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異矢腻,居然都是意外死亡门驾,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進店門多柑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奶是,“玉大人,你說我怎么就攤上這事竣灌∧羯常” “怎么了?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵初嘹,是天一觀的道長及汉。 經(jīng)常有香客問我,道長屯烦,這世上最難降的妖魔是什么坷随? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮驻龟,結(jié)果婚禮上温眉,老公的妹妹穿的比我還像新娘。我一直安慰自己翁狐,他們只是感情好类溢,可當我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著露懒,像睡著了一般闯冷。 火紅的嫁衣襯著肌膚如雪砂心。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天蛇耀,我揣著相機與錄音计贰,去河邊找鬼。 笑死蒂窒,一個胖子當著我的面吹牛躁倒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播洒琢,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼秧秉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了衰抑?” 一聲冷哼從身側(cè)響起象迎,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎呛踊,沒想到半個月后砾淌,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡谭网,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年汪厨,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片愉择。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡劫乱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出锥涕,到底是詐尸還是另有隱情衷戈,我是刑警寧澤,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布层坠,位于F島的核電站殖妇,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏破花。R本人自食惡果不足惜谦趣,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望旧乞。 院中可真熱鬧蔚润,春花似錦磅氨、人聲如沸尺栖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽延赌。三九已至除盏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間挫以,已是汗流浹背者蠕。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留掐松,地道東北人踱侣。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像几睛,于是被迫代替她去往敵國和親恢总。 傳聞我的和親對象是個殘疾皇子角溃,可洞房花燭夜當晚...
    茶點故事閱讀 43,492評論 2 348

推薦閱讀更多精彩內(nèi)容