TensorFlow基础语句语法个人笔记
【TensorFlow】
阅读《TensorFlow深度学习》笔记,供个人查询,记录。
TensorFlow基础
数据类型
张量
-
维度数dim>2的数组,在TensorFlow中,标量向量,矩阵都统称张量。
-
improt tensorflow as tf a=tf.constant(1.1) a=tf.constant([1.1,1.2,1.3]) a=tf.constant([1,2],[3,4]) a=tf.constant([[[1,2],[3,4],[5,6],[7,8]]])
字符串类型
-
(tf.strings)中也提供了常见的字符串工具函数,比如小写化lower(),拼接join(),长度length(),切分split()等。
-
improt tensorflow as tf a=tf.constant('Hello,Deep Learning.') tf.strings.lower(a)
布尔类型
-
improt tensorflow as tf a=tf.constant(True)#创建布尔类型标量 a=tf.constant([True,False])#创建布尔类型向量 #TensorFlow的布尔类型和Python语言的布尔类型不等价,不能通用
数值精度
-
常见类型有tf.int16,tf.int32,tf.int64,tf.float16,tf.float32,tf.float64等,其中tf.float64即为tf.double。
-
improt tensorflow as tf #创建指定精度的张量 tf.constant(123456789,dtype=tf.int16)
读取精度
-
通过访问张量的 dtype 成员属性可以判断张量的保存精度
-
improt tensorflow as tf print('before:',a.dtype) # 读取原有张量的数值精度 if a.dtype != tf.float32: # 如果精度不符合要求,则进行转换 a = tf.cast(a,tf.float32) # tf.cast 函数可以完成精度转换 print('after :',a.dtype) # 打印转换后的精度
类型转换
-
系统的每个模块使用的数据类型、 数值精度可能各不相同, 对于不符合要求的张量的
类型及精度, 需要通过 tf.cast 函数进行转换。 -
improt tensorflow as tf a = tf.constant(123456789, dtype=tf.int32) tf.cast(a, tf.int16) # 转换为低精度整型 a = tf.constant([True, False]) tf.cast(a, tf.int32) #<tf.Tensor: id=48, shape=(2,), dtype=int32, numpy=array([1, 0])> #一般默认 0 表示 False, 1 表示 True,在 TensorFlow 中,将非 0 数字都视为 True
待优化张量
-
为了区分需要计算梯度信息的张量与不需要计算梯度信息的张量, TensorFlow 增加了
一种专门的数据类型来支持梯度信息的记录: tf.Variable。 tf.Variable 类型在普通的张量类
型基础上添加了 name, trainable 等属性来支持计算图的构建。 由于梯度运算会消耗大量的
计算资源,而且会自动更新相关参数,对于不需要的优化的张量,如神经网络的输入𝑿,
不需要通过 tf.Variable 封装;相反,对于需要计算梯度并优化的张量, 如神经网络层的𝑾
和𝒃, 需要通过 tf.Variable 包裹以便 TensorFlow 跟踪相关梯度信息 -
improt tensorflow as tf a = tf.constant([-1, 0, 1, 2]) # 创建 TF 张量 aa = tf.Variable(a) # 转换为 Variable 类型 aa.name, aa.trainable # Variable 类型张量的属性 #('Variable:0', True)
-
其中张量的 name 和 trainable 属性是 Variable 特有的属性, name 属性用于命名计算图中的
变量,这套命名体系是 TensorFlow 内部维护的, 一般不需要用户关注 name 属性; trainable
属性表征当前张量是否需要被优化,创建 Variable 对象时是默认启用优化标志,可以设置
trainable=False 来设置张量不需要优化。
除了通过普通张量方式创建 Variable, 也可以直接创建,例如 -
a = tf.Variable([[1,2],[3,4]]) # 直接创建 Variable 张量
创建张量
- 在 TensorFlow 中,可以通过多种方式创建张量,如从 Python 列表对象创建,从
Numpy 数组创建,或者创建采样自某种已知分布的张量等。
从数组,列表对象创建
-
通过 tf.convert_to_tensor 函数可以创建新 Tensor,并将保存在 Python List 对象或者
Numpy Array 对象中的数据导入到新 Tensor 中, -
tf.convert_to_tensor([1,2.]) # 从列表创建张量 tf.convert_to_tensor(np.array([[1,2.],[3,4]])) # 从数组中创建张量
创建全 0 或全 1 张量
-
tf.zeros([]),tf.ones([]) # 创建全 0,全 1 的标量 tf.zeros([1]),tf.ones([1]) # 创建全 0,全 1 的向量 tf.zeros([2,2]) # 创建全 0 矩阵,指定 shape 为 2 行 2 列 tf.ones([3,2]) # 创建全 1 矩阵,指定 shape 为 3 行 2 列
-
通过 tf.zeros_like, tf.ones_like 可以方便地新建与某个张量 shape 一致, 且内容为全 0 或
全 1 的张量。例如,创建与张量𝑨形状一样的全 0 张量 -
a = tf.ones([2,3]) # 创建一个矩阵 tf.zeros_like(a) # 创建一个与 a 形状相同,但是全 0 的新矩阵 a = tf.zeros([3,2]) # 创建一个矩阵 tf.ones_like(a) # 创建一个与 a 形状相同,但是全 1 的新矩阵
创建自定义数值张量
-
通过 tf.fill(shape, value)可以创建全为自定义数值 value 的张量,形状由 shape 参数指
定。 例如,创建元素为-1的标量: -
tf.fill([], -1) # 创建-1 的标量 tf.fill([1], -1) # 创建-1 的向量 tf.fill([2,2], 99) # 创建 2 行 2 列,元素全为 99 的矩阵
创建已知分布的张量
-
通过 tf.random.normal(shape, mean=0.0, stddev=1.0)可以创建形状为 shape,均值为
mean,标准差为 stddev 的正态分布𝒩(mean, stddev2)。例如,创建均值为 0,标准差为 1
的正态分布: -
tf.random.normal([2,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]的矩
阵: -
tf.random.uniform([2,2]) # 创建采样自[0,1)均匀分布的矩阵 tf.random.uniform([2,2],maxval=10) # 创建采样自[0,10)均匀分布的矩阵 # 如果需要均匀采样整形类型的数据,必须指定采样区间的最大值 maxval 参数,同时指 # 定数据类型为 tf.int*型: # 创建采样自[0,100)均匀分布的整型矩阵 tf.random.uniform([2,2],maxval=100,dtype=tf.int32)
创建序列
-
在循环计算或者对张量进行索引时,经常需要创建一段连续的整型序列,可以通过
tf.range()函数实现。 tf.range(limit, delta=1)可以创建[0, limit)之间,步长为 delta 的整型序
列,不包含 limit 本身。例如,创建 0~10,步长为 1 的整型序列: -
tf.range(10) # 0~10,不包含 10 tf.range(10,delta=2) # 创建 0~10,步长为 2 的整形序列
-
通过 tf.range(start, limit, delta=1)可以创建[start, limit),步长为 delta 的序列,不包含
limit 本身: -
tf.range(1,10,delta=2) # 1~10
张量的典型应用
标量
- 在 TensorFlow 中,标量最容易理解,它就是一个简单的数字,维度数为 0, shape 为
[]。 - 标量的一些典型用途是误差值的表示、 各种测量指标的表示,比如准确度(Accuracy,
简称 acc),精度(Precision)和召回率(Recall)等,方便直接化为曲线图。
向量
- 向量是一种非常常见的数据载体,如在全连接层和卷积神经网络层中,偏置张量𝒃就
使用向量来表示。 - 待补充
矩阵
-
矩阵也是非常常见的张量类型,比如全连接层的批量输入张量𝑿的形状为[𝑏, 𝑑in],其
中𝑏表示输入样本的个数,即 Batch Size, 𝑑in表示输入特征的长度。 例如特征长度为 4,一
共包含 2 个样本的输入可以表示为矩阵: -
x = tf.random.normal([2,4]) # 2 个样本,特征长度为 4 的张量
-
令全连接层的输出节点数为 3,则它的权值张量𝑾的 shape 为[4,3],我们利用张量𝑿、 𝑾和
向量𝒃可以直接实现一个网络层,代码如下: -
w = tf.ones([4,3]) # 定义 W 张量 b = tf.zeros([3]) # 定义 b 张量 o = x@w+b # X@W+b 运算
-
其中𝑿和𝑾张量均是矩阵,上述代码实现了一个线性变换的网络层,激活函数为空。 一般
地, 𝜎(𝑿@𝑾 + 𝒃)网络层称为全连接层, 在 TensorFlow 中可以通过 Dense 类直接实现, 特
别地,当激活函数𝜎为空时, 全连接层也称为线性层。 我们通过 Dense 类创建输入 4 个节
点,输出 3 个节点的网络层, 并通过全连接层的 kernel 成员名查看其权值矩阵𝑾: -
fc = layers.Dense(3) # 定义全连接层的输出节点为 3 fc.build(input_shape=(2,4)) # 定义全连接层的输入节点为 4 fc.kernel # 查看权值矩阵 W <tf.Variable 'kernel:0' shape=(4, 3) dtype=float32, numpy= array([[ 0.06468129, -0.5146048 , -0.12036425], [ 0.71618867, -0.01442951, -0.5891943 ], [-0.03011459, 0.578704 , 0.7245046 ], [ 0.73894167, -0.21171576, 0.4820758 ]], dtype=float32)>
三维张量
-
三维的张量一个典型应用是表示序列信号,它的格式是
-
𝑿 = [𝑏, sequence len, feature len]
-
其中𝑏表示序列信号的数量, sequence len 表示序列信号在时间维度上的采样点数或步数,feature len 表示每个点的特征长度。
-
# 自动加载 IMDB 电影评价数据集 (x_train,y_train),(x_test,y_test)=keras.datasets.imdb.load_data(num_words=10000) # 将句子填充、截断为等长 80 个单词的句子 x_train = keras.preprocessing.sequence.pad_sequences(x_train,maxlen=80) x_train.shape #Out : (25000, 80) # 创建词向量 Embedding 层类 embedding=layers.Embedding(10000, 100) # 将数字编码的单词转换为词向量 out = embedding(x_train) out.shape #Out: TensorShape([25000, 80, 100])
四维张量
- 四维张量在卷积神经网络中应用非常广泛,它用于保存特征图(Feature maps)数据,格
式一般定义为 - [𝑏, ℎ, , 𝑐]
- 其中𝑏表示输入样本的数量, ℎ/ 分别表示特征图的高/宽, 𝑐表示特征图的通道数, 部分深
度学习框架也会使用[𝑏, 𝑐, ℎ, ]格式的特征图张量。
索引与切片
- 通过索引与切片操作可以提取张量的部分数据, 它们的使用频率非常高。
索引
-
在 TensorFlow 中, 支持基本的[𝑖][𝑗] ⋯标准索引方式,也支持通过逗号分隔索引号的索
引方式。 -
x[0][1][2] x[1,9,2] #两种方法都可
切片
-
通过start: end: step切片方式可以方便地提取一段数据,其中 start 为开始读取位置的索
引, end 为结束读取位置的索引(不包含 end 位), step 为采样步长。 -
特别地, step 可以为负数,考虑最特殊的一种例子, 当step = -1时, start: end: -1表
示从 start 开始, 逆序读取至 end 结束(不包含 end),索引号𝑒𝑛𝑑 ≤ 𝑠𝑡𝑎𝑟𝑡。 -
为了避免出现像 [: , : , : ,1]这样过多冒号的情况,可以使用⋯符号表示取多个维度上所
有的数据, 其中维度的数量需根据规则自动推断:当切片方式出现⋯符号时, ⋯符号左边
的维度将自动对齐到最左边, ⋯符号右边的维度将自动对齐到最右边,此时系统再自动推
断⋯符号代表的维度数量,它的切片方式总结
维度变换
- 基本的维度变换操作函数包含了改变视图 reshape、 插入新维度 expand_dims,删除维
度 squeeze、 交换维度 transpose、 复制数据 tile 等函数。
改变视图
-
我们通过 tf.range()模拟生成一个向量数据,并通过 tf.reshape 视图改变函数产生不同的
视图,例如: -
x=tf.range(96) # 生成向量 x=tf.reshape(x,[2,4,4,3]) # 改变 x 的视图,获得 4D 张量,存储并未改变 Out: # 可以观察到数据仍然是 0~95 的顺序,可见数据并未改变,改变的是数据的结构 <tf.Tensor: id=11, shape=(2, 4, 4, 3), dtype=int32, numpy= array([[[[ 0, 1, 2], [ 3, 4, 5], [ 6, 7, 8], [ 9, 10, 11]],… tf.reshape(x,[2,-1]) #其中的参数−1表示当前轴上长度需要根据张量总元素不变的法则自动推导, 从而方便用户 #书写。 Out:<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]])>
增、 删维度
-
增加维度
-
考虑一个具体例子,一张28 × 28大小的灰度图片的数据保存为 shape 为[28,28]的张
量, 在末尾给张量增加一新维度, 定义为通道数维度,此时张量的 shape 变为[28,28,1],实现如下: -
# 产生矩阵 x = tf.random.uniform([28,28],maxval=10,dtype=tf.int32) #通过 tf.expand_dims(x, axis)可在指定的 axis 轴前可以插入一个新的维度: x = tf.expand_dims(x,axis=2) # axis=2 表示宽维度后面的一个维度 <tf.Tensor: id=13, shape=(28, 28, 1), dtype=int32, numpy= array([[[6], [2], [0], [0], [6], [7], [3],…
-
插入一个新维度后,数据的存储顺序并没有改变,依然按着6,2,0,0,6,7, ⋯的顺
序保存,仅仅是在插入一个新的维度后,改变了数据的视图 -
x = tf.expand_dims(x,axis=0) # 高维度之前插入新维度
-
需要注意的是, tf.expand_dims 的 axis 为正时,表示在当前维度之前插入一个新维度; 为负时,表示当前维度之后插入一个新的维度。 以[𝑏, ℎ, , 𝑐]张量为例,不同 axis 参数的实际插入位置如图
-
-
删除维度
-
是增加维度的逆操作,与增加维度一样,删除维度只能删除长度为 1 的维
度,也不会改变张量的存储。 继续考虑增加维度后 shape 为[1,28,28,1]的例子,如果希望将图片数量维度删除,可以通过 tf.squeeze(x, axis)函数, axis 参数为待删除的维度的索引号,例如, 图片数量的维度轴 axis=0: -
x = tf.squeeze(x, axis=0) # 删除图片数量维度 #如果不指定维度参数 axis,即 tf.squeeze(x), 那么它会默认删除所有长度为 1 的维度
-
建议使用 tf.squeeze()时逐一指定需要删除的维度参数,防止 TensorFlow 意外删除某些长度为 1 的维度,导致计算结果不合法。
-
交换维度
-
改变视图、 增删维度都不会影响张量的存储。在实现算法逻辑时, 在保持维度顺序不
变的条件下, 仅仅改变张量的理解方式是不够的,有时需要直接调整的存储顺序,即交换
维度(Transpose)。通过交换维度操作,改变了张量的存储顺序,同时也改变了张量的视
图。 -
我们以[𝑏, ℎ, w, 𝑐]转换到[𝑏, 𝑐, ℎ, w]为例,介绍如何使用 tf.transpose(x, perm)函数完成维度交换操作,其中参数 perm表示新维度的顺序 List。
-
考虑图片张量 shape 为[2,32,32,3], “图片数量、行、列、通道数” 的维度索引分别为 0、 1、 2、 3,如果需要交换为[𝑏, 𝑐, ℎ, w]格式,则新维度的排序为“图片数量、通道数、行、列”,对应的索引号为[0,3,1,2], 因此参数 perm 需设置为[0,3,1,2], 实现如下:
-
x = tf.random.normal([2,32,32,3]) tf.transpose(x,perm=[0,3,1,2]) # 交换维度
-
需要注意的是, 通过 tf.transpose 完成维度交换后,张量的存储顺序已经改变, 视图也
随之改变, 后续的所有操作必须基于新的存续顺序和视图进行。 相对于改变视图操作,维
度交换操作的计算代价更高。
复制数据
-
可以通过 tf.tile(x, multiples)函数完成数据在指定维度上的复制操作, multiples 分别指
定了每个维度上面的复制倍数,对应位置为 1 表明不复制,为 2 表明新长度为原来长度的
2 倍,即数据复制一份,以此类推。 -
b = tf.constant([1,2]) # 创建向量 b b = tf.expand_dims(b, axis=0) # 插入新维度,变成矩阵 b = tf.tile(b, multiples=[2,1]) # 样本维度上复制一份
Broadcasting
数学运算
加减乘除运算
-
加、 减、 乘、 除是最基本的数学运算,分别通过 tf.add, tf.subtract, tf.multiply, tf.divide
函数实现, TensorFlow 已经重载了+、 - 、 ∗ 、 /运算符,一般推荐直接使用运算符来完成
加、 减、 乘、 除运算。整除和余除也是常见的运算之一,分别通过//和%运算符实现。 -
a = tf.range(5) b = tf.constant(2) a//b # 整除运算 a%b # 余除运算
乘方运算
-
通过 tf.pow(x, a)可以方便地完成𝑦 = 𝑎的乘方运算,也可以通过运算符**实现 ∗∗ 𝑎运
算,实现如下: -
x = tf.range(4) tf.pow(x,3) # 乘方运算 x**2 # 乘方运算符 x**(0.5) # 平方根
-
特别地,对于常见的平方和平方根运算,可以使用 tf.square(x)和 tf.sqrt(x)实现。平方运算
实现如下: -
x = tf.range(5) x = tf.cast(x, dtype=tf.float32) # 转换为浮点数 x = tf.square(x) # 平方 tf.sqrt(x) # 平方根
指数和对数运算
-
通过 tf.pow(a, x)或者**运算符也可以方便地实现指数运算𝑎𝑥,例如:
-
x = tf.constant([1.,2.,3.]) 2**x # 指数运算 #特别地,对于自然指数e𝑥, 可以通过 tf.exp(x)实现,例如: tf.exp(1.) # 自然指数运算 #在 TensorFlow 中,自然对数loge 可以通过 tf.math.log(x)实现,例如: x=tf.exp(3.) tf.math.log(x) # 对数运算
矩阵相乘运算
-
神经网络中间包含了大量的矩阵相乘运算,前面我们已经介绍了通过@运算符可以方便的实现矩阵相乘,还可以通过 tf.matmul(a, b)函数实现。需要注意的是, TensorFlow 中的
矩阵相乘可以使用批量方式,也就是张量𝑨和𝑩的维度数可以大于 2。当张量𝑨和𝑩维度数大
于 2 时, TensorFlow 会选择𝑨和𝑩的最后两个维度进行矩阵相乘,前面所有的维度都视作
Batch 维度。 -
矩阵相乘函数同样支持自动 Broadcasting 机制。
-
a = tf.random.normal([4,28,32]) b = tf.random.normal([32,16]) tf.matmul(a,b) # 先自动扩展,再矩阵相乘
TensorFlow进阶
合并与分割
合并
-
张量的合并可以使用拼接(Concatenate)和堆叠(Stack)操作实现,拼接操作并不会产生新
的维度, 仅在现有的维度上合并, 而堆叠会创建新维度。 选择使用拼接还是堆叠操作来合并张量,取决于具体的场景是否需要创建新维度。 -
拼接
-
在 TensorFlow 中,可以通过 tf.concat(tensors, axis)函数拼接张量,其中参数
tensors 保存了所有需要合并的张量 List, axis 参数指定需要合并的维度索引。 -
a = tf.random.normal([4,35,8]) # 模拟成绩册 A b = tf.random.normal([6,35,8]) # 模拟成绩册 B tf.concat([a,b],axis=0) # 拼接合并成绩册 # <tf.Tensor: id=13, shape=(10, 35, 8), dtype=float32, numpy= # array([[[ 1.95299834e-01, 6.87859178e-01, -5.80048323e-01, ..., # 1.29430830e+00, 2.56610274e-01, -1.27798581e+00], # [ 4.29753691e-01, 9.11329567e-01, -4.47975427e-01, ...,
-
从语法上来说,拼接合并操作可以在任意的维度上进行,唯一的约束是非合并维度的
长度必须一致 。
-
-
堆叠
-
拼接操作直接在现有维度上合并数据, 并不会创建新的维度。 如果在合并数据
时,希望创建一个新的维度,则需要使用 tf.stack 操作。考虑张量𝑨保存了某个班级的成绩册, shape 为[35,8], 张量𝑩保存了另一个班级的成绩册, shape 为[35,8]。合并这 2 个班级的数据时, 则需要创建一个新维度,定义为班级维度, 新维度可以选择放置在任意位置,一般根据大小维度的经验法则,将较大概念的班级维度放置在学生维度之前, 则合并后的张量的新 shape 应为[2,35,8]。 -
a = tf.random.normal([35,8]) b = tf.random.normal([35,8]) tf.stack([a,b],axis=0) # 堆叠合并为 2 个班级,班级维度插入在最前
-
分隔
- 通过 tf.split(x, num_or_size_splits, axis)可以完成张量的分割操作, 参数意义如下
- x 参数:待分割张量。
- num_or_size_splits 参数:切割方案。当 num_or_size_splits 为单个数值时,如 10,表示等长切割为 10 份;当 num_or_size_splits 为 List 时, List 的每个元素表示每份的长度,如[2,4,2,2]表示切割为 4 份,每份的长度依次是 2、 4、 2、 2。
- axis 参数:指定分割的维度索引号。
数据统计
向量范数
-
向量范数(Vector Norm)是表征向量“长度”的一种度量方法, 它可以推广到张量上。
在神经网络中,常用来表示张量的权值大小,梯度大小等。常用的向量范数有: -
在 TensorFlow 中,可以通过 tf.norm(x, ord)求解张量的 L1、 L2、 ∞等范数,其中参数
ord 指定为 1、 2 时计算 L1、 L2 范数,指定为 np.inf 时计算∞ -范数,例如: -
x = tf.ones([2,2]) tf.norm(x,ord=1) # 计算 L1 范数 tf.norm(x,ord=2) # 计算 L2 范数 import numpy as np tf.norm(x,ord=np.inf) # 计算∞范数
最值、均值、和
-
通过 tf.reduce_max、 tf.reduce_min、 tf.reduce_mean、 tf.reduce_sum 函数可以求解张量
在某个维度上的最大、最小、 均值、和,也可以求全局最大、最小、均值、和信息。 -
x = tf.random.normal([4,10]) # 模型生成概率 tf.reduce_max(x,axis=1) # 统计概率维度上的最大值 tf.reduce_min(x,axis=1) # 统计概率维度上的最小值 tf.reduce_mean(x,axis=1) # 统计概率维度上的均值 #当不指定 axis 参数时, tf.reduce_*函数会求解出全局元素的最大、最小、 均值、和等数据 # 统计全局的最大、最小、均值、和,返回的张量均为标量 tf.reduce_max(x),tf.reduce_min(x),tf.reduce_mean(x) #tf.reduce_sum(x, axis), 它可以求解张量在 axis 轴上所有特征的和 tf.reduce_sum(out,axis=-1) # 求最后一个维度的和 #通过 tf.argmax(x, axis)和 tf.argmin(x, axis)可以求解在 axis 轴上, x 的最大值、 最小值所在的索引号 pred = tf.argmax(out, axis=1) # 选取概率最大的位置
张量比较
-
为了计算分类任务的准确率等指标,一般需要将预测结果和真实标签比较,统计比较
结果中正确的数量来计算准确率。 -
通过 tf.equal(a, b)(或 tf.math.equal(a,b),两者等价)函数可以比较这 2 个张量是否相等,例如:
-
out = tf.equal(pred,y) # 预测值与真实值比较,返回布尔类型的张量 #tf.equal()函数返回布尔类型的张量比较结果,只需要统计张量中 True 元素的个数,即可知道预测正确的个数。为了达到这个目的,我们先将布尔类型转换为整形张量,即 True 对应为 1, False 对应为 0, 再求和其中 1 的个数, 就可以得到比较结果中 True 元素的个数: out = tf.cast(out, dtype=tf.float32) # 布尔型转 int 型 correct = tf.reduce_sum(out) # 统计 True 的个数
填充与复制
填充
-
重复复制数据会破坏原有的数据结构,并不适合于此处。通常的做法是,在需要补充长度的数据开始或结束处填充足够数量的特定数值, 这些特定数值一般代表了无效意义,例如 0,使得填充后的长度满足系统要求。那么这种操作就叫作填充(Padding)。
-
填充操作可以通过 tf.pad(x, paddings)函数实现, 参数 paddings 是包含了多个[Left Padding, Right Padding]的嵌套方案 List,如[[0,0], [2,1], [1,2]]表示第一个维度不填充, 第二个维度左边(起始处)填充两个单元, 右边(结束处)填充一个单元, 第三个维度左边填充一个单元, 右边填充两个单元。
-
a = tf.constant([1,2,3,4,5,6]) # 第一个句子 b = tf.constant([7,8,1,6]) # 第二个句子 b = tf.pad(b, [[0,2]]) # 句子末尾填充 2 个 0
-
上述填充方案可以表达为[[0,0], [2,2], [2,2], [0,0]], 实现如下:
-
x = tf.random.normal([4,28,28,1]) # 图片上下、左右各填充 2 个单元 tf.pad(x,[[0,0],[2,2],[2,2],[0,0]])
复制
- 通过 tf.tile 函数可以在任意维度将数据重复复制多份,如 shape 为[4,32,32,3]的数据,
复制方案为 multiples=[2,3,3,1],即通道数据不复制,高和宽方向分别复制 2 份,图片数再复制 1 份,实现如下:
数据限幅
-
在 TensorFlow 中,可以通过 tf.maximum(x, a)实现数据的下限幅,即𝑥 ∈ [𝑎, +∞);可
以通过 tf.minimum(x, a)实现数据的上限幅,即𝑥 ∈ (-∞, 𝑎],举例如下: -
x = tf.range(9) tf.maximum(x,2) # 下限幅到 2 tf.minimum(x,7) # 上限幅到 7 基于 tf.maximum 函数,我们可以实现 ReLU 函数如下: def relu(x): # ReLU 函数 return tf.maximum(x,0.) # 下限幅为 0 即可 tf.minimum(tf.maximum(x,2),7) # 限幅为 2~7 更方便地,我们可以使用 tf.clip_by_value 函数实现上下限幅: x = tf.range(9) tf.clip_by_value(x,2,7) # 限幅为 2~7
高级操作
tf.gather
-
tf.gather 可以实现根据索引号收集数据的目的。考虑班级成绩册的例子, 假设共有 4个班级,每个班级 35 个学生, 8 门科目,保存成绩册的张量 shape 为[4,35,8]。
-
x = tf.random.uniform([4,35,8],maxval=100,dtype=tf.int32) # 成绩册张量 tf.gather(x,[0,1],axis=0) # 在班级维度收集第 1~2 号班级成绩册 tf.gather(x,[0,3,8,11,12,26],axis=1)# 收集第 1,4,9,12,13,27 号同学成绩 tf.gather(x,[2,4],axis=2) # 第 3, 5 科目的成绩 其中索引号可以乱序排列,此时收集的数据也是对应顺序,例如: tf.gather(a,[3,1,0,2],axis=0) # 收集第 4,2,1,3 号元素
tf.gather_nd
-
通过 tf.gather_nd 函数,可以通过指定每次采样点的多维坐标来实现采样多个点的目
的。 -
# 根据多维坐标收集数据 tf.gather_nd(x,[[1,1],[2,2],[3,3]])
-
一般地,在使用 tf.gather_nd 采样多个样本时, 例如希望采样𝑖号班级, 𝑗个学生, 𝑘门
科目的成绩,则可以表达为[. . . , [𝑖, 𝑗, 𝑘], . . . ], 外层的括号长度为采样样本的个数,内层列表包含了每个采样点的索引坐标。
tf.boolean_mask
-
除了可以通过给定索引号的方式采样,还可以通过给定掩码(Mask)的方式进行采样。
-
mask = [True, False, False, True] tf.boolean_mask(x,mask=[True, False,False,True],axis=0)
tf.where
-
通过 tf.where(cond, a, b)操作可以根据 cond 条件的真假从参数𝑨或𝑩中读取数据, 条件判定规则如下:
-
其中𝑖为张量的元素索引, 返回的张量大小与𝑨和𝑩一致, 当对应位置的cond𝑖为 True, 𝑜𝑖从𝑎𝑖中复制数据;当对应位置的cond𝑖为 False, 𝑜𝑖从𝑏𝑖中复制数据。
-
a = tf.ones([3,3]) # 构造 a 为全 1 矩阵 b = tf.zeros([3,3]) # 构造 b 为全 0 矩阵 # 构造采样条件 cond = tf.constant([[True,False,False],[False,True,False],[True,True,False]]) tf.where(cond,a,b) # 根据条件从 a,b 中采样
-
当参数 a=b=None 时, 即 a 和 b 参数不指定, tf.where 会返回 cond 张量中所有 True 的元素的索引坐标。
scatter_nd
-
通过 tf.scatter_nd(indices, updates, shape)函数可以高效地刷新张量的部分数据,但是这个函数只能在全 0 的白板张量上面执行刷新操作,因此可能需要结合其它操作来实现现有张量的数据刷新功能
-
# 构造需要刷新数据的位置参数,即为 4、 3、 1 和 7 号位置 indices = tf.constant([[4], [3], [1], [7]]) # 构造需要写入的数据, 4 号位写入 4.4,3 号位写入 3.3,以此类推 updates = tf.constant([4.4, 3.3, 1.1, 7.7]) # 在长度为 8 的全 0 向量上根据 indices 写入 updates 数据 tf.scatter_nd(indices, updates, [8]) Out:<tf.Tensor: id=467, shape=(8,), dtype=float32, numpy=array([0. , 1.1, 0. , 3.3, 4.4, 0. , 0. , 7.7], dtype=float32)>
meshgrid
-
通过在 x 轴上进行采样 100 个数据点, y 轴上采样 100 个数据点,然后利用
tf.meshgrid(x, y)即可返回这 10000 个数据点的张量数据, 保存在 shape 为[100,100,2]的张量中。为了方便计算, tf.meshgrid 会返回在 axis=2 维度切割后的 2 个张量𝑨和𝑩,其中张量𝑨包含了所有点的 x 坐标, 𝑩包含了所有点的 y 坐标, shape 都为[100,100],实现如下: -
x = tf.linspace(-8.,8,100) # 设置 x 轴的采样点 y = tf.linspace(-8.,8,100) # 设置 y 轴的采样点 x,y = tf.meshgrid(x,y) # 生成网格点,并内部拆分后返回 x.shape,y.shape # 打印拆分后的所有点的 x,y 坐标张量 shape Out: (TensorShape([100, 100]), TensorShape([100, 100]))
经典数据集加载
对于常用的经典数据集, 例如:
❑ Boston Housing, 波士顿房价趋势数据集,用于回归模型训练与测试。
❑ CIFAR10/100, 真实图片数据集,用于图片分类任务。
❑ MNIST/Fashion_MNIST, 手写数字图片数据集,用于图片分类任务。
❑ IMDB, 情感分类任务数据集,用于文本分类任务。
通过 datasets.xxx.load_data()函数即可实现经典数据集的自动加载,其中 xxx 代表具体
的数据集名称,如“CIFAR10”、“MNIST”。 TensorFlow 会默认将数据缓存在用户目录下
的.keras/datasets 文件夹,
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import datasets # 导入经典数据集加载模块
# 加载 MNIST 数据集
(x, y), (x_test, y_test) = datasets.mnist.load_data()
print('x:', x.shape, 'y:', y.shape, 'x test:', x_test.shape, 'y test:',
y_test)
-
数据加载进入内存后,需要转换成 Dataset 对象, 才能利用 TensorFlow 提供的各种便
捷功能。通过 Dataset.from_tensor_slices 可以将训练部分的数据图片 x 和标签 y 都转换成
Dataset 对象: -
train_db = tf.data.Dataset.from_tensor_slices((x, y)) # 构建 Dataset 对象
随机打散
-
通过 Dataset.shuffle(buffer_size)工具可以设置 Dataset 对象随机打散数据之间的顺序,
防止每次训练时数据按固定顺序产生,从而使得模型尝试“记忆”住标签信息,代码实现
如下: -
train_db = train_db.shuffle(10000) # 随机打散样本,不会打乱样本与标签映射关系
批训练
-
为了利用显卡的并行计算能力,一般在网络的计算过程中会同时计算多个样本,我们
把这种训练方式叫做批训练,其中一个批中样本的数量叫做 Batch Size。为了一次能够从
Dataset 中产生 Batch Size 数量的样本,需要设置 Dataset 为批训练方式,实现如下: -
train_db = train_db.batch(128) # 设置批训练, batch size 为 128
预处理
-
从 keras.datasets 中加载的数据集的格式大部分情况都不能直接满足模型的输入要求,
因此需要根据用户的逻辑自行实现预处理步骤。 Dataset 对象通过提供 map(func)工具函
数, 可以非常方便地调用用户自定义的预处理逻辑, 它实现在 func 函数里。例如,下方代
码调用名为 preprocess 的函数完成每个样本的预处理 -
# 预处理函数实现在 preprocess 函数中,传入函数名即可 train_db = train_db.map(preprocess)
循环训练
-
for epoch in range(20): # 训练 Epoch 数 for step, (x,y) in enumerate(train_db): # 迭代 Step 数 # training... #此外, 也可以通过设置 Dataset 对象,使得数据集对象内部遍历多次才会退出,实现如下: train_db = train_db.repeat(20) # 数据集迭代 20 遍才终止
总结
TensorFlow基本语句,和张量概念,一些预处理函数,但无整体神经网络搭建逻辑。