第七部分 让 学习率 和 学习潜能 随时间的变化
光训练就花了一个小时的时间。等结果并非一个令人心情愉快的事情。这一部分。我们将讨论将两个技巧结合让网络训练的更快!
直觉上的解决的方法是,開始训练时取一个较高的学习率,随着迭代次数的增多不停的减小这个值。这是有道理的,由于開始的时候我们距离全局最长处很远。我们想要朝着最长处的方向大步前进;然而里最长处越近,我们就前进的越慎重,以免一步跨过去。举个样例说就是你乘火车回家,但你进家门的时候肯定是走进去。不能让火车开进去。
从讨论深度学习中初始化和学习势的重要性的资料,我们得到了一种新的技巧来加速网络的训练:添加最优化方法的“动量參数”。假设你还不知道什么是学习势,请阅读【參考】。
在我们上一个模型中,我们将学习率和学习势初始化为0.01和0.9。让我们来改变这两个參数,使得学习率随着迭代次数线性减小。同一时候让学习势增大。
NeuralNet同意我们在训练时通过on_epoch_finished钩子函数来更新參数。于是我们传一个函数给on_epoch_finished,使得这个函数在每次迭代之后被调用。
然而。在我们改变学习率和学习势这两个參数之前,我们必须将这两个參数改变为Theano shared variables。
好在这很easy。
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次迭代的训练错误。net4都比net2低了一半。可是泛华程度到750次就不再变好了,所以看起来没有必要训练这么多次。
看看打开了数据扩充之后的net5表现怎样:
Epoch | 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相比net5训练的快多了。并且获得了更好的结果。
在1000次迭代后,结果比net3迭代了3000次的效果还要好。此外,使用了数据扩充的网络的验证错误也比不使用数据扩充好了10%。
第八部分 丢弃技巧(Dropout)
2012年。这篇paper中引入了一种叫做“Dropout”的技巧,作为正则化方法,dropout工作的出奇的好。这里不会讲dropout之所以好用的细节,只是你能够阅读这些參考。
和其它的正则化技巧一样。dropout仅仅有当网络过拟合的时候才有意义。上个部分我们训练的net5是明显过拟合的。注意一定要使你的网络过拟合,再使用正则化。
在Lasagne中使用正则化技巧。我们仅仅要在网络中加入DropoutLayer并指定每层dropout的概率。
这里是我们最新网络的完整定义,我在新加入的行后面加入了#。来标识与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 |
仍然过拟合不一定是坏事,虽然我们必须小心这些数字:训练错误和验证错误的比率如今的意义稍有不同,由于训练错误是受过dropout调制的,而验证错误没有。更有比較意义的数值是:
from sklearn.metrics import mean_squared_error
print mean_squared_error(net6.predict(X), y)
# prints something like 0.0010073791
在我们上一个没有dropout的模型里,训练集上的错误是0.00373。所以如今我们的dropout net不但表现稍好,并且过拟合水平更低。
这是好消息,由于我们能够通过使得网络更大获得更好的效果。这也正是我们接下来要做的。我们加倍网络最后两个隐层的单元个数。更新这两行:
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 |
使用CNN(convolutional neural nets)检測脸部关键点教程(一):环境搭建和数据
使用CNN(convolutional neural nets)检測脸部关键点教程(二):浅层网络训练和測试
使用CNN(convolutional neural nets)检測脸部关键点教程(三):卷积神经网络训练和数据扩充
使用CNN(convolutional neural nets)检測脸部关键点教程(五):通过前训练(pre-train)训练专项网络