基础示例:线性回归

与前面的NumPy和Eager Execution模式不同,TensorFlow的Graph Execution模式使用 符号式编程 来进行数值运算。首先,我们需要将待计算的过程抽象为数据流图,将输入、运算和输出都用符号化的节点来表达。然后,我们将数据不断地送入输入节点,让数据沿着数据流图进行计算和流动,最终到达我们需要的特定输出节点。以下代码展示了如何基于TensorFlow的符号式编程方法完成与前节相同的任务。其中, tf.placeholder() 即可以视为一种“符号化的输入节点”,使用 tf.get_variable() 定义模型的参数(Variable类型的张量可以使用 tf.assign() 进行赋值),而 sess.run(output_node, feed_dict={input_node: data}) 可以视作将数据送入输入节点,沿着数据流图计算并到达输出节点并返回值的过程。

  1. import tensorflow as tf
  2.  
  3. # 定义数据流图
  4. learning_rate_ = tf.placeholder(dtype=tf.float32)
  5. X_ = tf.placeholder(dtype=tf.float32, shape=[5])
  6. y_ = tf.placeholder(dtype=tf.float32, shape=[5])
  7. a = tf.get_variable('a', dtype=tf.float32, shape=[], initializer=tf.zeros_initializer)
  8. b = tf.get_variable('b', dtype=tf.float32, shape=[], initializer=tf.zeros_initializer)
  9.  
  10. y_pred = a * X_ + b
  11. loss = tf.constant(0.5) * tf.reduce_sum(tf.square(y_pred - y_))
  12.  
  13. # 反向传播,手动计算变量(模型参数)的梯度
  14. grad_a = tf.reduce_sum((y_pred - y_) * X_)
  15. grad_b = tf.reduce_sum(y_pred - y_)
  16.  
  17. # 梯度下降法,手动更新参数
  18. new_a = a - learning_rate_ * grad_a
  19. new_b = b - learning_rate_ * grad_b
  20. update_a = tf.assign(a, new_a)
  21. update_b = tf.assign(b, new_b)
  22.  
  23. train_op = [update_a, update_b]
  24. # 数据流图定义到此结束
  25. # 注意,直到目前,我们都没有进行任何实质的数据计算,仅仅是定义了一个数据图
  26.  
  27. num_epoch = 10000
  28. learning_rate = 1e-3
  29. with tf.Session() as sess:
  30. # 初始化变量a和b
  31. tf.global_variables_initializer().run()
  32. # 循环将数据送入上面建立的数据流图中进行计算和更新变量
  33. for e in range(num_epoch):
  34. sess.run(train_op, feed_dict={X_: X, y_: y, learning_rate_: learning_rate})
  35. print(sess.run([a, b]))

在上面的两个示例中,我们都是手工计算获得损失函数关于各参数的偏导数。但当模型和损失函数都变得十分复杂时(尤其是深度学习模型),这种手动求导的工程量就难以接受了。TensorFlow提供了 自动求导机制 ,免去了手工计算导数的繁琐。利用TensorFlow的求导函数 tf.gradients(ys, xs) 求出损失函数loss关于a,b的偏导数。由此,我们可以将上节中的两行手工计算导数的代码

  1. # 反向传播,手动计算变量(模型参数)的梯度
  2. grad_a = tf.reduce_sum((y_pred - y_) * X_)
  3. grad_b = tf.reduce_sum(y_pred - y_)

替换为

  1. grad_a, grad_b = tf.gradients(loss, [a, b])

计算结果将不会改变。

甚至不仅于此,TensorFlow附带有多种 优化器 (optimizer),可以将求导和梯度更新一并完成。我们可以将上节的代码

  1. # 反向传播,手动计算变量(模型参数)的梯度
  2. grad_a = tf.reduce_sum((y_pred - y_) * X_)
  3. grad_b = tf.reduce_sum(y_pred - y_)
  4.  
  5. # 梯度下降法,手动更新参数
  6. new_a = a - learning_rate_ * grad_a
  7. new_b = b - learning_rate_ * grad_b
  8. update_a = tf.assign(a, new_a)
  9. update_b = tf.assign(b, new_b)
  10.  
  11. train_op = [update_a, update_b]

整体替换为

  1. optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate_)
  2. grad = optimizer.compute_gradients(loss)
  3. train_op = optimizer.apply_gradients(grad)

这里,我们先实例化了一个TensorFlow中的梯度下降优化器 tf.train.GradientDescentOptimizer() 并设置学习率。然后利用其 compute_gradients(loss) 方法求出 loss 对所有变量(参数)的梯度。最后通过 apply_gradients(grad) 方法,根据前面算出的梯度来梯度下降更新变量(参数)。

以上三行代码等价于下面一行代码:

  1. train_op = tf.train.GradientDescentOptimizer(learning_rate=learning_rate_).minimize(loss)

简化后的代码如下:

  1. import tensorflow as tf
  2.  
  3. learning_rate_ = tf.placeholder(dtype=tf.float32)
  4. X_ = tf.placeholder(dtype=tf.float32, shape=[5])
  5. y_ = tf.placeholder(dtype=tf.float32, shape=[5])
  6. a = tf.get_variable('a', dtype=tf.float32, shape=[], initializer=tf.zeros_initializer)
  7. b = tf.get_variable('b', dtype=tf.float32, shape=[], initializer=tf.zeros_initializer)
  8.  
  9. y_pred = a * X_ + b
  10. loss = tf.constant(0.5) * tf.reduce_sum(tf.square(y_pred - y_))
  11.  
  12. # 反向传播,利用TensorFlow的梯度下降优化器自动计算并更新变量(模型参数)的梯度
  13. train_op = tf.train.GradientDescentOptimizer(learning_rate=learning_rate_).minimize(loss)
  14.  
  15. num_epoch = 10000
  16. learning_rate = 1e-3
  17. with tf.Session() as sess:
  18. tf.global_variables_initializer().run()
  19. for e in range(num_epoch):
  20. sess.run(train_op, feed_dict={X_: X, y_: y, learning_rate_: learning_rate})
  21. print(sess.run([a, b]))