PyTorch Scala 高校计算机硕士研一课程基本张量操作对 PyTorch 张量执行基本操作是一项主要技能。张量支持多种数学和逻辑运算。这些运算许多都与 NumPy 中的对应功能类似都是按元素进行的。这些运算是神经网络计算的根本。按元素算术运算最常见的运算是将标准算术函数独立应用于参与运算的张量的每个元素。这些运算通常要求张量具有兼容的形状关于形状兼容性我们将在下一章讨论广播时详细说明。您可以使用标准 Python 算术运算符或等效的torch函数加法:或torch.add()减法:-或torch.sub()乘法:*或torch.mul()除法:/或torch.div()幂运算:**或torch.pow()让我们看看这些操作如何运行importtorch.*// 创建两个张量valatorch.tensor(Seq(Seq(1.,2.),Seq(3.,4.)))valbtorch.tensor(Seq(Seq(5.,6.),Seq(7.,8.)))// 加法valsum_tensorab println(加法 (a b):\n,sum_tensor)println(加法 (torch.add(a, b)):\n,torch.add(a,b))// 减法valdiff_tensora-b println(\n减法 (a - b):\n,diff_tensor)// 按元素乘法valmul_tensora*b println(\n按元素乘法 (a * b):\n,mul_tensor)println(按元素乘法 (torch.mul(a, b)):\n,torch.mul(a,b))// 除法valdiv_tensora/b println(\n除法 (a / b):\n,div_tensor)// 幂运算valpow_tensora**2println(\n幂运算 (a ** 2):\n,pow_tensor)println(幂运算 (torch.pow(a, 2)):\n,torch.pow(a,2))这些操作会创建包含结果的新张量。原始张量a和b保持不变。就地操作PyTorch 也提供许多操作的就地版本。这些操作直接修改张量而不创建新对象这可以节省内存。就地函数通常可以通过名称中末尾的下划线_来识别例如add_、mul_。importtorch.*valatorch.tensor(Seq(Seq(1.,2.),Seq(3.,4.)))valbtorch.tensor(Seq(Seq(5.,6.),Seq(7.,8.)))println(原始张量 a:\n,a)// 执行就地加法a.add_(b)// a 被直接修改println(\na.add_(b) 后张量 a:\n,a)// 如果取消注释这将引发错误// 因为 a b 的结果是一个新张量// 不适合直接重新赋值给 a 的内存// a a b // 标准加法会创建一个新张量// 另一个就地操作a.mul_(2)// 将 a 就地乘以 2println(\na.mul_(2) 后张量 a:\n,a)虽然就地操作可以提高内存效率但请谨慎使用。如果在计算图中的其他地方需要原始值就地修改张量可能会导致自动求导Autograd第 3 章会介绍中的梯度计算问题。通常更稳妥的做法尤其是在学习时是使用返回新张量的标准操作。标量操作您可以对张量和单个数字标量执行算术运算。PyTorch 会自动扩展标量以匹配张量的形状从而进行按元素运算。importtorch.*valttorch.tensor(Seq(Seq(1.,2.,3.),Seq(4.,5.,6.)))valscalar10.0// 加标量println(t 标量:\n,tscalar)// 乘以标量println(\nt * 标量:\n,t*scalar)// 减去标量println(\nt - 标量:\n,t-scalar)其他数学函数PyTorch 提供丰富的数学函数库这些函数按元素对张量进行操作类似于 NumPy 的通用函数ufuncs。importtorch.*valttorch.tensor(Seq(Seq(1.,4.),Seq(9.,16.)))// 平方根println(平方根 (torch.sqrt(t)):\n,torch.sqrt(t))// 指数println(\n指数 (torch.exp(t)):\n,torch.exp(t))// e^x// 自然对数// 注意确保对数的值为正valt_postorch.abs(t)1e-6// 添加小的 epsilon 以提高稳定性如果存在零println(\n自然对数 (torch.log(t_pos)):\n,torch.log(t_pos))// 绝对值valt_negtorch.tensor(Seq(Seq(-1.,2.),Seq(-3.,4.)))println(\n绝对值 (torch.abs(t_neg)):\n,torch.abs(t_neg))torch模块中还有许多其他函数例如torch.sin()、torch.cos()、torch.tanh()、torch.sigmoid()等。归约操作归约操作会减少张量中的元素数量通常用于汇总信息。常见例子包括求和、求平均值、求最小值和最大值。importtorch.*valttorch.tensor(Seq(Seq(1.,2.,3.),Seq(4.,5.,6.)))println(原始张量:\n,t)// 所有元素的和valtotal_sumtorch.sum(t)println(\n所有元素的和 (torch.sum(t)):,total_sum)// 所有元素的平均值// 注意平均值计算需要浮点张量valmean_valtorch.mean(t.float())println(所有元素的平均值 (torch.mean(t.float())):,mean_val)// 最大值valmax_valtorch.max(t)println(张量中的最大值 (torch.max(t)):,max_val)// 最小值valmin_valtorch.min(t)println(张量中的最小值 (torch.min(t)):,min_val)您还可以使用dim参数沿着特定维度执行归约操作。这会折叠指定的维度返回一个维度减少的张量。importtorch.*// 创建一个张量valttorch.tensor(Seq(Seq(1.,2.,3.),Seq(4.,5.,6.)))println(原始张量:\n,t)// 沿着维度 0 求和对行求和valsum_dim0torch.sum(t,dim0)println(\n沿着 dim0 求和列:\n,sum_dim0)// 沿着维度 1 求和对列求和valsum_dim1torch.sum(t,dim1)println(\n沿着 dim1 求和行:\n,sum_dim1)// 沿着维度 1 求平均值valmean_dim1torch.mean(t.float(),dim1)println(\n沿着 dim1 求平均值行:\n,mean_dim1)对张量[[1, 2, 3], [4, 5, 6]]沿着dim1求和结果为[123, 456] [6, 15]。了解dim的工作原理对许多深度学习操作很重要例如计算每个批次项的损失或应用批归一化。比较操作您可以使用标准比较运算符、、、、、!按元素比较张量。结果是一个布尔值张量torch.bool。importtorch.*// 创建张量valatorch.tensor(Seq(Seq(1,2),Seq(3,4)))valbtorch.tensor(Seq(Seq(1,5),Seq(0,4)))println(张量 a:\n,a)println(张量 b:\n,b)// 相等检查println(\na b:\n,ab)// 大于检查println(\na b:\n,ab)// 小于或等于检查println(\na b:\n,ab)布尔张量对掩码操作很有用您将在更高级的张量操作中遇到它们。逻辑操作逻辑操作torch.logical_and()、torch.logical_or()、torch.logical_not()按元素对布尔张量或可在布尔上下文中求值的张量进行操作其中 0 为假非零为真。importtorch.*// 创建布尔张量valbool_atorch.tensor(Seq(Seq(true,false),Seq(true,true)))valbool_btorch.tensor(Seq(Seq(false,true),Seq(true,false)))println(布尔张量 bool_a:\n,bool_a)println(布尔张量 bool_b:\n,bool_b)// 逻辑与println(\ntorch.logical_and(bool_a, bool_b):\n,torch.logical_and(bool_a,bool_b))// 逻辑或println(\ntorch.logical_or(bool_a, bool_b):\n,torch.logical_or(bool_a,bool_b))// 逻辑非println(\ntorch.logical_not(bool_a):\n,torch.logical_not(bool_a))这些基本操作为更复杂的计算提供了基本组成部分。掌握它们是在 PyTorch 中实现数值算法和神经网络层的第一步。在下一章中我们将介绍更高级的张量操作方法包括索引、切片、重塑和广播。与 NumPy 的关联如果你对 Python 中的科学计算有经验你可能很熟悉 NumPy 及其ndarray对象。NumPy 提供了一种强大的 N 维数组结构已成为 Python 中数值操作的标准。PyTorch 认识到这种普遍性并提供了与 NumPy 数组出色的互操作性。事实上PyTorch 张量与 NumPy 数组非常相似它们都是数字多维网格的抽象。这种密切关联使得两者之间的切换变得简单让你在 PyTorch 生态系统中工作时能够运用现有的 NumPy 代码或库。相似结构不同能力从本质上看PyTorch 张量和 NumPy 数组都表示多维的密集数值数据。你可能在 NumPy 数组上执行的许多操作在 PyTorch 张量中都有直接对应通常具有相似的命名约定创建两个库都提供函数来创建填充零、一、随机数或从现有 Python 列表创建数组/张量。数学运算逐元素加法、减法、乘法、除法、求幂、三角函数等操作方式相似。索引和切片访问和修改元素或子数组/子张量使用可比较的语法。形状操作改变数组/张量的形状、转置和连接遵循相似的原则。然而主要由于 PyTorch 对深度学习的侧重两者之间存在一些基本区别GPU 加速PyTorch 张量可以移到图形处理单元 (GPU) 上处理。这使得大规模并行计算成为可能为深度学习中常见的矩阵乘法及其他操作提供了显著的速度提升。NumPy 数组主要为 CPU 计算设计。自动微分PyTorch 张量通过Autograd系统第 3 章会讲到内置支持自动微分。这种机制会自动跟踪对需要梯度的张量执行的操作并在反向传播期间计算这些梯度这对训练神经网络来说很重要。NumPy 数组不具备此能力。NumPy 数组与张量之间的转换PyTorch 使得在这两种数据结构之间转换变得简单。NumPy 数组到 PyTorch 张量你可以使用torch.from_numpy()函数直接从 NumPy 数组创建 PyTorch 张量。importnumpyas npimporttorch.*// 创建一个 NumPy 数组valnumpy_arraynp.array(Seq(Seq(1,2),Seq(3,4)),dtypenp.float32)println(fNumPy 数组:\n{numpy_array})println(fNumPy 数组类型: {numpy_array.dtype})// 将 NumPy 数组转换为 PyTorch 张量valpytorch_tensortorch.from_numpy(numpy_array)println(f\nPyTorch 张量:\n{pytorch_tensor})println(fPyTorch 张量类型: {pytorch_tensor.dtype})内存共享的重要说明使用torch.from_numpy()时生成的 PyTorch 张量和原始 NumPy 数组在 CPU 上共享相同的底层内存位置。这意味着修改一个对象会影响另一个。这种行为很高效因为它避免了数据复制但你需要注意这一点。// 修改 NumPy 数组numpy_array(0,0)99println(f\n修改后的 NumPy 数组:\n{numpy_array})println(f修改 NumPy 数组后的 PyTorch 张量:\n{pytorch_tensor})# 修改 PyTorch 张量 pytorch_tensor(1,1)-1println(f\n修改后的 PyTorch 张量:\n{pytorch_tensor})println(f修改 PyTorch 张量后的 NumPy 数组:\n{numpy_array})如你所见更改会反映在两个对象中因为它们指向内存中的相同数据。PyTorch 张量到 NumPy 数组反之你可以使用.numpy()方法将位于 CPU 上的 PyTorch 张量转换回 NumPy 数组。// 在 CPU 上创建一个 PyTorch 张量valcpu_tensortorch.tensor(Seq(Seq(10.0,20.0),Seq(30.0,40.0)))println(f原始 PyTorch 张量 (CPU):\n{cpu_tensor})// 将张量转换为 NumPy 数组valnumpy_array_convertedcpu_tensor.numpy()println(f\n转换后的 NumPy 数组:\n{numpy_array_converted})println(fNumPy 数组类型: {numpy_array_converted.dtype})同样生成的 NumPy 数组和原始 CPU 张量共享相同的底层内存。对一个的修改会影响另一个。// 修改张量cpu_tensor(0,1)25.0println(f\n修改后的 PyTorch 张量:\n{cpu_tensor})println(f修改张量后的 NumPy 数组:\n{numpy_array_converted})# 修改 NumPy 数组 numpy_array_converted(1,0)35.0println(f\n修改后的 NumPy 数组:\n{numpy_array_converted})println(f修改 NumPy 数组后的张量:\n{cpu_tensor})GPU 张量.numpy()方法仅适用于存储在 CPU 上的张量。如果你的张量在 GPU 上你必须先使用.cpu()方法将其移到 CPU然后才能将其转换为 NumPy 数组。直接在 GPU 张量上调用.numpy()会导致错误。// 假设有 GPU 可用的示例iftorch.cuda.is_available()thenvalgpu_tensortorch.tensor(Seq(Seq(1.0,2.0),Seq(3.0,4.0)),devicecuda)println(f\nGPU 上的张量:\n{gpu_tensor})// 这将导致错误: numpy_from_gpu gpu_tensor.numpy()// 正确方法: 先移到 CPUvalcpu_tensor_from_gpugpu_tensor.cpu()valnumpy_from_gpucpu_tensor_from_gpu.numpy()println(f\n转换后的 NumPy 数组 (来自 GPU 张量):\n{numpy_from_gpu})// 注意: numpy_from_gpu 与 cpu_tensor_from_gpu 共享内存// 但不与原始的 gpu_tensor 共享。elseprintln(\nCUDA 不可用跳过 GPU 到 NumPy 的示例。)结合两者的优势轻松地在 NumPy 数组和 PyTorch 张量之间转换的能力非常实用。你可能使用熟悉的 NumPy 函数或其他操作 NumPy 数组的库来执行初始数据加载和预处理。然后当需要构建或训练深度学习模型时你可以将数据转换为 PyTorch 张量以借助于 GPU 加速和自动微分。同样模型输出即张量可以转换回 NumPy 数组以便使用 Matplotlib 或 Seaborn 等库进行分析或可视化。理解这种关联和内存共享的含义让你能够编写高效的代码有效连接通用科学 Python 生态系统与 PyTorch 提供的专门深度学习能力。动手实践环境配置与张量基本操作收藏这些实践练习将提供PyTorch安装和基本张量对象的实践经验。它们将巩固对环境配置和执行基本张量操作的理解这些都是使用PyTorch构建任何深度学习模型的前提条件。验证你的PyTorch安装首先让我们确保PyTorch已正确安装并在你的Python环境中可用。打开你的Python解释器或Jupyter Notebook并运行以下命令importtorch.*importnumpyas np// 之后我们将用NumPy进行比较// 打印PyTorch版本println(fPyTorch Version: {torch.__version__})// 检查CUDAGPU支持是否可用iftorch.cuda.is_available()then println(fCUDA is available. Device: {torch.cuda.get_device_name(0)})// 获取PyTorch将使用的默认设备valdevicetorch.device(cuda)elseprintln(CUDA not available. Using CPU.)valdevicetorch.device(cpu)println(fDefault device: {device})执行这段代码可以确认torch库能够被导入并显示其版本。它还会检查GPU的可用性这对于后续章节中加速计算很重要。如果你有兼容的NVIDIA GPU并且安装了正确的PyTorch版本应该会看到CUDA被报告为可用。目前我们将主要使用CPU但知道如何检查GPU也很重要。创建张量让我们练习使用之前介绍的多种方法创建张量。1. 从Python列表创建从一个嵌套的Python列表创建一个2x3的张量。// 从Python列表创建张量valdataSeq(Seq(1,2,3),Seq(4,5,6))valtensor_from_listtorch.tensor(data)println(从列表创建的张量)println(tensor_from_list)println(f形状: {tensor_from_list.shape})println(f数据类型: {tensor_from_list.dtype})// 通常默认为int642. 指定数据类型创建相同的张量但显式地将其数据类型设置为32位浮点数。// 创建指定数据类型的张量valtensor_float32torch.tensor(data,dtypetorch.float32)println(\nfloat32数据类型的张量)println(tensor_float32)println(f形状: {tensor_float32.shape})println(f数据类型: {tensor_float32.dtype})注意dtype的变化以及数字的表示方式例如1.而不是1。3. 使用工厂函数创建具有特定形状和初始值的张量。// 创建一个3x4的全零张量valzeros_tensortorch.zeros(3,4)println(\n全零张量 (3x4))println(zeros_tensor)// 创建一个2x2的全一张量类型为整数valones_tensor_inttorch.ones(2,2,dtypetorch.int32)println(\n全一张量 (2x2, int32))println(ones_tensor_int)// 创建一个表示数字范围的一维张量valrange_tensortorch.arange(start0,end5,step1)// 类似于Python的range函数println(\n范围张量 (0到4))println(range_tensor)// 创建一个包含随机值0到1均匀分布的2x3张量valrand_tensortorch.rand(2,3)println(\n随机张量 (2x3))println(rand_tensor)这些工厂函数便于初始化张量无需使用像列表这样的预先存在的数据结构。张量基本操作现在让我们对已创建的张量执行一些基本操作。// 使用之前创建的float32张量valatorch.tensor(Seq(Seq(1,2),Seq(3,4)),dtypetorch.float32)valbtorch.ones(2,2)// 默认为float32println(张量 a)println(a)println(张量 b)println(b)// 元素级加法valsum_tensorab// 另一种写法sum_tensor torch.add(a, b)println(\n元素级和 (a b))println(sum_tensor)// 元素级乘法valprod_tensora*b// 另一种写法prod_tensor torch.mul(a, b)println(\n元素级积 (a * b))println(prod_tensor)// 标量乘法valscalar_multa*3println(\n标量乘法 (a * 3))println(scalar_mult)// 就地加法修改张量aprintln(f\n就地加法前 aID {id(a)})a.add_(b)// 注意就地操作的下划线后缀println(就地加法后 a (a.add_(b)))println(a)println(f就地加法后 aID {id(a)})// ID保持不变// 矩阵乘法// 确保维度兼容矩阵乘法// 让我们创建兼容的张量x (2x3), y (3x2)valxtorch.rand(2,3)valytorch.rand(3,2)valmatmul_resulttorch.matmul(x,y)// 另一种写法matmul_result x yprintln(\n矩阵乘法 (x y))println(f张量 x 的形状: {x.shape}, 张量 y 的形状: {y.shape})println(f结果形状: {matmul_result.shape})println(matmul_result)请注意元素级操作如、*与矩阵乘法torch.matmul或之间的区别。此外还要注意就地操作如add_如何直接修改张量而不创建新对象。与NumPy的交互PyTorch与NumPy良好配合。让我们练习在NumPy数组和PyTorch张量之间进行转换。// 1. NumPy数组到PyTorch张量valnumpy_arraynp.array([[1.0,2.0],[3.0,4.0]])println(\nNumPy数组)println(numpy_array)println(f类型: {type(numpy_array)})// 转换为PyTorch张量valtensor_from_numpytorch.from_numpy(numpy_array)println(\n从NumPy数组创建的张量)println(tensor_from_numpy)println(f类型: {type(tensor_from_numpy)})// 重要提示在CPU上torch.from_numpy与NumPy数组共享内存// 修改其中一个会影响另一个numpy_array(0,0)99.0println(\n修改后的NumPy数组)println(numpy_array)println(修改NumPy数组后的张量共享内存)println(tensor_from_numpy)// 2. PyTorch张量到NumPy数组// 让我们使用不同的张量以避免之前的修改valanother_tensortorch.tensor(Seq(Seq(5,6),Seq(7,8)),dtypetorch.float64)println(\n另一个PyTorch张量)println(another_tensor)// 转换为NumPy数组valnumpy_from_tensoranother_tensor.numpy()println(\n从张量创建的NumPy数组)println(numpy_from_tensor)println(f类型: {type(numpy_from_tensor)})// 同样在CPU上内存是共享的another_tensor(1,1)100.0println(\n修改后的张量)println(another_tensor)println(修改张量后的NumPy数组共享内存)println(numpy_from_tensor)NumPy数组和CPU张量之间的这种内存共享行为效率高但需要谨慎处理因为可能会发生意外修改。如果你需要一个独立的副本可以在转换之前使用张量的.clone()方法或者使用标准的Python/NumPy复制机制。本实践环节涵盖了验证你的配置、以多种方式创建张量、执行基本的算术和矩阵操作以及在PyTorch张量和NumPy数组之间进行转换。掌握这些基础知识很重要因为我们将在后续章节转向自动微分和构建神经网络模块等更复杂的主题。