1 引言
TensorFlow2.0版本已经发布,虽然不是正式版,但预览版都发布了,正式版还会远吗?相比于1.X,2.0版的TensorFlow修改的不是一点半点,这些修改极大的弥补了1.X版本的反人类设计,提升了框架的整体易用性,绝对好评!
不多说了,赶紧来学习一波吧,做最先吃螃蟹的那一批人!先从TensorFlow的基本数据结构——张量(tensor)开始。
2 创建
2.1 constant()方法
>>> import tensorflow as tf >>> tf.constant(1) # 创建一个整型张量 <tf.Tensor: id=414, shape=(), dtype=int32, numpy=1> >>> tf.constant(1.) # 创建一个浮点型张量 <tf.Tensor: id=416, shape=(), dtype=float32, numpy=1.0> >>> tf.constant(2., dtype=tf.double) # 创建的同时指定数据类型 <tf.Tensor: id=418, shape=(), dtype=float64, numpy=2.0> >>> tf.constant([[1.,2.,3.],[4.,5.,6.]]) # 通过传入一个list参数创建 <tf.Tensor: id=420, shape=(2, 3), dtype=float32, numpy= array([[1., 2., 3.], [4., 5., 6.]], dtype=float32)>
如果输入的数据与指定的数据类型不相符,会报错:
>>> tf.constant(2.1, dtype=tf.int32)
Traceback (most recent call last):
……
TypeError: Cannot convert provided value to EagerTensor. Provided value: 2.1 Requested dtype: int32
2.2 convert_to_tensor()方法
>>> tf.convert_to_tensor(np.ones([2, 3])) >>> import numpy as np >>> tf.convert_to_tensor(np.ones([2, 3])) <tf.Tensor: id=0, shape=(2, 3), dtype=float64, numpy= array([[1., 1., 1.], [1., 1., 1.]])> convert_to_tensor()方法也接收普通list类型作为参数来转换为tensor类型: >>> tf.convert_to_tensor([[2.,3.],[3., 4.]]) <tf.Tensor: id=2, shape=(2, 2), dtype=float32, numpy= array([[2., 3.], [3., 4.]], dtype=float32)>
2.3 创建元素为指定值的tensor
如果你熟悉numpy创建数组的方法,你一定见过zeros()、ones()等方法,TensorFlow中也有这些方法。
zeros()、ones()、zeros_like()、ones_like()、fill():
>>> tf.zeros([2, 3, 3]) # 创建一个元素全为0,形状为[2, 3, 3]的tensor <tf.Tensor: id=10, shape=(2, 3, 3), dtype=float32, numpy= array([[[0., 0., 0.], [0., 0., 0.], [0., 0., 0.]], [[0., 0., 0.], [0., 0., 0.], [0., 0., 0.]]], dtype=float32)> >>> tf.ones([2, 3]) # 创建一个元素全为1,形状为[2, 3]的tensor <tf.Tensor: id=14, shape=(2, 3), dtype=float32, numpy= array([[1., 1., 1.], [1., 1., 1.]], dtype=float32)> >>> tf.zeros_like(a) # 仿照a的shape创建一个全为0的tensor <tf.Tensor: id=19, shape=(2, 3), dtype=float32, numpy= array([[0., 0., 0.], [0., 0., 0.]], dtype=float32)> >>> b = tf.zeros([2, 3, 3]) >>> tf.ones_like(b) # 仿照b的shape创建一个全为1的tensor <tf.Tensor: id=30, shape=(2, 3, 3), dtype=float32, numpy= array([[[1., 1., 1.], [1., 1., 1.], [1., 1., 1.]], [[1., 1., 1.], [1., 1., 1.], [1., 1., 1.]]], dtype=float32)> >>> tf.fill([2,3],5) # 创建爱元素全为5,形状为[2,3]的tensor <tf.Tensor: id=34, shape=(2, 3), dtype=int32, numpy= array([[5, 5, 5], [5, 5, 5]])>
2.4 随机初始化
在实际应用中,经常需要随机初始化元素服从某种分布的tensor,TensorFlow中也提供了这种功能。
(1)从指定正态分布中随机取值:tf.random.normal()。例如,随机初始化一个元素服从均值为1,方差为1的正态分布且形状为[2, 3]的tensor:
>>> tf.random.normal([2, 3], mean=1, stddev=1)
<tf.Tensor: id=41, shape=(2, 3), dtype=float32, numpy=
array([[-1.7992694 , 1.7001245 , 1.4642581 ],
[ 0.43032944, 0.76772875, 1.4315588 ]], dtype=float32)>
tf.random.normal()在默认情况下,均值为0.0,方差为1.0,第一个参数shape为必传参数。
(2)从指定的截断正态分布中随机取值:truncated_normal()。意思是从指定的正太分布中取值,但是取值范围在两个标准差范围内,也就是:[ mean – 2 * stddev, mean + 2 * stddev ]
>>> tf.random.truncated_normal([2, 3], mean=1, stddev=1) <tf.Tensor: id=48, shape=(2, 3), dtype=float32, numpy= array([[-0.40271735, 1.406056 , 1.3271518 ], [ 0.7627655 , 0.70162296, 0.5711335 ]], dtype=float32)>
(3)从指定均匀分布中随机取值:tf.random.uniform()。
>>> tf.random.uniform([2, 3], minval=1, maxval=2) # 在1~2之间均匀分布 <tf.Tensor: id=56, shape=(2, 3), dtype=float32, numpy= array([[1.3824016, 1.6685069, 1.1284984], [1.4562235, 1.9348056, 1.7280834]], dtype=float32)>
3 索引
下面所有的索引都以一下张量a为例进行演示:
>>> a = tf.convert_to_tensor(np.arange(80).reshape(2,2,4,5)) >>> a <tf.Tensor: id=113, shape=(2, 2, 4, 5), dtype=int32, numpy= 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, 24], [25, 26, 27, 28, 29], [30, 31, 32, 33, 34], [35, 36, 37, 38, 39]]], [[[40, 41, 42, 43, 44], [45, 46, 47, 48, 49], [50, 51, 52, 53, 54], [55, 56, 57, 58, 59]], [[60, 61, 62, 63, 64], [65, 66, 67, 68, 69], [70, 71, 72, 73, 74], [75, 76, 77, 78, 79]]]])>
3.1 基础索引
TensorFlow支持Python原生的基础索引方式,即多个方括号逐步索引取值:[idx][idx][idx],每个方括号对应一个维度。
>>> a[0] # 取第一个维度 <tf.Tensor: id=118, shape=(2, 4, 5), dtype=int32, numpy= 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, 24], [25, 26, 27, 28, 29], [30, 31, 32, 33, 34], [35, 36, 37, 38, 39]]])> >>> a[0][1] # 同时筛选两个维度 <tf.Tensor: id=127, shape=(4, 5), dtype=int32, numpy= array([[20, 21, 22, 23, 24], [25, 26, 27, 28, 29], [30, 31, 32, 33, 34], [35, 36, 37, 38, 39]])> >>> a[0][1][3][3] # 同时对4个维度进行筛选 <tf.Tensor: id=144, shape=(), dtype=int32, numpy=38>
这种索引数据的方法简单,易于理解,但是可读性差,只能按维度依次索引数据,也不能索引列。
3.2 numpy索引
TensorFlow也继承了numpy中的部分索引方式,如果对numpy索引方式不熟悉,可以查看我的前几篇博客(https://www.cnblogs.com/chenhuabin/p/11412818.html):
(1)[idx1, idx2, idx3]
这种索引方式是在一个方括号内写下所有的索引,每个索引序号之间用逗号隔开。
>>> a[1] # 筛选第一维度,这跟基础索引一样 <tf.Tensor: id=165, shape=(2, 4, 5), dtype=int32, numpy= array([[[40, 41, 42, 43, 44], [45, 46, 47, 48, 49], [50, 51, 52, 53, 54], [55, 56, 57, 58, 59]], [[60, 61, 62, 63, 64], [65, 66, 67, 68, 69], [70, 71, 72, 73, 74], [75, 76, 77, 78, 79]]])> >>> a[1,1, 3] # 同时帅选3个维度 <tf.Tensor: id=175, shape=(5,), dtype=int32, numpy=array([75, 76, 77, 78,
(2)冒号切片与步长:[start:end:step]
这种索引方式在Python原生的list类型中也是常见的,而且使用方法也是一样的。
>>> a[1,:,0:2] # 对第1维度选第二块数据,对第二维度选所有数据,对第三维度选前两行 <tf.Tensor: id=180, shape=(2, 2, 5), dtype=int32, numpy= array([[[40, 41, 42, 43, 44], [45, 46, 47, 48, 49]], [[60, 61, 62, 63, 64], [65, 66, 67, 68, 69]]])> >>> a[1,:,0:2,0:4] # 继续上面的例子,对第4维度筛选去前4列 <tf.Tensor: id=190, shape=(2, 2, 4), dtype=int32, numpy= array([[[40, 41, 42, 43], [45, 46, 47, 48]], [[60, 61, 62, 63], [65, 66, 67, 68]]])> >>> a[1,:,0:2,0:4:2] # 对第4维度加上步长,每隔一个数据取一次 <tf.Tensor: id=200, shape=(2, 2, 2), dtype=int32, numpy= array([[[40, 42], [45, 47]], [[60, 62], [65, 67]]])>
也可以使用负值步长表示逆序索引,但要注意,负数步长时,原本的[start : end : step]也要跟着编程[end : start : step]:
>>> a[1,:,0:2,4:0:-1] <tf.Tensor: id=245, shape=(2, 2, 4), dtype=int32, numpy= array([[[44, 43, 42, 41], [49, 48, 47, 46]], [[64, 63, 62, 61], [69, 68, 67, 66]]])> >>> a[1,:,0:2,4:0:-2] <tf.Tensor: id=250, shape=(2, 2, 2), dtype=int32, numpy= array([[[44, 42], [49, 47]], [[64, 62], [69, 67]]])>
在numpy和TensorFlow中还有“…”(三个英文句号)的使用,“…”用于表示连续多个维度全选:
>>> a[1,...,0:4] # 等同于a[1, : , : ,0:4] <tf.Tensor: id=303, shape=(2, 4, 4), dtype=int32, numpy= array([[[40, 41, 42, 43], [45, 46, 47, 48], [50, 51, 52, 53], [55, 56, 57, 58]], [[60, 61, 62, 63], [65, 66, 67, 68], [70, 71, 72, 73], [75, 76, 77, 78]]])> >>> a[0,0,...] # 等同于a[0,0,:,:] <tf.Tensor: id=351, shape=(4, 5), dtype=int32, numpy= array([[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9], [10, 11, 12, 13, 14], [15, 16, 17, 18, 19]])>
3.3 gather与gather_nd
gather与gather_nd是指TensorFlow通过gather()方法和gather_nd()方法提供的两种索引方式。在numpy中,可以通过嵌套list的方式来指定无规则的索引:
>>> b = np.arange(20).reshape(4,5) >>> b array([[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9], [10, 11, 12, 13, 14], [15, 16, 17, 18, 19]]) >>> b[1, [0,3,4]] # 选取第2行的第1列、第4列、第5列 array([5, 8, 9]) >>> b[[1,2], [0,3,4]]
但是在TensorFlow中,这种索引方式并没有从numpy中继承下来:
>>> t = tf.convert_to_tensor(b) >>> t <tf.Tensor: id=253, shape=(4, 5), dtype=int32, numpy= array([[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9], [10, 11, 12, 13, 14], [15, 16, 17, 18, 19]])> >>> t[1, [0,3,4]] Traceback (most recent call last): …… TypeError: Only integers, slices (`:`), ellipsis (`...`), tf.newaxis (`None`) and scalar tf.int32/tf.int64 tensors are valid indices, got [0, 3, 4]
还好的是,在TensorFlow中通过gather()方法和gather_nd()方法提供了这种索引方法。
(1)gather()方法
>>> tf.gather(b, axis=0, indices=[0, 2, 3]) # 选取第1行,第3行,第4行 <tf.Tensor: id=269, shape=(3, 5), dtype=int32, numpy= array([[ 0, 1, 2, 3, 4], [10, 11, 12, 13, 14], [15, 16, 17, 18, 19]])> >>> tf.gather(b, axis=1, indices=[0, 2, 3]) # 选取第1列,第3列,第4列 <tf.Tensor: id=274, shape=(4, 3), dtype=int32, numpy= array([[ 0, 2, 3], [ 5, 7, 8], [10, 12, 13], [15, 17, 18]])>
仔细观察上面gather()方法例子,可以发现,第一个参数时数据源,还有两个参数中,axis指的是将要的维度,indices指的是需要选取的序号。
(2)gather_nd()
gather()方法一次只能对一个维度进行索引,gather_nd()方法可以同时对多个维度进行索引。
>>> tf.gather_nd(b, [[0, 2],[3, 3]]) # 选取第1行第3列的那个数据,和第4行第4列的数据 <tf.Tensor: id=284, shape=(2,), dtype=int32, numpy=array([ 2, 18])>
4 维度变换
4.1 reshape()
numpy中的ndarray数组有个一reshape()方法,用来改变数组的shape,TensorFlow中的reshape()方法,功能也是一样的,不过TensorFlow中的reshape()没有绑定到tensor中:
>>> a = tf.ones([2,3,4]) >>> a.shape TensorShape([2, 3, 4]) >>> a <tf.Tensor: id=359, shape=(2, 3, 4), dtype=float32, numpy= array([[[1., 1., 1., 1.], [1., 1., 1., 1.], [1., 1., 1., 1.]], [[1., 1., 1., 1.], [1., 1., 1., 1.], [1., 1., 1., 1.]]], dtype=float32)> >>> b = tf.reshape(a, [2, 2, 6]) >>> b.shape TensorShape([2, 2, 6]) >>> b <tf.Tensor: id=362, shape=(2, 2, 6), dtype=float32, numpy= array([[[1., 1., 1., 1., 1., 1.], [1., 1., 1., 1., 1., 1.]], [[1., 1., 1., 1., 1., 1.], [1., 1., 1., 1., 1., 1.]]], dtype=float32)> >>> c = tf.reshape(a, [3, 2, 4]) >>> c <tf.Tensor: id=365, shape=(3, 2, 4), dtype=float32, numpy= array([[[1., 1., 1., 1.], [1., 1., 1., 1.]], [[1., 1., 1., 1.], [1., 1., 1., 1.]], [[1., 1., 1., 1.], [1., 1., 1., 1.]]], dtype=float32)>
可以看到,在上面的例子中,通过reshape()方法可以很方便的改变tensor的形状,得到一个新的tensor,需要注意的是在进行维度变换时,数据的重量是不变的,上面的例子无论是[2,3,4], [2, 2, 6]还是[3, 2, 4]都对应总量24,如果对应不上,就会产生异常。
4.2 转置:transpose()
transpose()方法提供了一种类似于装置的操作:
>>> a = tf.constant([[1,2,3],[4,5,6]]) >>> a.shape TensorShape([2, 3]) >>> a = tf.constant([[1,2,3],[4,5,6]]) >>> a.shape TensorShape([2, 3]) >>> b = tf.transpose(a) >>> b.shape TensorShape([3, 2]) >>> b <tf.Tensor: id=376, shape=(3, 2), dtype=int32, numpy= array([[1, 4], [2, 5], [3, 6]])>
在默认情况下,transpose()方法会将所有维度按逆序方式完全转置,当然也可以通过perm参数执行需要转置的维度:
>>> a=tf.constant([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]]) >>> a <tf.Tensor: id=378, shape=(2, 2, 3), dtype=int32, numpy= array([[[ 1, 2, 3], [ 4, 5, 6]], [[ 7, 8, 9], [10, 11, 12]]])> >>> b = tf.transpose(a) # 不指定perm参数时,相当于tf.transpose(a, perm=[2, 1, 0]) >>> b <tf.Tensor: id=381, shape=(3, 2, 2), dtype=int32, numpy= array([[[ 1, 7], [ 4, 10]], [[ 2, 8], [ 5, 11]], [[ 3, 9], [ 6, 12]]])> >>> c = tf.transpose(a, perm=[2, 1, 0]) >>> c <tf.Tensor: id=384, shape=(3, 2, 2), dtype=int32, numpy= array([[[ 1, 7], [ 4, 10]], [[ 2, 8], [ 5, 11]], [[ 3, 9], [ 6, 12]]])> >>> d = tf.transpose(a, perm=[0, 2, 1]) # 第一个维度不做变换,对第二、第三维度进行转置 >>> d <tf.Tensor: id=387, shape=(2, 3, 2), dtype=int32, numpy= array([[[ 1, 4], [ 2, 5], [ 3, 6]], [[ 7, 10], [ 8, 11], [ 9, 12]]])>
4.3 添加维度:expand_dims()
>>> a=tf.constant([[1,2,3],[4,5,6]]) >>> a <tf.Tensor: id=390, shape=(2, 3), dtype=int32, numpy= array([[1, 2, 3], [4, 5, 6]])> >>> tf.expand_dims(a, axis=0) <tf.Tensor: id=396, shape=(1, 2, 3), dtype=int32, numpy= array([[[1, 2, 3], [4, 5, 6]]])> >>> tf.expand_dims(a, axis=1) <tf.Tensor: id=399, shape=(2, 1, 3), dtype=int32, numpy= array([[[1, 2, 3]], [[4, 5, 6]]])> >>> tf.expand_dims(a, axis=-1) <tf.Tensor: id=402, shape=(2, 3, 1), dtype=int32, numpy= array([[[1], [2], [3]], [[4], [5], [6]]])> >>> tf.expand_dims(a, axis=2) <tf.Tensor: id=405, shape=(2, 3, 1), dtype=int32, numpy= array([[[1], [2], [3]], [[4], [5], [6]]])>
expand_dims()方法添加维度时,通过axis参数指定添加维度的位置,正数表示从前往后数,负数表示从后往前数。
4.4 压缩维度:squeeze()
squeeze()方法与expand_dims()方法作用刚好相反,其作用是删除张量中dim为1的维度:
>>> a = tf.ones([1,3,1,2]) >>> a <tf.Tensor: id=410, shape=(1, 3, 1, 2), dtype=float32, numpy= array([[[[1., 1.]], [[1., 1.]], [[1., 1.]]]], dtype=float32)> >>> tf.squeeze(a) <tf.Tensor: id=412, shape=(3, 2), dtype=float32, numpy= array([[1., 1.], [1., 1.], [1., 1.]], dtype=float32)>