TensorFlow的基本数据结构
按属性而言,张量可分为常量(constant)和变量(Variable)。常量就是值不会发生变化的量,而变量是开始给定初始值,但是值会发生变化的量。
张量在概念上等同于数组,这一点和numpy中的array数组类似。可以用来表示数学中的标量(scalar)、向量(vector)、矩阵(matrix)以及多维数组。
# 导入tensorflow
import tensorflow as tf
print('tensorflow版本', tf.__version__, sep=':')
输出:
tensorflow版本:2.1.0
对于张量,其主要属性包括维度、值和类型(值的类型),可分别通过张量的 .shape、 .numpy()和 dtype方法获得。对于变量张量,还有名称和是否参与训练的属性,前者可通过 .name获得,后者默认是参与训练,也就是trainable=True。
# 常量
ctensor = tf.constant(1024.)
print(ctensor)
print('维度:', ctensor.shape)
print('值:', ctensor.numpy())
print('类型:', ctensor.dtype)
输出:
tf.Tensor(1024.0, shape=(), dtype=float32)
维度: ()
值: 1024.0
类型:
# 变量
vtensor = tf.Variable(2)
print(vtensor)
print('维度:', vtensor.shape)
print('值:', vtensor.numpy())
print('类型:', vtensor.dtype)
print('名称:', vtensor.name)
print('是否参与训练:', vtensor.trainable)
输出:
维度: ()
值: 2
类型:
名称: Variable:0
是否参与训练: True
当然,这几个属性也可以在定义张量时直接给出,注意给出的维度应该可以由给出的值转化得到,给出的类型也要符合给出的值的类型,否则会出现错误。并且变量张量设定的维度要和给定的值的维度一样,在实际的机器学习操作中,变量的初始值都是根据需要的维度随机给定的,基本不会涉及这样的维度转换操作。
ctensor = tf.constant([[[3, 3]]], dtype=tf.int64, shape=(2, 1))
print(ctensor)
vtensor = tf.Variable([3, 3], dtype=tf.int64, shape=(2,), name='v_0', trainable=False)
print(vtensor)
输出:
tf.Tensor(
[[3]
[3]], shape=(2, 1), dtype=int64)
数、列表、numpy的array形式都可以直接作为张量的值。张量的维度其实就是张量的值的维度。
# 标量,例如机器学习房价预测数据集中一个房子的价格,或者猫狗大战中一个图片的标签。
tensor_0 = tf.constant(1024000)
print('标量')
print('维度:', tensor_0.shape, '\n', '值:', tensor_0.numpy(), sep='')
输出:
标量
维度:()
值:1024000
# 向量,例如机器学习房价预测数据集中很多个房子的价格列表,或者猫狗大战中很多图片的标签集合
# 或者房价预测数据集中表示一个房子很多属性值的集合
tensor_1 = tf.constant(['cats', 'dogs', 'cats'])
print('向量')
print('维度:', tensor_1.shape, '\n', '值:', tensor_1.numpy(), sep='')
输出:
向量
维度:(3,)
值:[b'cats' b'dogs' b'cats']
# 矩阵, 例如房价预测数据集中表示很多房子属性值组成的矩阵,其中每一行表示一个房子
# 或者一个图片单通道的数字矩阵,矩阵的行数和列数就是该图片高度和宽度上的像素数
tensor_2 = tf.constant([[1, 3500, 9], [0, 6700, 12]])
print('矩阵')
print('维度:', tensor_2.shape, '\n','值:', '\n', tensor_2.numpy(), sep='')
输出:
矩阵
维度:(2, 3)
值:
[[ 1 3500 9]
[ 0 6700 12]]
# 立方体,例如单张图片的三通道的数字矩阵
import numpy as np
tensor_3 = tf.constant(np.arange(12).reshape(3, 2, 2))
print('立方体')
print('维度:', tensor_3.shape, '\n','值:', '\n', tensor_3.numpy(), sep='')
输出:
立方体
维度:(3, 2, 2)
值:
[[[ 0 1]
[ 2 3]]
[[ 4 5]
[ 6 7]]
[[ 8 9]
[10 11]]]
# 4维,例如卷积神经网络模型批量训练时,多张图片的三通道数字矩阵组成的数组
import numpy as np
tensor_4 = tf.constant(np.arange(48).reshape(4, 3, 2, 2))
print('多维度')
print('维度:', tensor_4.shape, '\n', '值:', '\n', tensor_4.numpy(), sep='')
# 多维度如何看:
# 该例子中的值可以看成4个立方体
# 每个立方体包括3个矩阵
# 每个矩阵是一个2行2列的矩阵
输出:
多维度
维度:(4, 3, 2, 2)
值:
[[[[ 0 1]
[ 2 3]]
[[ 4 5]
[ 6 7]]
[[ 8 9]
[10 11]]]
[[[12 13]
[14 15]]
[[16 17]
[18 19]]
[[20 21]
[22 23]]]
[[[24 25]
[26 27]]
[[28 29]
[30 31]]
[[32 33]
[34 35]]]
[[[36 37]
[38 39]]
[[40 41]
[42 43]]
[[44 45]
[46 47]]]]
这里的类型指的是张量中值的类型,下面给出所有的数值类型:
# 全0
v0 = tf.Variable(tf.zeros((3, 4)), name='v0')
v0
输出:
# 全1
v1 = tf.Variable(tf.ones((1, 5)), name='v1')
v1
输出:
# 全为同一个数
vs = tf.constant(tf.fill((4, ), -2))
vs
输出:
# 值服从均值为0,方差为1的标准正态分布
vn = tf.Variable(tf.random.normal((4, 2)), name='vn')
vn
输出:
# 值服从均值为mean,方差为stddev的正态分布
vn1 = tf.Variable(tf.random.normal((2, 6), mean=3, stddev=2), name='vn1')
vn1
输出:
# 值服从[0, 1)的均匀分布
va = tf.Variable(tf.random.uniform((2, 5)), name='va')
va
输出:
# 值服从[minval, maxval)的均匀分布
va1 = tf.constant(tf.random.uniform((2, 5), minval=2, maxval=8))
va1
输出:
# 其他形式的值
print(tf.range(12, 19)) # 序列值
输出:
tf.Tensor([12 13 14 15 16 17 18], shape=(7,), dtype=int32)
print(tf.linspace(6., 10, 5)) # 间隔线性序列值,开始的数必须为为浮点形式
# print(tf.linspace(6, 10., 5)) # 报错
输出:
tf.Tensor([ 6. 7. 8. 9. 10.], shape=(5,), dtype=float32)
d = tf.constant(tf.random.uniform((4, 3), minval=2, maxval=8))
print(tf.zeros_like(d)) # 和张量d具体相同维度的,全是0值
print(tf.ones_like(d)) # 和张量d具体相同维度的,全是1值
输出:
tf.Tensor(
[[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]], shape=(4, 3), dtype=float32)
tf.Tensor(
[[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]], shape=(4, 3), dtype=float32)
print(tf.random.truncated_normal([3, 5], mean=0.0, stddev=1.0)) # 截断正态分布,如果这个数值超过了2个标准差,那么将会被重新随机
输出:
tf.Tensor(
[[-0.73157704 0.7439553 -1.2554983 -1.0538561 0.8017968 ]
[ 1.7552785 1.8312547 -0.8315044 -1.1748996 -1.5099645 ]
[-1.482948 -1.0407513 0.19603679 -0.35750923 1.8271962 ]], shape=(3, 5), dtype=float32)
print(tf.random.gamma([3, 2], [0.5, -6, 9])) # Gamma分布
输出:
tf.Tensor(
[[[7.7962823e-02 1.1754944e-38 9.0213766e+00]
[2.1591300e-01 1.1754944e-38 4.2087584e+00]]
[[7.8149560e-06 1.1754944e-38 5.8359337e+00]
[6.5450810e-02 1.1754944e-38 1.6036793e+01]]
[[2.7894831e-01 1.1754944e-38 1.0213203e+01]
[1.0374104e-01 1.1754944e-38 1.3485026e+01]]], shape=(3, 2, 3), dtype=float32)
print(tf.eye(3,3)) #单位矩阵
print(tf.linalg.diag([1,2,3])) #对角阵
输出:
tf.Tensor(
[[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]], shape=(3, 3), dtype=float32)
tf.Tensor(
[[1 0 0]
[0 2 0]
[0 0 3]], shape=(3, 3), dtype=int32)
在机器学习模型中,很多情况下会涉及变换维度,下面介绍几种比较常用的导致维度变化的方法:
reshape 就是将原来的数据转换维度,注意前后维度中数字的乘积要保持相同。例如下面示例中的数据的维度为(24,),维度中数字的乘积为24。因为2*12=24,因此该数据可以转换为维度(2, 12)(见data0);也可以转化为维度(1, 4, 6)(见data1),因为1*4*6=24。并且维度转化前后,按照从上到下,从左到右的顺序排列数字,得到的数字序列是不变得。
# 示例张量
etensor = tf.constant(tf.range(24), dtype=tf.int32)
data = etensor.numpy()
print('维度:', data.shape)
print('示例数据:', data, sep='\n')
输出:
维度: (24,)
示例数据:
[ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23]
# 2*12=24
data0 = data.reshape(2, 12)
data0
输出:
array([[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
[12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]])
# 1*4*6=24
data1 = data.reshape(1, 4, 6)
data1
输出:
array([[[ 0, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11],
[12, 13, 14, 15, 16, 17],
[18, 19, 20, 21, 22, 23]]])
# 1*4*6=3*2*4
data2 = data1.reshape(3, 2, 4)
data2
输出:
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7]],
[[ 8, 9, 10, 11],
[12, 13, 14, 15]],
[[16, 17, 18, 19],
[20, 21, 22, 23]]])
在原数据的axis维度上,增加一个长度为1的维度。注意数据的顺序也没有发生变化。
# 示例张量
etensor1 = tf.constant(tf.range(5), dtype=tf.int32)
data1 = etensor1.numpy()
print('维度:', data1.shape)
print('示例数据:', data1, sep='\n')
输出:
维度: (5,)
示例数据:
[0 1 2 3 4]
data_e0 = tf.expand_dims(data1, axis=0) # 原来的维度为(5,),在0维度上增加一个长度为1的维度,维度变为(1,5)
data_e0
输出:
data_e1 = tf.expand_dims(data1, axis=1) # 原来的维度为(5,),在1维度上增加一个长度为1的维度,维度变为(5, 1)
data_e1
输出:
# data_e2 = tf.expand_dims(data1, axis=2) # 运行错误,因为原来的维度(5,),没有维度2
# data_e2
data_e3 = tf.expand_dims(data_e1, axis=2) # 原来的维度为(5,1),在2维度上增加一个长度为1的维度,维度变为(5, 1, 1)
data_e3
输出:
data_e4 = tf.constant(tf.eye(3,3))
data_e5 = tf.expand_dims(data_e4, axis=1) # 原来的维度为(3, 3),在1维度上增加一个长度为1的维度(3, 1, 3)
print(data_e4, data_e5, sep='\n')
输出:
tf.Tensor(
[[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]], shape=(3, 3), dtype=float32)
tf.Tensor(
[[[1. 0. 0.]]
[[0. 1. 0.]]
[[0. 0. 1.]]], shape=(3, 1, 3), dtype=float32)
注意只能删除长度为1的维度,否则会报错。如果不指定维度参数axis,那么它会默认删除所有长度为1的维度。和增加维度一样,删除维度也不会导致数据序列发生变化。
data_e6 = tf.squeeze(data_e5, axis=1) # data_e5维度为(3, 1, 3),删除1维度上的维度,变为(3, 3)
data_e6
输出:
datarr = tf.constant(tf.linspace(10., 12, 4).numpy().reshape(1, -1))
print(datarr)
dataee = tf.squeeze(datarr, axis=0)# datarr维度为(1, 4),删除0维度上的维度,变为(4,)
dataee
输出:
tf.Tensor([[10. 10.666667 11.333333 12. ]], shape=(1, 4), dtype=float32)
datarr = tf.constant(tf.linspace(10., 12, 4).numpy().reshape(1, 1, -1, 1))
print(datarr)
dataee = tf.squeeze(datarr) # 参数axis没有给定值,datarr维度为(1, 1, 4, 1),删除所有为1的维度,变为(4,)
dataee
输出:
tf.Tensor(
[[[[10. ]
[10.666667]
[11.333333]
[12. ]]]], shape=(1, 1, 4, 1), dtype=float32)
前面的操作都不会影响数据序列的变化,而维度交换会导致张量中值的数据序列发生变化。
# 示例
data_init = tf.constant(tf.random.normal([3, 4]))
print(data_init)
输出:
tf.Tensor(
[[-1.1893342 -1.2580125 0.1104317 -0.20893542]
[ 1.0300606 0.8565788 0.31826663 -1.8314791 ]
[ 0.47821558 1.7594677 0.4862449 0.37529895]], shape=(3, 4), dtype=float32)
data_trans = tf.transpose(data_init, perm=[0, 1]) # 和原始数据一样
print(data_trans)
输出:
tf.Tensor(
[[-1.1893342 -1.2580125 0.1104317 -0.20893542]
[ 1.0300606 0.8565788 0.31826663 -1.8314791 ]
[ 0.47821558 1.7594677 0.4862449 0.37529895]], shape=(3, 4), dtype=float32)
data_trans = tf.transpose(data_init, perm=[1, 0]) # 此时perm的意思就是,维度1和维度0互换。维度发生变化,数据序列的顺序也发生了变化。
print(data_trans)
输出:
tf.Tensor(
[[-1.1893342 1.0300606 0.47821558]
[-1.2580125 0.8565788 1.7594677 ]
[ 0.1104317 0.31826663 0.4862449 ]
[-0.20893542 -1.8314791 0.37529895]], shape=(4, 3), dtype=float32)
data_init = tf.constant(tf.random.normal([3, 4, 2]))
data_trans = tf.transpose(data_init, perm=[2, 0, 1]) # 此时维度变为(2, 3, 4),在实际应用中要注意数据序列是怎么变化的
print(data_init, data_trans, sep='\n')
输出:
tf.Tensor(
[[[ 0.0407722 1.3907919 ]
[-0.67140126 -0.20485616]
[ 0.28857562 0.38259038]
[ 0.46668136 -0.9853797 ]]
[[ 0.5792685 -0.72501785]
[ 1.882685 -0.01522868]
[-1.0789716 0.91662246]
[ 0.32646975 0.77869964]]
[[ 0.690978 0.84885603]
[-1.0967898 -1.5899065 ]
[ 1.7856964 2.6775556 ]
[ 0.3747548 -0.09259129]]], shape=(3, 4, 2), dtype=float32)
tf.Tensor(
[[[ 0.0407722 -0.67140126 0.28857562 0.46668136]
[ 0.5792685 1.882685 -1.0789716 0.32646975]
[ 0.690978 -1.0967898 1.7856964 0.3747548 ]]
[[ 1.3907919 -0.20485616 0.38259038 -0.9853797 ]
[-0.72501785 -0.01522868 0.91662246 0.77869964]
[ 0.84885603 -1.5899065 2.6775556 -0.09259129]]], shape=(2, 3, 4), dtype=float32)
拼接操作不会使得拼接后的张量在维度上发生越级的变化,也就是2个值为矩阵的张量拼接的结果不会出现值为立方体的张量。只是在具体的维度数字上发生了变化。 参数axis表示在哪个维度上拼接,拼接后该维度上的数字为2个张量的值的和,此时2个张量其他维度数字必须是相同的。拼接的两个张量的值的类型应该是一致的。
a = tf.constant([2., 3], dtype=tf.float64)
b = tf.constant([4., 6, 5, 5], dtype=tf.float64)
c = tf.concat([a, b], axis=0)
print('a的维度:%s' % a.shape, 'b的维度:%s' % b.shape, '在维度0上拼接得到',c, sep='\n')
输出:
a的维度:(2,)
b的维度:(4,)
在维度0上拼接得到
tf.Tensor([2. 3. 4. 6. 5. 5.], shape=(6,), dtype=float64)
a = tf.constant([[1., 2, 3, 4]])
b = tf.constant([[5., 6, 7, 8], [9, 10, 11, 12]])
c = tf.concat([a, b], axis=0)
print('a的维度:%s' % a.shape, 'b的维度:%s' % b.shape, '在维度0上拼接得到',c, sep='\n')
输出:
a的维度:(1, 4)
b的维度:(2, 4)
在维度0上拼接得到
tf.Tensor(
[[ 1. 2. 3. 4.]
[ 5. 6. 7. 8.]
[ 9. 10. 11. 12.]], shape=(3, 4), dtype=float32)
a = tf.constant([[1., 2, 3, 4, 5], [6, 7, 8, 9, 10]])
b = tf.constant([[5., 6, 7, 8], [9, 10, 11, 12]])
c = tf.concat([a, b], axis=1)
print('a的维度:%s' % a.shape, 'b的维度:%s' % b.shape, '在维度1上拼接得到',c, sep='\n')
输出:
a的维度:(2, 5)
b的维度:(2, 4)
在维度1上拼接得到
tf.Tensor(
[[ 1. 2. 3. 4. 5. 5. 6. 7. 8.]
[ 6. 7. 8. 9. 10. 9. 10. 11. 12.]], shape=(2, 9), dtype=float32)
a = tf.constant(tf.range(24).numpy().reshape(2, 3, 4))
b = tf.constant(tf.range(16).numpy().reshape(2, 2, 4))
c = tf.concat([a, b], axis=1)
print('a的维度:%s' % a.shape, 'b的维度:%s' % b.shape, '在维度1上拼接得到',c, sep='\n')
输出:
a的维度:(2, 3, 4)
b的维度:(2, 2, 4)
在维度1上拼接得到
tf.Tensor(
[[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]
[ 0 1 2 3]
[ 4 5 6 7]]
[[12 13 14 15]
[16 17 18 19]
[20 21 22 23]
[ 8 9 10 11]
[12 13 14 15]]], shape=(2, 5, 4), dtype=int32)
和拼接不同,堆叠会导致维度发生越级。其中axis默认为0,堆叠必须保证2个张量的维度是一致的,也就是可以在越级后的任意维度上实现堆叠。需要注意观察在某个维度上是如何完成堆叠的。
x = tf.constant([[2., 3, 4], [4, 5, 4]])
y = tf.constant([[4., 6, 4], [5, 6, 0]])
z = tf.stack([x, y], axis=2)
print('x的维度:%s' % x.shape, 'y的维度:%s' % y.shape, '在维度1上堆叠得到',z, sep='\n')
输出:
x的维度:(2, 3)
y的维度:(2, 3)
在维度1上堆叠得到
tf.Tensor(
[[[2. 4.]
[3. 6.]
[4. 4.]]
[[4. 5.]
[5. 6.]
[4. 0.]]], shape=(2, 3, 2), dtype=float32)
x = tf.constant(tf.range(60).numpy().reshape(5, 3, 4))
y = tf.constant(tf.range(100, 160).numpy().reshape(5, 3, 4))
print('x的维度:%s' % x.shape, 'y的维度:%s' % y.shape, '升级后的维度形式为(axis0,, axis1, axis2, axis3)')
for i in range(4):
z = tf.stack([x, y], axis=i)
print('在维度%d上堆叠得到:%s' % (i, z.shape), sep='\n')
输出:
x的维度:(5, 3, 4) y的维度:(5, 3, 4) 升级后的维度形式为(axis0,, axis1, axis2, axis3)
在维度0上堆叠得到:(2, 5, 3, 4)
在维度1上堆叠得到:(5, 2, 3, 4)
在维度2上堆叠得到:(5, 3, 2, 4)
在维度3上堆叠得到:(5, 3, 4, 2)
tf.split(x, axis, num_or_size_splits)可以完成张量的分割操作,返回的是列表形式。其中
该操作可用于机器学习中数据集的划分,划分为训练数据集、测试数据集、验证数据集。
x = tf.random.normal([6, 3, 2])
y = tf.split(x,axis=0,num_or_size_splits=6)
print(type(y))
print(y)
输出:
[, , , , , ]
y = tf.split(x,axis=1,num_or_size_splits=[2, 1]) # 维度1上数字为3,所以可以划分为2和1的2份。
for k in y:
print(k)
输出:
tf.Tensor(
[[[-1.6093986 -1.1092653 ]
[ 1.8113784 -0.88262904]]
[[-1.5421635 -1.2405977 ]
[ 0.23959504 -0.6319787 ]]
[[ 2.6677513 -1.015126 ]
[-1.4229087 0.947736 ]]
[[ 2.2470863 -0.2636285 ]
[-1.0136322 -0.30603406]]
[[ 1.1257079 -0.03071762]
[ 0.4726098 -2.1097198 ]]
[[-1.8244735 1.3874811 ]
[-0.745719 0.14895873]]], shape=(6, 2, 2), dtype=float32)
tf.Tensor(
[[[ 1.7928717 -0.5655064 ]]
[[ 0.5162947 0.12239873]]
[[ 2.3775442 0.32105592]]
[[ 0.42018703 0.839727 ]]
[[-0.69796985 0.1200094 ]]
[[ 1.3949952 2.3773785 ]]], shape=(6, 1, 2), dtype=float32)
直接在axis上全部按长度为1的方式分割。
x = tf.random.normal([6, 3])
y = tf.unstack(x,axis=1)
for k in y:
print(k)
输出:
tf.Tensor([-1.068996 0.78755 -1.6862708 -1.1415191 0.79779094 0.54102904], shape=(6,), dtype=float32)
tf.Tensor([-0.4848505 1.4189752 0.96606773 -2.255599 0.53675944 0.35354418], shape=(6,), dtype=float32)
tf.Tensor([-0.37911695 -0.08008205 0.24171692 -1.3721107 -1.3534086 -0.71941084], shape=(6,), dtype=float32)