題目
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)