本文将介绍优化训练神经网络模型的一些常用方法,并给出使用TensorFlow实现深度学习的最佳实践样例代码。为了更好的介绍优化神经网络训练过程,我们将首先介绍优化神经网络的算法——梯度下降算法。然后在后面的部分中,我们将围绕该算法中的一些元素来优化模型训练过程。

\\


梯度下降算法

\\


梯度下降算法主要用于优化单个参数的取值,而反向传播算法给出了一个高效的方式在所有参数上使用梯度下降算法,从而使神经网络模型在训练数据上的损失函数尽可能小。反向传播算法是训练神经网络的核心算法,它可以根据定义好的损失函数优化神经网络中参数的取值,从而使神经网络模型在训练数据集上的损失函数达到一个较小值。神经网络模型中参数的优化过程直接决定了模型的质量,是使用神经网络时非常重要的一步。

\\


tensorflow 训练图片 疾病 tensorflow怎么训练模型_tensorflow 训练图片 疾病

\\


tensorflow 训练图片 疾病 tensorflow怎么训练模型_数据_02

\\


从表1中可以看出,经过5次迭代之后,参数x的值变成了0.0512,这个和参数最优值0已经比较接近了。虽然这里给出的是一个非常简单的样例,但是神经网络的优化过程也是可以类推的。神经网络的优化过程可以分为两个阶段,第一个阶段先通过前向传播算法计算得到预测值,并将预测值和真实值做对比得出两者之间的差距。然后在第二个阶段通过反向传播算法计算损失函数对每一个参数的梯度,再根据梯度和学习率使用梯度下降算法更新每一个参数。本书将略去反向传播算法具体的实现方法和数学证明,有兴趣的读者可以参考论文Learning representations by back-propagating errors

\\


tensorflow 训练图片 疾病 tensorflow怎么训练模型_python_03

\\


为了综合梯度下降算法和随机梯度下降算法的优缺点,在实际应用中一般采用这两个算法的折中——每次计算一小部分训练数据的损失函数。这一小部分数据被称之为一个batch。通过矩阵运算,每次在一个batch上优化神经网络的参数并不会比单个数据慢太多。另一方面,每次使用一个batch可以大大减小收敛所需要的迭代次数,同时可以使收敛到的结果更加接近梯度下降的效果。

\\


学习率的设置

\\


上面提到在优化神经网络时,需要设置学习率(learning rate)控制参数更新的速度。学习率决定了参数每次更新的幅度。如果幅度过大,那么可能导致参数在极优值的两侧来回移动。还是以优化J(x)=x2函数为样例。如果在优化中使用的学习率为1,那么整个优化过程将会如表2所示。

\\


表2.当学习率过大时,梯度下降算法的运行过程

\\\\


\\t\t\t

轮数

\\t\t\t

\\t\t\t

当前轮参数值

\\t\t\t

\\t\t\t

梯度学习率

\\t\t\t

\\t\t\t

更新后参数值

\\t\t\t

\\t\t\t

1

\\t\t\t

\\t\t\t

5

\\t\t\t

\\t\t\t

2*5*1=10

\\t\t\t

\\t\t\t

5-10=-5

\\t\t\t

\\t\t\t

2

\\t\t\t

\\t\t\t

-5

\\t\t\t

\\t\t\t

2*(-5)*1=-10

\\t\t\t

\\t\t\t

-5-(-10)=5

\\t\t\t

\\t\t\t

3

\\t\t\t

\\t\t\t

5

\\t\t\t

\\t\t\t

2*5*1=10

\\t\t\t

\\t\t\t

5-10=-5

\\t\t\t

从上面的样例可以看出,无论进行多少轮迭代,参数将在5和-5之间摇摆,而不会收敛到一个极小值从上面的样例可以看出,无论进行多少轮迭代,参数将在5和-5之间摇摆,而不会收敛到一个极小值。相反,当学习率过小时,虽然能保证收敛性,但是这会大大降低优化速度。我们会需要更多轮的迭代才能达到一个比较理想的优化效果。比如当学习率为0.001时,迭代5次之后,x的值将为4.95。要将x训练到0.05需要大约2300轮;而当学习率为0.3时,只需要5轮就可以达到。综上所述,学习率既不能过大,也不能过小。为了解决设定学习率的问题,TensorFlow提供了一种更加灵活的学习率设置方法——指数衰减法。tf.train.exponential_decay函数实现了指数衰减学习率。通过这个函数,可以先使用较大的学习率来快速得到一个比较优的解,然后随着迭代的继续逐步减小学习率,使得模型在训练后期更加稳定。exponential_decay函数会指数级地减小学习率,它实现了以下代码的功能:

\\

\decayed_learning_rate = \    learning_rate * decay_rate ^ (global_step / decay_ steps)

\\


其中decayed_learning_rate为每一轮优化时使用的学习率,learning_rate为事先设定的初始学习率,decay_rate为衰减系数,decay_steps为衰减速度。下面给出了一段代码来示范如何在TensorFlow中使用tf.train.exponential_decay函数。

\\


\  # 通过exponential_decay函数生成学习率。\learning_rate = tf.train.exponential_decay(\learning_rate_base, global_step, decay_step, decay_rate)\\  # 使用指数衰减的学习率。在minimize函数中传入global_step将自动更新\  # global_step参数,从而使得学习率也得到相应更新。\learning_step = \    tf.train.GradientDescentOptimizer(learning_rate)\\\      .minimize(...my loss..., global_step=global_step)

\\


过拟合问题

\\


在使用梯度下降优化神经网络时,被优化的函数就是神经网络的损失函数。这个损失函数刻画了在训练数据集上预测结果和真实结果之间的差距。然而在真实的应用中,我们想要的并不是让模型尽量模拟训练数据的行为,而是希望通过训练出来的模型对未知的数据给出判断。模型在训练数据上的表现并不一定代表了它在未知数据上的表现。过拟合问题就是可以导致这个差距的一个很重要因素。所谓过拟合,指的是当一个模型过为复杂之后,它可以很好地“记忆”每一个训练数据中随机噪音的部分而忘记了要去“学习”训练数据中通用的趋势。举一个极端的例子,如果一个模型中的参数比训练数据的总数还多,那么只要训练数据不冲突,这个模型完全可以记住所有训练数据的结果从而使得损失函数为0。可以直观地想象一个包含n个变量和n个等式的方程组,当方程不冲突时,这个方程组是可以通过数学的方法来求解的。然而,过度拟合训练数据中的随机噪音虽然可以得到非常小的损失函数,但是对于未知数据可能无法做出可靠的判断。

\\


图2显示了模型训练的三种不同情况。在第一种情况下,由于模型过于简单,无法刻画问题的趋势。第二个模型是比较合理的,它既不会过于关注训练数据中的噪音,又能够比较好地刻画问题的整体趋势。第三个模型就是过拟合了,虽然第三个模型完美地划分了灰色和黑色的点,但是这样的划分并不能很好地对未知数据做出判断,因为它过度拟合了训练数据中的噪音而忽视了问题的整体规律。比如图中浅色方框更有可能和“X”属于同一类,而不是根据图上的划分和“O”属于同一类。

\\


tensorflow 训练图片 疾病 tensorflow怎么训练模型_数据_04

\\


图2 神经网络模型训练的三种情况

\\


tensorflow 训练图片 疾病 tensorflow怎么训练模型_人工智能_05

\\


无论是哪一种正则化方式,基本的思想都是希望通过限制权重的大小,使得模型不能任意拟合训练数据中的随机噪音。但这两种正则化的方法也有很大的区别。首先,L1正则化会让参数变得更稀疏,而L2正则化不会。所谓参数变得更稀疏是指会有更多的参数变为0,这样可以达到类似特征选取的功能。之所以L2正则化不会让参数变得稀疏的原因是当参数很小时,比如0.001,这个参数的平方基本上就可以忽略了,于是模型不会进一步将这个参数调整为0。其次,L1正则化的计算公式不可导,而L2正则化公式可导。因为在优化时需要计算损失函数的偏导数,所以对含有L2正则化损失函数的优化要更加简洁。优化带L1正则化的损失函数要更加复杂,而且优化方法也有很多种。以下代码给出了一个简单的带L2正则化的损失函数定义:

\\


\w= tf.Variable(tf.random_normal([2, 1], stddev=1, seed=1))\y = tf.matmul(x, w)\\loss = tf.reduce_mean(tf.square(y_ - y)) + \        tf.contrib.layers.l2_regularizer(lambda)(w)

\\


tensorflow 训练图片 疾病 tensorflow怎么训练模型_神经网络_06

\\


滑动平均模型

\\


在采用随机梯度下降算法训练神经网络时,使用滑动平均模型在很多应用中都可以在一定程度提高最终模型在测试数据上的表现。滑动平均模型可以有效的减小训练数据中的噪音对模型带来的影响。在TensorFlow中提供了tf.train.ExponentialMovingAverage来实现滑动平均模型。在初始化ExponentialMovingAverage时,需要提供一个衰减率(decay)。这个衰减率将用于控制模型更新的速度。ExponentialMovingAverage对每一个变量会维护一个影子变量(shadow variable),这个影子变量的初始值就是相应变量的初始值,而每次运行变量更新时,影子变量的值会更新为:

\\


tensorflow 训练图片 疾病 tensorflow怎么训练模型_python_07

\\


下面通过一段代码来解释ExponentialMovingAverage是如何被使用的。

\\


\import tensorflow as tf\\v1 = tf.Variable(0.0, dtype=tf.float32)\step = tf.Variable(0, trainable=False)\\# 定义一个滑动平均的类(class)。初始化时给定了衰减率(0.99)和控制衰减率的变量step。\ema = tf.train.ExponentialMovingAverage(0.99, step)\# 定义一个更新变量滑动平均的操作。这里需要给定一个列表,每次执行这个操作时\# 这个列表中的变量都会被更新。\maintain_averages_op = ema.apply([v1])             \                \with tf.Session() as sess:\    init_op = tf.initialize_all_variables()\sess.run(init_op)\\    # 更新变量v1的值到5。\sess.run(tf.assign(v1, 5))\    # 更新v1的滑动平均值。衰减率为min{0.99,(1+step)/(10+step)= 0.1}=0.1,\    # 所以v1的滑动平均会被更新为0.10+0.95=4.5。\sess.run(maintain_averages_op)\    print sess.run([v1, ema.average(v1)])        #  输出[5.0, 4.5]\    \\    # 更新step的值为10000。\    sess.run(tf.assign(step, 10000))  \    # 更新v1的值为10。\sess.run(tf.assign(v1, 10))\    # 更新v1的滑动平均值。衰减率为min{0.99,(1+step)/(10+step)  0.999}=0.99,\    # 所以v1的滑动平均会被更新为0.994.5+0.0110=4.555。\    sess.run(maintain_averages_op)\print sess.run([v1, ema.average(v1)])      #  输出[10.0, 4.5549998]

\\


ensorFlow最佳实践样例程序

\\


将训练和测试分成两个独立的程序,这可以使得每一个组件更加灵活。比如训练神经网络的程序可以持续输出训练好的模型,而测试程序可以每隔一段时间检验最新模型的正确率,如果模型效果更好,则将这个模型提供给产品使用。除了可以将不同功能模块分开,本节还将前向传播的过程抽象成一个单独的库函数。因为神经网络的前向传播过程在训练和测试的过程中都会用到,所以通过库函数的方式使用起来既可以更加方便,又可以保证训练和测试过程中使用的前向传播方法一定是一致的。下面我们将给出TensorFlow模型训练的一个最佳实践,它使用了上文中提到的所有优化方法来解决MNIST问题。在这儿最佳实践中总共有三个程序,第一个是mnist_inference.py,它定义了前向传播的过程以及神经网络中的参数。第二个是mnist_train.py,它定义了神经网络的训练过程。第三个是mnist_eval.py,它定义了测试过程。以下代码给出了mnist_inference.py中的内容。

\\


# -*- coding: utf-8 -*-\import tensorflow as tf\\# 定义神经网络结构相关的参数。\INPUT_NODE = 784  \OUTPUT_NODE = 10  \LAYER1_NODE = 500 \\# 通过tf.get_variable函数来获取变量。在训练神经网络时会创建这些变量;在测试时会通\# 过保存的模型加载这些变量的取值。而且更加方便的是,因为可以在变量加载时将滑动平均变量\# 重命名,所以可以直接通过同样的名字在训练时使用变量自身,而在测试时使用变量的滑动平\# 均值。在这个函数中,将变量的正则化损失加入损失集合。\def get_weight_variable(shape, regularizer):\   weights = tf.get_variable(\ \"weights\