数据类型
TensorFlow 中的基本数据类型,它包含了数值型、字符串型和布尔型。
数值类型
数值类型的张量是TensorFlow 的主要数据载体,分为:
- 标量(Scalar) 单个的实数,如1.2, 3.4 等,维度数(Dimension,也叫秩)为0,shape 为[]
- 向量(Vector) n 个实数的有序集合,通过中括号包裹,如[1.2],[1.2, 3.4]等,维度数为1,长度不定,shape 为[𝑛]
- 矩阵(Matrix) n 行m 列实数的有序集合,维度数为2,每个维度上的长度不定,shape 为[𝑛, 𝑚]
- 张量(Tensor) 所有维度数dim > 2的数组统称为张量。张量的每个维度也做轴(Axis),一般维度代表了具体的物理含义
在 TensorFlow 中间,为了表达方便,一般把标量、向量、矩阵也统称为张量,不作区分,需要根据张量的维度数和形状自行判断。首先来看标量在TensorFlow 是如何创建的:
a = tf.constant(1.2) # 创建标量
a = tf.constant([1.2]) # 创建向量
a = tf.constant([[1,2],[3,4]]) # 创建矩阵
a = tf.constant([[[1,2],[3,4]],[[5,6],[7,8]]]) # 创建三维张量
字符串类型
通过传入字符串对象即可创建字符串类型的张量
a = tf.constant('Hello, Deep Learning.')
布尔类型
TensorFlow 还支持布尔类型(Boolean, bool)的张量。布尔类型的张量只需要传入Python 语言的布尔类型数据,转换成TensorFlow 内部布尔型即可:
a = tf.constant(True)
a = tf.constant([True, False])
数值精度
常用的精度类型有tf.int16, tf.int32, tf.int64,tf.float16,tf.float32,tf.float64,其中tf.float64 即为tf.double
tf.constant(123456789, dtype=tf.int16)
tf.constant(123456789, dtype=tf.int32)
读取精度
通过访问张量的dtype 成员属性可以判断张量的保存精度:
print('before:',a.dtype)
类型转换
通过tf.cast 函数进行转换
a = tf.constant(np.pi, dtype=tf.float16)
tf.cast(a, tf.double)
进行类型转换时,需要保证转换操作的合法性,例如将高精度的张量转换为低精度的张量时,可能发生数据溢出隐患
待优化张量
为了区分需要计算梯度信息的张量与不需要计算梯度信息的张量,TensorFlow 增加了一种专门的数据类型来支持梯度信息的记录:tf.Variable。tf.Variable 类型在普通的张量类型基础上添加了name,trainable 等属性来支持计算图的构建。由于梯度运算会消耗大量的计算资源,而且会自动更新相关参数,对于不需要的优化的张量,如神经网络的输入X,不需要通过tf.Variable 封装;相反,对于需要计算梯度并优化的张量,如神经网络层的W和𝒃,需要通过tf.Variable 包裹以便TensorFlow 跟踪相关梯度信息。
通过 tf.Variable()函数可以将普通张量转换为待优化张量:
a = tf.constant([-1, 0, 1, 2])
aa = tf.Variable(a)
aa.name, aa.trainable
Out[20]:
('Variable:0', True)
其中张量的name 和trainable 属性是Variable 特有的属性,name属性用于命名计算图中的变量,这套命名体系是TensorFlow内部维护的,一般不需要用户关注name 属性;trainable
表征当前张量是否需要被优化,创建Variable 对象是默认启用优化标志,可以设置trainable=False 来设置张量不需要优化。
除了通过普通张量方式创建Variable,也可以直接创建:
a = tf.Variable([[1,2],[3,4]])
创建张量
从 Numpy, List 对象创建
Numpy Array 数组和Python List 是Python 程序中间非常重要的数据载体容器,很多数据都是通过Python 语言将数据加载至Array 或者List 容器,再转换到Tensor 类型,通过TensorFlow 运算处理后导出到Array 或者List 容器,方便其他模块调用。
通过 tf.convert_to_tensor 可以创建新Tensor,并将保存在Python List 对象或者Numpy Array 对象中的数据导入到新Tensor 中:
tf.convert_to_tensor(np.array([[1,2.],[3,4]]))
Numpy 中浮点数数组默认使用64-Bit 精度保存数据,转换到Tensor 类型时精度为tf.float64,可以在需要的时候转换为tf.float32 类型。
实际上,tf.constant()和tf.convert_to_tensor()都能够自动的把Numpy 数组或者PythonList 数据类型转化为Tensor 类型.
创建全0,全1 张量
将张量创建为全0 或者全1 数据是非常常见的张量初始化手段。
通过tf.zeros()和tf.ones()即可创建任意形状全0 或全1 的张量。
创建为0 和为1 的标量张量:
tf.zeros([]),tf.ones([])
创建全0 和全1 的向量:
tf.zeros([1]),tf.ones([1])
创建全0 的矩阵:
tf.zeros([2,2])
创建全1 的矩阵:
tf.ones([2,2])
通过tf.zeros_like, tf.ones_like 可以方便地新建与某个张量shape 一致,内容全0 或全1的张量。
a = tf.ones([2,3])
tf.zeros_like(a)
tf.ones_like(a)
创建自定义数值张量
通过 tf.fill(shape, value)可以创建全为自定义数值value 的张量。
创建元素为-1的标量:
tf.fill([], -1)
创建所有元素为99 的矩阵:
tf.fill([2,2], 99)
创建已知分布的张量
正态分布(Normal Distribution,或Gaussian Distribution)和均匀分布(UniformDistribution)是最常见的分布之一,创建采样自这2 种分布的张量非常有用,比如在卷积神经网络中,卷积核张量W 初始化为正态分布有利于网络的训练;在对抗生成网络中,隐藏变量z 一般采样自均匀分布。
通过 tf.random.normal(shape, mean=0.0, stddev=1.0)可以创建形状为shape,均值为mean,标准差为stddev 的正态分布𝒩(𝑚𝑒𝑎𝑛, 𝑠𝑡𝑑𝑑𝑒𝑣2)。例如,创建均值为0,标准差为1的正太分布:
tf.random.normal([2,2])
创建均值为1,标准差为2 的正太分布:
In [34]: tf.random.normal([2,2], mean=1,stddev=2)
通过tf.random.uniform(shape, minval=0, maxval=None, dtype=tf.float32)可以创建采样自[𝑚𝑖𝑛𝑣𝑎𝑙, 𝑚𝑎𝑥𝑣𝑎𝑙]区间的均匀分布的张量。例如创建采样自区间[0,10],shape 为[2,2]的矩阵:
In [36]: tf.random.uniform([2,2],maxval=10)
Out[36]:
<tf.Tensor: id=166, shape=(2, 2), dtype=float32, numpy=
array([[4.541913 , 0.26521802],
[2.578913 , 5.126876 ]], dtype=float32)>
创建序列
在循环计算或者对张量进行索引时,经常需要创建一段连续的整形序列,可以通过tf.range()函数实现。tf.range(limit, delta=1)可以创建[0, 𝑙𝑖𝑚𝑖𝑡)之间,步长为delta 的整形序列,不包含limit 本身。
通过tf.range(start, limit, delta=1)可以创建[𝑠𝑡𝑎𝑟𝑡, 𝑙𝑖𝑚𝑖𝑡),步长为delta 的序列,不包含limit本身:
In [40]: tf.range(1,10,delta=2)
Out[40]:
<tf.Tensor: id=190, shape=(5,), dtype=int32, numpy=array([1, 3, 5, 7, 9])>
张量的典型应用
标量
在 TensorFlow 中,标量最容易理解,它就是一个简单的数字,维度数为0,shape 为[]。标量的典型用途之一是误差值的表示、各种测量指标的表示,比如准确度(Accuracy,acc),精度(Precision)和召回率(Recall)等。
向量
向量是一种非常常见的数据载体,如在全连接层和卷积神经网络层中,偏置张量𝒃就使用向量来表示。
矩阵
矩阵也是非常常见的张量类型,比如全连接层的批量输入𝑋 = [𝑏, 𝑑𝑖𝑛 ],其中𝑏表示输入样本的个数,即batch size,𝑑𝑖𝑛表示输入特征的长度。
三维张量
三维的张量一个典型应用是表示序列信号,它的格式是
𝑋 = [𝑏, 𝑠𝑒𝑞𝑢𝑒𝑛𝑐𝑒 𝑙𝑒𝑛, 𝑓𝑒𝑎𝑡𝑢𝑟𝑒 𝑙𝑒𝑛]
其中𝑏表示序列信号的数量,sequence len 表示序列信号在时间维度上的采样点数,feature len 表示每个点的特征长度。
4 维张量
我们这里只讨论3/4 维张量,大于4 维的张量一般应用的比较少,如在元学习(metalearning)中会采用5 维的张量表示方法,理解方法与3/4 维张量类似。
4 维张量在卷积神经网络中应用的非常广泛,它用于保存特征图(Feature maps)数据,格式一般定义为
[𝑏, ℎ, , 𝑐]
其中𝑏表示输入的数量,h/w分布表示特征图的高宽,𝑐表示特征图的通道数,
索引
在 TensorFlow 中,支持基本的[𝑖][𝑗] …标准索引方式,也支持通过逗号分隔索引号的索引方式。
x = tf.random.normal([4,32,32,3])
In [51]: x[0]
切片
通过𝑠𝑡𝑎𝑟𝑡: 𝑒𝑛𝑑: 𝑠𝑡𝑒𝑝切片方式可以方便地提取一段数据,其中start 为开始读取位置的索引,end 为结束读取位置的索引(不包含end 位),step 为读取步长。
以 shape 为[4,32,32,3]的图片张量为例,读取第 2,3 张图片:
In [56]: x[1:3]
start: end: step切片方式有很多简写方式,其中start、end、step 3 个参数可以根据需要选择性地省略,全部省略时即::,表示从最开始读取到最末尾,步长为1,即不跳过任何元素。
特别地,step 可以为负数,考虑最特殊的一种例子,step = −1时,start: end: −1表示从start 开始,逆序读取至end 结束(不包含end),索引号𝑒𝑛𝑑 ≤ 𝑠𝑡𝑎𝑟𝑡.
维度变换
Reshape
改变张量的视图仅仅是改变了张量的理解方式,并不会改变张量的存储顺序.
为了能够正确恢复出数据,必须保证张量的存储顺序与新视图的维度顺序一致.
在 TensorFlow 中,可以通过张量的ndim 和shape 成员属性获得张量的维度数和形状:
In [68]: x.ndim,x.shape
Out[68]:(4, TensorShape([2, 4, 4, 3]))
通过tf.reshape(x, new_shape),可以将张量的视图任意的合法改变:
In [69]: tf.reshape(x,[2,-1])
Out[69]:<tf.Tensor: id=520, shape=(2, 48), 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,…
80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95]])>
其中的参数-1 表示当前轴上长度需要根据视图总元素不变的法则自动推导,从而方便用户书写.
增删维度
增加维度 增加一个长度为1 的维度相当于给原有的数据增加一个新维度的概念,维度长度为1,故数据并不需要改变,仅仅是改变数据的理解方式,因此它其实可以理解为改变视图的一种特殊方式。
通过tf.expand_dims(x, axis)可在指定的axis 轴前可以插入一个新的维度.tf.expand_dims 的axis 为正时,表示在当前维度之前插入一个新维度;为负时,表示当前维度之后插入一个新的维度。以[𝑏, ℎ, , 𝑐]张量为例,不同axis 参数的实际插入位置如下图 4.6 所示:
删除维度 是增加维度的逆操作,与增加维度一样,删除维度只能删除长度为1 的维度,也不会改变张量的存储。继续考虑增加维度后shape 为[1,28,28,1]的例子,如果希望将图片数量维度删除,可以通过tf.squeeze(x, axis)函数,axis 参数为待删除的维度的索引号,图片数量的维度轴axis=0:
In [75]: x = tf.squeeze(x, axis=0)
Out[75]:
<tf.Tensor: id=586, shape=(28, 28, 1), dtype=int32, numpy=
array([[[8],
[2],
[2],
[0],…
如果不指定维度参数axis,即tf.squeeze(x),那么他会默认删除所有长度为1 的维度.
交换维度
改变视图、增删维度都不会影响张量的存储。在实现算法逻辑时,在保持维度顺序不变的条件下,仅仅改变张量的理解方式是不够的,有时需要直接调整的存储顺序,即交换维度(Transpose)。通过交换维度,改变了张量的存储顺序,同时也改变了张量的视图。
交换维度操作是非常常见的,比如在TensorFlow 中,图片张量的默认存储格式是通道后行格式:[𝑏, ℎ, , 𝑐],但是部分库的图片格式是通道先行:[𝑏, 𝑐, ℎ, ],因此需要完成[𝑏, ℎ, , 𝑐]到[𝑏, 𝑐, ℎ, ]维度交换运算。我们以[𝑏, ℎ, , 𝑐]转换到[𝑏, 𝑐, ℎ, ]为例,介绍如何使用tf.transpose(x, perm)函数完成维度交换操作,其中perm 表示新维度的顺序List。考虑图片张量shape 为[2,32,32,3],图片数量、行、列、通道数的维度索引分别为0,1,2,3,如果需要交换为[𝑏, 𝑐, ℎ, ]格式,则新维度的排序为图片数量、通道数、行、列,对应的索引号为[0,3,1,2],实现如下:
In [78]: x = tf.random.normal([2,32,32,3])
tf.transpose(x,perm=[0,3,1,2])
如果希望将[𝑏, ℎ, , 𝑐]交换为[𝑏, , ℎ, 𝑐],即将行列维度互换,则新维度索引为[0,2,1,3]:
x = tf.random.normal([2,32,32,3])
tf.transpose(x,perm=[0,2,1,3])
通过tf.transpose 完成维度交换后,张量的存储顺序已经改变,视图也随之
改变,后续的所有操作必须基于新的存续顺序进行。
数据复制
可以通过tf.tile(x, multiples)函数完成数据在指定维度上的复制操作,multiples 分别指定了每个维度上面的复制倍数,对应位置为1 表明不复制,为2 表明新长度为原来的长度的2 倍,即数据复制一份,以此类推。
b = tf.constant([1,2])
b = tf.expand_dims(b, axis=0)
b = tf.tile(b, multiples=[2,1])
Broadcasting
Broadcasting 也叫广播机制(自动扩展也许更合适),它是一种轻量级张量复制的手段,在逻辑上扩展张量数据的形状,但是只要在需要时才会执行实际存储复制操作。对于大部分场景,Broadcasting 机制都能通过优化手段避免实际复制数据而完成逻辑运算,从而相对于tf.tile 函数,减少了大量计算代价。
Broadcasting 机制的核心思想是普适性,即同一份数据能普遍适合于其他位置。
通过 tf.broadcast_to(x, new_shape)可以显式将现有shape 扩张为new_shape:
In [87]:
A = tf.random.normal([32,1])
tf.broadcast_to(A, [2,32,32,3])
Out[87]:
<tf.Tensor: id=13, shape=(2, 32, 32, 3), dtype=float32, numpy=
array([[[[-1.7571245 , -1.7571245 , -1.7571245 ],
[ 1.580159 , 1.580159 , 1.580159 ],
[-1.5324328 , -1.5324328 , -1.5324328 ],...
数学运算
加减乘除
加减乘除是最基本的数学运算,分别通过tf.add, tf.subtract, tf.multiply, tf.divide 函数实现,TensorFlow 已经重载了+ −∗/运算符,一般推荐直接使用运算符来完成加减乘除运算。
整除和余除也是常见的运算之一,分别通过//和%运算符实现。
乘方
通过 tf.pow(x, a)可以方便地完成𝑦 = 𝑥𝑎乘方运算,也可以通过运算符**实现𝑥 ∗∗ 𝑎运
算.
对于常见的平方和平方根运算,可以使用tf.square(x)和tf.sqrt(x)实现。
指数、对数
通过 tf.pow(a, x)或者**运算符可以方便实现指数运算𝑎𝑥.
在 TensorFlow 中,自然对数l g𝑒 𝑥可以通过tf.math.log(x)实现
矩阵相乘
神经网络中间包含了大量的矩阵相乘运算,前面我们已经介绍了通过@运算符可以方
便的实现矩阵相乘,还可以通过tf.matmul(a, b)实现。需要注意的是,TensorFlow 中的矩阵相乘可以使用批量方式,也就是张量a,b 的维度数可以大于2。当张量a,b 维度数大于2时,TensorFlow 会选择a,b 的最后两个维度进行矩阵相乘,前面所有的维度都视作Batch 维度。