使用CNN(convolutional neural nets)检测脸部关键点教程(四):学习率,学习势,dropout

第七部分 让 学习率学习势 随着时间改变

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

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

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

在我们上一个模型中,我们将学习率和学习势初始化为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的学习过程如下:

EpochTrain lossValid lossTrain / Val
500.0042160.0039961.055011
1000.0035330.0033821.044791
2500.0015570.0017810.874249
5000.0009150.0014330.638702
7500.0006530.0013550.481806
10000.0004960.0013870.357917

可以看到,训练速度快多了。第500次、1000次迭代的训练错误,net4都比net2低了一半。但是泛华程度到750次就不再变好了,所以看起来没有必要训练这么多次。

看看打开了数据扩充之后的net5表现如何:

EpochTrain lossValid lossTrain / Val
500.0043170.0040811.057609
1000.0037560.0035351.062619
2500.0017650.0018450.956560
5000.0011350.0014370.790225
7500.0008780.0013130.668903
10000.0007050.0012600.559591
15000.0004920.0011990.410526
20000.0003730.0011840.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:

EpochTrain lossValid lossTrain / Val
500.0046190.0051980.888566
1000.0043690.0041821.044874
2500.0038210.0035771.068229
5000.0025980.0022361.161854
10000.0019020.0016071.183391
15000.0016600.0013831.200238
20000.0014960.0012621.185684
25000.0013830.0011811.171006
30000.0013060.0011211.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的网络,改进效果更加明显:

EpochTrain lossValid lossTrain / Val
500.0047560.0070430.675330
1000.0044400.0053210.834432
2500.0039740.0039281.011598
5000.0025740.0023471.096366
10000.0018610.0016131.153796
15000.0015580.0013721.135849
20000.0014090.0012301.144821
25000.0012950.0011461.130188
30000.0011950.0010871.099271

有一点过拟合,但效果着实不错。我的感觉是,如果继续增加训练次数,模型效果会变得更棒。试一下:

net12 = NeuralNet(
    # ...
    max_epochs=10000,
    # ...
    )
EpochTrain lossValid lossTrain / Val
500.0047560.0070270.676810
1000.0044390.0053210.834323
5000.0025760.0023461.097795
10000.0018630.0016141.154038
20000.0014060.0012331.140188
30000.0011840.0010741.102168
40000.0010680.0009831.086193
50000.0009810.0009201.066288
60000.0009040.0008841.021837
70000.0008510.0008491.002314
80000.0008100.0008210.985769
90000.0007690.0008030.957842
100000.0007600.0007870.966583

现在你是dropout魔力的见证者了。:-)

让我们比较一下到目前为止我们训练过的网络:

NameDescriptionEpochsTrain lossValid loss
net1single hidden4000.0022440.003255
net2convolutions10000.0010790.001566
net3augmentation30000.0006780.001288
net4mom + lr adj10000.0004960.001387
net5net4 + augment20000.0003730.001184
net6net5 + dropout30000.0013060.001121
net7net6 + epochs100000.0007600.000787

使用CNN(convolutional neural nets)检测脸部关键点教程(一):环境搭建和数据
使用CNN(convolutional neural nets)检测脸部关键点教程(二):浅层网络训练和测试
使用CNN(convolutional neural nets)检测脸部关键点教程(三):卷积神经网络训练和数据扩充
使用CNN(convolutional neural nets)检测脸部关键点教程(五):通过前训练(pre-train)训练专项网络

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值