4-1张量的结构操作——eat_tensorflow2_in_30_days
4-1张量的结构操作
张量的操作主要包括张量的结构操作和张量的数学运算
张量的结构操作诸如:张量创建,索引切片,维度变换,合并分割
张量数学运算主要有:标量运算,向量运算,矩阵运算。另外,这里会介绍张量运算的广播机制
创建张量
- 张量创建的许多方法和numpy中创建array的方法很像
import tensorflow as tf
import numpy as np
a = tf.constant([1,2,3], dtype = tf.float32)
tf.print(a)
"""
[1 2 3]
"""
# tf.range(start,limit=None,delta=1,name='range') 返回一个tensor等差数列,
# 该tensor中的数值在start到limit之间,不包括limit,delta是等差数列的差值。
b = tf.range(1, 10, delta=2)
tf.print(b)
print(b)
"""
[1 3 5 7 9]
tf.Tensor([1 3 5 7 9], shape=(5,), dtype=int32)
"""
# tf.linspace(start,stop,num,name=None) 返回一个tensor,
# 该tensor中的数值在start到stop区间之间取等差数列(包含start和stop)
c = tf.linspace(0.0, 2*3.14, 100)
tf.print(c, len(c))
"""
[0 0.0634343475 0.126868695 ... 6.15313148 6.21656609 6.28] 100
"""
d = tf.zeros([3, 3])
tf.print(d)
"""
[[0 0 0]
[0 0 0]
[0 0 0]]
"""
a = tf.ones([3, 3]) # 创建全1矩阵
b = tf.ones_like(a) # 新建一个与给定的tensor类型大小一致的tensor,其所有元素为1
c = tf.zeros_like(a, dtype=tf.float32) # 新建一个与给定tensor大小一致,类型自定义的tensor,其所有元素为0
tf.print(a)
tf.print(b)
tf.print(c)
"""
[[1 1 1]
[1 1 1]
[1 1 1]]
[[1 1 1]
[1 1 1]
[1 1 1]]
[[0 0 0]
[0 0 0]
[0 0 0]]
"""
# tf.fill(dim, value, name=None)创建一个形状大小为dim的tensor,其初始值为value
b = tf.fill([3, 2], 5)
tf.print(b)
"""
[[5 5]
[5 5]
[5 5]]
"""
# 均匀分布,元素服从minval和maxval之间的均匀分布
tf.random.set_seed(42)
a = tf.random.uniform([5], minval=0, maxval=10)
tf.print(a)
"""
[6.6456213 4.41006756 3.52882504 4.64482546 0.336604118]
"""
# 正态分布
b = tf.random.normal([3, 3], mean=0.0, stddev=1.0)
tf.print(b)
"""
[[0.0842245817 -0.86090374 0.378123045]
[-0.00519627379 -0.494531959 0.61781919]
[-0.330820471 -0.00138408062 -0.423734099]]
"""
# 正态分布,剔除2倍方差以外2数据重新生成
c = tf.random.truncated_normal((5, 5), mean=0.0, stddev=1.0, dtype=tf.float32)
tf.print(c)
"""
[[-0.559097409 -0.534721375 -1.57259309 0.805505633 -0.00413081655]
[0.172509521 0.292330414 0.447463274 -0.205126196 1.06962538]
[-1.62758112 0.45803377 -0.150247112 -1.12316787 1.42139065]
[-0.589732349 0.156277 0.379462808 -1.34369051 0.0508602373]
[0.481147289 -1.28629398 0.173078209 -0.173267394 1.01196456]]
"""
# 特殊矩阵
I = tf.eye(3, 3) # 单位矩阵
tf.print(I)
tf.print("")
t = tf.linalg.diag([1, 2, 3]) # 对角阵
tf.print(t)
"""
[[1 0 0]
[0 1 0]
[0 0 1]]
[[1 0 0]
[0 2 0]
[0 0 3]]
"""
索引切片
- 张量的索引切片方式和numpy几乎是一样的。切片时支持缺省参数和省略号
- 对于tf.Variable,可以通过索引和切片对部分元素进行修改
- 对于提取张量的连续子区域,也可以使用tf.slice.
- 此外,对于不规则的切片提取,可以使用tf.gather, tf.gather_nd, tf.boolean_mask
- tf.boolean_mask功能最为强大,它可以实现tf.gather, tf.gather_nd的功能,并且tf.boolean_mask还可以实现布尔索引
- 如果要通过修改张量的某些元素得到新的张量,可以使用tf.where, tf.scatter_nd
tf.random.set_seed(42)
t = tf.random.uniform([5, 5], minval=0, maxval=10, dtype=tf.int32)
tf.print(t)
"""
[[7 9 1 6 2]
[4 3 3 1 1]
[2 0 1 1 0]
[8 9 2 9 9]
[1 2 7 4 9]]
"""
tf.print("第0行 t[0]:", t[0])
tf.print("倒数第一行 t[-1]:", t[-1])
# 第一行第三列
tf.print("第一行第三列 t[1, 3]:", t[1, 3])
tf.print("第一行第三列 t[1][3]:", t[1][3])
"""
第0行 t[0]: [7 9 1 6 2]
倒数第一行 t[-1]: [1 2 7 4 9]
第一行第三列 t[1, 3]: 1
第一行第三列 t[1][3]: 1
"""
# 第一行至第三行
tf.print("第一行至第三行 \n")
tf.print("t[1:4, :]", "\n", t[1:4, :])
# tf.slice(input, begin_vector, size_vector)
tf.print("\n tf.slice切片法")
tf.print(tf.slice(t, [1, 0], [3, 5])) # 第一行第0列开始切,切3行5列
"""
第一行至第三行
t[1:4, :]
[[4 3 3 1 1]
[2 0 1 1 0]
[8 9 2 9 9]]
tf.slice切片法
[[4 3 3 1 1]
[2 0 1 1 0]
[8 9 2 9 9]]
"""
# 第一行至最后一行,第0列到最后一列每隔两列取一列
tf.print(t[1:, ::2])
"""
[[4 3 1]
[2 1 0]
[8 2 9]
[1 7 9]]
"""
# 第一行至倒数第二行,第0列到倒数第二列每隔两列取一列
tf.print(t[1:-1, :-1:2])
"""
[[4 3]
[2 1]
[8 2]]
"""
# 对变量来说,还可以使用索引和切片修改部分元素
x = tf.Variable([[1, 2], [3, 4]], dtype=tf.float32)
x[1, :].assign(tf.constant([0.0, 0.0]))
tf.print(x)
"""
[[1 2]
[0 0]]
"""
a = tf.random.uniform([3, 3, 3], minval=0, maxval=10, dtype=tf.int32)
tf.print(a)
"""
[[[8 3 9]
[4 2 3]
[4 2 6]]
[[4 1 3]
[6 0 9]
[9 0 1]]
[[4 7 0]
[8 1 6]
[2 4 9]]]
"""
# 省略号可以表示多个冒号
tf.print(a[..., 1]) # 第一个维度和第二个维度全部取,第三个维度取第二列(维度1-三个矩阵,维度2-行,维度3-列)
"""
[[3 2 2]
[1 0 0]
[7 1 4]]
"""
以上切片方式相对规则,对于不规则的切片提取,可以使用tf.gather, tf.gather_nd, tf.boolean_mask。
- tf.gather: 类似于数组的索引,可以把向量中某些索引值提取出来,得到新的向量,适用于要提取的索引为不连续的情况。根据indices从params的指定轴axis索引元素(类似于仅能在指定轴进行一维索引).
- tf.gather_nd:将params索引为indices指定形状的切片数组中(indices代表索引后的数组形状) indices将切片定义为params的前N个维度,其中N = indices.shape [-1]
- tf.greater 判断函数。首先张量x和张量y的尺寸要相同,输出的tf.greater(x, y)也是一个和x,y尺寸相同的张量。如果x的某个元素比y中对应位置的元素大,则tf.greater(x, y)对应位置返回True,否则返回False。与此类似的函数还有tf.greater_equal。
考虑班级成绩册的例子,有4个班级,每个班级10个学生,每个学生7门科目成绩。可以用一个4107的张量来表示。
scores = tf.random.uniform((4, 10, 7), minval=0, maxval=100, dtype=tf.int32)
tf.print(scores)
"""
[[[4 92 77 ... 23 7 84]
[19 12 27 ... 74 62 94]
[27 9 87 ... 33 14 17]
...
[67 92 3 ... 36 62 6]
[69 86 88 ... 78 60 89]
[80 38 72 ... 16 84 99]]
[[65 54 78 ... 0 68 9]
[2 51 28 ... 63 78 87]
[19 75 20 ... 39 72 21]
...
[23 37 23 ... 92 31 25]
[98 25 92 ... 86 27 57]
[95 44 33 ... 56 5 62]]
[[2 38 23 ... 74 90 94]
[7 80 46 ... 53 14 96]
[97 49 2 ... 36 32 33]
...
[88 73 99 ... 51 36 71]
[45 47 91 ... 64 16 31]
[97 50 40 ... 62 91 90]]
[[4 24 12 ... 45 65 60]
[53 89 56 ... 14 92 11]
[10 11 64 ... 58 72 38]
...
[89 12 25 ... 58 21 23]
[93 99 32 ... 97 62 61]
[92 43 98 ... 43 50 75]]]
"""
# 抽取每个班级第0个学生,第5个学生,第9个学生的全部成绩
p = tf.gather(scores, [0, 5, 9], axis=1)
tf.print(p)
"""
[[[4 92 77 ... 23 7 84]
[30 78 10 ... 61 18 38]
[80 38 72 ... 16 84 99]]
[[65 54 78 ... 0 68 9]
[82 23 21 ... 90 6 54]
[95 44 33 ... 56 5 62]]
[[2 38 23 ... 74 90 94]
[2 95 58 ... 28 86 55]
[97 50 40 ... 62 91 90]]
[[4 24 12 ... 45 65 60]
[82 74 67 ... 84 72 90]
[92 43 98 ... 43 50 75]]]
"""
# 抽取每个班级第0个学生,第5个学生,第9个学生的第一门课程,第三门课程,第六门课程成绩
q = tf.gather(tf.gather(scores, (0, 5, 9), axis=1), [1, 3, 6], axis=2)
tf.print(q)
"""
[[[92 24 84]
[78 67 38]
[38 99 99]]
[[54 61 9]
[23 3 54]
[44 64 62]]
[[38 45 94]
[95 1 55]
[50 10 90]]
[[24 60 60]
[74 13 90]
[43 70 75]]]
"""
# 抽取第0个班级第0个学生,第2个班级的第四个学生,第三个班级的第6个学生的全部成绩
# indices的长度为采样样本的个数,每个元素为采样位置的坐标
s = tf.gather_nd(scores, indices=[(0, 0), (2, 4), (3, 6)])
tf.print(s)
"""
[[4 92 77 ... 23 7 84]
[80 66 37 ... 27 35 99]
[35 69 41 ... 91 3 97]]
"""
# 抽取每个班级第0个学生,第5个学生,第9个学生的全部成绩
p = tf.boolean_mask(scores, [True, False, False, False, False, True, False, False, False, True], axis=1)
tf.print(p)
"""
[[[4 92 77 ... 23 7 84]
[30 78 10 ... 61 18 38]
[80 38 72 ... 16 84 99]]
[[65 54 78 ... 0 68 9]
[82 23 21 ... 90 6 54]
[95 44 33 ... 56 5 62]]
[[2 38 23 ... 74 90 94]
[2 95 58 ... 28 86 55]
[97 50 40 ... 62 91 90]]
[[4 24 12 ... 45 65 60]
[82 74 67 ... 84 72 90]
[92 43 98 ... 43 50 75]]]
"""
# 抽取第0个班级第0个学生,第二个班级的第四个学生,第三个班级的第6个学生的全部成绩
s = tf.boolean_mask(
scores,
[[True,False,False,False,False,False,False,False,False,False],
[False,False,False,False,False,False,False,False,False,False],
[False,False,False,False,True,False,False,False,False,False],
[False,False,False,False,False,False,True,False,False,False]])
tf.print(s)
"""
[[4 92 77 ... 23 7 84]
[80 66 37 ... 27 35 99]
[35 69 41 ... 91 3 97]]
"""
# 利用tf.boolean_mask可以实现布尔索引
# 找到矩阵中小于0的元素
c = tf.constant([[-1, 1, -1], [2, 2, -2], [3, -3, 3]], dtype=tf.float32)
tf.print(c, "\n")
tf.print(tf.boolean_mask(c, c<0), "\n")
tf.print(c[c<0]) # 布尔索引,为boolean_mask的语法糖形式
"""
[[-1 1 -1]
[2 2 -2]
[3 -3 3]]
[-1 -1 -2 -3]
[-1 -1 -2 -3]
"""
以上这些方法仅能提取张量的部分元素值,但不能更改张量的部分元素值得到新的张量。
如果要通过修改张量的部分元素值得到新的张量,可以使用tf.where和tf.scatter_nd。
- tf.where可以理解为if的张量版本,此外它还可以用于找到满足条件的所有元素的位置坐标。
- tf.scatter_nd的作用和tf.gather_nd有些相反,tf.gather_nd用于收集张量的给定位置的元素,tf.scatter_nd可以将某些值插入到一个给定shape的全0的张量的指定位置处。
# 如果where只有一个参数,将返回所有满足条件的位置坐标
indices = tf.where(c<0)
tf.print(indices)
"""
[[0 0]
[0 2]
[1 2]
[2 1]]
"""
# 将张量的第[0, 0], [2, 1]两个位置元素替换为0得到新的张量
d = c - tf.scatter_nd([[0, 0], [2, 1]], [c[0, 0], c[2, 1]], c.shape)
tf.print(d)
"""
[[0 1 -1]
[2 2 -2]
[3 0 3]]
"""
# scatter_nd的作用和gather_nd有些相反
# 可以将某些值插到一个给定shape的全0的张量的指定位置处
indices = tf.where(c<0)
tf.scatter_nd(indices, tf.gather_nd(c, indices), c.shape)
"""
<tf.Tensor: shape=(3, 3), dtype=float32, numpy=
array([[-1., 0., -1.],
[ 0., 0., -2.],
[ 0., -3., 0.]], dtype=float32)>
"""
维度变换
- 维度变换相关函数主要有 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)
"""
TensorShape([1, 3, 3, 2])
[[[[29 120]
[14 54]
[132 110]]
[[27 202]
[12 3]
[90 161]]
[[57 129]
[191 227]
[71 128]]]]
"""
# 改成(3, 6)的形状
b = tf.reshape(a, [3, 6])
tf.print(b.shape)
tf.print(b)
"""
TensorShape([3, 6])
[[29 120 14 54 132 110]
[27 202 12 3 90 161]
[57 129 191 227 71 128]]
"""
# 改回成[1, 3, 3, 2]形状的张量
c = tf.reshape(b, [1, 3, 3, 2])
tf.print(c)
"""
[[[[29 120]
[14 54]
[132 110]]
[[27 202]
[12 3]
[90 161]]
[[57 129]
[191 227]
[71 128]]]]
"""
如果张量在某个维度上只有一个元素,利用tf.squeeze可以消除这个维度。和tf.reshape相似,它本质上不会改变张量元素的存储顺序。
张量的各个元素在内存中是线性存储的,其一般规律是,同一层级中的相邻元素的物理地址也相邻。
s = tf.squeeze(a)
tf.print(s.shape)
tf.print(s)
"""
TensorShape([3, 3, 2])
[[[29 120]
[14 54]
[132 110]]
[[27 202]
[12 3]
[90 161]]
[[57 129]
[191 227]
[71 128]]]
"""
d = tf.expand_dims(s, axis=0) # 在第0维插入长度为1的一个维度
tf.print(d)
tf.print(d.shape)
"""
[[[[29 120]
[14 54]
[132 110]]
[[27 202]
[12 3]
[90 161]]
[[57 129]
[191 227]
[71 128]]]]
TensorShape([1, 3, 3, 2])
"""
d = tf.expand_dims(s, axis=2) # 在第2维度插入长度为1的一个维度
tf.print(d)
tf.print(d.shape)
"""
[[[[29 120]]
[[14 54]]
[[132 110]]]
[[[27 202]]
[[12 3]]
[[90 161]]]
[[[57 129]]
[[191 227]]
[[71 128]]]]
TensorShape([3, 3, 1, 2])
"""
tf.transpose可以交换张量的维度,与tf.reshape不同,它会改变张量元素的存储顺序。 tf.transpose常用于图片存储格式的变换上。
# Batch, Height, Width, Chanel
a = tf.random.uniform(shape=[100,600,600,4], minval=0, maxval=255, dtype=tf.int32)
tf.print(a.shape)
# 转换成Channel, Height, Width, Batch
s = tf.transpose(a, perm=[3, 1, 2, 0])
tf.print(s.shape)
"""
TensorShape([100, 600, 600, 4])
TensorShape([4, 600, 600, 100])
"""
合并分割
- 和numpy类似,可以用:
- tf.concat和tf.stack方法对多个张量进行合并;
- tf.split方法把一个张量分割成多个张量。
- tf.concat和tf.stack有略微的区别:
- tf.concat是连接,不会增加维度;
- tf.stack是堆叠,会增加维度。
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]])
tf.print(tf.concat([a, b, c], axis=0))
"""
[[1 2]
[3 4]
[5 6]
[7 8]
[9 10]
[11 12]]
"""
tf.concat([a, b, c], axis=1)
"""
tf.concat([a, b, c], axis=1)
<tf.Tensor: shape=(2, 6), dtype=float32, numpy=
array([[ 1., 2., 5., 6., 9., 10.],
[ 3., 4., 7., 8., 11., 12.]], dtype=float32)>
"""
tf.print((tf.concat([a,b,c], axis=0)).shape)
tf.print((tf.concat([a,b,c], axis=1)).shape)
"""
TensorShape([6, 2])
TensorShape([2, 6])
"""
t = tf.stack([a, b, c])
tf.print(t)
tf.print(t.shape)
"""
[[[1 2]
[3 4]]
[[5 6]
[7 8]]
[[9 10]
[11 12]]]
TensorShape([3, 2, 2])
"""
t = tf.stack([a, b, c], axis=1)
tf.print(t)
tf.print(t.shape)
"""
[[[1 2]
[5 6]
[9 10]]
[[3 4]
[7 8]
[11 12]]]
TensorShape([2, 3, 2])
"""
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.split是tf.concat的逆运算,可以指定分割份数平均分割,也可以通过指定每份的记录数量进行分割。
# tf.split(value, num_or_size_splits, axis)
tf.split(c, 3, axis=0) # 指定分割分数,平均分割
"""
[<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[1., 2.],
[3., 4.]], dtype=float32)>,
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[5., 6.],
[7., 8.]], dtype=float32)>,
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[ 9., 10.],
[11., 12.]], dtype=float32)>]
"""
tf.split(c, [2, 1, 3], axis=0) # 指定每份的记录数量
"""
[<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[1., 2.],
[3., 4.]], dtype=float32)>,
<tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[5., 6.]], dtype=float32)>,
<tf.Tensor: shape=(3, 2), dtype=float32, numpy=
array([[ 7., 8.],
[ 9., 10.],
[11., 12.]], dtype=float32)>]
"""