tensorflow 是一个很流行的计算框架, 目前主要用来做深度学习. 但实际上, tensorflow 不仅仅可以做深度学习, 理论上说任何算法都可以用 tensorflow 来描述, 就在于它做了计算流图这样的抽象, 而 tensorflow 这个名字实际上很自然流. 其实提到计算流图, 这样的抽象并不是 tensorflow 首作, 计算模型中经常会有图计算, 编译器中离开了这玩意玩不转, 乃至我们平时的工程涉及到大的规模乃至需要需求模板化的时候, 可能都离不开计算流图或者类似这样的模型. 所以, 其实它对于我们每个人并不是什么新鲜的玩意.
以下代码包括其注释说明了计算流图的建立以及使用, 包含了对 tensorflow 的最基本概念认识.
- # 先要导入库
- import tensorflow as tf
- # 建立 session 才可以进行图运算
- # 实际上 session 只需要在计算前建立即可
- # 但此处为了方便就先建立了
- s = tf.Session()
建立计算图, 包含了操作和张量. tensorflow 内部带有各种操作, 甚至自己也可以用 C++ 来添加新的操作.
以下 multipy 和 reduce_sum 都是操作, 其实 constant 也被看成是操作.
- # 以下是建立计算图, tensorflow 建立计算图
- # 只是定义各个 tensor 之间的计算关系, 并未开始计算
- # 每个操作都可以带一个名字, 操作和张量之间是一对一的关系
- # 以下是两个常量
- a = tf.constant(list(range(1,5)), name="a")
- b = tf.constant(list(range(11,15)), name="b")
- # 将两个形状一样的张量依次按相同位置相乘
- # 得到一个形状一样的张量
- c = tf.multiply(a, b, name="c")
- # 将张量里所有元素累和
- d = tf.reduce_sum(c, name="d")
不要被名字所误导, 以上并不产生计算, 只是布置流图, 或许 tf.place(tf.multiply, a, b)这样的名字可能更好一点 ^_^
在 session 里使用 run 方法才会产生真实的计算, run 带有两个参数, 一个是 fetches, 表示要计算的张量, 一个是 feed_dict, 表示要临时赋值的张量.
以下 s.run(d)其实等同于 s.run(fetches=d)
- # 使用 Session 的方法 run 可以根据计算图计算需要的 tensor
- #1*11+2*12+3*13+4*14=130
- # 输出 130
- print(s.run(d))
- # 可以选择一组 tensor
- # 输出[array([1, 2, 3, 4], dtype=int32), array([11, 12, 13, 14], dtype=int32), array([11, 24, 39, 56], dtype=int32), 130]
- print(s.run([a,b,c,d]))
- #feed_dict 可以修改任何想传入的 tensor, 此处希望修改 a 的传入
- #31*11+32*12+33*13+34*14=1630
- # 输出 1630
- print(s.run(d, feed_dict={
- a:list(range(31,35))
- }))
- #feed_dict 可以一次修改多个 tensor
- #41*51+42*52+43*53+44*54=8930
- # 输出 8930
- print(s.run(d, feed_dict={
- a:list(range(41,45)), b:list(range(51,55))
- }))
- # 不是 constant 一样可以修改传入
- # 此时计算 c 不依赖于 a 和 b
- #1+2+3+4+5=10
- # 输出 10
- print(s.run(d, feed_dict={
- c:list(range(1,5))
- }))
- # 流图中 d 依赖于 c 更短, 所以 a 的传入不影响计算
- # 输出 10
- print(s.run(d, feed_dict={
- a:list(range(11,15)), c:list(range(1,5))
- }))
实际上, 大多情况下, 总是会有一些张量要在计算开始指定的, 那么这些张量为 constant 已经失去意义, 从而引入 placeholder.
- # 引入 placeholder, 这样此处不需要 constant
- # 以下建立的张量为一阶张量, 也就是向量, 维度为 4
- e = tf.placeholder(tf.int32, shape=[4], name="e")
- f = tf.constant(list(range(1,5)), name="f")
- g = tf.multiply(e, f, name="g")
- h = tf.reduce_sum(g, name="h")
- #h 的计算最终依赖于 e 这个 placeholder
- # 而如果依赖的 placeholder 如果不传值, 则不可运算
- # 以下会产生异常
- try:
- print(s.run(h))
- except:
- print("EROR HERE!")
- # 给 placeholder 传值
- # 输出 10
- print(s.run(h, feed_dict={e:list(range(1,5))}))
- #a,b,c,d 的计算和 h 的计算是两 ge 独立的计算连通图
- # 但依然可以一起计算
- # 输出[array([1, 2, 3, 4], dtype=int32), array([11, 12, 13, 14], dtype=int32), array([11, 24, 39, 56], dtype=int32), 130, 30]
- print(s.run([a,b,c,d,h], feed_dict={e:list(range(1,5))}))
一个计算图中可以有多个 placeholder
- #placeholder 可以建立多个
- e2 = tf.placeholder(tf.int32, shape=[4], name="e2")
- f2 = tf.placeholder(tf.int32, shape=[4], name="f2")
- g2 = tf.multiply(e2, f2, name="g2")
- h2 = tf.reduce_sum(g2, name="h2")
- # 要计算 h2, 依赖的两个 placeholder 必须都传值
- # 输出 30
- print(s.run(h2, feed_dict={
- e2:list(range(1,5)), f2:list(range(1,5))
- }))
上面 placeholder 在 shape 参数中定死了张量的形状, 实际上可以不定死维数
- # 甚至可以只指定 placeholder 是几阶张量, 而不指定维度数
- e3 = tf.placeholder(tf.int32, shape=[None], name="e3")
- f3 = tf.placeholder(tf.int32, shape=[None], name="f3")
- g3 = tf.multiply(e3, f3, name="g3")
- h3 = tf.reduce_sum(g3, name="h3")
- # 输出 30
- print(s.run(h3, feed_dict={
- e3:list(range(1,5)), f3:list(range(1,5))
- }))
- # 元组当然没有问题
- # 输出 5
- print(s.run(h3, feed_dict={
- e3:(1,2), f3:(1,2)
- }))
二阶张量当然也可以, 同理三阶, 四阶... 都是可以得
- # 一阶张量可以, 二阶张量当然没有问题
- e4 = tf.placeholder(tf.int32, shape=[None, None], name="e4")
- f4 = tf.placeholder(tf.int32, shape=[None, None], name="f4")
- g4 = tf.multiply(e4, f4, name="g4")
- h4 = tf.reduce_sum(g4, name="h4")
- # 输出 30
- print(s.run(h4, feed_dict={
- e4:[[1,2],[3,4]], f4:[[1,2],[3,4]]
- }))
- # 通过名字也可以找到张量
- _e4 = tf.get_default_graph().get_tensor_by_name("e4:0")
- _f4 = tf.get_default_graph().get_tensor_by_name("f4:0")
- _g4 = tf.get_default_graph().get_tensor_by_name("g4:0")
- _h4 = tf.get_default_graph().get_tensor_by_name("h4:0")
- # 输出[array([[ 100, 400],[ 900, 1600]], dtype=int32), 3000]
- print(s.run([_g4, _h4], feed_dict={
- _e4:[[10,20],[30,40]], _f4:[[10,20],[30,40]]
- }))
甚至, placeholder 不指定张量阶数也是没有问题的.
- x = tf.placeholder(tf.int32)
- y = tf.reduce_sum(x)
- # 输出 10
- s.run(y,feed_dict={
- x:[[1,2],[3,4]]
- })
- # 输出 36
- s.run(y,feed_dict={
- x:[[[1,2],[3,4]], [[5,6],[7,8]]]
- })
以上提到的计算图本身不带有记忆, 从而可以引入带有记忆的计算图, 那就是需要引入变量 (variable) 概念, 也就是可以把一些张量定义为变量.
- # 引入变量, 从而希望在内存中常驻, 以便修改
- m=tf.Variable(list(range(1,3)), name="m")
- n=tf.reduce_sum(m, name="n")
- p=tf.add(m, n, name="p")
- # 把 p 赋值给变量 m
- q=tf.assign(m, p, name="q")
- # 当使用变量的时候, 需要利用这个将变量初始化一下
- s.run(tf.global_variables_initializer())
- #[1,2]求和为 3,[1,2]加 3 得到[4,5], 赋值给 m
- # 输出[4 5]
- print(s.run(q))
- #[4,5]求和为 9,[4,5]加 9 得到[13,14], 赋值给 m
- # 输出[13 14]
- print(s.run(q))
变量一旦形状确定, 是不能进行调整的.
- try:
- #concat 是把两个张量拼接, 从而张量的维度发生了变化
- #而 m2 作为变量, 一开始形状就被确定, assign 是不能对形状金勋哥调整的
- #从而这里会发生异常
- m2=tf.Variable([1,2], expected_shape=[None],dtype=tf.int32)
- n2=tf.reduce_sum(m2)
- p2=tf.add(m2,n2)
- r2=tf.concat([m2,p2],0)
- q2=tf.assign(m2,r2)
- s.run(tf.global_variables_initializer())
- s.run(q2)
- except:
- print("ERROR HERE!")
或许只能用以下方法?
- # 我能想到的只好用以下方法来实现改变形状
- m3=tf.placeholder(tf.int32, shape=[None], name="m3")
- n3=tf.reduce_sum(m3, name="n3")
- p3=tf.add(m3, n3, name="p2")
- r3=tf.concat([m3,p3], 0, name="r3")
- # 输出[1 2 4 5]
- x=s.run(r3, feed_dict={
- m3:[1,2]
- })
- print(x)
- # 输出[1 2 4 5 13 14 16 17]
- x=s.run(r3, feed_dict={
- m3:x
- })
- print(x)
关于以上变量的维度一旦确定, 就无法改变, 可能是因为 tensorflow 一开始就分配好了内存. 我就想, 如果未来出现那种结构不断调整的 AI 模型该怎么办, 似乎前段时间听说了已经出现了不断在调整尺寸的基于神经网络的 AI 模型, 但不知道是用什么实现的.
来源: https://www.cnblogs.com/Colin-Cai/p/10741013.html