第4章 TensorFlow基础
4.1数据类型
基本数据类型,包含数值型、字符串型和布尔型。
4.1.1数值类型
-
张量:所有维度数dim > 2的数组统称为张量 ,张量的每个维度也做轴 。
-
标量:在TensorFlow中创建标量
a = 1.2 aa = tf.constant(1.2)
-
向量:向量的定义须通过 List 类型传给 tf.constant()
a = tf.constant([1.2]) # 一个元素向量 aa = tf.constant([1, 2, 3.]) # 两个元素向量 aaa = tf.constant([[1, 2],[3, 4]]) # 定义矩阵
-
3维张量
a = tf.constant([[[1,2],[3,4]],[[5,6],[7,8]]])
4.1.2 字符串类型
-
创建字符串类型张量
a = tf.constant('Hello, Deep Learning')
在
tf.strings
模块中,提供了常见的字符串型的工具函数,如拼接join()
,长度length()
,切分split()
4.1.3 布尔类型
布尔类型的张量只需要传入 Python 语言的布尔类型数据,转换成 TensorFlow 内部布尔型即可:
-
布尔类型张量
g = tf.constant(True) g1 = tf.constant([True, False])
需要注意的是, TensorFlow 的布尔类型和 Python 语言的布尔类型并不对等,不能通用 :
a = tf.constant(True)
a == True
# 输出:False
4.2 数值精度
在创建张量的时,可以指定张量的保存精度:
tf.constant(123456789, dtype = tf.int32)
4.2.2 读取精度
-
通过访问张量的 dtype 成员属性可以判断张量的保存精度
print('before:', a.dtype) if a.dtype != tf.float32: a = tf.cast(a, tf.float32) print('after:', a.dtype)
4.2.2 类型转换
-
转换精度需要用到
tf.cast
(低精度转换为高精度)a = tf.constant(np.pi, dtype=tf.float16) tf.cast(a, tf.double)
-
高精度的张量转换为低精度的张量时,可能发生数据溢出隐患
-
布尔型与整型之间相互转换
j = tf.constant([True, False]) j1 = tf.cast(j, tf.int32) # 输出[1,0]
一般默认 0 表示 False, 1 表示 True,在 TensorFlow 中,将非 0 数字都视为 True
4.3 待优化张量
作用:为了区分需要计算梯度信息的张量和不需要计算梯度信息的张量,TensorFlow增加了一种专门的数据类型来支持梯度信息的记录: tf.Variable
.
-
将普通张量转换为待优化张量
k = tf.constant([-1, 0, 1, 2]) k1 = tf.Variable(k) print(k1.name, k1.trainable) # 输出:Variable:0 True
name
和trainable
是 Variable 特有的属性, name 属性用于命名计算图中的变量,trainable
表征当前张量是否需要被优化,创建 Variable 对象是默认启用优化标志,可以设置trainable=False
来设置张量不需要优化。 -
直接创建张量
a = tf.Variable([[1,2],[3,4]])
待优化张量可看做普通张量的特殊类型,普通张量也可以通过GradientTape.watch()
方法临时加入跟踪梯度信息的列表。
4.4创建张量
4.4.1从Numpy,List对象创建
通过 tf.convert_to_tensor
可以创建新 Tensor,并将保存在 Python List 对象或者 Numpy Array 对象中的数据导入到新 Tensor 中 :
tf.convert_to_tensor([1, 2.])
tf.convert_to_tensor([[1,2.],[3,4]])
- 注:Numpy 中浮点数数组默认使用 64-Bit 精度保存数据,转换到 Tensor 类型时精度为 tf.float64,可以在需要的时候转换为 tf.float32 类型 。
tf.constant()
和tf.convert_to_tensor()
都能够自动的把 Numpy 数组或者 Python List 数据类型转化为 Tensor 类型 ,使用其一即可。
4.4.2 创建全0,全1张量
通过 tf.zeros()
和 tf.ones()
即可创建任意形状全 0 或全 1 的张量 。
-
创建为0和为1的标量张量:
tf.zeros([]),tf.ones([])
-
创建全 0 和全 1 的向量:
l5 = tf.zeros([1]) l6 = tf.ones([1])
-
创建全 0 的矩阵:
l7 = tf.zeros([2, 2])
-
创建全 1 的矩阵:
l8 = tf.ones([3, 2])
-
通过
tf.zeros_like
,tf.ones_like
可以方便地新建与某个张量 shape 一致,内容全 0 或全 1的张量 -
创建与张量 l9 形状一样的全 0 张量
l9 = tf.ones([2, 3]) l10 = tf.zeros_like(l9)
-
创建与张量 l11 形状一样的全 1 张量
l11 = tf.zeros([3, 2]) l12 = tf.ones_like(l11)
tf.*_like
是一个便捷函数,可以通过 tf.zeros(a.shape)
等方式实现 。
4.4.3创建自定义数值张量
通过 tf.fill(shape, value)
可以创建全为自定义数值 value 的张量 :
-
创建元素为-1 的标量
l13 = tf.fill([], -1)
-
创建所有元素为-1 的向量:
l14 = tf.fill([1], -1)
-
创建所有元素为 99 的矩阵:
l15 = tf.fill([2, 2], 99)
4.4.4 创建已知分布的张量
在卷积神经网络中,卷积核张量 W 初始化为正态分布有利于网络的训练。
在对抗生成网络中,隐藏变量 z 一般采样自均匀分布。
通过 tf.random.normal(shape, mean=0.0, stddev=1.0)
可以创建形状为 shape,均值为mean,标准差为 stddev 的正态分布。
-
创建均值为 0, 标准差为 1 的正态分布
tf.random.normal([2, 2])
-
创建均值为 1,标准差为 2 的正态分布
tf.random.normal([2, 2], mean=1, stddev=2)
-
通过
tf.random.uniform(shape,minval=0,maxval=None,dtype=tf.float32)
可以创建采样自[minval, maxval]区间的均匀分布的张量# 创建采样自区间[0,1], shape 为[2,2]的矩阵 l18 = tf.random.uniform([2, 2], maxval=1, minval=0) # 创建采样自区间[0,10], shape 为[2,2]的矩阵 l19 = tf.random.uniform([2, 2], maxval=10) # 均匀采样整形类型的数据 l20 = tf.random.uniform([2, 2], maxval=100, dtype=tf.int32)
4.4.5 创建序列
在循环计算或者对张量进行索引时,经常需要创建一段连续的整形序列,可以通过tf.range()
函数实现
# 创建0-9,步长为1的整形序列
l21 = tf.range(10)
# 创建0-9,步长为2的整形序列
l22 = tf.range(10, delta=2)
通过 tf.range(start, limit, delta=1)可以创建[𝑠𝑡𝑎𝑟𝑡, 𝑙𝑖𝑚𝑖𝑡),步长为 delta 的序列,不包含 limit本身
# 创建[1,10),步长为2的整型序列
tf.range(1, 10, delta=2)
4.6 索引与切片
4.6.1 索引
x = tf.random.normal([4, 32, 32, 3])
# 取第一张图片的数据
print(x[0])
# 取第一张图片的第2行
print(x[0][1])
# 取第1张图片,第2行,第3列的像素
print(x[0][1][2])
# 取第 3 张图片,第 2 行,第 1 列的像素, B 通道(第 2 个通道)颜色强度值
print(x[2][1][0][1])
当张量的维度数较高时, 使用[𝑖][𝑗]. . . [𝑘]
的方式书写不方便,可以采用[𝑖, 𝑗, … , 𝑘]
的方式索引,它们是等价的。
4.6.2切片
-
通过
𝑠𝑡𝑎𝑟𝑡: 𝑒𝑛𝑑: 𝑠𝑡𝑒𝑝
切片方式可以方便地提取一段数据 -
简写方式 :
-
3个参数可根据需要选择性地省略,全部省略时
::
,表示从最开始读取到最末尾,步长为 1,即不跳过任何元素 。 -
x[0,::]
表示读取第一张图片的所有行,::
表示在行维度上读取所有行,等价于x[0]
-
为了更加简洁,::可以简写为单个冒号
# 取所有图片,隔行采样,隔列采样,所有通道信息,相当于在图片的高宽各缩放至原来的50% x[:,0:28:2,0:28:2,:]
-
切片方式 | 意义 |
---|---|
start:end:step |
从 start 开始读取到 end(不包含 end),步长为 step |
start:end |
从 start 开始读取到 end(不包含 end),步长为 1 |
start: |
从 start 开始读取完后续所有元素,步长为 1 |
start::step |
从 start 开始读取完后续所有元素,步长为 step |
:end:step |
从 0 开始读取到 end(不包含 end),步长为 step |
:end |
从 0 开始读取到 end(不包含 end),步长为 1 |
::step |
每隔 step-1 个元素采样所有 |
:: |
读取所有元素 |
: |
读取所有元素 |
-
step为负数的时候表示逆序读取。
# 读取每张图片的所有通道,其中行按着逆序隔行采样,列按着逆序隔行采样 print(x[0, ::-2, ::-2])
-
当张量的维度数量较多时,不需要采样的维度一般用单冒号
:
表示采样所有元素 ,为了避免出现像𝑥[: , : , : ,1]
这样出现过多冒号的情况,可以使用⋯
符号表示取多个维度 ,⋯
符号左边的维度将自动对齐到最左边 ,⋯
符号右边的维度将自动对齐到最右边...
切片方式小结切片方式 意义 a,⋯,b a维度对齐到最左边,b维度对齐到最右边,中间的维度全部读取,其他维度按a/b的方式读取 a,⋯ a维度对齐到最左边,a维度后的所有维度全部读取,a维度按a方式读取。 ⋯, b b维度对齐到最右边,b 之前的所有维度全部读取, b 维度按 b 方式读取 ⋯ 读取所有张量数据
4.7 维度变换
当现有的数据格式不满足算法要求时,需要通过维度变换将数据调整为正确的格式, 这就是维度变换的功能 。
基本的维度变换包括:
-
改变视图 reshape
-
插入新维度 expand_dims
通过
tf.expand_dims(x,axis)
可在指定的axis轴前可以插入一个新的维度# 若x的维度是(1,5,7) axis = 0 a = tf.expand_dims(x , 0) #则转换后是a的维度是(1, 1, 5, 7) # 若x的维度是(1,5,7) axis = 2 a = tf.expand_dims(x , 2) #则转换后是a的维度是(1, 5,1, 7) #若x的维度是(1,5,7) axis = 3 a = tf.expand_dims(x , 3) #则转换后是a的维度是(1, 5,7, 1)
注:
axis
为正时,表示在当前维度之前插入一个新维度;axis
为负时,表示当前维度之后插入一个新的维度 。 -
删除维度 squeeze
删除维度只能删除长度为 1 的维度,也不会改变张量的存储
图片数量维度删除,可以通过
tf.squeeze(x, axis)
函数,axis
参数为待删除的维度的索引号x = tf.squeeze(x, axis=0)
如果不指定维度参数
axis
,即tf.squeeze(x)
, 那么他会默认删除所有长度为 1 的维度:x=tf.random.uniform([1,28,28,1],maxval=10,dtype=tf.int32) print(tf.squeeze(x)
-
交换维度 transpose
改变视图、 增删维度都不会影响张量的存储。通过交换维度,改变了张量的存储顺序,同时也改变了张量的视图 。
用
tf.transpose(x, perm)
函数完成维度交换操作,其中perm
表示新维度的顺序List
。# 将[b,h,w,c]格式转为[b,c,h,w]格式 x = tf.random.normal([2, 32, 32, 3]) a = tf.transpose(x, perm=[0, 3, 1, 2]) print(a, a.shape)
通过
tf.transpose
完成维度交换后,张量的存储顺序已经改变, 视图也随之改变, 后续的所有操作必须基于新的存续顺序进行 -
复制数据 tile
x = tf.range(4) x = tf.reshape(x, [2, 2]) x = tf.tile(x, multiples=[1, 2]) print(x, x.shape) # shape(2,4),array([[0 1 0 1] # 2 3 2 3])
tf.tile
会创建一个新的张量来保存复制后的张量
4.8 Broadcasting
Broadcasting 也叫广播机制(自动扩展也许更合适), 它是一种轻量级张量复制的手段,在逻辑上扩展张量数据的形状, 但是只要在需要时才会执行实际存储复制操作。
需满足普适性原则:在验证普适性之前,需要将张量 shape 靠右对齐, 然后进行普适性判断: 对于长度为 1 的维度,默认这个数据普遍适合于当前维度的其他位置对于不存在的维度, 则在增加新维度后默认当前数据也是普适性于新维度的, 从而可以扩展为更多维度数、 其他长度的张量形状。
-
通过
tf.broadcast_to(x, new_shape)
可以显式将现有shape扩张为new_shapeA = tf.random.normal([32, 1]) B = tf.broadcast_to(A, [2, 32, 32, 3]) print(B, B.shape)
4.9 数学运算
4.9.1加减乘除
-
+ - * /
也可通过tf.add
,tf.subtract
,tf.multiply
,tf.divide
实现 -
整除:
a//b
-
余除:
a%b
4.9.2 乘方
-
通过
tf.pow(x,a)
可以方便地完成$$y = x^a$$的乘方运算,也可以通过运算符**实现𝑥 ∗∗ 𝑎运算
x = tf.range(4)
y = tf.pow(x, 3)
print(y)
print(x**2)
-
设置指数为1/𝑎形式即可实现根号运算: $$a\sqrt x$$
-
对于常见的平方和平方根运算,可以使用
tf.square(x)
和tf.sqrt(x)
实现
4.9.3 指数、对数
通过tf.pow(a,x)
或者**
运算符可以方便实现指数运算$$a^x$$
对于自然指数 $$e^x$$可以通过tf.exp(x)
实现。
自然对数$$log_ex$$可以通过tf.math.log(x)
实现
如果希望计算其他底数的对数,可以根据对数的换底公式 :$$log_ax=\frac{log_ex}{log_ea}$$
例如:计算$$log_10x$$可以通过$$log_ex/log_e10$$
4.9.4矩阵相乘
@运算符可以方便的实现矩阵相乘,还可以通过 tf.matmul(a, b)
实现。
根据矩阵相乘的定义, a 和 b 能够矩阵相乘的条件是, a 的倒数第一个维度长度(列)和b 的倒数第二个维度长度(行)必须相等。
4.9.10 前项传播实战
tf.reduce_mean
函数用于计算张量tensor沿着指定的数轴(tensor的某一维度)上的的平均值,主要用作降维或者计算tensor(图像)的平均值。