Tensorflow的基本结构

tensor(张量):tensorflow的数据中央控制单元 每一个tensor都有一系列的初始值组成,这些初始值形成一个任意维数的数组(一般tensor的列即为它的维度)。flow(流动)。

Tensorflow程序通常有两个阶段:构建图阶段和执行图阶段。

构建图阶段:定义数据(张量[^tensor])与操作(节点[^Op]),这些执行步骤被描述成一个图。

执行图阶段:使用会话(Session)执行构建好的图中的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import tensorflow as tf

node1 = tf.constant(3.0)
node2 = tf.constant(4.0)
node3 = tf.add(node1,node2)
print(node1) # Tensor("Const:0", shape=(), dtype=float32)
print(node2) # Tensor("Const_1:0", shape=(), dtype=float32)
print(node3) # Tensor("Add:0", shape=(), dtype=float32)

#执行一个图为默认的会话(Session(图为空))
with tf.Session() as sess:
node1_value=sess.run(node1)
node2_value=sess.run(node2)
node3_value=sess.run(node3)
print(node1_value) # 3.0
print(node2_value) # 4.0
print(node3_value) # 7.0
sess.close()

tensorflow中的图 tf.graph()

  1. 通过张量本身直接出graph
1
2
3
4
5
6
7
8
9
10
11
12
import tensorflow as tf

node=tf.constant(2.0)
# 通过张量本身直接出graph
default_graph=tf.get_default_graph() # 获取当前的默认图结构
print(default_graph)
# <tensorflow.python.framework.ops.Graph object at 0x000001EFCE019EF0>

sess=tf.Session()
node_value=sess.run(node)
print(node.graph)
print(default_graph==node.graph) # True
  1. 通过声明一个默认的图,然后定义张量内容,在后面可以调用或保存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import tensorflow as tf

node1=tf.constant(4.0)
g=tf.Graph()
with g.as_default():
node2=tf.constant(2.0)

sess=tf.Session(graph=g)
node_value=sess.run(node2)
print(g)
print(tf.get_default_graph())
print(node1.graph)
# node2:<tensorflow.python.framework.ops.Graph object at 0x0000014501B59DA0>
# <tensorflow.python.framework.ops.Graph object at 0x0000014501B39F60>
# <tensorflow.python.framework.ops.Graph object at 0x0000014501B39F60>
  1. 通过多个声明,在后面可通过变量名分别调用(调用时Session()要写入对应的图的变量名)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import tensorflow as tf

g1=tf.Graph()
with g1.as_default():
node1=tf.constant(2.0)
g2=tf.Graph()
with g2.as_default():
node2=tf.constant(5.0)

with tf.Session(graph=g1) as sess1:
node1_value=sess1.run(node1)
print(g1)
with tf.Session(graph=g2) as sess2:
node2_value=sess2.run(node2)
print(g2)

TensorFlow的对graph的常用操作

  1. 保存图(pb文件)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import tensorflow as tf

g1=tf.Graph()
with g1.as_default():
node1=tf.constant(2.0,name='node1')
g2=tf.Graph()
with g2.as_default():
node2=tf.constant(3.0,name='node2')

with tf.Session(graph=g1) as sess1:
print(sess1.run(node1))
with tf.Session(graph=g2) as sess2:
print(sess2.run(node2))
# g1的图定义 包含pb的path,pb文件名,是否为文本 默认False
tf.train.write_graph(g1.as_graph_def(),'.','graph.pb',False)

tf.reset_default_graph:对图进行重置。每使用一次,重置的新图,分配不同的地址。

g1.as_graph_def():将图进行序列化。

tf.train.write_graph():将序列化的图保存。一般不适用这个api,这样保存的模型,只能用于测试。(一般保存为 .ckpt 文件,用于继续训练或测试,直到效果ok再进行保存pb文件 )

  1. 调用pb文件的图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import tensorflow as tf
from tensorflow.python.platform import gfile

# load graph
with gfile.FastGFile('./graph.pb','rb') as f:
graph_def=tf.GraphDef() # 创建一个序列化的空图
graph_def.ParseFromString(f.read()) # 把pb文件导入到创建的序列化空图中
tf.import_graph_def(graph_def,name='') # 将序列化图导入到当前图中

sess=tf.Session()
node1_tensor=sess.graph.get_tensor_by_name('node1:0')
# 从图中获取到命名为"node1:0"的tensor,该tensor为"node1"节点的第0个输出
node1=sess.run(node1_tensor)
print(node1)

序列化和反序列化

序列化:把对象转化为可传输的字节序列过程称为序列化。

反序列化:把字节序列还原为对象的过程称为反序列化。

序列化最终的目的是为了对象可以跨平台存储,和进行网络传输。而我们进行跨平台存储和网络传输的方式就是IO,而我们的IO支持的数据格式就是字节数组。

因为我们单方面的只把对象转成字节数组还不行,因为没有规则的字节数组我们是没办法把对象的本来面目还原回来的,所以我们必须在把对象转成字节数组的时候就制定一种规则(序列化),那么我们从IO流里面读出数据的时候再以这种规则把对象还原回来(反序列化)。

一般来讲,TensorFlow 有三种文件格式:

checkpoint:一种独有的文件格式,包含四个文件。保存了计算图和权重,无法直接打开阅读。多用于训练时。
pb:protobuf 格式的二进制文件,可以只保存计算图(很小),也同时保存了权重和计算图(很大),无法直接打开阅读。
包含权重的文件中所有的 variable 都已经变成了 tf.constant 和 graph 一起 frozen 到一个文件。
pbtxt:pb 文件的可读文本,如果同时保存权重,文件会很大,一般用的比较少,可用于调试查看网络结构。

会话

  • tf.Session() :用于完整的程序当中,会话掌握着资源,用完需要回收,因此一般使用with上下文管理器。

    如果在创建Session时没有指定Graph,则该Session会加载默认Graph。如果创建了多个Graph,则需要创建不同的Session来加载每个Graph,而每个Graph则可以加载在多个Session中进行计算。

    tf.eval()只适用于tensor,一次只能获得一个张量的值;而session.run()不仅适用于tensor,还可用于没有输出的op,可以在同一步骤中获取许多张量的值;对于tensor,调用session.run()与tensor.eval()是等价的。

    初始化会话以下三个参数:

    • target
    • graph
    • config
  • tf.interactiveSession:用于交互式上下文的TensorFlow。

Fetch and Feed

  • Fetch操作是指在会话当中,可以在sess.run()时同时运行一个或多个op;将多个op组成列表,传入run中可以得到多个op的输出结果。
1
2
3
4
5
6
7
8
9
10
11
12
import tensorflow as tf

node1 = tf.constant(3.0)
node2 = tf.constant(4.0)
add = tf.add(node1, node2)
mul = tf.multiply(node1, node2)

with tf.Session() as sess:
node1_value = sess.run(node1)
node2_value = sess.run(node2)
value = sess.run([add,mul])
print(value) # [7.0, 12.0]
  • Feed操作需要在开始时通过占位符(placeholder)来声明数据。

为什么需要feed操作:当我们构建一个模型时,常常需要我们在运行时候输入一系列数据,通过占位符可以提前声明这些数据;在train数据时,可以在一个graph上同时使用一个op,每一次写入的时候都会替换上一次的值,减少了graph的开销。

由定义def placeholder(dtype,shape=None,name=None);可知声明时需要给定类型dtype。

占位符不需要初始化。可以把feed_dict看作它特有的一种初始化方式。shape默认下feed_dict的数据的维度可任意。

1
2
3
4
5
6
7
8
9
import tensorflow as tf

a=tf.placeholder(tf.float32)
b=tf.placeholder(tf.float32)
c=tf.add(a,b)

with tf.Session() as sess:
result=sess.run(c,feed_dict={a:1,b:2})
print(result)

张量(Tensors)

TensorFlow 是一个定义和运行张量计算的框架。张量是各种维度的向量和矩阵的统称。在内部,TensorFlow 用基本数据类型的多维数组来表示张量。

在写 TensorFlow 程序的时候,主要操作和传递的对象就是 tf.Tensor,即张量。

tf.Tensor对象的数据类型和张量的类型不是同一个概念。tf.Tensor对象的数据类型一般为float32,int32等;张量的类型一般为tf.constant``,tf.Variabale,tf.placeholder,除了tf.Variable以外,张量的值是不可变的,也就是说张量在单次执行的上下文中值是唯一的。但是,两次对同一个张量求值可能返回不同的值,比如,张量的值可能是从磁盘读取的数据,或者是一个随机数,那么每次产生的结果可能是不一样的。

  • 四种常见的Tensor

    • 1. 常量Tensor:值不能改变,常用tf.constant(value,dtype=None,shape=None,name='Const',verify_shape=False)创建,其中value值必须给定,verify_shape默认该常量的形状不可改。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import tensorflow as tf

    # tf.zeros(shape,dtype=tf.float32,name=None) 产生全0的张量
    tensor1=tf.zeros(shape=[3,4])
    # 创建一个和输入图像(tensor1)一样维度元素都为 0 的张量
    tensor1_1=tf.zeros_like(tensor1)

    # tf.ones(shape,dtype=tf.float32,name=None) 产生全1的张量
    tensor2=tf.ones(shape=[3,4])
    # 创建一个和输入图像(tensor2)一样维度元素都为 1 的张量
    tensor2_1=tf.ones_like(tensor1)

    with tf.Session() as sess:
    print(sess.run(tensor1))
    print(sess.run(tensor2))

    常量Tensor无需初始化,但仍需在会话tf.Session()中获取数据,即数据只会在会话中流动。

    • 2. 变量Tensor:值可以改变,可训练。在神经网络中,变量一般可作为储存权重和其他信息的矩阵,而常量可作为储存超参数或其他结构信息的变量,变量必须初始化

      • tf.Variable(initial_value, dtype=None, name=None, trainable=True, collections=None,validate_shape=True)

        全局变量初始化:tf.global_variables_initializer()

        将一个变量的值赋值给另一个变量:initialized_value()

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        import tensorflow as tf

        weight1 = tf.Variable(tf.ones(shape=[3],name='weight1'))
        # 用已经初始化的weight1作为weight2的值
        weight2 = tf.Variable(weight1.initialized_value(),name='weight2')
        weight_twice = tf.Variable(weight1.initialized_value() * 2,name='weight_twice')
        init = tf.global_variables_initializer()

        with tf.Session() as sess:
        sess.run(init)
        print(weight1.eval(),weight2.eval(),weight_twice.eval())

        运行变量的初始化函数:variable.initializer()

        1
        2
        3
        4
        5
        6
        7
        import tensorflow as tf

        weight = tf.Variable(tf.ones(shape=[3]),name='weight')
        with tf.Session() as sess:
        # weight.initializer.run() 两者相同
        sess.run(weight.initializer) # 仅仅初始化weight本身
        print(weight.eval())
      • tf.get_variable( name, shape=None,dtype=None, initializer=None, trainable=True, regularizer=None,collections=None, caching_device=None, partitioner=None, validate_shape=True, use_resource=None, custom_getter=None),这里的initializer初始化之后并没有实际的数据,当开启session时,才将相应的初始化数据传给tensor。

        1. 初始化initializer常量

        通过tf.constant初始化variable不需要给定shape,其dtype由constant的值决定。

        通过tf.constant_initializer初始化variable需要给定shape ,其dtype为float32_ref

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        import tensorflow as tf

        # init_const = tf.constant([[1,2],[3,4]])
        # x = tf.get_variable(initializer=init_const,name='x')
        # [[1 2]
        # [3 4]]
        # <tf.Variable 'x:0' shape=(2, 2) dtype=int32_ref>

        init_const = tf.constant_initializer([[1,2],[3,4]])
        x = tf.get_variable(initializer=init_const,name='x',shape=[2,2])
        # [[1. 2.]
        # [3. 4.]]
        # <tf.Variable 'x:0' shape=(2, 2) dtype=float32_ref>


        init = tf.global_variables_initializer()
        with tf.Session() as sess:
        sess.run(init)
        print(x.eval())
          • 初始化initializer为 标准正态分布:tf.random_normal_initializer()

          • 初始化initializer为 截断正态分布:tf.truncated_normal_initializer()根据正态分布的3σ原则,将小于μ-3σ和大于μ+3σ的值截断剩下 μ-3σ<x<μ+3σ 之间的值。

          • 初始化initializer为 均匀分布: tf.random_uniform_initializer()

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        import tensorflow as tf

        init_random = tf.random_normal_initializer(mean=0.0, stddev=1.0)
        y1 = tf.get_variable(initializer=init_random, name='y', shape=[10])

        init_truncated = tf.truncated_normal_initializer(mean=0.0, stddev=1.0)
        y2 = tf.get_variable(initializer=init_truncated, name='y2', shape=[10])

        init_uniform = tf.random_uniform_initializer(minval=0,maxval=10)
        y3 = tf.get_variable(initializer=init_uniform,name='y3',shape=[10])

        init = tf.global_variables_initializer()

        with tf.Session() as sess:
        sess.run(init)
        print(y1.eval())
        print(y2.eval())
        print(y3.eval())
      • tf.Variable()tf.get_variable()的区别

        1. 变量共享

        使用tf.Variable()时,如果系统检测到命名相同,系统会自动加后缀不支持变量共享
        使用tf.get_variable(),如果系统检测到命名相同,且参数reuse=True,那么直接共享之前的变量值,支持变量共享

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        import tensorflow as tf

        with tf.variable_scope('scope1'):
        w1 = tf.Variable(1, name='w1')
        w2 = tf.get_variable(initializer=2.0, name='w2')

        with tf.variable_scope('scope1', reuse=True):
        w1_p = tf.Variable(5, name='w1')
        w2_p = tf.get_variable(initializer=3.0, name='w2')

        init = tf.global_variables_initializer()
        with tf.Session() as sess:
        sess.run(init)
        print(w1.eval()) # 1
        print(w2.eval()) # 2.0
        print(w1_p.eval()) # 5 变量共享不了
        print(w2_p.eval()) # 2.0 变量共享了W1

        print(w1.name, w1_p.name) # scope1/w1:0 scope1_1/w1:0
        print(w2.name, w2_p.name) # scope1/w2:0 scope1/w2:0
        1. tf.variable_scope()可以对所有op加上前缀,指明作用域空间。tf.name_scope()不能tf.get_variable()变量指明作用域空间。
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        import tensorflow as tf

        # tf.name_scope()
        with tf.name_scope('scope1'):
        w0 = tf.constant(1,name='w0')
        w1 = tf.Variable(1,name='w1')
        w2 = tf.get_variable(initializer=2.0,name='w2')
        # tf.variable_scope()
        with tf.variable_scope('scope2'):
        w0_p = tf.constant(2,name='w0_p')
        w1_p = tf.Variable(5,name='w1_p')
        w2_p = tf.get_variable(initializer=3.0,name='w2_p')
        # w2没有确定作用域空间
        print(w0.name, w1.name, w2.name) # scope1/w0:0 scope1/w1:0 w2:0
        print(w0_p.name,w1_p.name,w2_p.name) # scope2/w0_p:0 scope2/w1_p:0 scope2/w2_p:0
    • 3. 占位符placeholder

    • 4. 稀疏张量SparseTensor:定义时只需要定义非0的数,其他数会自动填充。def tf.SparseTensor(values,indices,dense_shape)

      • indices:非零值所对应的索引。
      • values:对应索引位置的值。
      • dense_shape:稀疏张量所对应的稠密张量的形状。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import tensorflow as tf

    # 稀疏张量 只需要给对应位置非0的值即可
    sparse_tensor=tf.SparseTensor(indices=[[0,0],values=[1,5],[1,2]],dense_shape=[3,4])
    # 稠密张量 将稀疏张量转化为稠密张量
    dense_tensor=tf.sparse_tensor_to_dense(sparse_tensor)

    with tf.Session() as sess:
    print(sess.run(sparse_tensor))
    print(sess.run(dense_tensor))

    '''
    输出如下:
    SparseTensorValue(indices=array([[0, 0],
    [1, 2]], dtype=int64), values=array([1, 5]), dense_shape=array([3, 4], dtype=int64))
    [[1 0 0 0]
    [0 0 5 0]
    [0 0 0 0]]
    '''
  • 张量的秩

tf.Tensor 对象的秩就是它维度的数量。秩的也可以叫做阶数、度数或者是 n 维。注意:Tensorflow 里的秩和数学中矩阵的秩是不一样的。

数学实体
0 标量
1 向量
2 矩阵(二维矩阵)
3 3维张量(由数构成的方体)
4 4维张量(常用于图像处理)
n n维张量
  • tf.rank获取张量的秩
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import tensorflow as tf

tensor0 = tf.constant(1) # 0阶
tensor1 = tf.constant([2, 3]) # 1阶
tensor2 = tf.constant([[4, 5, 6], [1, 2, 3]]) # 2阶
# 图像处理常用
image = tf.zeros([10, 150, 150, 3]) # 4阶
# 各个维度分别代表一个样本批次的大小、图像的宽度、图像的高度以及颜色通道数。

with tf.Session() as sess:
print(sess.run(tf.rank(tensor0))) # 0
print(sess.run(tf.rank(tensor1))) # 1
print(sess.run(tf.rank(tensor2))) # 2
print(sess.run(tf.rank(image))) # 4
  • tf.reshape改变tensor对象的形状

变形前后的张量元素个数必须相同,如果不同,就会产生错误。

Tensor.eval用来求取张量的值。只能在启用了一个默认的会话才能正常使用,当Tensor的信息是不确定的,如使用placeholder的张量在没有给placeholder提供值之前是无法进行评估的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import tensorflow as tf

matrixA=tf.ones([3,4,5])
matrixB=tf.reshape(matrixA,[4,15])
matrixC=tf.reshape(matrixB,[3,-1])
matrixD=tf.reshape(matrixB,[4,3,-1])
# -1 会自动计算这一维度的大小
with tf.Session() as sess:
print(matrixA.eval())
print(matrixA) # Tensor("ones:0", shape=(3, 4, 5), dtype=float32)
print(matrixB) # Tensor("Reshape:0", shape=(4, 15), dtype=float32)
print(matrixC) # Tensor("Reshape_1:0", shape=(3, 20), dtype=float32)
print(matrixD) # Tensor("Reshape_2:0", shape=(4, 3, 5), dtype=float32)
print(sess.run(matrixA))
print(sess.run(matrixB))
print(sess.run(matrixC))
print(sess.run(matrixD))
  • tf.slice()分割张量。

tf.slice()从张量中提取想要的切片,由begin指定位置开始的张量提取尺寸为size的切片。

1
2
3
4
5
6
def slice(input_,begin,size,name=None)	

input_: 类型为一个tensor,表示的是输入的tensor,也就是被切的那个.
begin: 是一个int32或int64类型的tensor,表示的是每一个维度的起始位置.
size: 是一个int32或int64类型的tensor,表示的是每个维度要拿的元素数.
name: 操作的名称,可写可不写.

通过切片实现简单的ROI(Region of interest)提取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import tensorflow as tf
import cv2 as cv

src = cv.imread("F:/Software/opencv-python-image/image/camera.jpg")
cv.imshow("input", src)
image = tf.placeholder(shape=[None, None, 3], dtype=tf.uint8, name="image")
roi_image = tf.slice(image, [35,20, 0], [180, 180, -1]) # -1自动计算总通道数 若改为1则为灰度图像

with tf.Session() as sess:
slice = sess.run(roi_image, feed_dict={image:src})
print(roi_image)
cv.imshow("roi", slice)
cv.waitKey(0)
cv.destroyAllWindows()

参考资料

[1] TensorFlow 中的几个关键概念:Tensor,Operation,Graph,Session - 知乎 (zhihu.com)

[2] 序列化理解起来很简单 - 知乎 (zhihu.com)