利用TensorFlow构建神经网络的一般步骤及常用方法和函数

  一、利用TensorFlow构建神经网络主要分为三个步骤:

    1.使用TensorFlow定义神经网络结构及其参数和定义根据定义的神经网络结构定义前向传播输出的结果。

    2.利用TensorFlow定义输入的数据,定义损失函数并选择合适的反向传播优化算法,并在其中加入可能用到的滑移平均,学习率衰减等。

    3.利用前两步建立起来的图构建会话,安排batch数据送往前向传播进行计算以及反向传播过程进行优化,并定时保存模型。

二、第一步骤具体过程及常用方法

    2.1.首先定义神经网络的结构参数,例如包括输入层结点数、输出层结点数、隐藏层结点数、卷积层尺寸,深度、输入图像尺寸、通道数等等这些结构上的参数。一般都是采用直接赋值的方法,比如:input_node=784.

    2.2.构建整体神经网络结构同时定义前向传播过程的输出(一般前向传播定义在一个函数体中)。

        2.2.1 一般每一层使用with tf.variable_scope('layer_name'):里进行定义,这样的好处可以重复使用名字,当层数多时,不会因为变量的名称而出现错误,比如每层都可以有weight这个变量,但互不影响。

     2.2.2 在with tf.variable_scope('layer_name'):内部的weight、bias等一般是使用tf.get_variable()进行定义,tf.get_variable*可通过利用tf.variable_scope()内传入参数reuse=True来创建上下文管理器中来获取通过tf.get_variable创建的变量。并且在scope内部进行print变量,会发现输出的前面还带着scope的名字,证明了名字的可以重用。

            *tf.get_variable()使用:weights = tf.get_variable("weight", shape=[],initializer=...)。其中第一个参数是变量名称,这是一个必填参数,不可以省略。第二个参数是变量的尺寸。第三个变量是初始值,传入初始化函数。关于变量的初始化函数,在最下面附表1中。

        2.2.3 with tf.variable_scope('layer_name'):内部声明完weight、bias等结构后,对传进来的每一层的输出产生输出。如在卷积层使用tf.nn.conv2d*后再加tf.nn.relu,在全连接层直接使用tf.nn.relu进行定义每层的输出,最后一层的输出就是前向传播的输出结果。

            *关于tf.nn.conv2d等卷积层、池化层函数可以参考我这篇博客:TensorFlow卷积层函数

        2.2.4 正则化的加入 。因为当网络结构十分复杂,深度比较大时,定义网络结构的部分和计算损失函数的部分可能不在同一个函数中进行定义计算,所以可在每次将weight定义完后将权重的正则化加入定义一个名称为losses的集合当中,例如下:

weight=tf.get_variable(...)
tf.add_to_collection('losses',regularizer(weight))
regularizer是正则化函数,如L2正则化函数:tf.contrib.layers.l2_regularizer()

        2.2.5 dropout的引入。dropout也是一种防止过拟合的一种方法,dropout一般在全连接层中引入而不在卷积层、池化层引入。在定义完某一全连接层的输出后引入。如:

fc1 = tf.nn.relu(tf.matmul(input, fc1_weights) + fc1_biases)
fc1 = tf.nn.dropout(fc1, 0.5)

关于dropout函数,参考官方API:dropout

        2.2.6 全连接层和卷积层之间的连接要注意有一个拉直化向量过程。因为卷积层的输出是矩阵形式,是多维的,要把这个多维矩阵拉直成一个一维向量(一个batch的输入就是batch个一维向量),才能输入到全连接层。使用的是tf.reshape()函数。

reshaped_output = tf.reshape(conv_output, [conv_shape[0], nodes)
#conv_output指的是卷积层输出
#conv_shape[0]是conv第一维数据,即batch的大小
#nodes是拉直成一维向量的长度,即卷积层长度*卷积层宽度*卷积层通道数

三、第二步骤具体过程及常用方法

    一般二、三步骤合并在一个train()函数体中,方便调用。

    3.1 定义输入。输入即数据集的特征和标签、即x和label,输入一般使用TensorFlow的placeholder机制。placeholder建立时是在计算图中定义一个位置,等到程序运行时,将数据再传入进来。使用tf.placeholder的好处还有在训练神经网络时,一般都是讲数据一个batch一个batch一次迭代输入,placeholder也可以很好的支持。使用placeholder:

x = tf.placeholder(tf.float32, shape=[1,2], name="input")
#其中第一个参数是x的数据类型,必须在placeholder中指定,且不可改变。
#第二个参数是输入x的维度。
#第三个参数是输入x的名称,可在计算图中显示。

        当传入数据时,需要使用feed_dict={}字典形式将数据传入。

sess.run(y, feed_dict={x: [0.7, 0.9]})
#y是和x有关联的计算,意思是计算y必须先定义x。

    3.2 定义存储训练轮数的变量,这个变量用于后面其他的功能调用。因为这个变量不是模型参数,要设定为不能训练类型变量。

global_step = tf.Variable(0, trainable=False)

    3.3 定义滑动平均(可选)。在TensorFlow中提供了tf.train.ExponentialMovingAverage接口来实现滑动平均模型。滑动平均模型可以使得模型在测试数据集上更加鲁棒。定义时,需要提供一个衰减率decay,用于控制模型更新速度。decay越大,模型越趋于稳定。一般会设定为接近于1的数字,如0.999,0.9999等。并且它的使用要结合之前定义的训练轮数变量global_step。具体使用:

variable_average = tf.train.ExponentialMovingAverage(0.999, global_step)
variable_average_op = variable_average.apply(tf.trainable_variables())

这样就可以把滑动平均用在所有可训练参数中。但要将其传入前向传播定义的函数内并将前面的定义神经网络前向传播过程中的训练参数也要随之改变加上滑动平均:

#没使用滑动平均
output = tf.nn.relu(tf.matmul(input, weights) + biases)
#使用了滑动平均
output=tf.nn.relu(tf.matmul(input, variable_average.average(weight))+ tf.variable_average.average(biases))

    3.4 定义损失函数。多分类问题的损失函数一般是交叉熵损失函数。交叉熵损失函数也在TensorFlow中定义了:

cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1))
#logits=y 是由前向传播定义的输出
#labels是在前面placeholder就传入的训练集label,因为是one-hot变量,所以取argmax,argmax里的1代表在第一维度上,并非1与y_进行比较

    根据上面代码得到的交叉熵损失是一个batch的,因此要对其进行求平均:

cross_entropy_mean = tf.reduce_mean(cross_entropy)

    此时得到的仅仅是经验损失函数,要想得到整体损失函数,还要加上前面的正则化损失(正则化损失是一个可选项,如若没有正则化,则交叉熵损失就是最后的损失函数)。因为之前把正则化损失加到损失集合当中,于是现在可以把交叉熵损失也加入这个集合中:

loss = cross_entropy_mean + tf.add_n(tf.get_collection('losses'))
#其中add_n是将列表中的Tensor依次相加

    loss就是最后的总损失函数。

    3.5 定义学习率。学习率可能会是一个变化的学习率,训练前期较大的学习率可以加速收敛,但是接近极小值点时,如果学习率过大,会导致一直在附近震荡甚至发散无法收敛。可以设置一个衰减的学习率,既可以前期快速接近最优解,又可以保证在训练后期不会有太大波动。TensorFlow提供了一种指数衰减的函数:

learning_rate = tf.train.exponential_decay(base_learning_rate, global_step, n_example/ batch_size, learning_rate_decay_rate)

第一个参数是基学习率,即初始的学习率,在此基础上进行衰减。第二个参数是globel_step,同前面。第三个参数是decay_steps代表的是完整使用一般训练数据所需到的迭代轮数。每当完整地过完一遍数据,就会使得学习率衰减一次。第四个参数是学习率的衰减率。

    3.6 定义优化器。TensorFlow提供了多种优化器,即反向传播过程中的优化算法,如梯度下降、随机梯度下降等。具体参考官方文档api,这是Adam优化算法官方文档。具体在程序中使用时,定义为一个训练步骤:

training_step = tf.train.GradientDescentOptmimzer(learning_rate).minimize(loss, global_step=global_step)

因为每过一遍数据既要通过反向传播来更新神经网络中的参数,又要更新每一个参数的滑动平均值,为了一次完成多个操作,使用下面这种机制完成:

with tf.control_dependencies([train_step, variables_averages_op]):
    train_op = tf.no_op(name = "train)
    3.7 初始化模型持久化类。即保存函数类。
saver = tf.train.Saver()

四、第三步骤常用过程及具体方法

    4.1 定义会话。这是一个常规的步骤:

with tf.Session() as sess:

    4.2 初始化变量。一般使用一行代码,直接就可以初始化所有变量。

tf.global_variables_initializer().run()

    4.3 循环训练。首先定义循环体,再将需要训练的数据进行定义,之后运行训练步骤。

for i in range(training_steps):
    x, y = mnist.train.next_batch(batch_size)#使用mnist数据集举例
    _, loss_value, step = sess.run([train_op, loss, global_step], feed_dict={x:x, y:y})
    #一个逗号前面的值代表run列表里的一个返回

    4.4 定时打印模型训练情况、定时保存模型,防止意外训练中断还能继续训练。

#每过1000步进行一次训练情况打印和模型保存
if i % 1000 == 0:
    print("After %d training step(s), loss on training batch is %g" % (step, loss_value))#打印训练情况
    saver.save(sess, os.path.join(model_save_path, model_name), global_step=global_step)#定时保存模型

五、运行整体函数

def main(argv=None):
    train(data)

if __name__ == '__main__':
    tf.app.run()

附表1 TensorFlow中变量初始化函数
初始化函数 功能 主要参数
tf.constant_initializer 将变量初始化为给定常量 常量的取值
tf.random_normal_initializer 将变量初始化为满足正态分布的随机值 正态分布的均值和标准差
tf.truncated_normal_initializer

将变量初始化为满足正态分布的随机值,但如果随机出来的随机值偏离标准值

2个标准差,那么这个数将会重新随机

正态分布的均值和标准差
tf.random_uniform_initializer 将变量初始化为满足平均分布的随机值 最大、最小值
tf.uniform_unit_scaling_initializer 将变量初始化为满足平均分布但不影响输出数量级的随机值

factor(产生随机变量乘以的

系数)

tf.zeros_initializer 将变量全部设置为0 变量维度
tf.ones_initializer 将变量全部设置为1 变量维度

部分引用书:TensorFlow实战Google深度学习框架


猜你喜欢

转载自blog.csdn.net/im_chenxi/article/details/80287497