在上一篇文章中,我们介绍了使用TensorFlow框架有两部分组成:构建计算图,用会话启动计算图。今天我们来介绍 tf.placeholder,feed_dict,实现一个 Wx+b 的函数。本文章仍然保持不够专业的传统,只求达到效果,可以实现一些东西;或者说“路子比较野”。同时确实不一定保证章节与章节之间条理清晰。
首先还是上一段可执行的代码。
import tensorflow as tf
# 开始构建计算图
W = tf.constant(
[[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]]
)
# 注意 x 和 b 是列向量
x = tf.placeholder(tf.float32, shape=[3, 1])
b = tf.constant([[1.0], [2.0]])
result1 = tf.matmul(W, x) + b
result2 = result1 + b
# 结束构建计算图
init = tf.global_variables_initializer()
with tf.Session() as sess:
# get_result1 = sess.run(result1, feed_dict={x: [[3.0], [4.0], [5.0]]})
get_result1, get_result2 = sess.run([result1, result2], feed_dict={x: [[3.0], [4.0], [5.0]]})
print(get_result1)
print(get_result2)
这段代码中构建计算图的部分比较长,我仍然会细致地讲解。
首先是W矩阵,我把它设为 2 × 3 的矩阵。根据上一节的讲解,它是一个常量。在TensorFlow里,需要特别注意矩阵和向量的shape,即线性代数里学的,2 × 3 的矩阵只能和 3 × n 的矩阵相乘,如果在报错信息里面看到“shape”的字样,十有八九是矩阵的大小搞错了。
讲真,我真的很佩服那些学数学的,是如何记住那些矩阵的shape,保证写出来是对的,当然我确实也记不住大写的是矩阵,小写的是向量这样的规则(Is it right?)。我看公式时总是会想着shape是多少,这样代码跑出来是怎样的效果。
x = tf.placeholder(tf.float32, shape=[3, 1])
b = tf.constant([[1.0], [2.0]])
声明 x 是一个placeholder,就是说假设这里有一个矩阵,矩阵的大小是shape决定的。在运行计算图的时候, x 会被填入相应的数据,这个在稍后会说明。总之它是一个 3 × 1 的列向量。同样 b 也是一个 2 × 1 的列向量。
如果你需要 b 是行向量呢?如下所示,自行注意其中的差别。记不住的话,写代码的时候多试几次,调试到正确为止。
b = tf.constant([1.0, 2.0])
然后关于我们希望获得的结果:
result1 = tf.matmul(W, x) + b
result2 = result1 + b
其实写result1这一行就可以了,result2只是为了说明一个技巧。tf.matmul是TensorFlow中的矩阵乘法运算。为什么不直接用“*”呢?我也是写到这边的时候刚刚想到的,然后测试了一下,“*”好像是向量乘法(就是,[1, 2, 3] * [4, 5, 6] = [4, 10, 18]),没有严格测试,欢迎指正。
总之result1就是Wx+b在计算图中的结果。注意计算图中的变量类型都是tensor,必须依靠会话启动计算图才能得到结果。(不信可以试试直接print,啥也看不到)
get_result1, get_result2 = sess.run([result1, result2], feed_dict={x: [[3.0], [4.0], [5.0]]})
我们刚刚说了,构建计算图时假设 x 是一个矩阵,因为我们也没告诉它实际的数值。在这行代码中,我们使用feed_dict这个参数,给 x 传入一个列向量。这样可以一次性得到result1和result2的结果。可以使用构建一个带tensor的list在一行代码中获取所有的tensor在计算后对应的值。这里还有个细节,result2是以来result1的。在获取result2的时候,我觉得,只是个人觉得,计算图只会被计算一次。就是说,获取result2的时候,计算图不会从头计算。(我们能想到的这么简单的优化,TensorFlow团队应该也想得到,除非不可为)只计算一次的好处就是,如果你的计算图流程特别长,那么只计算一次的话可以大大提高计算速度,节约计算资源。虽然只是个人猜测,有机会可以测试一下,但直接写在一行代码里会合适一些。
今天就讲到这儿吧,建议把代码手敲一遍,然后尝试各种改动,调戏一下代码,下一节我们将讲变量和优化器。