上個模型令人討厭的地方是光訓練就花了一個小時的時間,等結果并不是一個令人心情愉快的事情。這一部分,我們將討論將兩個技巧結合讓網絡訓練的更快!
直覺上的解決辦法是,開始訓練時取一個較高的學習率,隨著迭代次數的增多不停的減小這個值。這是有道理的,因為開始的時候我們距離全局最優點非常遠,我們想要朝著最優點的方向大步前進;然而里最優點越近,我們就前進的越謹慎,以免一步跨過去。舉個例子說就是你乘火車回家,但你進家門的時候肯定是走進去,不能讓火車開進去。
關于深度學習中的初始化和動量的重要性是Ilya Sutskever等人的談話和論文的標題。在那里,我們學習了另一個有用的技巧來促進深度學習:即在訓練期間增加優化方法的動量參數。
在我們以前的模型中,我們將學習率和學習勢初始化為靜態的0.01和0.9。讓我們來改變這兩個參數,使得學習率隨著迭代次數線性減小,同時讓學習動量增大。
NeuralNet允許我們在訓練時通過on_epoch_finished函數來更新參數。于是我們傳一個函數給on_epoch_finished,使得這個函數在每次迭代之后被調用。然而,在我們改變學習率和學習勢這兩個參數之前,我們必須將這兩個參數改變為Theano shared variables。好在這非常簡單。
import theano
def float32(k):
return np.cast['float32'](k)
net4 = NeuralNet(
# ...
update_learning_rate=theano.shared(float32(0.03)),
update_momentum=theano.shared(float32(0.9)),
# ...
)
我們傳遞的回調函數或者回調列表在調用時需要兩個參數:nn,它是NeuralNet的實例;train_history,它和nn.history是同一個值。
不使用硬編碼值的毀掉函數,我們將使用一個可參數化的類,在其中定義一個call函數來作為我們的回調函數。讓我們把這個類叫做AdjustVariable,實現是相當簡單的:
class AdjustVariable(object):
def __init__(self, name, start=0.03, stop=0.001):
self.name = name
self.start, self.stop = start, stop
self.ls = None
def __call__(self, nn, train_history):
if self.ls is None:
self.ls = np.linspace(self.start, self.stop, nn.max_epochs)
epoch = train_history[-1]['epoch']
new_value = float32(self.ls[epoch - 1])
getattr(nn, self.name).set_value(new_value)
現在讓我們把這些變化放到一起,并開始準備訓練網絡:
net4 = NeuralNet(
# ...
update_learning_rate=theano.shared(float32(0.03)),
update_momentum=theano.shared(float32(0.9)),
# ...
regression=True,
# batch_iterator_train=FlipBatchIterator(batch_size=128),
on_epoch_finished=[
AdjustVariable('update_learning_rate', start=0.03, stop=0.0001),
AdjustVariable('update_momentum', start=0.9, stop=0.999),
],
max_epochs=3000,
verbose=1,
)
X, y = load2d()
net4.fit(X, y)
with open('net4.pickle', 'wb') as f:
pickle.dump(net4, f, -1)
我們將訓練兩個網絡:net4不使用我們的FlipBatchIterator,net5采用了。 除此之外,他們是相同的。
這是net4的學習:
Epoch | Train loss | Valid loss | Train / Val
--------|--------------|--------------|----------------
50 | 0.004216 | 0.003996 | 1.055011
100 | 0.003533 | 0.003382 | 1.044791
250 | 0.001557 | 0.001781 | 0.874249
500 | 0.000915 | 0.001433 | 0.638702
750 | 0.000653 | 0.001355 | 0.481806
1000 | 0.000496 | 0.001387 | 0.357917
酷,訓練發生得更快了! 在我們調整學習速度和學習動量之前,在500代和1000代的訓練誤差是以前在net2中的一半。 這一次,泛化程度似乎已經在750個左右的時期之后停止改善; 看起來沒有什么意義的培訓更長。
net5用了數據擴充之后怎么樣?
poch | Train loss | Valid loss | Train / Val
--------|--------------|--------------|----------------
50 | 0.004317 | 0.004081 | 1.057609
100 | 0.003756 | 0.003535 | 1.062619
250 | 0.001765 | 0.001845 | 0.956560
500 | 0.001135 | 0.001437 | 0.790225
750 | 0.000878 | 0.001313 | 0.668903
1000 | 0.000705 | 0.001260 | 0.559591
1500 | 0.000492 | 0.001199 | 0.410526
2000 | 0.000373 | 0.001184 | 0.315353
再次,我們有比net3更快的訓練,更好的結果。在1000次迭代之后,結果比net3迭代了3000次的效果還要好。 此外,使用數據擴充訓練的模型現在比沒有數據擴充的模型好約10%。
丟棄技巧(Dropout)
2012年,在通過防止特征探測器的共適應來改進神經網絡論文中引入了dropout,它是一種流行的正則化技術,工作非常好。我不會深入了解它為什么這么好的細節,你可以在別的地方讀到。
像任何其他正則化技術一樣,如果我們有一個過度擬合的網絡,dropout才有意義,這在上一節中我們訓練的net5網絡顯然是這樣。 重要的是要記住,讓你的網絡訓練得很好,首先過擬合,然后正則化。
要在Lasagne中使用dropout,我們將在現有圖層之間添加DropoutLayer圖層,并為每個圖層指定退出概率。 這里是我們新網的完整定義。我在這些行的末尾添加了一個#!,用于區分和net5的不同。
net6 = NeuralNet(
layers=[
('input', layers.InputLayer),
('conv1', layers.Conv2DLayer),
('pool1', layers.MaxPool2DLayer),
('dropout1', layers.DropoutLayer), # !
('conv2', layers.Conv2DLayer),
('pool2', layers.MaxPool2DLayer),
('dropout2', layers.DropoutLayer), # !
('conv3', layers.Conv2DLayer),
('pool3', layers.MaxPool2DLayer),
('dropout3', layers.DropoutLayer), # !
('hidden4', layers.DenseLayer),
('dropout4', layers.DropoutLayer), # !
('hidden5', layers.DenseLayer),
('output', layers.DenseLayer),
],
input_shape=(None, 1, 96, 96),
conv1_num_filters=32, conv1_filter_size=(3, 3), pool1_pool_size=(2, 2),
dropout1_p=0.1, # !
conv2_num_filters=64, conv2_filter_size=(2, 2), pool2_pool_size=(2, 2),
dropout2_p=0.2, # !
conv3_num_filters=128, conv3_filter_size=(2, 2), pool3_pool_size=(2, 2),
dropout3_p=0.3, # !
hidden4_num_units=500,
dropout4_p=0.5, # !
hidden5_num_units=500,
output_num_units=30, output_nonlinearity=None,
update_learning_rate=theano.shared(float32(0.03)),
update_momentum=theano.shared(float32(0.9)),
regression=True,
batch_iterator_train=FlipBatchIterator(batch_size=128),
on_epoch_finished=[
AdjustVariable('update_learning_rate', start=0.03, stop=0.0001),
AdjustVariable('update_momentum', start=0.9, stop=0.999),
],
max_epochs=3000,
verbose=1,
)
我們的網路現在已經大到可以讓Python報一個“超過最大遞歸限制”錯誤了,所以為了避免這一點,我們最好增加python的遞歸限制。
import sys
sys.setrecursionlimit(10000)
X, y = load2d()
net6.fit(X, y)
import cPickle as pickle
with open('net6.pickle', 'wb') as f:
pickle.dump(net6, f, -1)
看一下我們現在的訓練,我們注意到訓練速度又變慢了,以為添加了dropout,這是不出意料的效果。然而,整個網絡的表現事實上超過了net5:
Epoch | Train loss | Valid loss | Train / Val
--------|--------------|--------------|---------------
50 | 0.004619 | 0.005198 | 0.888566
100 | 0.004369 | 0.004182 | 1.044874
250 | 0.003821 | 0.003577 | 1.068229
500 | 0.002598 | 0.002236 | 1.161854
1000 | 0.001902 | 0.001607 | 1.183391
1500 | 0.001660 | 0.001383 | 1.200238
2000 | 0.001496 | 0.001262 | 1.185684
2500 | 0.001383 | 0.001181 | 1.171006
3000 | 0.001306 | 0.001121 | 1.164100
過擬合也似乎沒有那么糟糕。雖然我們必須小心這些數字:訓練錯誤和驗證錯誤之間的比率現在有一個稍微不同的意義,因為訓練錯誤評估與遺漏,而驗證錯誤評估沒有遺漏。訓練錯誤的更有價值的值是
from sklearn.metrics import mean_squared_error
print mean_squared_error(net6.predict(X), y)
# prints something like 0.0010073791
在我們以前的沒有dropout的模型中,訓練上的誤差為0.000373。 所以不僅我們的dropout網表現略微好一點,它的過擬合也比我們以前的模型少得多。 這是個好消息,因為這意味著當我們使網絡更大(更具表現力)時,我們可以期望更好的性能。 這就是我們將嘗試下一步:我們將最后兩個隱藏層中的單位數從500增加到1000。我們需要修改這些行:
net7 = NeuralNet(
# ...
hidden4_num_units=1000, # !
dropout4_p=0.5,
hidden5_num_units=1000, # !
# ...
)
相比于沒有dropout的網絡,改進效果更加明顯:
Epoch | Train loss | Valid loss | Train / Val
--------|--------------|--------------|---------------
50 | 0.004756 | 0.007043 | 0.675330
100 | 0.004440 | 0.005321 | 0.834432
250 | 0.003974 | 0.003928 | 1.011598
500 | 0.002574 | 0.002347 | 1.096366
1000 | 0.001861 | 0.001613 | 1.153796
1500 | 0.001558 | 0.001372 | 1.135849
2000 | 0.001409 | 0.001230 | 1.144821
2500 | 0.001295 | 0.001146 | 1.130188
3000 | 0.001195 | 0.001087 | 1.099271
有一點過擬合,但效果著實不錯。我的感覺是,如果繼續增加訓練次數,模型效果會變得更棒。試一下:
net12 = NeuralNet(
# ...
max_epochs=10000,
# ...
)
Epoch | Train loss | Valid loss | Train / Val
--------|--------------|--------------|---------------
50 | 0.004756 | 0.007027 | 0.676810
100 | 0.004439 | 0.005321 | 0.834323
500 | 0.002576 | 0.002346 | 1.097795
1000 | 0.001863 | 0.001614 | 1.154038
2000 | 0.001406 | 0.001233 | 1.140188
3000 | 0.001184 | 0.001074 | 1.102168
4000 | 0.001068 | 0.000983 | 1.086193
5000 | 0.000981 | 0.000920 | 1.066288
6000 | 0.000904 | 0.000884 | 1.021837
7000 | 0.000851 | 0.000849 | 1.002314
8000 | 0.000810 | 0.000821 | 0.985769
9000 | 0.000769 | 0.000803 | 0.957842
10000 | 0.000760 | 0.000787 | 0.966583
現在你是dropout魔力的見證者了。:-)
讓我們比較一下到目前為止我們訓練過的網絡:
Name Description Epochs Train loss Valid loss
net1?single hidden?400?0.002244?0.003255?
net2?convolutions?1000?0.001079?0.001566?
net3?augmentation?3000?0.000678?0.001288?
net4?mom + lr adj?1000?0.000496?0.001387?
net5?net4 + augment?2000?0.000373?0.001184?
net6?net5 + dropout?3000?0.001306?0.001121?
net7?net6 + epochs?10000?0.000760?0.000787
評論
查看更多