使用CNN(convolutional neural nets)检测脸部关键点教程(四):学习率,学习势,dropout_cnn预测关键角点-程序员宅基地

技术标签: CNN  deep-learning  机器学习  dropout  训练技巧  计算机视觉学习  momentum  

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

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

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

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

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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/tanhongguang1/article/details/46492519

智能推荐

ros入门教程(五)-- 常用工具_rviz gazebo rqt_bag-程序员宅基地

文章浏览阅读386次。ROS常用工具仿真:Gazebo调试、可视化:Rviz、rqt命令行工具:rostopic、rosbag…专用工具:Moveit!Gazebo机器人仿真工具、ODE物理引擎、用于动力学,导航,感知等任务的模拟RVizThe Robot Visualization tool 可视化工具 方便监控和调试把一些抽象的传感器信息以图像的方式传送给我们,便于进行开发和调试rqt可视..._rviz gazebo rqt_bag

android 获取SD 卡权限_android 获取sd卡权限-程序员宅基地

文章浏览阅读603次。Java层获取权限:void GetExternalStoragePermission(){ if(!CheckExternalStoragePermission()) { ActivityCompat.requestPermissions(MainActivity.this, new String[]{android.Manifest.permission..._android 获取sd卡权限

jQuery常用事件方法整理-程序员宅基地

文章浏览阅读193次。jQuery的事件方法相比于js代码来说没有太大的区别,最大的区别就去去掉on,以及一些特殊的例子。例如鼠标事件中的hover的使用。hoverhover() 方法可以看做是mouseenter 和 mouseleave两个函数的结合,当只传入一个参数的时候,会在鼠标进入和离开指定元素时都执行参数函数,如果传入两个参数则是在进入指定元素时执行第一个函数,在离开指定元素时执行第二个函数。例如: $( ".div" ).hover( a, b) 当进入指定div标签时执行a函数,当离开指定div标._jquery常用事件

数值作业:Hermite插值法之C语言实现代码_hermit插值 c语言-程序员宅基地

文章浏览阅读7.8k次,点赞10次,收藏24次。这个问题个人也看的不太懂,参考百科上的计算说明,把它转化成相应成代码即可.埃尔米特插值是另一类插值问题,这类插值在给定的节点处,不但要求插值多项式的函数值与被插函数的函数值相同。同时还要求在节点处,插值多项式的一阶直至指定阶的导数值,也与被插函数的相应阶导数值相等,这样的插值称为埃尔米特插值,或称为Hermite插值。 Hermite插值在不同的节点,提出的差值条件个数可以不同,若在某节点xi,要求_hermit插值 c语言

远程桌面无法找到计算机不属于指定网络,远程桌面找不到计算机-程序员宅基地

文章浏览阅读8.2k次。这是我第一次尝试设置一些虚拟机。 我正在尝试rdp给他们,但我遇到了这个错误This is my first attempt in setting up some VMs. I am trying to rdp to them but I'm encountering this error**远程桌面无法找到计算机"\[服务器\]"。 这可能意味着"\[服务器\]"。 不属于指定的网络。 验证您尝..._远程桌面无法找到计算机 不属于指定网络

idea类存在找不到解决办法_idea 找不到类-程序员宅基地

文章浏览阅读8.9k次,点赞9次,收藏24次。idea类存在找不到解决办法996的程序猿 2019-01-09 13:23:07 51954 收藏 101分类专栏: 工具类 文章标签: idea 找不到类 idea 找不到符号版权1.刷新maven项目2.清理idea缓存3.maven clean install4.重新bulid5.如果使用了lombok插件开启之后重新build6.maven依赖冲突导致1.打开当前maven模块或者,父类模块对应的pom,哪个模块有冲突进入那个模块!2.ctlr+alt+shif_idea 找不到类

随便推点

【开发日常】Linux下设置机箱上的风扇转速_nct6775-程序员宅基地

文章浏览阅读1.2w次。最近在调试一个设备,非常的吵,决定把风扇转速调低,降低噪音。这篇文章可能不具有通用性,仅自己记录,用作以后参考。风扇的转速通过pwm调节,因此先找下有没有pwm模块:ls /sys/class/hwmon/hwmon*/对应路径下找不到pwm的话,说明模块没有加载,需要编译模块,加载后才可以到 https://www.kernel.org/ 下载内核源码windows下通过ssh连接运行linux..._nct6775

思科模拟器上完成单臂路由配置的试验纪实_cisco 实验 单臂路由器配置-程序员宅基地

文章浏览阅读5.4k次,点赞7次,收藏21次。实验环境:Cisco Packet Tracer Student 6.2.0.0052 全局模式下show run显示如下配置:--------------------------------------------------全局模式---------------------------------------------------------路由器:cisco 2620XM 配置_cisco 实验 单臂路由器配置

Docker学习笔记_when using add with more than one source file, the-程序员宅基地

文章浏览阅读1.8k次。学习来源:狂神说JavaDocker基本组成原理镜像(image)docker镜像就好比是一个模版,可以通过这个模版创建容器服务,tomcat===》run===〉tomcat01容器(提供服务),通过这个镜像可以创建多个容器(最终服务运行或者项目运行就在容器中)。容器(container)Docker利用容器技术,独立运行一个或一组应用,通过镜像来创建。启动,停止,删除等基本命令,目前可以将这个容器理解为一个简易的Linux。仓库(repository)仓库就是存放镜像的地方。_when using add with more than one source file, the destination must be a dir

Altium AD20电源分割的方法,AD内电层的电源分割,AD电源层电源分割_内电层分割-程序员宅基地

文章浏览阅读2w次,点赞47次,收藏224次。AD20内电层电源分割实例。我们通常会在 元件布局合理,且不影响其他信号线走线的情况下。将大电流的电源放在 Top、Bottom层,使用多边形铺铜进行电源分割。而内电层的电源分割操作方法与 Top、Bottom层不同(内电层不能使用多边形铺铜)。这里有一个实例,借由实例的操作来讲述AD20的 内电层电源分割 方式。AD20内电层的电源分割实例图中标记的网络 VIN,受限于元件及布局的遮挡,不容易从Top层进行铺铜连线。于是打算通过第3层 -PWR层 ,用电源分割的方式实现网络连接。1_内电层分割

Android消息推送MQTT实战_github android tv 推送消息-程序员宅基地

文章浏览阅读1.2k次。1 前言年初做了一款Android TV 应用,用到了MQTT。主要实现的是类似一些景区利用大屏幕实时显示景点人数,超过人数就不允许进入。即利用闸机设备监控到进景区的游客,然后通过MQTT将消息发送给大屏幕,最后大屏幕实时显示景区人数,并响应一个消息通知闸机设备已经收到了它发过来的消息(确保消息到达)。这篇文章会模拟真实的使用流程进行讲解,即闸机发布消息——服务器(代理)收到消息转发给大屏幕—..._github android tv 推送消息

json、数组、map相关的hive函数_hive array_distinct-程序员宅基地

文章浏览阅读1.9k次,点赞3次,收藏17次。hive-third-functions参考文献:https://github.com/aaronshan/hive-third-functions/blob/master/README-zh.md简介hive-third-functions 包含了一些很有用的hive udf函数,特别是数组和json函数.注意:hive-third-functions支持hive-0.11.0或更高..._hive array_distinct