使用CNN(convolutional neural nets)关键的一点是检测到的面部教程(四):学习率,学习潜能,dropout

时间:2024-01-01 11:10:27

第七部分 让 学习率学习潜能 随时间的变化

光训练就花了一个小时的时间。等结果并非一个令人心情愉快的事情。这一部分。我们将讨论将两个技巧结合让网络训练的更快!

直觉上的解决的方法是,開始训练时取一个较高的学习率,随着迭代次数的增多不停的减小这个值。这是有道理的,由于開始的时候我们距离全局最长处很远。我们想要朝着最长处的方向大步前进;然而里最长处越近,我们就前进的越慎重,以免一步跨过去。举个样例说就是你乘火车回家,但你进家门的时候肯定是走进去。不能让火车开进去。

从讨论深度学习中初始化和学习势的重要性的资料,我们得到了一种新的技巧来加速网络的训练:添加最优化方法的“动量參数”。假设你还不知道什么是学习势,请阅读【參考】。

在我们上一个模型中,我们将学习率和学习势初始化为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)训练专项网络