TensorFlow的低阶API主要包括张量操作,计算图和⾃动微分。
如果把模型⽐作⼀个房⼦,那么低阶API就是【模型之砖】。
在低阶API层次上,可以把TensorFlow当做⼀个增强版的numpy来使⽤。
TensorFlow提供的⽅法⽐numpy更全⾯,运算速度更快,如果需要的话,还可以使⽤GPU进⾏加速。
张量的结构操作:
张量的操作主要包括张量的结构操作 和 张量的数学运算
张量结构操作诸如:张量创建,索引切⽚,维度变换,合并分割。
张量数学运算主要有:标量运算,向量运算,矩阵运算。另外我们会介绍张量运算的⼴播机制。
Autograph计算图我们将介绍使⽤Autograph的规范建议,Autograph的机制原理,
Autograph和tf.Module
张量创建:
# 一阶张量
a = tf.constant([1, 2, 3], dtype=tf.float32)
tf.print(a) # 张量
tf.print(a.shape) # 张量形状
print(str(a.ndim) + "阶张量")
# 二阶张量
b = tf.constant([[1, 2, 3], [1, 2, 3]], dtype=tf.float32)
tf.print(b) # 张量
tf.print(b.shape) # 张量形状
print(str(b.ndim) + "阶张量")
# 三阶张量
c = tf.constant([[[1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3]]], dtype=tf.float32)
tf.print(c) # 张量
tf.print(c.shape) # 张量形状
print(str(c.ndim) + "阶张量")
索引切片:
张量的索引切⽚⽅式和numpy⼏乎是⼀样的。切⽚时⽀持缺省参数和省略号。
tf.random.set_seed(3)
t = tf.random.uniform([5, 5], minval=0, maxval=10, dtype=tf.int32)
tf.print(t)
# 第0⾏
tf.print(t[0])
# 倒数第⼀⾏
tf.print(t[-1])
# 第1⾏第3列
tf.print(t[1, 3])
tf.print(t[1][3])
print("第1⾏⾄第3⾏")
tf.print(t[1:4, :])
tf.print(tf.slice(t, [1, 0], [3, 5]))
print("前四行,前两列")
tf.print(t[0:4, 0:2])
维度变换:
维度变换相关函数主要有 tf.reshape, tf.squeeze, tf.expand_dims, tf.transpose.
tf.reshape 可以改变张量的形状。
tf.squeeze 可以减少维度。
tf.expand_dims 可以增加维度。
tf.transpose 可以交换维度。
a = tf.random.uniform(shape=[1, 3, 3, 2], minval=0, maxval=255, dtype=tf.int32)
tf.print(a.shape)
tf.print(a)
# 改成 (3,6)形状的张量
b = tf.reshape(a, [3, 6])
tf.print(b.shape)
tf.print(b)
# 改回成 [1,3,3,2] 形状的张量
c = tf.reshape(b, [1, 3, 3, 2])
tf.print(c)
# 如果张量在某个维度上只有⼀个元素,利⽤tf.squeeze可以消除这个维度。
# 张量的各个元素在内存中是线性存储的,其⼀般规律是,同⼀层级中的相邻元素的物理地址也相邻。
s = tf.squeeze(a)
tf.print(s.shape)
tf.print(s)
# tf.transpose可以交换张量的维度,与tf.reshape不同,它会改变张量元素的存储顺序。常⽤于图⽚存储格式的变换上
# Batch,Height,Width,Channel
a1 = tf.random.uniform(shape=[100, 600, 600, 4], minval=0, maxval=255, dtype=tf.int32)
tf.print(a1.shape)
# 转换成 Channel,Height,Width,Batch perm: 是指维度顺序
s1 = tf.transpose(a1, perm=[3, 1, 2, 0])
tf.print(s1.shape)
合并切割:
和numpy类似,可以⽤tf.concat和tf.stack⽅法对多个张量进⾏合并,可以⽤tf.split⽅法把⼀个张量分割成多个张量。
tf.concat和tf.stack有略微的区别,tf.concat是连接,不会增加维度,⽽tf.stack是堆叠,会增加维度。
def tensor_merge():
a = tf.constant([[1.0, 2.0], [3.0, 4.0]])
b = tf.constant([[5.0, 6.0], [7.0, 8.0]])
c = tf.constant([[9.0, 10.0], [11.0, 12.0]])
# a b c 按0轴进行叠加
concat_1 = tf.concat([a, b, c], axis=0)
tf.print(concat_1)
# a b c 按1轴进行叠加
concat_2 = tf.concat([a, b, c], axis=1)
tf.print(concat_2)
# stack 堆叠 增加了一个维度
stack_0 = tf.stack([a, b, c])
tf.print(stack_0)
# 按照 1轴叠加
stack_1 = tf.stack([a, b, c], axis=1)
tf.print(stack_1)
def tensor_marge():
a = tf.constant([[1.0, 2.0], [3.0, 4.0]])
b = tf.constant([[5.0, 6.0], [7.0, 8.0]])
c = tf.constant([[9.0, 10.0], [11.0, 12.0]])
c = tf.concat([a, b, c], axis=0)
tf.print(c)
# 将 c 按 0轴 分三份
split_0 = tf.split(c, 3, axis=0)
print(type(split_0))
for team in split_0:
tf.print(team)
# 指定每份的记录数量
split_1 = tf.split(c, [4, 2], axis=0)
for team in split_1:
tf.print(team)
张量的数学运算
张量的数学运算符可以分为标量运算符、向量运算符、以及矩阵运算符。
加减乘除乘⽅,以及三⻆函数,指数,对数等常⻅函数,逻辑⽐较运算符等都是标量运算符。
标量运算
标量运算符的特点是对张量实施逐元素运算。
有些标量运算符对常⽤的数学运算符进⾏了重载。并且⽀持类似numpy的⼴播特性
许多标量的运算符都在tf.math模块下
a = tf.constant([[1.0, 2], [-3, 4.0]])
b = tf.constant([[5.0, 6], [7.0, 8.0]])
print(a + b) # 运算符重载 等同于 tf.math.add(a, b)
print(tf.math.add(a, b))
向量运算 :
向量运算符只在⼀个特定轴上运算,将⼀个向量映射到⼀个标量或者另外⼀个向量。
许多向量运算符都以reduce开头
a = tf.range(1, 10)
tf.print(a)
tf.print(tf.reduce_sum(a)) # 向量之和
tf.print(tf.reduce_mean(a)) # 向量平均值
tf.print(tf.reduce_max(a)) # 最大值
tf.print(tf.reduce_min(a)) # 最小值
tf.print(tf.reduce_prod(a)) # 乘积
# 张量指定维度进⾏reduce
b = tf.reshape(a, (3, 3))
tf.print(tf.reduce_sum(b, axis=1, keepdims=True))
tf.print(tf.reduce_sum(b, axis=0, keepdims=True))
# bool类型的reduce
p = tf.constant([True, False, False])
q = tf.constant([False, False, True])
tf.print(tf.reduce_all(p))
tf.print(tf.reduce_any(q))
# cum扫描累积
tf.print(tf.math.cumsum(a))
tf.print(tf.math.cumprod(a))
# cum扫描累积
tf.print(tf.argmax(a))
tf.print(tf.argmin(a))
矩阵运算:
矩阵运算包括:矩阵乘法,矩阵转置,矩阵逆,矩阵求迹,矩阵范数,矩阵⾏列式,矩阵求特征值,矩阵分解等运算。除了⼀些常⽤的运算外,⼤部分和矩阵有关的运算都在tf.linalg⼦包中。
a = tf.constant([[1, 2], [3, 4]])
b = tf.constant([[2, 0], [0, 2]])
tf.print(a @ b) # 等价于tf.matmul(a,b)
# 矩阵转置
transpose = tf.transpose(a)
tf.print(transpose)
# 矩阵求逆,必须为tf.float32或tf.double类型
a1 = tf.constant([[1.0, 2], [3.0, 4]], dtype=tf.float32)
inv = tf.linalg.inv(a1)
tf.print(inv)
# 矩阵求范数
norm = tf.linalg.norm(a1)
tf.print(norm)
# 矩阵⾏列式
det = tf.linalg.det(a1)
tf.print(det)
# 矩阵的特征值
eigvalsh = tf.linalg.eigvalsh(a1)
tf.print(eigvalsh)
# 矩阵的迹
a2 = tf.constant([[1.0, 2], [3, 4]])
tf.linalg.trace(a2)
# 矩阵qr分解
a3 = tf.constant([[1.0, 2.0], [3.0, 4.0]], dtype=tf.float32)
q, r = tf.linalg.qr(a3)
tf.print(q)
tf.print(r)
tf.print(q @ r)
# 矩阵svd分解
v, s, d = tf.linalg.svd(a3)
matmul = tf.matmul(tf.matmul(s, tf.linalg.diag(v)), d)
tf.print(matmul)
⼴播机制:
TensorFlow的⼴播规则和numpy是⼀样的:
1、如果张量的维度不同,将维度较⼩的张量进⾏扩展,直到两个张量的维度都⼀样。
2、如果两个张量在某个维度上的⻓度是相同的,或者其中⼀个张量在该维度上的⻓度为1,那么
我们就说这两个张量在该维度上是相容的。
3、如果两个张量在所有维度上都是相容的,它们就能使⽤⼴播。
4、⼴播之后,每个维度的⻓度将取两个张量在该维度⻓度的较⼤值。
5、在任何⼀个维度上,如果⼀个张量的⻓度为1,另⼀个张量⻓度⼤于1,那么在该维度上,就好
像是对第⼀个张量进⾏了复制
a = tf.constant([1, 2, 3])
b = tf.constant([[0, 0, 0], [1, 1, 1], [2, 2, 2]])
# tf.broadcast_to 以显式的⽅式按照⼴播机制扩展张量的维度。
tf.print(tf.broadcast_to(a, b.shape))
tf.print(b + a) # 等价于 b + tf.broadcast_to(a,b.shape)
# 计算⼴播后计算结果的形状,静态形状,TensorShape类型参数
shape = tf.broadcast_static_shape(a.shape, b.shape)
tf.print(shape)
# 计算⼴播后计算结果的形状,动态形状,Tensor类型参数
c = tf.constant([1,2,3])
d = tf.constant([[1],[2],[3]])
dynamic_shape = tf.broadcast_dynamic_shape(tf.shape(c), tf.shape(d))
tf.print(dynamic_shape)
# ⼴播效果
# c+d #等价于 tf.broadcast_to(c,[3,3]) + tf.broadcast_to(d,[3,3])
AutoGraph的使⽤规范:
有三种计算图的构建⽅式:静态计算图,动态计算图,以及Autograph。
TensorFlow 2.0主要使⽤的是动态计算图和Autograph。
动态计算图易于调试,编码效率较⾼,但执⾏效率偏低。
静态计算图执⾏效率很⾼,但较难调试。
⽽Autograph机制可以将动态图转换成静态计算图,兼收执⾏效率和编码效率之利。
当然Autograph机制能够转换的代码并不是没有任何约束的,有⼀些编码规范需要遵循,否则可能会转
换失败或者不符合预期。
Autograph编码规范总结:
1,被@tf.function修饰的函数应尽可能使⽤TensorFlow中的函数⽽不是Python中的其他函数。例
如使⽤tf.print⽽不是print,使⽤tf.range⽽不是range,使⽤tf.constant(True)⽽不是True.
2,避免在@tf.function修饰的函数内部定义tf.Variable.
3,被@tf.function修饰的函数不可修改该函数外部的Python列表或字典等数据结构变量