备注
点击此处下载完整示例代码
简介 || 张量 || Autograd || 模型构建 || TensorBoard 支持 || 训练模型 || 模型理解
PyTorch 张量入门 ¶
创建于:2025 年 4 月 1 日 | 最后更新:2025 年 4 月 1 日 | 最后验证:2024 年 11 月 5 日
按照下面的视频或 YouTube 上的视频进行操作。
张量是 PyTorch 中的核心数据抽象。本交互式笔记本深入介绍了 torch.Tensor
类。
首先的事情先,让我们导入 PyTorch 模块。我们还将添加 Python 的 math 模块以方便一些示例。
import torch
import math
创建张量
创建张量的最简单方法是使用 torch.empty()
调用:
x = torch.empty(3, 4)
print(type(x))
print(x)
让我们拆解一下我们刚才做了什么:
我们使用模块
torch
的众多工厂方法之一创建了一个张量。张量本身是二维的,有 3 行 4 列。
返回的对象类型是
torch.Tensor
,它是torch.FloatTensor
的别名;默认情况下,PyTorch 张量填充的是 32 位浮点数。(关于数据类型将在下面详细介绍。)打印张量时,你可能看到一些看起来随机的值。
torch.empty()
调用为张量分配内存,但不会用任何值初始化它——所以你看到的是分配内存时的内存内容。
关于张量及其维数数量的简要说明,以及术语:
有时你会看到一个一维张量被称为向量。
同样,二维张量通常被称为矩阵。
任何超过两个维度的东西通常都被称为张量。
更多时候,您可能希望用某个值初始化您的张量。常见的情况是全零、全一或随机值,而 torch
模块提供了所有这些的工厂方法:
zeros = torch.zeros(2, 3)
print(zeros)
ones = torch.ones(2, 3)
print(ones)
torch.manual_seed(1729)
random = torch.rand(2, 3)
print(random)
这些工厂方法都符合您的预期 - 我们有一个全零的张量,另一个全一的张量,还有一个介于 0 和 1 之间的随机值张量。
随机张量和种子
说到随机张量,您注意到它前面的 torch.manual_seed()
调用了吗?用随机值初始化张量,例如模型的学习权重,是很常见的,但在某些情况下,尤其是在研究环境中,您可能希望确保结果的可重复性。手动设置随机数生成器的种子是这样做的方法。让我们更仔细地看看:
torch.manual_seed(1729)
random1 = torch.rand(2, 3)
print(random1)
random2 = torch.rand(2, 3)
print(random2)
torch.manual_seed(1729)
random3 = torch.rand(2, 3)
print(random3)
random4 = torch.rand(2, 3)
print(random4)
你应该看到的是, random1
和 random3
具有相同的值,同样 random2
和 random4
也具有相同的值。手动设置随机数生成器的种子会重置它,因此,在大多数情况下,依赖于随机数的相同计算应该提供相同的结果。
更多信息,请参阅 PyTorch 关于可重复性的文档。
张量形状
通常,当你对两个或多个张量执行操作时,它们需要具有相同的形状——也就是说,具有相同的维度数和每个维度中的相同单元格数。为此,我们提供了 torch.*_like()
方法:
x = torch.empty(2, 2, 3)
print(x.shape)
print(x)
empty_like_x = torch.empty_like(x)
print(empty_like_x.shape)
print(empty_like_x)
zeros_like_x = torch.zeros_like(x)
print(zeros_like_x.shape)
print(zeros_like_x)
ones_like_x = torch.ones_like(x)
print(ones_like_x.shape)
print(ones_like_x)
rand_like_x = torch.rand_like(x)
print(rand_like_x.shape)
print(rand_like_x)
代码单元格上方的第一个新特性是使用张量的 .shape
属性。该属性包含张量每个维度的范围列表——在我们的例子中, x
是一个形状为 2x2x3 的三维张量。
在下面,我们调用了 .empty_like()
、 .zeros_like()
、 .ones_like()
和 .rand_like()
方法。使用 .shape
属性,我们可以验证这些方法返回的张量具有相同的维数和范围。
创建一个将覆盖所有内容的张量的最后一种方法是指定其数据来自 PyTorch 集合:
some_constants = torch.tensor([[3.1415926, 2.71828], [1.61803, 0.0072897]])
print(some_constants)
some_integers = torch.tensor((2, 3, 5, 7, 11, 13, 17, 19))
print(some_integers)
more_integers = torch.tensor(((2, 4, 6), [3, 6, 9]))
print(more_integers)
如果您已经有 Python 元组或列表中的数据,使用 torch.tensor()
是创建张量的最直接方法。如上所示,嵌套集合将导致多维张量。
备注
创建数据副本。
张量数据类型 ¶
设置张量的数据类型有几种方法:
a = torch.ones((2, 3), dtype=torch.int16)
print(a)
b = torch.rand((2, 3), dtype=torch.float64) * 20.
print(b)
c = b.to(torch.int32)
print(c)
设置张量底层数据类型最简单的方法是在创建时使用一个可选参数。在上面的单元格的第一行中,我们为张量 a
设置了 dtype=torch.int16
。当我们打印 a
时,我们可以看到它充满了 1
而不是 1.
- 这是 Python 对这是一个整型而不是浮点型的微妙提示。
注意打印 a
的另一个问题是,与我们将 dtype
留为默认(32 位浮点数)时不同,打印张量还会指定其 dtype
。
你可能也注意到,我们从指定张量的形状作为一系列整数参数,转变为将这些参数组合成一个元组。这并不是严格必要的——PyTorch 会将一系列初始的无标签整数参数视为张量形状——但在添加可选参数时,可以使你的意图更易读。
设置数据类型的另一种方法是使用 .to()
方法。在上面的单元格中,我们以通常的方式创建了一个随机浮点数张量 b
。随后,我们通过使用 .to()
方法将 b
转换为 32 位整数来创建 c
。请注意, c
包含与 b
相同的所有值,但被截断为整数。
更多信息,请参阅数据类型文档。
PyTorch 张量与数学逻辑
现在你已经了解了一些创建张量的方法...你能用它们做什么呢?
首先让我们看看基本的算术运算,以及张量如何与简单的标量交互:
ones = torch.zeros(2, 2) + 1
twos = torch.ones(2, 2) * 2
threes = (torch.ones(2, 2) * 7 - 1) / 2
fours = twos ** 2
sqrt2s = twos ** 0.5
print(ones)
print(twos)
print(threes)
print(fours)
print(sqrt2s)
如上图所示,张量与标量之间的算术运算,如加法、减法、乘法、除法和指数运算,是对张量的每个元素进行分配的。因为这种运算的输出将是一个张量,所以你可以按照常规运算符优先级规则将它们链接起来,就像我们创建 threes
那一行一样。
两个张量之间的相似操作也符合你的直观预期:
powers2 = twos ** torch.tensor([[1, 2], [3, 4]])
print(powers2)
fives = ones + fours
print(fives)
dozens = threes * fours
print(dozens)
这里需要注意的是,上一个代码单元中的所有张量都具有相同的形状。如果我们尝试对形状不同的张量执行二进制操作会发生什么?
备注
以下单元格抛出运行时错误。这是故意的。
a = torch.rand(2, 3)
b = torch.rand(3, 2)
print(a * b)
在一般情况下,你不能以这种方式操作形状不同的张量,即使在上述单元中,张量具有相同数量的元素的情况下也不行。
简要介绍:张量广播
备注
如果你熟悉 NumPy ndarrays 中的广播语义,你会发现这里同样适用相同的规则。
与相同形状规则相悖的是张量广播。以下是一个例子:
rand = torch.rand(2, 4)
doubled = rand * (torch.ones(1, 4) * 2)
print(rand)
print(doubled)
这里的技巧是什么?我们是如何将一个 2x4 的张量与一个 1x4 的张量相乘的?
广播是一种在形状相似的张量之间执行操作的方法。在上面的例子中,一行的四列张量与两行的四列张量的每一行都进行了相乘。
这是在深度学习中一个重要的操作。常见的例子是将学习权重张量与输入张量批次相乘,分别对批次中的每个实例应用操作,并返回一个形状相同的张量——就像我们上面(2, 4) * (1, 4)的例子返回了一个形状为(2, 4)的张量一样。
广播的规则是:
每个张量至少必须有一个维度——没有空张量。
从后往前比较两个张量的维度大小:
每个维度必须相等,或者
其中一个维度的大小必须为 1,或者
其中一个张量中不存在该维度
形状相同的张量当然可以“广播”,正如你之前所看到的。
下面是一些遵守上述规则并允许广播的情况示例:
a = torch.ones(4, 3, 2)
b = a * torch.rand( 3, 2) # 3rd & 2nd dims identical to a, dim 1 absent
print(b)
c = a * torch.rand( 3, 1) # 3rd dim = 1, 2nd dim identical to a
print(c)
d = a * torch.rand( 1, 2) # 3rd dim identical to a, 2nd dim = 1
print(d)
仔细观察上面每个张量的值:
创建
b
的乘法操作在a
的每一“层”上进行了广播。对于
c
,操作被广播到每一层和每一行——每 3 个元素的列是相同的。对于
d
,我们将其反转——现在每一行在层和列之间都是相同的。
想了解更多关于广播的信息,请参阅 PyTorch 文档中的相关内容。
下面是一些广播尝试失败的例子:
备注
以下单元格抛出运行时错误。这是故意的。
a = torch.ones(4, 3, 2)
b = a * torch.rand(4, 3) # dimensions must match last-to-first
c = a * torch.rand( 2, 3) # both 3rd & 2nd dims different
d = a * torch.rand((0, )) # can't broadcast with an empty tensor
使用张量的更多数学运算
PyTorch 张量可以进行超过三百种操作。
下面是一些主要操作类别的示例:
# common functions
a = torch.rand(2, 4) * 2 - 1
print('Common functions:')
print(torch.abs(a))
print(torch.ceil(a))
print(torch.floor(a))
print(torch.clamp(a, -0.5, 0.5))
# trigonometric functions and their inverses
angles = torch.tensor([0, math.pi / 4, math.pi / 2, 3 * math.pi / 4])
sines = torch.sin(angles)
inverses = torch.asin(sines)
print('\nSine and arcsine:')
print(angles)
print(sines)
print(inverses)
# bitwise operations
print('\nBitwise XOR:')
b = torch.tensor([1, 5, 11])
c = torch.tensor([2, 7, 10])
print(torch.bitwise_xor(b, c))
# comparisons:
print('\nBroadcasted, element-wise equality comparison:')
d = torch.tensor([[1., 2.], [3., 4.]])
e = torch.ones(1, 2) # many comparison ops support broadcasting!
print(torch.eq(d, e)) # returns a tensor of type bool
# reductions:
print('\nReduction ops:')
print(torch.max(d)) # returns a single-element tensor
print(torch.max(d).item()) # extracts the value from the returned tensor
print(torch.mean(d)) # average
print(torch.std(d)) # standard deviation
print(torch.prod(d)) # product of all numbers
print(torch.unique(torch.tensor([1, 2, 1, 2, 1, 2]))) # filter unique elements
# vector and linear algebra operations
v1 = torch.tensor([1., 0., 0.]) # x unit vector
v2 = torch.tensor([0., 1., 0.]) # y unit vector
m1 = torch.rand(2, 2) # random matrix
m2 = torch.tensor([[3., 0.], [0., 3.]]) # three times identity matrix
print('\nVectors & Matrices:')
print(torch.linalg.cross(v2, v1)) # negative of z unit vector (v1 x v2 == -v2 x v1)
print(m1)
m3 = torch.linalg.matmul(m1, m2)
print(m3) # 3 times m1
print(torch.linalg.svd(m3)) # singular value decomposition
这是一个操作的小样本。有关详细信息以及数学函数的完整清单,请参阅文档。有关详细信息以及线性代数操作的完整清单,请参阅此文档。
在原地修改张量
对于张量的大多数二进制操作,将返回第三个新张量。当我们说 c = a * b
(其中 a
和 b
是张量)时,新张量 c
将占用与其它张量不同的内存区域。
然而,有时您可能希望原地修改张量——例如,如果您正在进行逐元素计算,可以丢弃中间值。为此,大多数数学函数都有一个带有附加下划线的版本( _
),它将原地修改张量。
例如:
a = torch.tensor([0, math.pi / 4, math.pi / 2, 3 * math.pi / 4])
print('a:')
print(a)
print(torch.sin(a)) # this operation creates a new tensor in memory
print(a) # a has not changed
b = torch.tensor([0, math.pi / 4, math.pi / 2, 3 * math.pi / 4])
print('\nb:')
print(b)
print(torch.sin_(b)) # note the underscore
print(b) # b has changed
对于算术运算,存在一些行为相似的函数:
a = torch.ones(2, 2)
b = torch.rand(2, 2)
print('Before:')
print(a)
print(b)
print('\nAfter adding:')
print(a.add_(b))
print(a)
print(b)
print('\nAfter multiplying')
print(b.mul_(b))
print(b)
注意,这些原地算术函数是 torch.Tensor
对象的方法,而不是像许多其他函数(例如 torch.sin()
)那样附加到 torch
模块上。正如 a.add_(b)
所示,被改变的调用张量是原地改变的那个。
将计算结果放置在现有已分配张量中的另一种选择。我们之前看到的大多数方法和函数(包括创建方法!)都有一个 out
参数,允许您指定一个接收输出的张量。如果 out
张量具有正确的形状和 dtype
,则可以发生这种情况而无需新的内存分配:
a = torch.rand(2, 2)
b = torch.rand(2, 2)
c = torch.zeros(2, 2)
old_id = id(c)
print(c)
d = torch.matmul(a, b, out=c)
print(c) # contents of c have changed
assert c is d # test c & d are same object, not just containing equal values
assert id(c) == old_id # make sure that our new c is the same object as the old one
torch.rand(2, 2, out=c) # works for creation too!
print(c) # c has changed again
assert id(c) == old_id # still the same object!
张量复制
与 Python 中的任何对象一样,将张量赋值给变量会使该变量成为张量的标签,而不是复制它。例如:
a = torch.ones(2, 2)
b = a
a[0][1] = 561 # we change a...
print(b) # ...and b is also altered
但如果你想要一个独立的数据副本来工作怎么办? clone()
方法就是为了你提供的:
a = torch.ones(2, 2)
b = a.clone()
assert b is not a # different objects in memory...
print(torch.eq(a, b)) # ...but still with the same contents!
a[0][1] = 561 # a changes...
print(b) # ...but b is still all ones
使用 `clone()` 时有一个重要的事情需要注意。如果你的源张量启用了 autograd,那么克隆后的张量也会启用 autograd。这将在关于 autograd 的视频中更深入地介绍,但如果你想要简要的细节,请继续阅读。
在许多情况下,这将是你的需求。例如,如果你的模型在 ` forward()
` 方法中有多个计算路径,并且原始张量和其克隆都对模型的输出有贡献,那么为了启用模型学习,你希望两个张量都开启 autograd。如果你的源张量启用了 autograd(通常情况下,如果它是一组学习权重或从涉及权重的计算中派生而来,则会启用),那么你将得到你想要的结果。
另一方面,如果你在进行一个计算,原始张量及其克隆都不需要跟踪梯度,那么只要源张量已经关闭了 autograd,就可以正常进行。
然而,还有一种情况:想象一下,你在模型的 forward()
函数中执行计算,默认情况下所有内容都开启了梯度跟踪,但你希望在计算过程中提取一些值以生成一些指标。在这种情况下,你不想你的源张量克隆副本跟踪梯度——关闭 autograd 的历史跟踪可以提高性能。为此,你可以在源张量上使用 .detach()
方法:
a = torch.rand(2, 2, requires_grad=True) # turn on autograd
print(a)
b = a.clone()
print(b)
c = a.detach().clone()
print(c)
print(a)
这里发生了什么?
我们使用
requires_grad=True
开启了a
。我们还没有介绍这个可选参数,但将在 autograd 单元中介绍。当我们打印
a
时,它会告诉我们属性requires_grad=True
已开启,这意味着自动微分和计算历史跟踪已被启用。我们克隆
a
并将其标记为b
。当我们打印b
时,我们可以看到它在跟踪其计算历史 - 它继承了a
的自动微分设置,并添加到计算历史中。我们将
a
克隆到c
中,但首先调用detach()
。打印
c
时,我们看不到任何计算历史,也没有requires_grad=True
。
detach()
方法将张量从其计算历史中分离出来。它表示,“接下来做什么就像 autograd 关闭一样。”它这样做而不改变 a
- 你可以看到当我们再次打印 a
时,它保留了其 requires_grad=True
属性。
移动到加速器 ¶
PyTorch 的一个主要优点是它在 CUDA、MPS、MTIA 或 XPU 等加速器上的强大加速。到目前为止,我们所做的一切都是在 CPU 上进行的。我们如何迁移到更快的硬件?
首先,我们应该检查是否可用加速器,使用 is_available()
方法。
备注
如果您没有加速器,本节中的可执行单元格将不会执行任何与加速器相关的代码。
if torch.accelerator.is_available():
print('We have an accelerator!')
else:
print('Sorry, CPU only.')
一旦我们确定一个或多个加速器可用,我们需要将我们的数据放置在加速器可以看到的地方。您的 CPU 在计算机的 RAM 中对数据进行计算。您的加速器连接有专用内存。每当您想要在设备上执行计算时,您必须将执行该计算所需的所有数据移动到该设备可访问的内存中。(通俗地说,“将数据移动到可由 GPU 访问的内存”简称为“将数据移动到 GPU”。)
将您的数据传输到目标设备有多种方式。您可以在创建时进行操作:
if torch.accelerator.is_available():
gpu_rand = torch.rand(2, 2, device=torch.accelerator.current_accelerator())
print(gpu_rand)
else:
print('Sorry, CPU only.')
默认情况下,新张量是在 CPU 上创建的,因此我们需要指定何时使用可选的 device
参数在加速器上创建我们的张量。当打印新张量时,PyTorch 会告诉我们它位于哪个设备上(如果不在 CPU 上)。
您可以使用 torch.accelerator.device_count()
查询加速器的数量。如果您有多个加速器,可以通过索引指定它们,以 CUDA 为例: device='cuda:0'
, device='cuda:1'
等。
作为编码实践,在所有地方使用字符串常量指定我们的设备非常脆弱。在一个理想的世界里,无论您是在 CPU 还是加速器硬件上,您的代码都应该能够稳健地运行。您可以通过创建一个设备句柄并将其传递给张量来代替字符串:
my_device = torch.accelerator.current_accelerator() if torch.accelerator.is_available() else torch.device('cpu')
print('Device: {}'.format(my_device))
x = torch.rand(2, 2, device=my_device)
print(x)
如果您有一个存在于某个设备上的现有张量,您可以使用 to()
方法将其移动到另一个设备。以下代码行在 CPU 上创建了一个张量,并将其移动到上一个单元格中获得的任何设备句柄。
y = torch.rand(2, 2)
y = y.to(my_device)
重要的是要知道,为了进行涉及两个或更多张量的计算,所有张量都必须在同一个设备上。以下代码将抛出运行时错误,无论是否有可用的加速器设备,以 CUDA 为例:
x = torch.rand(2, 2)
y = torch.rand(2, 2, device='cuda')
z = x + y # exception will be thrown
张量形状操作 ¶
有时,您可能需要更改张量的形状。下面,我们将探讨一些常见情况以及如何处理它们。
改变维度数量 ¶
需要更改维度数量的一个例子是将单个输入实例传递给您的模型。PyTorch 模型通常期望输入批次。
例如,想象一下有一个在 3 x 226 x 226 的图像上工作的模型——一个具有 3 个颜色通道的 226 像素正方形。当你加载和转换它时,你会得到一个形状为 (3, 226, 226)
的张量。然而,你的模型期望输入的形状为 (N, 3, 226, 226)
,其中 N
是批次的图像数量。那么你如何制作一个只有一个图像的批次呢?
a = torch.rand(3, 226, 226)
b = a.unsqueeze(0)
print(a.shape)
print(b.shape)
unsqueeze()
方法添加了一个大小为 1 的维度。 unsqueeze(0)
将其添加为新的零维——现在你有一个只有一个图像的批次了!
那么,如果这就是“解压”,我们所说的“压缩”是什么意思呢?我们正在利用这样一个事实:任何大小为 1 的维度都不会改变张量中的元素数量。
c = torch.rand(1, 1, 1, 1, 1)
print(c)
继续上面的例子,假设模型的输出是每个输入的 20 个元素的向量。那么,你期望输出具有形状 (N, 20)
,其中 N
是输入批次中的实例数量。这意味着对于我们的单个输入批次,我们将得到一个形状为 (1, 20)
的输出。
如果你想对输出进行一些非批处理计算,比如只期望一个 20 个元素的向量呢?
a = torch.rand(1, 20)
print(a.shape)
print(a)
b = a.squeeze(0)
print(b.shape)
print(b)
c = torch.rand(2, 2)
print(c.shape)
d = c.squeeze(0)
print(d.shape)
你可以从形状中看出,我们的二维张量现在变成了一维的,如果你仔细观察上面单元格的输出,你会看到打印 a
时出现了一组“额外”的方括号 []
,这是因为多了一个维度。
你只能有 squeeze()
个维度的大小为 1。参见上面我们尝试压缩一个大小为 2 的维度 c
,结果得到了与开始相同的形状。 squeeze()
和 unsqueeze()
的调用只能作用于大小为 1 的维度,因为否则会改变张量中的元素数量。
你还可以使用 unsqueeze()
来简化广播。回想一下上面的例子,我们有过以下代码:
a = torch.ones(4, 3, 2)
c = a * torch.rand( 3, 1) # 3rd dim = 1, 2nd dim identical to a
print(c)
这种效果是将操作广播到维度 0 和 2 上,导致随机的 3x1 张量逐元素乘以 a
中的每个 3 元素列。
如果随机向量只是一个 3 元素向量呢?我们将失去广播的能力,因为最终维度将无法根据广播规则匹配。 unsqueeze()
来拯救我们:
a = torch.ones(4, 3, 2)
b = torch.rand( 3) # trying to multiply a * b will give a runtime error
c = b.unsqueeze(1) # change to a 2-dimensional tensor, adding new dim at the end
print(c.shape)
print(a * c) # broadcasting works again!
squeeze()
和 unsqueeze()
方法也有原地版本, squeeze_()
和 unsqueeze_()
:
batch_me = torch.rand(3, 226, 226)
print(batch_me.shape)
batch_me.unsqueeze_(0)
print(batch_me.shape)
有时候你可能想更彻底地改变张量的形状,同时仍然保留元素的数量和内容。这种情况发生在模型的卷积层和线性层之间 - 这在图像分类模型中很常见。卷积核将产生一个形状为特征 x 宽度 x 高度的输出张量,但随后的线性层期望一个一维输入。如果请求的维度产生的元素数量与输入张量相同, reshape()
会为你做到这一点:
output3d = torch.rand(6, 20, 20)
print(output3d.shape)
input1d = output3d.reshape(6 * 20 * 20)
print(input1d.shape)
# can also call it as a method on the torch module:
print(torch.reshape(output3d, (6 * 20 * 20,)).shape)
备注
细胞上方的最后一行中的 (6 * 20 * 20,)
参数之所以存在,是因为 PyTorch 在指定张量形状时期望一个元组 - 但当形状是方法的第一个参数时,它允许我们作弊,只需使用一系列整数即可。这里,我们必须添加括号和逗号来让方法相信这实际上是一个只有一个元素的元组。
当可能时, reshape()
将返回要更改的张量的视图 - 即,一个查看相同内存区域的独立张量对象。这很重要:这意味着对源张量所做的任何更改都将反映在该张量的视图上,除非你 clone()
它。
在本介绍范围之外,存在一些条件下, reshape()
必须返回一个包含数据副本的张量。有关更多信息,请参阅文档。
NumPy 桥接
在上面的广播部分中提到,PyTorch 的广播语义与 NumPy 兼容,但 PyTorch 和 NumPy 之间的联系远不止于此。
如果您有现有的机器学习或科学代码,并且数据存储在 NumPy ndarrays 中,您可能希望将相同的数据表示为 PyTorch 张量,无论是为了利用 PyTorch 的 GPU 加速,还是为了其高效的构建机器学习模型的抽象。在 ndarrays 和 PyTorch 张量之间切换非常简单:
import numpy as np
numpy_array = np.ones((2, 3))
print(numpy_array)
pytorch_tensor = torch.from_numpy(numpy_array)
print(pytorch_tensor)
PyTorch 创建的张量与 NumPy 数组具有相同的形状和相同的数据,甚至保留了 NumPy 的默认 64 位浮点数据类型。
转换同样可以轻松地反向进行:
pytorch_rand = torch.rand(2, 3)
print(pytorch_rand)
numpy_rand = pytorch_rand.numpy()
print(numpy_rand)
这些转换后的对象使用与它们的源对象相同的底层内存,这意味着对其中一个对象的更改会反映在另一个对象上:
numpy_array[1, 1] = 23
print(pytorch_tensor)
pytorch_rand[1, 1] = 17
print(numpy_rand)
脚本总运行时间:(0 分钟 0.000 秒)