1.3 张量操作 (Tensor Operations) 第一章:PyTorch 基础领域 - 1.3 张量操作 (Tensor Operations) 详解 在PyTorch的世界中,张量(Tensor)是构建一切的基石。它们是多维数组,可以表示各种类型的数据,从简单的数值到复杂的图像和文本。而 张量操作 (Tensor Operations) 则是我们操控和处理这些数据的核心手段。理解并熟练运用张量操作,是掌握PyTorch,进而进行深度学习模型构建和训练的关键一步。 1.3.1 张量操作的重要性 张量操作在深度学习中扮演着至关重要的角色,原因如下: 数据处理的基础: 深度学习模型处理的数据,无论是图像、文本还是音频,最终都会被表示为张量。
在PyTorch的世界中,张量(Tensor)是构建一切的基石。它们是多维数组,可以表示各种类型的数据,从简单的数值到复杂的图像和文本。而 张量操作 (Tensor Operations) 则是我们操控和处理这些数据的核心手段。理解并熟练运用张量操作,是掌握PyTorch,进而进行深度学习模型构建和训练的关键一步。
张量操作在深度学习中扮演着至关重要的角色,原因如下:
数据处理的基础: 深度学习模型处理的数据,无论是图像、文本还是音频,最终都会被表示为张量。张量操作允许我们对这些数据进行预处理、特征提取、数据增强等操作,为模型的训练做好准备。
模型构建的工具: 神经网络的每一层,本质上都是一系列张量操作的组合。例如,线性层、卷积层、循环层等,其核心运算都是基于张量操作实现的。
模型训练的关键: 反向传播算法依赖于梯度计算,而梯度计算又离不开张量操作。PyTorch的自动求导机制正是建立在对张量操作的追踪和微分的基础之上。
高效计算的保证: PyTorch的张量操作底层由高性能的C++库(如ATen, CUDA)实现,并针对GPU进行了优化,能够实现高效的数值计算,加速深度学习模型的训练和推理过程。
PyTorch 提供了丰富的张量操作,可以大致分为以下几类:
基本算术运算 (Basic Arithmetic Operations): 加法、减法、乘法、除法、取余、幂运算等。
数学函数 (Mathematical Functions): 三角函数、指数函数、对数函数、绝对值、取整函数、裁剪函数等。
归约操作 (Reduction Operations): 求和、均值、最大值、最小值、标准差、方差、范数等,将张量维度缩减的操作。
比较操作 (Comparison Operations): 大于、小于、等于、不等于、大于等于、小于等于等,返回布尔张量的操作。
逻辑操作 (Logical Operations): 与、或、非、异或等,对布尔张量进行操作。
形状操作 (Shape Manipulation Operations): 改变张量形状的操作,如 reshape, view, transpose, permute, squeeze, unsqueeze 等。
索引、切片和连接操作 (Indexing, Slicing and Joining Operations): 访问和操作张量特定元素或子张量,以及拼接多个张量的操作,如 index_select, masked_select, cat, stack, split, chunk 等。
线性代数操作 (Linear Algebra Operations): 矩阵乘法、转置、求逆、特征值分解、奇异值分解等。
广播机制 (Broadcasting): 允许不同形状的张量进行某些操作的机制。
原地操作 (In-place Operations): 直接修改张量本身的操作,通常以 _ 结尾,如 add_, mul_ 等。
接下来,我们将逐一详细介绍这些操作,并结合代码实践进行演示。
PyTorch 提供了基本的算术运算符和相应的函数,用于进行元素级别的算术运算。
运算符: +, -, *, /, // (整除), % (取余), ** (幂运算)
函数: torch.add(), torch.sub(), torch.mul(), torch.div(), torch.floor_divide(), torch.remainder(), torch.pow()
代码实践:
import torch # 创建两个张量 tensor1 = torch.tensor([[1, 2], [3, 4]]) tensor2 = torch.tensor([[5, 6], [7, 8]]) # 加法 add_result_operator = tensor1 + tensor2 add_result_function = torch.add(tensor1, tensor2) print("加法 (运算符):\n", add_result_operator) print("加法 (函数):\n", add_result_function) # 减法 sub_result_operator = tensor2 - tensor1 sub_result_function = torch.sub(tensor2, tensor1) print("\n减法 (运算符):\n", sub_result_operator) print("减法 (函数):\n", sub_result_function) # 乘法 (元素级别) mul_result_operator = tensor1 * tensor2 mul_result_function = torch.mul(tensor1, tensor2) print("\n乘法 (运算符):\n", mul_result_operator) print("乘法 (函数):\n", mul_result_function) # 除法 div_result_operator = tensor2 / tensor1 div_result_function = torch.div(tensor2, tensor1) print("\n除法 (运算符):\n", div_result_operator) print("除法 (函数):\n", div_result_function) # 幂运算 pow_result_operator = tensor1 ** 2 pow_result_function = torch.pow(tensor1, 2) print("\n幂运算 (运算符):\n", pow_result_operator) print("幂运算 (函数):\n", pow_result_function)
输出:
加法 (运算符): tensor([[ 6, 8], [10, 12]]) 加法 (函数): tensor([[ 6, 8], [10, 12]]) 减法 (运算符): tensor([[4, 4], [4, 4]]) 减法 (函数): tensor([[4, 4], [4, 4]]) 乘法 (运算符): tensor([[ 5, 12], [21, 32]]) 乘法 (函数): tensor([[ 5, 12], [21, 32]]) 除法 (运算符): tensor([[5.0000, 3.0000], [2.3333, 2.0000]]) 除法 (函数): tensor([[5.0000, 3.0000], [2.3333, 2.0000]]) 幂运算 (运算符): tensor([[ 1, 4], [ 9, 16]]) 幂运算 (函数): tensor([[ 1, 4], [ 9, 16]])
详解:
基本算术运算符和函数都执行元素级别的操作。这意味着运算是针对两个张量中对应位置的元素进行的。
运算符和函数通常可以互换使用,选择哪种方式取决于个人偏好和代码可读性。
PyTorch 还支持标量与张量之间的运算,标量会被广播到张量的每个元素上。
PyTorch 提供了丰富的数学函数,用于进行更复杂的数学运算。
常用函数: torch.sin(), torch.cos(), torch.tan(), torch.exp(), torch.log(), torch.sqrt(), torch.abs(), torch.round(), torch.clamp() 等。
代码实践:
import torch tensor = torch.tensor([[0, 1, -1], [2, -2, 3.14159]]) # 正弦函数 sin_result = torch.sin(tensor) print("正弦函数:\n", sin_result) # 指数函数 exp_result = torch.exp(tensor) print("\n指数函数:\n", exp_result) # 对数函数 (自然对数) log_result = torch.log(torch.abs(tensor) + 1e-6) # 避免对0取对数 print("\n对数函数:\n", log_result) # 绝对值 abs_result = torch.abs(tensor) print("\n绝对值:\n", abs_result) # 裁剪函数 (将数值限制在 [min, max] 范围内) clamp_result = torch.clamp(tensor, min=-1, max=2) print("\n裁剪函数:\n", clamp_result)
输出:
正弦函数: tensor([[ 0.0000, 0.8415, -0.8415], [ 0.9093, -0.9093, 0.0000]]) 指数函数: tensor([[1.0000, 2.7183, 0.3679], [7.3891, 0.1353, 23.1407]]) 对数函数: tensor([[ -13.8155, 0.0000, 0.0000], [ 0.6931, -13.8155, 1.1447]]) 绝对值: tensor([[0.0000, 1.0000, 1.0000], [2.0000, 2.0000, 3.1416]]) 裁剪函数: tensor([[ 0., 1., -1.], [ 2., -1., 2.]])
详解:
数学函数也都是元素级别的操作。
PyTorch 提供了大量的数学函数,涵盖了常用的科学计算需求。
在使用对数函数等可能产生特殊值(如 NaN, Inf)的函数时,需要注意数值稳定性,例如在计算 log(0) 时可能会导致错误,需要进行特殊处理。
归约操作用于将张量的维度缩减,例如求和、均值、最大值等。
常用函数: torch.sum(), torch.mean(), torch.max(), torch.min(), torch.std(), torch.var(), torch.norm() (范数) 等。
代码实践:
import torch tensor = torch.tensor([[1, 2, 3], [4, 5, 6]]) # 求和 sum_all = torch.sum(tensor) # 对所有元素求和 sum_dim0 = torch.sum(tensor, dim=0) # 沿维度0求和 (列求和) sum_dim1 = torch.sum(tensor, dim=1) # 沿维度1求和 (行求和) print("所有元素求和:", sum_all) print("沿维度0求和 (列求和):\n", sum_dim0) print("沿维度1求和 (行求和):\n", sum_dim1) # 均值 (需要转换为浮点型张量才能求均值) mean_all = torch.mean(tensor.float()) mean_dim0 = torch.mean(tensor.float(), dim=0) mean_dim1 = torch.mean(tensor.float(), dim=1) print("\n所有元素均值:", mean_all) print("沿维度0均值 (列均值):\n", mean_dim0) print("沿维度1均值 (行均值):\n", mean_dim1) # 最大值和索引 max_all = torch.max(tensor) max_dim0 = torch.max(tensor, dim=0) # 返回最大值和最大值索引 max_dim1 = torch.max(tensor, dim=1) print("\n所有元素最大值:", max_all) print("沿维度0最大值和索引:\n", max_dim0) print("沿维度1最大值和索引:\n", max_dim1)
输出:
所有元素求和: tensor(21) 沿维度0求和 (列求和): tensor([5, 7, 9]) 沿维度1求和 (行求和): tensor([ 6, 15]) 所有元素均值: tensor(3.5000) 沿维度0均值 (列均值): tensor([2.5000, 3.5000, 4.5000]) 沿维度1均值 (行均值): tensor([2., 5.]) 所有元素最大值: tensor(6) 沿维度0最大值和索引: torch.return_types.max( values=tensor([4, 5, 6]), indices=tensor([1, 1, 1])) 沿维度1最大值和索引: torch.return_types.max( values=tensor([3, 6]), indices=tensor([2, 2]))
详解:
归约操作可以对整个张量进行,也可以沿着指定的维度进行。
dim 参数用于指定要缩减的维度。例如,对于一个形状为 (m, n) 的张量,dim=0 表示沿着行方向进行归约,结果的形状变为 (n,);dim=1 表示沿着列方向进行归约,结果的形状变为 (m,)。
torch.max() 和 torch.min() 在指定 dim 时,会返回一个包含两个张量的元组,第一个张量是最大值/最小值,第二个张量是对应的索引。
求均值时需要将张量转换为浮点型,因为整数张量求均值可能会导致精度损失。
比较操作用于对张量进行元素级别的比较,返回布尔张量。
运算符: ==, !=, >, <, >=, <=
函数: torch.eq(), torch.ne(), torch.gt(), torch.lt(), torch.ge(), torch.le()
代码实践:
import torch tensor1 = torch.tensor([[1, 2], [3, 4]]) tensor2 = torch.tensor([[2, 2], [1, 4]]) # 等于 eq_result_operator = tensor1 == tensor2 eq_result_function = torch.eq(tensor1, tensor2) print("等于 (运算符):\n", eq_result_operator) print("等于 (函数):\n", eq_result_function) # 大于 gt_result_operator = tensor1 > tensor2 gt_result_function = torch.gt(tensor1, tensor2) print("\n大于 (运算符):\n", gt_result_operator) print("大于 (函数):\n", gt_result_function)
输出:
等于 (运算符): tensor([[False, True], [False, True]]) 等于 (函数): tensor([[False, True], [False, True]]) 大于 (运算符): tensor([[False, False], [ True, False]]) 大于 (函数): tensor([[False, False], [ True, False]])
详解:
比较操作符和函数都执行元素级别的比较。
返回的布尔张量中,True 表示对应位置的比较结果为真,False 表示为假。
布尔张量可以用于条件索引和逻辑操作。
逻辑操作用于对布尔张量进行逻辑运算。
运算符: & (与), | (或), ~ (非), ^ (异或)
函数: torch.logical_and(), torch.logical_or(), torch.logical_not(), torch.logical_xor()
代码实践:
import torch bool_tensor1 = torch.tensor([[True, False], [True, True]]) bool_tensor2 = torch.tensor([[False, True], [True, False]]) # 逻辑与 and_result_operator = bool_tensor1 & bool_tensor2 and_result_function = torch.logical_and(bool_tensor1, bool_tensor2) print("逻辑与 (运算符):\n", and_result_operator) print("逻辑与 (函数):\n", and_result_function) # 逻辑非 not_result_operator = ~bool_tensor1 not_result_function = torch.logical_not(bool_tensor1) print("\n逻辑非 (运算符):\n", not_result_operator) print("逻辑非 (函数):\n", not_result_function)
输出:
逻辑与 (运算符): tensor([[False, False], [ True, False]]) 逻辑与 (函数): tensor([[False, False], [ True, False]]) 逻辑非 (运算符): tensor([[False, True], [False, False]]) 逻辑非 (函数): tensor([[False, True], [False, False]])
详解:
逻辑操作符和函数都执行元素级别的逻辑运算。
逻辑操作常用于结合比较操作,进行更复杂的条件判断和数据筛选。
形状操作用于改变张量的形状,而不改变其元素内容。
常用函数: torch.reshape(), torch.view(), torch.transpose(), torch.permute(), torch.squeeze(), torch.unsqueeze()
代码实践:
import torch tensor = torch.arange(12) # 创建一个包含 0-11 的 1D 张量 print("原始张量:\n", tensor) print("原始形状:", tensor.shape) # reshape reshaped_tensor = tensor.reshape(3, 4) # 改变形状为 (3, 4) print("\nreshape 后的张量:\n", reshaped_tensor) print("reshape 后的形状:", reshaped_tensor.shape) # view (与 reshape 类似,但要求张量在内存中是连续的) viewed_tensor = tensor.view(4, 3) # 改变形状为 (4, 3) print("\nview 后的张量:\n", viewed_tensor) print("view 后的形状:", viewed_tensor.shape) # transpose (交换维度) transposed_tensor = reshaped_tensor.transpose(0, 1) # 交换维度 0 和维度 1 print("\ntranspose 后的张量:\n", transposed_tensor) print("transpose 后的形状:", transposed_tensor.shape) # squeeze (移除维度大小为 1 的维度) squeezed_tensor = torch.tensor([[[1, 2, 3]]]) # 形状为 (1, 1, 3) print("\nsqueeze 前的张量:\n", squeezed_tensor) print("squeeze 前的形状:", squeezed_tensor.shape) squeezed_tensor = squeezed_tensor.squeeze() # 移除所有大小为 1 的维度 print("\nsqueeze 后的张量:\n", squeezed_tensor) print("squeeze 后的形状:", squeezed_tensor.shape) # unsqueeze (在指定位置添加维度大小为 1 的维度) unsqueezed_tensor = tensor.unsqueeze(dim=0) # 在维度 0 的位置添加维度 print("\nunsqueeze 后的张量:\n", unsqueezed_tensor) print("unsqueeze 后的形状:", unsqueezed_tensor.shape)
输出:
原始张量: tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) 原始形状: torch.Size([12]) reshape 后的张量: tensor([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]) reshape 后的形状: torch.Size([3, 4]) view 后的张量: tensor([[ 0, 1, 2], [ 3, 4, 5], [ 6, 7, 8], [ 9, 10, 11]]) view 后的形状: torch.Size([4, 3]) transpose 后的张量: tensor([[ 0, 4, 8], [ 1, 5, 9], [ 2, 6, 10], [ 3, 7, 11]]) transpose 后的形状: torch.Size([4, 3]) squeeze 前的张量: tensor([[[1, 2, 3]]]) squeeze 前的形状: torch.Size([1, 1, 3]) squeeze 后的张量: tensor([1, 2, 3]) squeeze 后的形状: torch.Size([3]) unsqueeze 后的张量: tensor([[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]]) unsqueeze 后的形状: torch.Size([1, 12])
Graph TD 图示 reshape 操作:
详解:
reshape 和 view 都可以改变张量的形状,但 view 要求张量在内存中是连续存储的,而 reshape 可以处理不连续的张量。通常情况下,优先使用 view,如果遇到错误再尝试 reshape。
transpose 和 permute 用于交换张量的维度顺序。transpose 只能交换两个维度,permute 可以指定任意维度顺序。
squeeze 和 unsqueeze 用于移除或添加维度大小为 1 的维度。这在处理不同维度的数据时非常有用,例如,在卷积神经网络中,输入图像的形状通常是 (batch_size, channels, height, width),而某些层可能需要形状为 (batch_size, 1, height, width) 的输入,这时就可以使用 unsqueeze 添加通道维度。
这些操作用于访问和操作张量的特定元素或子张量,以及拼接多个张量。
常用操作:
索引 (Indexing): 使用索引访问张量的单个元素或多个元素。
切片 (Slicing): 使用切片访问张量的子张量。
高级索引 (Advanced Indexing): 使用整数张量或布尔张量进行索引。
连接 (Joining): torch.cat(), torch.stack()
分割 (Splitting): torch.split(), torch.chunk()
代码实践:
import torch tensor = torch.arange(12).reshape(3, 4) print("原始张量:\n", tensor) # 索引 element = tensor[0, 1] # 访问第 0 行,第 1 列的元素 print("\n索引元素:", element) # 切片 row_slice = tensor[0, :] # 切片第 0 行 col_slice = tensor[:, 1] # 切片第 1 列 sub_tensor = tensor[0:2, 1:3] # 切片子张量 print("\n行切片:\n", row_slice) print("列切片:\n", col_slice) print("子张量切片:\n", sub_tensor) # 高级索引 (使用整数张量) indices = torch.tensor([0, 2]) advanced_index_result = tensor[indices] # 选择第 0 行和第 2 行 print("\n高级索引 (整数张量):\n", advanced_index_result) # 连接 (cat) tensor_cat_dim0 = torch.cat([tensor, tensor], dim=0) # 沿维度 0 连接 tensor_cat_dim1 = torch.cat([tensor, tensor], dim=1) # 沿维度 1 连接 print("\n沿维度 0 连接:\n", tensor_cat_dim0) print("沿维度 1 连接:\n", tensor_cat_dim1) # 连接 (stack) tensor_stack_dim0 = torch.stack([tensor, tensor], dim=0) # 沿维度 0 堆叠 tensor_stack_dim1 = torch.stack([tensor, tensor], dim=1) # 沿维度 1 堆叠 print("\n沿维度 0 堆叠:\n", tensor_stack_dim0) print("沿维度 1 堆叠:\n", tensor_stack_dim1)
输出:
原始张量: tensor([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]) 索引元素: tensor(1) 行切片: tensor([0, 1, 2, 3]) 列切片: tensor([ 1, 5, 9]) 子张量切片: tensor([[1, 2], [5, 6]]) 高级索引 (整数张量): tensor([[0, 1, 2, 3], [8, 9, 10, 11]]) 沿维度 0 连接: tensor([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11], [ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]) 沿维度 1 连接: tensor([[ 0, 1, 2, 3, 0, 1, 2, 3], [ 4, 5, 6, 7, 4, 5, 6, 7], [ 8, 9, 10, 11, 8, 9, 10, 11]]) 沿维度 0 堆叠: tensor([[[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]], [[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]]) 沿维度 1 堆叠: tensor([[[ 0, 1, 2, 3], [ 0, 1, 2, 3]], [[ 4, 5, 6, 7], [ 4, 5, 6, 7]], [[ 8, 9, 10, 11], [ 8, 9, 10, 11]]])
详解:
PyTorch 的索引和切片操作与 NumPy 类似,可以使用整数索引、切片、冒号 : 等。
高级索引允许使用整数张量或布尔张量来选择更复杂的子张量。
torch.cat() 和 torch.stack() 用于连接多个张量。 torch.cat() 沿着现有维度连接,而 torch.stack() 会创建一个新的维度进行堆叠。理解它们的区别对于数据处理非常重要。
PyTorch 提供了常用的线性代数操作,用于矩阵运算和向量运算。
常用函数: torch.matmul() (矩阵乘法), torch.t() (转置), torch.inverse() (矩阵求逆), torch.linalg.eig() (特征值分解), torch.linalg.svd() (奇异值分解) 等。
代码实践:
import torch tensor1 = torch.tensor([[1, 2], [3, 4]]) tensor2 = torch.tensor([[5, 6], [7, 8]]) # 矩阵乘法 (matmul) matmul_result = torch.matmul(tensor1, tensor2) print("矩阵乘法:\n", matmul_result) # 转置 (t) transposed_tensor = tensor1.t() print("\n转置:\n", transposed_tensor) # 矩阵求逆 (需要是方阵且可逆) try: inverse_tensor = torch.inverse(tensor1.float()) # 需要转换为浮点型 print("\n矩阵求逆:\n", inverse_tensor) except Exception as e: print("\n矩阵求逆失败:", e) # 特征值分解 (eig) eigenvalues, eigenvectors = torch.linalg.eig(tensor1.float()) print("\n特征值分解:") print("特征值:\n", eigenvalues) print("特征向量:\n", eigenvectors)