欠拟合和过拟合

几乎所有的复杂方程都存在结果跟预期差异的情况,越复杂的方程,这种情况就越严重。这里面通常都是算法造成的,当然也存在数据集的个体差异问题。

所以”欠拟合“和”过拟合“是机器学习过程中重要的调优指标之一。

如图所示:



以篇(2)中房价的程序为例,上图中间的那幅图,是比较满意的一种结果。对于我们给出的所有样本,模型的预测结果同实际房价比较贴切的“拟合”。

左图则是“欠拟合”,有些样本和房价能对应的比较好,有些预测出来的值同事实则差距较大,“预测不大准”。

右侧图是“过拟合”,过拟合是很尴尬的一种情况,对于所有的训练样本都拟合非常好,但投产实际的数据就差别巨大。过拟合也很严重,在很多角度上比欠拟合更严重。如果在以前手工编写算法的年代,出现这种情况,模型的调优将会非常麻烦。并且在一个研发周期比较长的项目中,因为往往是到了投产阶段才发现过拟合问题,造成的损失也会比较大。

在分类问题中,一样会出现这两个问题,如图:



同样,中间的图表示拟合较好。左侧图表示欠拟合,右侧图表示过拟合。

欠拟合和过拟合情况发生之后,传统的办法有以下几种手段解决:

  1. 减少我们的参数数量,降低方程的维度。这个方法对于图像识别这种情况显然不适用,因为维度是固定的。
  2. 样本本身归一化做的不好。样本归一化我们前面说了,正常情况都应当先做归一化再进行训练。
  3. 通常增加样本的数量对改善“欠拟合”和“过拟合”都有效果。但是在工程中,样本就是钱啊。
  4. 手工实现的算法中,可以添加归一化参数(Regularization Parameter),通常称为λ。在TensorFlow这种成熟框架中,则使用了Dropout机制。

Dropout

Dropout可以当做神经网络中的一层,串联于神经网络中。接收上一层的输入,根据参数值抛弃一部分,把输出再接入到下一层输入,后面还是原来的神经网络。

这种方法对于整体算法几乎没有改变,代码变动最小,效果优秀。具体实现的数学公式在下面参考链接中有论文可供研究。TensorFlow中则已经有了内置的函数。

抛弃率也是一个学问,有论文表示,对于隐藏层来讲,50%的抛弃率有最优的效果,所以通常这部分就不用动脑子了,直接用0.5作为参数调用就好,不过我实际实践中,大多还是要尝试不同值看看效果。

最后同归一化参数λ的使用一样,在训练时,启用Dropout机制,也就是使用50%保留率(当然也是50%的抛弃率)调用tf.nn.dropout()。

等到实际生产的时候,因为模型用于真正预测,则无需再使用dropout。大多情况下我们训练的模型跟生产的模型,通常是同一个,这时候可以用100%保留率为参数调用dropout,等于所有数据都会输出,也就等于取消dropout层的效果。

注意,虽然函数名叫dropout,其中的参数确是表示数据保留下来的比例(当然不是简单的保留的意思,请参考后面的例子),其余的数据会用0替代。看一个小程序来验证dropout的效果:

#!/usr/bin/env python
# -*- coding=UTF-8 -*- import tensorflow as tf dropout = tf.placeholder(tf.float32)
x = tf.Variable(tf.ones([10, 10]))
y = tf.nn.dropout(x, dropout) init = tf.initialize_all_variables()
sess = tf.Session()
sess.run(init) print sess.run(y, feed_dict = {dropout: 1})
print sess.run(y, feed_dict = {dropout: 0.1})
print sess.run(y, feed_dict = {dropout: 0.2})

上面例子分别使用参数1/0.1/0.2调用dropout,运行输出将是:

[[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]] [[ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[ 0. 0. 10. 0. 0. 0. 0. 0. 0. 0.]
[ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[10. 0. 0. 0. 0. 10. 0. 0. 0. 0.]
[ 0. 0. 10. 0. 0. 0. 0. 0. 0. 0.]
[ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[ 0. 0. 0. 0. 0. 0. 10. 0. 0. 0.]
[ 0. 10. 0. 0. 0. 0. 0. 0. 0. 0.]
[ 0. 10. 0. 0. 10. 0. 0. 0. 0. 0.]] [[0. 0. 0. 0. 0. 0. 5. 0. 0. 0.]
[0. 0. 5. 5. 0. 0. 5. 5. 0. 5.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 5. 0.]
[0. 0. 5. 0. 5. 0. 0. 5. 5. 0.]
[0. 0. 0. 5. 0. 0. 0. 0. 0. 0.]
[5. 0. 0. 0. 0. 0. 0. 0. 0. 5.]
[0. 5. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 5. 0. 0. 0. 0. 5. 0.]
[5. 0. 0. 5. 0. 0. 0. 0. 0. 0.]]

dropout并不是简单的抛弃,每个保留下来的结果,实际上汇总了其它被设置为0的值。希望你对dropout彻底理解了。

卷积

通常讲,一项新技术的引入,往往是通常采用的技术发现了问题。为了解决这个问题经历大量研究和尝试之后,诞生了新的技术来对原有技术进行改进。卷积恰恰就是如此。

将DNN用于图像识别,识别率仍然不够高是表象。

一个很容易想到的根源则是一副二维的图片,变成了一维的浮点数组,其中点与相邻点之间的关系,本应当是这样图像识别中“图形”的部分理应关注的重点,而在DNN中反而完全把这种关系信息抛弃了。

为了改善这种情况,很早就曾经出现过名为“Edge filter”的算法,在神经网络的中间寻找这种可能出现的“边缘”的滤镜层,这其实就是卷积的雏形。

所采用的原理,你可以理解成用一副放大镜近距离、细致的观察一幅画面的局部,注意是二维的画面,不是一维的数组。

因为是局部,所以这个“卷积”核的维度不会很大,至少要远远小于整个要识别的图像。具体采用多大的尺寸,取决于你关注的那些边缘、或者笔画、或者微小的图形,是什么样的尺寸。在本案例中,将会采用5x5的卷积尺寸。

同时应当很容易理解,用放大镜观看图像嘛,看到的局部,虽然是一副小图像,但色深等特征,跟原图是一模一样的。所以卷积的深度,同原图必然是相同的,本例中是灰度图,1个数据的深度。如果是RGB彩色图,则是3个数据的深度。



这个用于近距离、细致观察局部的小窗口,叫做“卷积核”,也有被称为“观察视野”的,同样都应当是转折的外来词,你理解含义,阅读文档的时候能看懂就好了。

“观察”这个小窗口所得到的信息,因为附加了边缘、笔画、图形等更精细的信息,这些信息显然不是原来的点阵图的概念。所以虽然这个窗口小了,但输出结果,必然要远远大于原有的深度。比如在本例中,我们使用5x5的窗口。而卷积输出的数据,原有图形仅是1维,这里将达到32维(这个值是我们根据本案例的需求,自行设置的)的深度。

这一点一定要注意,国内很多的译文中,不知道是哪个环节出的问题,居然很多人将卷积理解为“降维”的算法。这显然是没有真正理解卷积的内涵,并且把深度这么多倍的增长忽略掉了。

如同放大镜的边缘会稍有变形,卷积算法重点关注在卷积核中心,周围的数据也会有细微变形,这是由算法的数学模型决定的。



此外也如同放大镜在画面上一点点移动,逐渐观察整个画面一样,“观察窗”也将在屏幕上滚动,最终覆盖整个要识别的图像。这个滚动过程会有移动的步长设置,对于一些重点稀疏的图像,我们可能会增加步长来减少数据及提高效率。

因为上面这两个原因,卷积最终所得图像的输出尺寸,是可能小于原图像长宽尺寸的。这可能也是一部分人理解为降维的原因之一。但算上增加的深度,通常输出的数据,会几十倍于原有的输入数据量。

在本案例中,我们设置TensorFlow卷积的步长为1,padding参数为“SAME”,表示输出图像的尺寸跟原图完全相同。padding参数还可能取值为“VALID”,表示仅保留有效数据,这种情况下,即便步长仍然是1,因为刚才说过的边缘变形等原因,输出的尺寸也会缩小。

这里不断强调这一点,是因为多层神经网络之间,用于计算的矩阵维度,是互相对应的,你必须能清晰的知道你最终输入下一层的数据维度是什么。



如同DNN一样,卷积也可以逐层关联,去深入挖掘信息与信息之间的细微关系。而卷积这种特点也逐层传递,成为尺寸越来越小,但深度越来越深的形状。用示意图来看,很像一个金字塔,所以也称为“卷积金字塔”。

问题来了,在我们这样的例子中,我们每一层都没有缩小图像尺寸,仅增加了深度。数据量逐层大幅增加,总会达到无法承受的境地啊?

而且,即便不考虑承受能力,假设我们所用的计算机无比强大,这些不是问题。但我们增加的数据,不可能凭空出现。从数学的推导来看,这些数据其实都是使用不同系数相乘而来的结果,原图中可能出现很小的一点误差,这样多倍的放大之后,必将也对结果产生很大的影响。这又如何解决呢?

池化

通常说,卷积跟池化都是联合使用的。



如图所示,上面部分,假设我们使用卷积的步长为2,那么会得到一个缩小一倍的数据量。

下面的部分,则是假设我们使用了步长为1,实际上得到的数据长宽尺寸,跟原图是相同的,而深度大大增加了。

这时候可以在卷积之后附加一层“池化”,以池化设置为2x2为例。池化算法会在输入的图像中,以2x2为视窗,提取其中的重点,形成一组数据。

在我们的案例中,等于输入2x2x32,输出1x1x32的数据。

在池化的“提取”中,有不同算法供选择,我们这里会采用max,就是取大者,这个大的部分不论在2x2的点阵中在哪一点,都会被提取出来。

所以池化算法对于消除数据抖动、增加系统鲁棒性也很有帮助。降维数据,则是本身就具有的能力。

TensorFlow中还提供了池化的“平均”提取算法,需要的时候可以查看TensorFlow相关资料。

总之在本例中,输入的图像经过2x2的池化之后,图像的深度不变,尺寸会长、宽各缩减一倍,数据总量将减少4倍。

网络模型构建

同DNN一样,CNN的构建也没有什么必须的规则。但是TensorFlow手写数字识别这个例子中所采用的构建方式被认为是比较典型的一种手段,在Google的教学视频中有介绍,相关的论文资料没有查到。

这里列出来供参考吧:

卷积层1->relu->池化层1->卷积层2->relu->池化层2->全连接神经网络层->relu->dropout层->神经网络输出层->softmax

总计11层,也算一个复杂的网络了。这个模式可以记下来,以后碰到新项目在自己还吃不准的时候,漫无目的大量实验之前套上这个模型试试,很可能会有惊喜的收获。

源码

提前说一下,每一次新的源码,其中跟以前例子重复的部分,我会减少注释或者取消注释,避免过多打断阅读程序的连贯性。

#!/usr/bin/env python
# -*- coding=UTF-8 -*- import input_data
mnist = input_data.read_data_sets('MNIST_data', one_hot=True) import tensorflow as tf
sess = tf.InteractiveSession() def weight_variable(shape):
initial = tf.truncated_normal(shape, stddev=0.1)
return tf.Variable(initial)
def bias_variable(shape):
initial = tf.constant(0.1, shape=shape)
return tf.Variable(initial) #定义卷积,设定步长(stride size)为1,
#边距(padding size)为0,SAME就是指边距为0
def conv2d(x, W):
return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')
#池化是2x2
def max_pool_2x2(x):
return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],
strides=[1, 2, 2, 1], padding='SAME') x = tf.placeholder("float", shape=[None, 784])
y_ = tf.placeholder("float", shape=[None, 10]) #第一层卷积
#在每个5x5的patch中算出32个特征。卷积的权重张量形状是[5, 5, 1, 32],
#前两个维度是patch的大小,接着是输入的通道数目,这里灰度图是1个数据,
#最后是输出的通道数目。 而对于每一个输出通道都有一个对应的偏置量
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
#卷积是在平面2D图的方向上进行,
#所以为了使用这一层卷积,我们先要把x恢复成一组图,
#2D加上第一维是样本数量,以及图的色深,是一个4d向量,
#这里第2、第3维对应图片的宽、高,
#最后一维代表图片的颜色通道数(因为是灰度图所以这里的通道数为1,如果是rgb彩色图,则为3)。
x_image = tf.reshape(x, [-1,28,28,1]) #卷积计算,跟上个例子一样,使用relu激活函数
#本层卷积结果因为padding是SAME,
#所以输出同样是28x28,只是变成了32层深
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
#池化是2x2,所以输出结果是28/2=14,14x14x32的图
#max池化算法是指在2x2的空间中取最大值
h_pool1 = max_pool_2x2(h_conv1) #第二层卷积:
#为了构建一个更深的网络,我们会把几个类似的层堆叠起来。
#第二层中,每个5x5的patch会得到64个特征。
W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64]) #再次卷积,输出同输入一样是14x14,深度变成了64
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
#再次2x2池化,14/2=7,最后结果是7x7x64的图
h_pool2 = max_pool_2x2(h_conv2) #密集连接层,也就是通常的神经网络层:
#现在,图片尺寸减小到7x7,接入到1024个神经元的全连接层
#因为传统神经网络层工作在一维模式,
#所以我们把池化层输出的张量reshape成一维向量
#算法跟上例中完全相同
W_fc1 = weight_variable([7 * 7 * 64, 1024])
b_fc1 = bias_variable([1024])
h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1) #为了减少过拟合,我们在输出层之前加入dropout。
#我们用一个placeholder来代表一个神经元的输出在dropout中保持不变的概率。
#这样我们可以在训练过程中启用dropout,在测试过程中关闭dropout。
#TensorFlow的tf.nn.dropout操作除了可以屏蔽神经元的输出外,
#还会自动处理神经元输出的比例。
#所以用dropout的时候可以不用考虑比例。
#此外keep_prob更像是逻辑运算中的if then 逻辑,
#在机器学习中,利用规范的数学运算替代需要程序的逻辑运算是很常用的模式
keep_prob = tf.placeholder("float")
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob) #输出层:最后,我们添加一个softmax分类层
#对于这种分类型的深度学习,不管前面多么复杂的算法,
#最后往往仍然需要softmax进行分类
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10]) y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2) #训练和评估模型:
#请参考前一个源码中的注释
#在feed_dict中加入额外的参数keep_prob来控制dropout比例。然后每100次迭代输出一次日志。
cross_entropy = -tf.reduce_sum(y_*tf.log(y_conv))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
sess.run(tf.global_variables_initializer())
for i in range(20000):
batch = mnist.train.next_batch(50)
if i%100 == 0:
train_accuracy = accuracy.eval(feed_dict={
#测试过程同生产过程相同关闭dropout.
#1代表dropout的保留比例是100%,相当于没起作用
x:batch[0], y_: batch[1], keep_prob: 1.0})
print "step %d, training accuracy %g"%(i, train_accuracy)
#训练过程,启用dropout,保留50%
train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5}) print "test accuracy %g"%accuracy.eval(feed_dict={
x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0})

这个真的是当前比较先进的识别算法了,最终的正确率大约是超过98%,绝对达到了商用的标准。只是在运行的时候你可能注意到了,三个不同的实现,模型训练的速度一个比一个慢。为了提高这2%的识别率,可说无比艰辛。

从视觉上看卷积的本质

我们说过了卷积在这里主要用于提取细节中相邻点之间的关系。这里“视觉”就是指肉眼能看到的线、边、笔画、小图形。

那么如果把这些卷积的结果图形化出来是什么样子呢?下图就是一个展示:



是不是看到这种直观的图示令人更印象更深刻?

所以由于这些特点,CNN大量的用于图像识别、人脸识别、文字识别、语音识别等很广泛的领域,涵盖当前比较火爆的机器学习中很大一部分行业。到了这里,你也可以自豪的喊一声:真的入行了。

(待续...)

引文及参考

如何理解卷积神经网络(CNN)中的卷积和池化?

理解dropout

归一化参数

从锅炉工到AI专家(6)的更多相关文章

  1. TensorFlow从1到2(一)续讲从锅炉工到AI专家

    引言 原来引用过一个段子,这里还要再引用一次.是关于苹果的.大意是,苹果发布了新的开发语言Swift,有非常多优秀的特征,于是很多时髦的程序员入坑学习.不料,经过一段头脑体操一般的勤学苦练,发现使用S ...

  2. 从锅炉工到AI专家 ---- 系列教程

    TensorFlow从1到2(十二)生成对抗网络GAN和图片自动生成 那些令人惊艳的TensorFlow扩展包和社区贡献模型  从锅炉工到AI专家(11)(END) 从锅炉工到AI专家(10)  从锅 ...

  3. 从锅炉工到AI专家(2)

    大数据 上一节说到,大多的AI问题,会有很多个变量,这里深入的解释一下这个问题. 比如说某个网站要做用户行为分析,从而指导网站建设的改进.通常而言如果没有行为分析,并不需要采集用户太多的数据. 比如用 ...

  4. 从锅炉工到AI专家(1)

    序言 标题来自一个很著名的梗,起因是知乎上一个问题:<锅炉设计转行 AI,可行吗?>,后来就延展出了很多类似的问句,什么"快递转行AI可行吗?"."xxx转行 ...

  5. 从锅炉工到AI专家(7)

    说说计划 不知不觉写到了第七篇,理一下思路: 学会基本的概念,了解什么是什么不是,当前的位置在哪,要去哪.这是第一篇希望做到的.同时第一篇和第二篇的开始部分,非常谨慎的考虑了非IT专业的读者.希望借此 ...

  6. 从锅炉工到AI专家(5)

    图像识别基本原理 从上一篇开始,我们终于进入到了TensorFlow机器学习的世界.采用第一个分类算法进行手写数字识别得到了一个91%左右的识别率结果,进展可喜,但成绩尚不能令人满意. 结果不满意的原 ...

  7. 从锅炉工到AI专家(4)

    手写数字识别问题 图像识别是深度学习众多主流应用之一,手写数字识别则是图像识别范畴简化版的入门学习经典案例.在TensorFlow的官方文档中,把手写数字识别"MNIST"案例称为 ...

  8. TensorFlow从1到2(二)续讲从锅炉工到AI专家

    图片样本可视化 原文第四篇中,我们介绍了官方的入门案例MNIST,功能是识别手写的数字0-9.这是一个非常基础的TensorFlow应用,地位相当于通常语言学习的"Hello World!& ...

  9. 从锅炉工到AI专家(11)(END)

    语音识别 TensorFlow 1.x中提供了一个语音识别的例子speech_commands,用于识别常用的命令词汇,实现对设备的语音控制.speech_commands是一个很成熟的语音识别原型, ...

  10. 从锅炉工到AI专家(10)

    RNN循环神经网络(Recurrent Neural Network) 如同word2vec中提到的,很多数据的原型,前后之间是存在关联性的.关联性的打破必然造成关键指征的丢失,从而在后续的训练和预测 ...

随机推荐

  1. Pycharm相对路径

    问题: 今天有个程序,明显路径是存在的,但是os.path.exists的返回结果是False. 仔细想了想, 是相对路径的问题. 情况描述: 我的路径是: dir_path = 'data/mark ...

  2. shell 命令 使用

    [root@ken ~]# cat ken.sh http://www.baidu.com http://www.baidu.com http://www.sina.com http://www.si ...

  3. Win10家庭版WindowsUpdate属性为灰色

    一般的取消Windows更新只需要打开任务管理器,点击服务 然后点击左下角的打开服务 找到WindowsUpdate,右键属性 按照正常的电脑只要在启动类型中选择禁用,然后在恢复里的第一次操作选择无操 ...

  4. 光纤网卡与HBA卡区别

    1.简介 光纤网卡 ,指的是光纤以太网适配器,简称光纤网卡,学名Fiber Ethernet Adapter.传输输的是以太网通信协议,一般通过光纤线缆与光纤以太网交换机连接.按传输速率可以分为100 ...

  5. linux的软件安装方式总结

    Linux系统中软件的“四”种安装原理详解:源码包安装.RPM二进制安装.YUM在线安装.脚本安装包   一.Linux软件包分类 1.1 源码包 优点: 开源,如果有足够的能力,可以修改源代码: 可 ...

  6. :nth-child() 与 :nth-of-type(n)的区别

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  7. git教程——工作流程

    Git 工作流程 本章节我们将为大家介绍 Git 的工作流程. 一般工作流程如下: 克隆 Git 资源作为工作目录. 在克隆的资源上添加或修改文件. 如果其他人修改了,你可以更新资源. 在提交前查看修 ...

  8. [LeetCode] Minimum Cost to Merge Stones 混合石子的最小花费

    There are N piles of stones arranged in a row.  The i-th pile has stones[i] stones. A move consists ...

  9. 发布版本Debug和Release的区别

    1.Debug是调试版本不会对项目发布的内容进行优化,并且包含调试信息容量会大很多 2.Release不对源代码进行调试,对应用程序的速度进行优化,使得发布的内容容量等都是最优的

  10. Web发展史

    Web 万维网常称为WWW(World Wide Web)发展至今仅30年,英国计算机科学家,蒂姆 伯纳斯 李爵士 提出了 World Wide Web的设计方案,1990年李爵士完成了Web 所有的 ...