- 教程
- 深度学习
- 机器学习
【免费下载链接】eat_tensorflow2_in_30_days
Tensorflow2.0 🍎🍊 is delicious, just eat it! 😋😋
张量(Tensor)是 TensorFlow 中最基本的数据结构,TensorFlow 程序可以概括为「张量数据结构 + 图中的算法」。张量操作分为结构操作与数学运算两大类:结构操作包括张量创建、索引切片、维度变换、合并分割;数学运算包括标量运算、向量运算、矩阵运算及广播机制。本文以开源仓库 eat_tensorflow2_in_30_days 的 english/Chapter4-1.md(对应中文版 4-1,张量的结构操作.md)为主体,系统讲解张量结构操作的全部 API,并结合仓库中低阶 API(english/Chapter3-1.md)、中阶 API(english/Chapter3-2.md)等章节的真实代码,展示这些操作在数据管道、模型训练与评估中的落地用法。读完本文,你将掌握用 TensorFlow 2 完成张量创建、任意切片、维度重排与合并分割的完整工具箱,并能直接复用文中代码。
一、先看全局:结构操作与数学运算的边界
在仓库中,Tensor 操作被明确划分为两大分支(见 english/Chapter4.md 章节导言):
- 结构操作:张量创建、索引切片、维度变换、合并分割;
- 数学运算:标量运算、向量运算、矩阵运算、广播机制(见 english/Chapter4-2.md)。
如果把模型比作一栋房子,这些低阶 API 就是"砖块"。TensorFlow 提供的张量操作方法比 numpy 更完备、执行效率更高,且在必要时可被 GPU 进一步加速。本章(Chapter 4)与第 3 章(english/Chapter3-1.md 低阶 API 示范)同属"低阶 API"体系,本章更侧重系统性地讲解张量操作本身。
运行环境说明:仓库所有示例在 Jupyter 中测试通过,代码基于 TensorFlow 2.1(见 README_eng.md 的环境说明);可用 jupytext 将 Markdown 文件转换为 ipynb 后在 Jupyter 中交互运行。
二、创建张量:与 numpy 一一对应的构造器
张量创建的许多方法与 numpy 创建 array 的方法极其相似。首先引入依赖:
import tensorflow as tf import numpy as np1. 从数值/序列直接构造
a = tf.constant([1,2,3],dtype = tf.float32) tf.print(a)输出:
[1 2 3]tf.constant是创建常量张量的基础方法,dtype参数可显式指定数据类型。关于张量的数据类型与阶(rank),可参见 english/Chapter2-1.md:常量张量的值在图中不能被重新赋值,而tf.Variable可以通过assign等操作重新赋值。
2. 等差序列与等间距序列
b = tf.range(1,10,delta = 2) tf.print(b)输出:
[1 3 5 7 9]tf.range(start, limit, delta)遵循左闭右开区间[start, limit),步长为delta,因此 1 到 10(不含)以 2 为步长得到 5 个元素。
c = tf.linspace(0.0,2*3.14,100) tf.print(c)输出:
[0 0.0634343475 0.126868695 ... 6.15313148 6.21656609 6.28]与tf.range不同,tf.linspace(start, stop, num)是闭区间均匀取点,在[0, 2π]上均匀取出 100 个点——这是画三角函数曲线、构造正弦输入时最常用的序列生成方式。
3. 全 0 / 全 1 / 填充张量
d = tf.zeros([3,3]) tf.print(d)输出:
[[0 0 0] [0 0 0] [0 0 0]]a = tf.ones([3,3]) b = tf.zeros_like(a,dtype= tf.float32) tf.print(a) tf.print(b)输出:
[[1 1 1] [1 1 1] [1 1 1]] [[0 0 0] [0 0 0] [0 0 0]]b = tf.fill([3,2],5) tf.print(b)输出:
[[5 5] [5 5] [5 5]]要点对照(与 numpy 语义一致):
| 方法 | 语义 | 关键参数 |
|---|---|---|
tf.zeros(shape) | 全 0 张量 | shape可以是列表[3,3] |
tf.ones(shape) | 全 1 张量 | shape |
tf.zeros_like(x, dtype=...) | 形状与x相同的全 0 张量 | 可另指定dtype |
tf.fill(shape, value) | 用标量value填充 | 适合生成形状确定的填充张量 |
4. 随机张量:均匀、正态、截断正态
# 均匀分布随机 tf.random.set_seed(1.0) a = tf.random.uniform([5],minval=0,maxval=10) tf.print(a)输出:
[1.65130854 9.01481247 6.30974197 4.34546089 2.9193902]tf.random.uniform(shape, minval, maxval)在[minval, maxval)上采样;tf.random.set_seed用于固定随机种子,保证结果可复现。
# 正态分布随机 b = tf.random.normal([3,3],mean=0.0,stddev=1.0) tf.print(b)输出:
[[0.403087884 -1.0880208 -0.0630953535] [1.33655667 0.711760104 -0.489286453] [-0.764221311 -1.03724861 -1.25193381]]# 正态分布随机,剔除2倍方差以外数据重新生成 c = tf.random.truncated_normal((5,5), mean=0.0, stddev=1.0, dtype=tf.float32) tf.print(c)输出:
[[-0.457012236 -0.406867266 0.728577733 -0.892977774 -0.369404584] [0.323488563 1.19383323 0.888299048 1.25985599 -1.95951891] [-0.202244401 0.294496894 -0.468728036 1.29494202 1.48142183] [0.0810953453 1.63843894 0.556645 0.977199793 -1.17777884] [1.67368948 0.0647980496 -0.705142677 -0.281972528 0.126546144]]tf.random.truncated_normal与tf.random.normal的区别在于:前者会剔除超出均值 2 倍标准差以外的样本并重新生成,从而避免出现极端的离群初始化值——这在深度网络参数初始化中非常实用,能有效抑制早期梯度爆炸。
5. 特殊矩阵:单位阵与对角阵
# 特殊矩阵 I = tf.eye(3,3) #单位矩阵 tf.print(I) tf.print(" ") t = tf.linalg.diag([1,2,3]) #对角阵 tf.print(t)输出:
[[1 0 0] [0 1 0] [0 0 1]] [[1 0 0] [0 2 0] [0 0 3]]tf.eye(N, M)生成单位矩阵,tf.linalg.diag(diagonal)以向量为对角线生成对角矩阵。这类矩阵在正则化项、线性代数运算中频繁出现;仓库 english/Chapter4-2.md 的矩阵运算章节就使用了tf.linalg.diag构造奇异值对角阵以完成 SVD 分解验证。
三、索引切片:从规则切片到不规则提取
张量的索引切片方式与 numpy 几乎一样,切片时支持缺省参数和省略号(...)。先构造一个 5×5 的随机整数张量作为演示对象:
tf.random.set_seed(3) t = tf.random.uniform([5,5],minval=0,maxval=10,dtype=tf.int32) tf.print(t)输出:
[[4 7 4 2 9] [9 1 2 4 7] [7 2 7 4 0] [9 6 9 7 2] [3 7 0 0 3]]1. 基础下标与负索引
# 第0行 tf.print(t[0])输出:
[4 7 4 2 9]# 倒数第一行 tf.print(t[-1])输出:
[3 7 0 0 3]# 第1行第3列 tf.print(t[1,3]) tf.print(t[1][3])输出:
4 4t[1,3]与t[1][3]完全等价,说明张量支持逗号分隔的多维下标。
2. 连续区域切片:切片语法与 tf.slice
# 第1行至第3行 tf.print(t[1:4,:]) tf.print(tf.slice(t,[1,0],[3,5])) #tf.slice(input,begin_vector,size_vector)输出:
[[9 1 2 4 7] [7 2 7 4 0] [9 6 9 7 2]] [[9 1 2 4 7] [7 2 7 4 0] [9 6 9 7 2]]t[1:4,:]与tf.slice(t,[1,0],[3,5])输出完全一致。tf.slice(input, begin_vector, size_vector)的参数含义为:起始坐标向量[1,0]与尺寸向量[3,5]。对于提取张量的连续子区域,官方推荐使用tf.slice。
# 第1行至最后一行,第0列到最后一列每隔两列取一列 tf.print(t[1:4,:4:2])输出:
[[9 2] [7 7] [9 9]]t[1:4,:4:2]同时演示了行方向的范围切片(第 1 行到第 3 行)与列方向的步长切片(第 0 列到第 3 列、每隔 2 列取一列)。
3. 对 tf.Variable 的索引赋值
# 对变量来说,还可以使用索引和切片修改部分元素 x = tf.Variable([[1,2],[3,4]],dtype = tf.float32) x[1,:].assign(tf.constant([0.0,0.0])) tf.print(x)输出:
[[1 2] [0 0]]tf.Variable支持通过索引和切片配合.assign()修改部分元素的值,这是常量张量不具备的能力,也是模型训练中就地更新参数(如某些自定义层)的基础手段。
4. 省略号代表多个冒号
a = tf.random.uniform([3,3,3],minval=0,maxval=10,dtype=tf.int32) tf.print(a)输出:
[[[7 3 9] [9 0 7] [9 6 7]] [[1 3 3] [0 8 1] [3 1 0]] [[4 0 6] [6 2 2] [7 9 5]]]# 省略号可以表示多个冒号 tf.print(a[...,1])输出:
[[3 0 6] [3 8 1] [0 2 9]]a[...,1]等价于a[:,:,1]:省略号...自动展开为补齐中间所有维度所需的冒号,在操作高维张量(如取所有通道的某个分量)时非常省事。
5. 不规则切片:tf.gather / tf.gather_nd / tf.boolean_mask
以上切片方式相对规则。对于不规则的切片提取,可以使用tf.gather、tf.gather_nd、tf.boolean_mask。其中tf.boolean_mask功能最为强大:它可以实现tf.gather、tf.gather_nd的功能,并且还支持布尔索引。
考虑班级成绩册的例子:有 4 个班级,每个班级 10 个学生,每个学生 7 门科目成绩,可用一个4×10×7的张量表示:
scores = tf.random.uniform((4,10,7),minval=0,maxval=100,dtype=tf.int32) tf.print(scores)输出(节选):
[[[52 82 66 ... 17 86 14] [8 36 94 ... 13 78 41] [77 53 51 ... 22 91 56] ... [24 99 38 ... 97 44 74]] [[79 73 73 ... 35 3 81] [83 36 31 ... 75 38 85] [54 26 67 ... 60 68 98] ... [0 21 89 ... 53 10 90]] ...用tf.gather按某个维度抽取不连续的下标:
# 抽取每个班级第0个学生,第5个学生,第9个学生的全部成绩 p = tf.gather(scores,[0,5,9],axis=1) tf.print(p)输出:
[[[52 82 66 ... 17 86 14] [24 80 70 ... 72 63 96] [24 99 38 ... 97 44 74]] [[79 73 73 ... 35 3 81] [46 10 94 ... 23 18 92] [0 21 89 ... 53 10 90]] ...tf.gather(params, indices, axis)沿指定axis抽取下标列表对应的子张量,下标可以是不连续的、任意顺序的。这里axis=1表示沿"学生"维度抽取第 0、5、9 号学生。
# 抽取每个班级第0个学生,第5个学生,第9个学生的第1门课程,第3门课程,第6门课程成绩 q = tf.gather(tf.gather(scores,[0,5,9],axis=1),[1,3,6],axis=2) tf.print(q)输出:
[[[82 55 14] [80 46 96] [99 58 74]] [[73 48 81] [10 38 92] [21 86 90]] ...tf.gather可以嵌套使用:先沿axis=1抽出目标学生,再沿axis=2抽出目标课程,最终得到 4×3×3 的成绩子集。
用tf.gather_nd按多维坐标批量取样:
# 抽取第0个班级第0个学生,第2个班级的第4个学生,第3个班级的第6个学生的全部成绩 # indices的长度为采样样本的个数,每个元素为采样位置的坐标 s = tf.gather_nd(scores,indices = [(0,0),(2,4),(3,6)]) s输出:
<tf.Tensor: shape=(3, 7), dtype=int32, numpy= array([[52, 82, 66, 55, 17, 86, 14], [99, 94, 46, 70, 1, 63, 41], [46, 83, 70, 80, 90, 85, 17]], dtype=int32)>tf.gather_nd的参数indices是一个坐标列表:列表长度等于采样样本个数,每个元素是采样位置的完整多维坐标(此处为"班级,学生"二元坐标),输出形状为(3, 7)——3 个样本,每个样本 7 门课成绩。
用tf.boolean_mask实现上述两种功能:
# 抽取每个班级第0个学生,第5个学生,第9个学生的全部成绩 p = tf.boolean_mask(scores,[True,False,False,False,False, True,False,False,False,True],axis=1) tf.print(p)输出:
[[[52 82 66 ... 17 86 14] [24 80 70 ... 72 63 96] [24 99 38 ... 97 44 74]] [[79 73 73 ... 35 3 81] [46 10 94 ... 23 18 92] [0 21 89 ... 53 10 90]] ...这是tf.boolean_mask的轴掩码用法:掩码长度为 10(与"学生"维度对齐),True的位置即被保留的位置,与tf.gather(scores,[0,5,9],axis=1)结果一致。
# 抽取第0个班级第0个学生,第2个班级的第4个学生,第3个班级的第6个学生的全部成绩 s = tf.boolean_mask(scores, [[True,False,False,False,False,False,False,False,False,False], [False,False,False,False,False,False,False,False,False,False], [False,False,False,False,True,False,False,False,False,False], [False,False,False,False,False,False,True,False,False,False]]) tf.print(s)输出:
[[52 82 66 ... 17 86 14] [99 94 46 ... 1 63 41] [46 83 70 ... 90 85 17]]这是tf.boolean_mask的全维掩码用法:掩码形状与scores前两维 4×10 完全一致,True的位置对应需要保留的坐标,效果等价于tf.gather_nd(scores, [(0,0),(2,4),(3,6)])。
布尔索引:tf.boolean_mask的语法糖
# 利用tf.boolean_mask可以实现布尔索引 # 找到矩阵中小于0的元素 c = tf.constant([[-1,1,-1],[2,2,-2],[3,-3,3]],dtype=tf.float32) tf.print(c,"\n") tf.print(tf.boolean_mask(c,c<0),"\n") tf.print(c[c<0]) # 布尔索引,为boolean_mask的语法糖形式输出:
[[-1 1 -1] [2 2 -2] [3 -3 3]] [-1 -1 -2 -3] [-1 -1 -2 -3]c[c<0]是tf.boolean_mask(c, c<0)的语法糖,直接把条件表达式作为掩码,筛选出所有满足条件的元素并展平返回。
6. 通过修改元素生成新张量:tf.where 与 tf.scatter_nd
以上这些方法仅能提取张量的部分元素值,但不能更改张量的部分元素值以得到新的张量。如果需要通过修改张量的部分元素值得到新张量,可以使用tf.where和tf.scatter_nd。
tf.where可以理解为 if 的张量版本;此外它还可以用于找到满足条件的所有元素的位置坐标。tf.scatter_nd的作用与tf.gather_nd有些相反:tf.gather_nd用于收集张量给定位置的元素,而tf.scatter_nd可以将某些值插入到给定 shape 的全 0 张量的指定位置处。
# 找到张量中小于0的元素,将其换成np.nan得到新的张量 # tf.where和np.where作用类似,可以理解为if的张量版本 c = tf.constant([[-1,1,-1],[2,2,-2],[3,-3,3]],dtype=tf.float32) d = tf.where(c<0,tf.fill(c.shape,np.nan),c) d输出:
<tf.Tensor: shape=(3, 3), dtype=float32, numpy= array([[nan, 1., nan], [ 2., 2., nan], [ 3., nan, 3.]], dtype=float32)>三参数形式的tf.where(condition, x, y)逐元素执行"if 条件成立取x,否则取y",这里把小于 0 的元素全部替换为np.nan。
# 如果where只有一个参数,将返回所有满足条件的位置坐标 indices = tf.where(c<0) indices输出:
<tf.Tensor: shape=(4, 2), dtype=int64, numpy= array([[0, 0], [0, 2], [1, 2], [2, 1]])>单参数形式的tf.where(condition)返回所有满足条件位置的坐标张量,这正是配合tf.scatter_nd、tf.gather_nd使用的"坐标生成器"。
# 将张量的第[0,0]和[2,1]两个位置元素替换为0得到新的张量 d = c - tf.scatter_nd([[0,0],[2,1]],[c[0,0],c[2,1]],c.shape) d输出:
<tf.Tensor: shape=(3, 3), dtype=float32, numpy= array([[ 0., 1., -1.], [ 2., 2., -2.], [ 3., 0., 3.]], dtype=float32)>这里先用tf.scatter_nd(indices, updates, shape)把[c[0,0], c[2,1]]两个值插到全 0 张量的[0,0]、[2,1]位置,再用原张量减去它,等效于"将这两个位置置 0"。
# scatter_nd的作用和gather_nd有些相反 # 可以将某些值插入到一个给定shape的全0的张量的指定位置处。 indices = tf.where(c<0) tf.scatter_nd(indices,tf.gather_nd(c,indices),c.shape)输出:
<tf.Tensor: shape=(3, 3), dtype=float32, numpy= array([[-1., 0., -1.], [ 0., 0., -2.], [ 0., -3., 0.]], dtype=float32)>tf.gather_nd(c, indices)先把负元素收集成一维向量,tf.scatter_nd(indices, updates, c.shape)再把这些值按原坐标插回全 0 张量——一收一放,正好演示了tf.gather_nd与tf.scatter_nd的互逆关系。
四、维度变换:reshape、squeeze、expand_dims、transpose
维度变换相关函数主要有tf.reshape、tf.squeeze、tf.expand_dims、tf.transpose:
| 函数 | 作用 |
|---|---|
tf.reshape | 改变张量的形状 |
tf.squeeze | 减少维度(消除长度为 1 的维) |
tf.expand_dims | 增加维度(插入长度为 1 的维) |
tf.transpose | 交换维度 |
1. tf.reshape:不改变元素存储顺序,极快且可逆
tf.reshape可以改变张量的形状,但其本质上不会改变张量元素的存储顺序,所以该操作实际上非常迅速,并且是可逆的。
a = tf.random.uniform(shape=[1,3,3,2], minval=0,maxval=255,dtype=tf.int32) tf.print(a.shape) tf.print(a)输出:
TensorShape([1, 3, 3, 2]) [[[[135 178] [26 116] [29 224]] [[179 219] [153 209] [111 215]] [[39 7] [138 129] [59 205]]]]# 改成 (3,6)形状的张量 b = tf.reshape(a,[3,6]) tf.print(b.shape) tf.print(b)输出:
TensorShape([3, 6]) [[135 178 26 116 29 224] [179 219 153 209 111 215] [39 7 138 129 59 205]]# 改回成 [1,3,3,2] 形状的张量 c = tf.reshape(b,[1,3,3,2]) tf.print(c)输出:
[[[[135 178] [26 116] [29 224]] [[179 219] [153 209] [111 215]] [[39 7] [138 129] [59 205]]]]从输出可见,[1,3,3,2]→[3,6]→[1,3,3,2]的两次 reshape 完全复原了数据,元素值及其相对顺序没有任何变化。约束条件:reshape 前后元素总数必须一致(此处 1×3×3×2 = 18 = 3×6)。另外可借助-1让 TensorFlow 自动推断某一维的长度,例如tf.reshape(a, [3,-1])。
2. tf.squeeze:消除长度为 1 的维度
如果张量在某个维度上只有一个元素,利用tf.squeeze可以消除这个维度。和tf.reshape相似,它本质上不会改变张量元素的存储顺序。张量的各个元素在内存中是线性存储的,其一般规律是:同一层级中的相邻元素的物理地址也相邻。
s = tf.squeeze(a) tf.print(s.shape) tf.print(s)输出:
TensorShape([3, 3, 2]) [[[135 178] [26 116] [29 224]] [[179 219] [153 209] [111 215]] [[39 7] [138 129] [59 205]]]tf.squeeze默认消除所有长度为 1 的维度,也可用tf.squeeze(x, axis=[0])精确指定只消除某个轴。这在模型输出形状为(1, N)需要降为(N,)时非常常用——仓库 english/Chapter3-1.md 中就用tf.squeeze(model(X)>=0.5)把(batch,1)的预测掩码压缩成一维后配合tf.boolean_mask使用。
3. tf.expand_dims:插入长度为 1 的新维度
d = tf.expand_dims(s,axis=0) # 在第0维插入长度为1的一个维度 d输出:
<tf.Tensor: shape=(1, 3, 3, 2), dtype=int32, numpy= array([[[[135, 178], [ 26, 116], [ 29, 224]], [[179, 219], [153, 209], [111, 215]], [[ 39, 7], [138, 129], [ 59, 205]]]], dtype=int32)>tf.expand_dims(s, axis=0)在第 0 维插入一个长度为 1 的新维度,形状由(3,3,2)变为(1,3,3,2),与tf.squeeze互为逆操作。这是给单样本数据补 batch 维度、给向量补"特征维"(如(N,)→(N,1))的标准手段。
4. tf.transpose:交换维度并改变存储顺序
tf.transpose可以交换张量的维度,与tf.reshape不同,它会改变张量元素的存储顺序。tf.transpose常用于图片存储格式的变换上。
# Batch,Height,Width,Channel a = tf.random.uniform(shape=[100,600,600,4],minval=0,maxval=255,dtype=tf.int32) tf.print(a.shape) # 转换成 Channel,Height,Width,Batch s= tf.transpose(a,perm=[3,1,2,0]) tf.print(s.shape)输出:
TensorShape([100, 600, 600, 4]) TensorShape([4, 600, 600, 100])tf.transpose(a, perm=[3,1,2,0])的含义是:新张量的第 0 维取原张量的第 3 维(Channel),第 1、2 维保持 Height、Width 不变,第 3 维取原第 0 维(Batch),从而把NHWC(Batch, Height, Width, Channel,TensorFlow 默认图片布局)转换为CHWB(Channel, Height, Width, Batch)。这种转换在跨框架加载预训练权重、调整存储布局时经常需要。仓库 english/Chapter4-2.md 的 SVD 分解演示中也用到了tf.transpose(v)求右奇异向量的转置。
五、合并分割:tf.concat、tf.stack 与 tf.split
与 numpy 类似,可以用tf.concat和tf.stack方法对多个张量进行合并,用tf.split方法把一个张量分割成多个张量。
tf.concat和tf.stack有略微的区别:tf.concat是连接,不会增加维度;而tf.stack是堆叠,会增加维度。
a = tf.constant([[1.0,2.0],[3.0,4.0]]) b = tf.constant([[5.0,6.0],[7.0,8.0]]) c = tf.constant([[9.0,10.0],[11.0,12.0]]) tf.concat([a,b,c],axis = 0)输出:
<tf.Tensor: shape=(6, 2), dtype=float32, numpy= array([[ 1., 2.], [ 3., 4.], [ 5., 6.], [ 7., 8.], [ 9., 10.], [11., 12.]], dtype=float32)>tf.concat([a,b,c], axis=0)沿第 0 维(行方向)拼接,形状(2,2)+(2,2)+(2,2) → (6,2),维度数不变。
tf.concat([a,b,c],axis = 1)输出:
<tf.Tensor: shape=(2, 6), dtype=float32, numpy= array([[ 1., 2., 5., 6., 9., 10.], [ 3., 4., 7., 8., 11., 12.]], dtype=float32)>沿axis=1(列方向)拼接则得到(2,6)。注意:tf.concat要求所有待拼接张量在非拼接维度上的形状一致。
tf.stack([a,b,c])输出:
<tf.Tensor: shape=(3, 2, 2), dtype=float32, numpy= array([[[ 1., 2.], [ 3., 4.]], [[ 5., 6.], [ 7., 8.]], [[ 9., 10.], [11., 12.]]], dtype=float32)>tf.stack([a,b,c],axis=1)输出:
<tf.Tensor: shape=(2, 3, 2), dtype=float32, numpy= array([[[ 1., 2.], [ 5., 6.], [ 9., 10.]], [[ 3., 4.], [ 7., 8.], [11., 12.]]], dtype=float32)>tf.stack([a,b,c])默认沿新插入的第 0 维堆叠:(2,2)×3 → (3,2,2);指定axis=1则在中间插入新维:→ (2,3,2)。堆叠的本质是把一组张量"排成一层",因此必然增加一个维度。
tf.split是tf.concat的逆运算,可以指定分割份数平均分割,也可以通过指定每份的记录数量进行分割:
a = tf.constant([[1.0,2.0],[3.0,4.0]]) b = tf.constant([[5.0,6.0],[7.0,8.0]]) c = tf.constant([[9.0,10.0],[11.0,12.0]]) c = tf.concat([a,b,c],axis = 0)# tf.split(value,num_or_size_splits,axis) tf.split(c,3,axis = 0) # 指定分割份数,平均分割输出:
[<tf.Tensor: shape=(2, 2), dtype=float32, numpy= array([[1., 2.], [3., 4.]], dtype=float32)>, <tf.Tensor: shape=(2, 2), dtype=float32, numpy= array([[5., 6.], [7., 8.]], dtype=float32)>, <tf.Tensor: shape=(2, 2), dtype=float32, numpy= array([[ 9., 10.], [11., 12.]], dtype=float32)>]tf.split(c,[2,2,2],axis = 0) # 指定每份的记录数量输出:
[<tf.Tensor: shape=(2, 2), dtype=float32, numpy= array([[1., 2.], [3., 4.]], dtype=float32)>, <tf.Tensor: shape=(2, 2), dtype=float32, numpy= array([[5., 6.], [7., 8.]], dtype=float32)>, <tf.Tensor: shape=(2, 2), dtype=float32, numpy= array([[ 9., 10.], [11., 12.]], dtype=float32)>]tf.split(value, num_or_size_splits, axis)的第二个参数有两种传法:传整数3表示均分成 3 份;传列表[2,2,2]表示按每份的长度切分,各份长度之和必须等于该维的总长度(此处 2+2+2=6)。两种方式在本例中得到相同结果。tf.split返回的是张量列表。
六、结构操作在仓库项目中的真实应用
以上 API 并非孤立概念,而是贯穿仓库全部章节的基础能力。下面列举几个可直接追溯源码的真实用例。
1. 数据管道中的乱序取批:tf.gather
english/Chapter3-1.md 的低阶 API 线性回归示例中,作者手写了一个基于tf.gather的批数据生成器:
def data_iter(features, labels, batch_size=8): num_examples = len(features) indices = list(range(num_examples)) np.random.shuffle(indices) # 随机化样本读取顺序 for i in range(0, num_examples, batch_size): indexs = indices[i: min(i + batch_size, num_examples)] yield tf.gather(features,indexs), tf.gather(labels,indexs)先用np.random.shuffle打乱整数下标,再用tf.gather按下标批量取出特征与标签——这正是tf.gather在自定义训练循环中最重要的应用场景之一。
2. 构造分类数据集:tf.concat
同一章节(english/Chapter3-1.md)在构造 DNN 二分类样本时,把正负两类样本拼接成完整数据集:
Xp = tf.concat([r_p*tf.cos(theta_p),r_p*tf.sin(theta_p)],axis = 1) Xn = tf.concat([r_n*tf.cos(theta_n),r_n*tf.sin(theta_n)],axis = 1) X = tf.concat([Xp,Xn],axis = 0) Y = tf.concat([Yp,Yn],axis = 0)先沿axis=1把极坐标的半径与角度分量合成二维坐标,再沿axis=0拼接正负样本,得到用于训练的特征矩阵与标签向量。tf.concat的这种用法在 english/Chapter3-2.md 与 english/Chapter3-3.md 中完全一致地复现。
3. 预测阈值化:tf.where
english/Chapter3-1.md 在评估模型时用tf.where把连续概率输出转换为离散类别:
y_pred = tf.where(y_pred>0.5,tf.ones_like(y_pred,dtype = tf.float32), tf.zeros_like(y_pred,dtype = tf.float32))这正是"if 的张量版本":预测概率大于 0.5 置 1,否则置 0,全程无 Python 循环、可被图模式编译。
4. 按预测类别分离样本:tf.boolean_mask
english/Chapter3-1.md 在绘制分类边界时,用tf.boolean_mask配合布尔条件把正负样本分开着色:
Xp_pred = tf.boolean_mask(X,tf.squeeze(model(X)>=0.5),axis = 0) Xn_pred = tf.boolean_mask(X,tf.squeeze(model(X)<0.5),axis = 0)先tf.squeeze把(batch,1)的布尔掩码压成一维,再沿axis=0过滤样本——tf.squeeze与tf.boolean_mask的组合用法在这里体现得淋漓尽致。
5. 展平标签计算损失:tf.reshape
english/Chapter3-2.md 中,中阶 API 的自定义损失函数里用tf.reshape将预测与标签统一展平为[-1]后计算损失:
loss = model.loss_func(tf.reshape(labels,[-1]), tf.reshape(predictions,[-1]))-1让 TensorFlow 自动推断该维长度,这一写法在仓库的多个损失计算处(如 english/Chapter3-2.md)反复出现。
6. 指标计算中的按序重排:tf.gather
english/Chapter5-6.md 在实现排序类评估指标(如 AUC 类指标计算)时,用tf.gather按排序索引重排预测值与真实值:
y_pred_sorted = tf.gather(y_pred,t.indices) y_true_sorted = tf.gather(y_true,t.indices)tf.gather在这里承担了"按任意顺序取下标"的通用重排能力。
7. 矩阵分解中的转置:tf.transpose
english/Chapter4-2.md 数学运算章节在 SVD 分解后重建矩阵时使用tf.transpose(v)获取右奇异向量的转置,与本文介绍的维度交换操作一脉相承。
七、小结与延伸阅读
本文系统梳理了 TensorFlow 2 张量的全部结构操作:
- 创建:
tf.constant、tf.range、tf.linspace、tf.zeros/ones/zeros_like/fill、tf.random.uniform/normal/truncated_normal、tf.eye、tf.linalg.diag; - 索引切片:规则切片(下标、负索引、步长、省略号、
tf.slice、tf.Variable的assign)与不规则提取(tf.gather、tf.gather_nd、tf.boolean_mask); - 修改元素生成新张量:
tf.where(if 的张量版本)与tf.scatter_nd(tf.gather_nd的逆操作); - 维度变换:
tf.reshape(不改存储顺序、可逆)、tf.squeeze(降维)、tf.expand_dims(升维)、tf.transpose(交换维度并改变存储顺序,常用于图片布局转换); - 合并分割:
tf.concat(连接、不增维)、tf.stack(堆叠、增维)、tf.split(均分或按份数切分)。
在此基础上,可以继续阅读:
- english/Chapter4-2.md:张量的数学运算(标量、向量、矩阵运算与广播机制);
- english/Chapter3-1.md:低阶 API 示范(线性回归与 DNN 二分类的完整实现);
- english/Chapter2-1.md:张量数据类型与阶的详细说明;
- english/Chapter4-3.md:AutoGraph 使用规范(理解为何本教程统一使用
tf.print、tf.range等 TensorFlow 定义的函数而非原生 Python 函数); - README_eng.md:仓库学习路线与环境配置说明。
掌握结构操作,是流畅书写 TensorFlow 2 代码的基础功——无论是手写数据管道、自定义训练循环,还是实现排序类评估指标,这些操作都会反复出现,值得像熟悉 numpy 一样彻底掌握。
- 教程
- 深度学习
- 机器学习
【免费下载链接】eat_tensorflow2_in_30_days
Tensorflow2.0 🍎🍊 is delicious, just eat it! 😋😋
相关推荐
TensorFlow2 张量结构操作全解:创建、索引切片、维度变换与合并分割(《30天吃掉那只TensorFlow2》第4-1节)
TensorFlow2 张量结构操作全解:创建、索引切片、维度变换与合并分割(《30天吃掉那只TensorFlow2》第4 1节) 张量的结构操作是 Tenso
教程深度学习机器学习如何免费把QQ空间历史说说批量备份到本地
如何免费把QQ空间历史说说批量备份到本地 凌晨两点,你想找几张几年前旅行时发说说的配图,QQ空间的时间线却越刷越短,那一页早就不见了。GetQzonehisto
网页爬虫数据分析TensorFlow2 张量数据结构全解析:从常量、变量到多维张量(eat_tensorflow2_in_30_days 第 2-1 节)
TensorFlow2 张量数据结构全解析:从常量、变量到多维张量(eat_tensorflow2_in_30_days 第 2 1 节) 本文基于开源教程《e
教程深度学习机器学习
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考