参考    如何用tensorflow训练神经网络 - 云+社区 - 腾讯云

在使用神经网络解决实际的分类或回归问题时需要设置好参数取值。下面介绍使用监督学习的方式来合理地设置参数取值,同时也将给出tensorflow程序来完成这个过程。设置神经网络参数的过程就是神经网络的训练过程。只有经过有效训练的神经网络模型才可以真正地解决分类或者回归问题。

用监督学习的方式设置神经网络参数需要有一个标注好的训练数据集。监督学习最重要的思想是,在一直答案的标注数据集上,模拟给出预测结果要尽量逼近真实的答案。通过调整神经网络中地参数对训练数据进行拟合,可以使得模块对未知的样本提供预测的能力。在神经网络优化算法中,最常用的方法是反向传播算法(backpropagation)。反向传播算法的具体工作原理如下图

                     tensorflow入门(四)如何用tensorflow训练神经网络-LMLPHP

从上图可以看出,反向传播算法实现了一个迭代过程。在每次迭代的开始,首先需要选取一部分训练数据,这一小部分数据叫做一个batch。然后,这个batch的样例通过前向传播算法得到神经网络模型的预测结果。因为训练数据都是有正确答案标注的,所以可以计算出当前神经网络模型的预测答案与真实答案之间的差距。最后,基于预测值和真实值之间的差距,反向传播算法会相应更新神经网络参数的取值,使得在这个batch上的神经网络模型的预测结果和真实结果更加接近。

通过tensorflow实现反向传播算法的第一步是使用tensorflow表达一个batch的数据。例如使用常量来表达过一个样例:

x = tf.constant([0.7, 0.9])

但如果每轮迭代中选取的数据都要通过常量来表示,那么tensorflow的计算图将会太大。因为每生成一个常量,tensorflow都会在计算图中增加一个节点。一般来说,一个神经网络的训练过程会需要几百万甚至几亿轮的迭代,这样计算图就会非常大,而且利用率很低。

为了避免这个问题,tensorflow提供了placeholder机制用于提供输入数据。

placeholder相当于定义了一个位置,这个位置中的数据在程序运行时再指定。

这样在程序中就不需要生成大量常数来提供输入数据,而只需要将数据通过placeholder传入tensorflow计算图。

在placeholder定义时,这个位置上的数据类型是需要指定的。

和其他张量一样,placeholder的类型也是不可以改变的。

placeholder中的数据的维度信息可以根据提供的数据推导得出,所以不一定要给出。

下面给出了通过placeholder实现前向传播算法的代码:

import tensorflow as tf

w1 = tf.Variable(tf.random_normal([2, ,3], stddev = 1, seed = 1))
w2 = tf.Variable(tf.random_normal([3, ,1], stddev = 1, seed = 1))

# 定义placeholder作为存放数据的地方。这里维度也不一定要定义。
# 但如果维度是确定的,那么给出维度可以降低出错的概率。
x = tf.placeholder(tf.float32, shape=(1,2), name = "input")
a = tf.matmul(x, w1)
y = tf.matmul(a, w2)

sess = tf.Session()
init_op = tf.global_variables_initializer()
sess.run(init_op)

# 下面一行将报错: InvalidArgumentError: You must feed a value for placeholder
# tensor 'input_1' with dtype float and shape [1, 2]
print(sess.run(y))

print(sess.run(y , feed_dict = {x: {[[0.7, 0.9]]}))

在这段程序中替换了原来通过常量定义的输入x。在新的程序中计算前向传播结果时,需要提供一个feed_dict来指定x的取值。feed_dict是一个字典(map),在字典中需要给出每个用到的placeholder的取值。如果某个需要的placeholder没有被指定取值,那么程序在运行时将会报错。

以上程序只计算了一个样例的前向传播结果,但如上图所示,在训练神经网络时需要每次提供一个batch的训练样例。对于这样的需求,placeholder也可以很好的支持。在以上程序中,如果将输入的1*2矩阵改为n*2的矩阵,那么就可以得到n个样例的前向传播结果了。其中n*2的矩阵的每一行为一个样例数据。这样前向传播的结果为n*1的矩阵,这个矩阵的每一行就代表了一个样例的前向传播结果。以下代码给出了一个示例。

x = tf.placeholder(tf.float32, shape=(3, 2, name = "input"))
... # 中间部分和上面的样例程序一样

# 因为x在定义是制定了n为3,所以在运行前向传播过程时需要提供3个样例数据
print(sess.run(y, feed_dict={x: [[0.7, 0.9],[0.1, 0.4],[0.5, 0.8]]}))


'''
输出结果为:
[[3.95757794]
[1.15376544]
[3.16749191]]

以上样例展示了一次性计算多个样例的前向传播结果。在运行时,需要将三个样例[0.7, 0.9]、[0.1, 0.4]和[0.5, 0.8]组成一个3*2的矩阵传入placeholder。计算得到的结果为3*1的矩阵。其中第一行3.95757794为样例[0.7, 0.9]的前向传播结果:1.15376544为样例[0.1, 0.4]的前向传播结果;3.16749191为样例[0.5, 0.8]的前向传播结果。

在得到一个batch的前向传播结果以后,需要定义一个损失函数来刻画当前的预测值和真实答案之间的差距。然后通过反向传播算法来调整神经网络参数的取值是的差距可以被缩小。下面代码定义了一个简单的损失函数,并通过tensorflow定义了反向传播算法。

# 使用sigmoid函数将y转换为0~1之间的数值。转换后y代表预测是正样本的概率,1-y代表
# 预测是负样本的概率
y = tf.sigmoid(y)


# 定义损失函数来刻画预测值与真实值得差距
cross_entropy = - tf.reduce_mean(
      y_ * tf.log(tf.clip_by_value(y, 1e-10, 1.0))
      + (1 - y_)*tf.log(tf.clip_by_value(1-y, 1e-10, 1.0)))


# 定义学习率
learning_rate = 0.001


# 定义反向传播算法来优化神经网络中的参数
train_step = \
    tf.train.AdaOptimizer(learning_rate).minimize(cross_entropy)

在以上代码中,cross_entropy定义了真实值和预测值之间的交叉熵(cross_entropy),这是一个分类问题中一个常用的损失函数。第二行train_step定义了反向传播的优化方法。目前,tensorflow支持10种不同的优化器,常用的优化器有三种:

tf.train.GradientDescentOptimizer

tf.train.AdamOptimizer

tf.train.MomentumOptimizer

在定义了反向传播算法之后,通过运行sess.run(train_step)就可以对所有在GraphKeys.TRAINBLE_VARIABLES集合中的变量进行优化,使得在当前batch下损失函数最小。

下面给出了一个完整的程序来训练神经网络解决二分类问题。

import tensorflow as tf

# NumPy是一个科学计算的工具包,这里通过NumPy工具包生成模拟数据集。
from numpy.random import RandomState

# 定义训练数据batch的大小。
batch_size = 8

# 定义神经网络的参数。
w1 = tf.Variable(tf.random_normal([2, 3], stddev=1, seed=1))
w2 = tf.Variable(tf.random_normal([3, 1], stddev=1, seed=1))

# 在shape的一个维度上使用None可以方便使用不同的batch大小。在训练时需要把数据分
# 成比较小的batch,但是在测试时,可以一次性使用全部的数据。当数据集比较小时这样
# 比较方便测试,但数据集比较大时,将大量数据放入一个batch可能会导致内存溢出。

x  = tf.placeholder(tf.float32, shape=[None, 2], name='x-input')
y_ = tf.placeholder(tf.float32, shape=[None, 1], name='y-input')

# 定义神经网络前向传播的过程。
a = tf.matmul(x, w1)
y = tf.matmul(a, w2)



# 定义损失函数和反向传播的算法.
y = tf.sigmoid(y)

cross_entropy = -tf.reduce_mean(
    y_ * tf.log(tf.clip_by_value(y, 1e-10, 1.0))
    + (1 - y_) * tf.log(tf.clip_by_value(1 - y, 1e-10, 1.0)))

train_step = tf.train.AdamOptimizer(0.001).minimize(cross_entropy)



# 通过随机数生成一个模拟数据集。
rdm = RandomState(1)
dataset_size = 128
X = rdm.rand(dataset_size, 2)
# 定义规则来给出样本的标签,在这里所有x1+x2<1的样例都被认为是正样本,
# 而其他为负样本
# 在这里使用0来表示负样本,1来表示正样本。大部分解决分类问题的神经网络都会采用
# 0和1的表示方法。
Y = [[int(x1 + x2 < 1)] for (x1, x2) in X]

with tf.Session() as sess:
    init_op = tf.global_variables_initializer()
    sess.run(init_op)

    # 设定训练的轮数。
    STEPS = 5000
    for i in range(STEPS):
       # 每次选取batch_size个样本进行训练。
       start = (i * batch_size) % dataset_size
       end = min(start + batch_size, dataset_size)

       # 通过选取的样本训练神经网络并更新参数。
       sess.run(train_step,
             feed_dict={x: X[start:end], y_: Y[start:end]})

       if i % 1000 == 0:
          # 每隔一段时间计算在所有数据上的交叉熵并输出。
          total_cross_entropy = sess.run(
            cross_entropy, feed_dict={x: X, y_: Y})
          print("After %d training step(s), cross entropy on all data is %g" %
           (i, total_cross_entropy))
    print(sess.run(w1))
    print(sess.run(w2))

以上程序实现了训练神经网络的全部过程。从这段程序可以总结出训练神经网络的过程可以分为三个步骤:

  1. 定义神经网络的结构和前向传播的结果
  2. 定义损失函数以及选择反向传播优化的算法
  3. 生成会话(tf.Session)并且在训练数据上反复运行反向传播优化算法

无论神经网络的结构如何变化,这三个步骤是不变的。

 

12-11 06:56