翻译: 2.1深度学习pytorch数据处理

Posted AI架构师易筋

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了翻译: 2.1深度学习pytorch数据处理相关的知识,希望对你有一定的参考价值。

为了完成任何事情,我们需要某种方式来存储和操作数据。一般来说,我们需要对数据做两件重要的事情:(i)获取它们;(ii) 一旦它们进入计算机就对其进行处理。如果没有某种方式来存储数据,获取数据是没有意义的,所以让我们先来玩弄合成数据。首先,我们介绍 𝑛 维数组,也称为张量。

如果您使用过 NumPy,这是 Python 中使用最广泛的科学计算包,那么您会发现本节很熟悉。无论您使用哪个框架,它的张量类(ndarray在 MXNet 中, Tensor在 PyTorch 和 TensorFlow 中)都与 NumPy 相似, ndarray但具有一些杀手级功能。首先,GPU 得到很好的支持以加速计算,而 NumPy 仅支持 CPU 计算。其次,张量类支持自动微分。这些属性使张量类适用于深度学习。在整本书中,当我们说张量时,我们指的是张量类的实例,除非另有说明。

2.1.1 入门

在本节中,我们的目标是让您开始并运行,为您配备基本的数学和数值计算工具,您将在阅读本书的过程中建立这些工具。如果您难以理解一些数学概念或库函数,请不要担心。以下部分将在实际示例的背景下重新审视这些材料,并且会深入了解。另一方面,如果您已经有一些背景并想更深入地了解数学内容,请跳过此部分。

首先,我们导入torch. 请注意,虽然它被称为 PyTorch,但我们应该导入torch而不是pytorch.

import torch

张量表示一个(可能是多维的)数值数组。对于一个轴,张量称为向量。有两个轴的张量称为矩阵。和 𝑘>2 轴,我们删除专门的名称,只是将对象称为 𝑘th 阶张量.

PyTorch 提供了多种函数来创建预先填充了值的新张量。例如,通过调用arange(n),我们可以创建一个等距值的向量,从 0(包括)开始,到n(不包括)结束。默认情况下,间隔大小为 1 . 除非另有说明,否则新张量存储在主内存中并指定用于基于 CPU 的计算。

x = torch.arange(12, dtype=torch.float32)
x
tensor([ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9., 10., 11.])

我们可以通过检查张量的属性来访问张量的形状(沿每个轴的长度) 。shape

x.shape
torch.Size([12])

如果我们只想知道张量中元素的总数,即所有形状元素的乘积,我们可以检查它的大小。因为我们在这里处理的是一个向量,所以它的单个元素 shape与其大小相同。

x.numel()
12

要改变张量的形状而不改变元素的数量或它们的值,我们可以调用该reshape函数。例如,我们可以将张量 ,x从形状为 (12,) 的行向量转换为形状为 (3, 4) 的矩阵。这个新张量包含完全相同的值,但将它们视为组织为 3 行和 4 列的矩阵。重申一下,虽然形状发生了变化,但元素没有。请注意,大小不会因重塑而改变。

X = x.reshape(3, 4)
X
tensor([[ 0.,  1.,  2.,  3.],
        [ 4.,  5.,  6.,  7.],
        [ 8.,  9., 10., 11.]])

通过手动指定每个维度来重塑是不必要的。如果我们的目标形状是一个形状为(高度,宽度)的矩阵,那么在我们知道宽度之后,隐式给出高度。为什么我们必须自己执行除法?在上面的例子中,为了得到一个有 3 行的矩阵,我们同时指定它应该有 3 行和 4 列。幸运的是,张量可以自动计算出剩下的一维。-1我们通过放置我们希望张量自动推断的维度来调用此功能。在我们的例子中,我们可以等效地调用 or ,而不是调用。x.reshape(3, 4) x.reshape(-1, 4) x.reshape(3, -1)

通常,我们希望我们的矩阵使用零、一、其他一些常数或从特定分布中随机采样的数字进行初始化。我们可以创建一个张量来表示一个所有元素都设置为 0 且形状为 (2, 3, 4) 的张量,如下所示:

torch.zeros((2, 3, 4))
tensor([[[0., 0., 0., 0.],
         [0., 0., 0., 0.],
         [0., 0., 0., 0.]],

        [[0., 0., 0., 0.],
         [0., 0., 0., 0.],
         [0., 0., 0., 0.]]])

同样,我们可以创建每个元素设置为 1 的张量,如下所示:

torch.ones((2, 3, 4))
tensor([[[1., 1., 1., 1.],
         [1., 1., 1., 1.],
         [1., 1., 1., 1.]],

        [[1., 1., 1., 1.],
         [1., 1., 1., 1.],
         [1., 1., 1., 1.]]])

通常,我们希望从某个概率分布中随机抽取张量中每个元素的值。例如,当我们构造数组作为神经网络中的参数时,我们通常会随机初始化它们的值。下面的代码片段创建了一个形状为 (3, 4) 的张量。它的每个元素都是从标准高斯(正态)分布中随机采样的,均值为 0,标准差为 1。

torch.tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
tensor([[2, 1, 4, 3],
        [1, 2, 3, 4],
        [4, 3, 2, 1]])

2.1.2. 运算

这本书不是关于软件工程的。我们的兴趣不仅限于简单地从/向数组读取和写入数据。我们想对这些数组执行数学运算。一些最简单和最有用的操作是元素操作。这些将标准标量操作应用于数组的每个元素。对于将两个数组作为输入的函数,逐元素运算对两个数组中的每对对应元素应用一些标准二元运算符。我们可以从任何从标量映射到标量的函数创建一个元素函数。

在数学符号中,我们将通过签名 表示这样的一元标量运算符(接受一个输入) 𝑓:ℝ→ℝ . 这只是意味着该函数是从任何实数映射的( ℝ ) 到另一个上。同样,我们用签名表示二元标量运算符(取两个实际输入,并产生一个输出) 𝑓:ℝ,ℝ→ℝ . 给定任意两个向量 𝐮 和 𝐯 形状相同,二元算子 𝑓 ,我们可以产生一个向量 𝐜=𝐹(𝐮,𝐯) 通过设置 𝑐𝑖←𝑓(𝑢𝑖,𝑣𝑖) 对所有人 𝑖 , 在哪里 𝑐𝑖,𝑢𝑖 , 和 𝑣𝑖 是 𝑖th 向量的元素 𝐜,𝐮 , 和 𝐯 . 在这里,我们产生了向量值 𝐹:ℝ𝑑,ℝ𝑑→ℝ𝑑 通过 将标量函数提升为元素向量运算。

常见的标准算术运算符(+、-、*、/和**)都已升级为任意形状的任何形状相同的张量的元素运算。我们可以在任何两个相同形状的张量上调用元素操作。在下面的示例中,我们使用逗号来表示一个 5 元素元组,其中每个元素都是元素运算的结果。

2.1.2.1。运算

常见的标准算术运算符(+、-、*、/和**)已全部提升为元素运算。

x = torch.tensor([1.0, 2, 4, 8])
y = torch.tensor([2, 2, 2, 2])
x + y, x - y, x * y, x / y, x ** y  # The ** operator is exponentiation
(tensor([ 3.,  4.,  6., 10.]),
 tensor([-1.,  0.,  2.,  6.]),
 tensor([ 2.,  4.,  8., 16.]),
 tensor([0.5000, 1.0000, 2.0000, 4.0000]),
 tensor([ 1.,  4., 16., 64.]))

可以按元素应用更多操作,包括像幂运算这样的一元运算符。

torch.exp(x)
tensor([2.7183e+00, 7.3891e+00, 5.4598e+01, 2.9810e+03])

除了元素计算之外,我们还可以执行线性代数运算,包括向量点积和矩阵乘法。我们将在2.3 节中解释线性代数的关键部分(没有假设的先验知识)。

我们还可以将多个张量连接在一起,将它们端到端堆叠以形成更大的张量。我们只需要提供张量列表并告诉系统沿着哪个轴连接。下面的示例显示了当我们沿行(轴 0,形状的第一个元素)与列(轴 1,形状的第二个元素)连接两个矩阵时会发生什么。我们可以看到第一个输出张量的axis-0长度( 6 ) 是两个输入张量的轴 0 长度之和 ( 3+3 ); 而第二个输出张量的轴 1 长度( 8 ) 是两个输入张量的轴 1 长度之和 ( 4+4 )。

X = torch.arange(12, dtype=torch.float32).reshape((3,4))
Y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
torch.cat((X, Y), dim=0), torch.cat((X, Y), dim=1)
(tensor([[ 0.,  1.,  2.,  3.],
         [ 4.,  5.,  6.,  7.],
         [ 8.,  9., 10., 11.],
         [ 2.,  1.,  4.,  3.],
         [ 1.,  2.,  3.,  4.],
         [ 4.,  3.,  2.,  1.]]),
 tensor([[ 0.,  1.,  2.,  3.,  2.,  1.,  4.,  3.],
         [ 4.,  5.,  6.,  7.,  1.,  2.,  3.,  4.],
         [ 8.,  9., 10., 11.,  4.,  3.,  2.,  1.]]))

有时,我们想通过逻辑语句构造一个二进制张量。举个例子。对于每个位置,如果 和在该位置相等,则新张量中的相应条目取值为 1,这意味着逻辑语句 在该位置为真;否则该位置为 0。

X == Y
tensor([[False,  True, False,  True],
        [False, False, False, False],
        [False, False, False, False]])

将张量中的所有元素相加得到一个只有一个元素的张量。

X.sum()
tensor(66.)

2.1.3 广播机制

在上一节中,我们看到了如何对两个相同形状的张量执行元素操作。在某些情况下,即使形状不同,我们仍然可以通过调用广播机制来执行元素操作。这种机制的工作方式如下:首先,通过适当地复制元素来扩展一个或两个数组,以便在此转换之后,两个张量具有相同的形状。其次,对结果数组执行元素操作。

在大多数情况下,我们沿着一个数组最初只有长度 1 的轴进行广播,例如在以下示例中:

a = torch.arange(3).reshape((3, 1))
b = torch.arange(2).reshape((1, 2))
a, b
(tensor([[0],
         [1],
         [2]]),
 tensor([[0, 1]]))

因为a和b是 3×1 和 1×2 矩阵,如果我们想添加它们,它们的形状不匹配。我们 将两个矩阵的条目广播到一个更大的 3×2 矩阵如下:对于矩阵a,它复制列,对于矩阵b,它在将两个元素相加之前复制行。

a + b
tensor([[0, 1],
        [1, 2],
        [2, 3]])

2.1.4 索引和切片

就像在任何其他 Python 数组中一样,张量中的元素可以通过索引访问。与任何 Python 数组一样,第一个元素的索引为 0,范围被指定为包括第一个但在最后一个元素之前。与标准 Python 列表一样,我们可以使用负索引根据它们与列表末尾的相对位置来访问元素。

因此,[-1]选择最后一个元素并[1:3]选择第二个和第三个元素,如下所示:

X[-1], X[1:3]
(tensor([ 8.,  9., 10., 11.]),
 tensor([[ 4.,  5.,  6.,  7.],
         [ 8.,  9., 10., 11.]]))

除了读取之外,我们还可以通过指定索引来写入矩阵的元素。

X[1, 2] = 9
X
tensor([[ 0.,  1.,  2.,  3.],
        [ 4.,  5.,  9.,  7.],
        [ 8.,  9., 10., 11.]])

如果我们想为多个元素分配相同的值,我们只需索引所有元素,然后为它们分配值。例如, 访问第一行和第二行,其中获取沿轴 1(列)的所有元素。虽然我们讨论了矩阵的索引,但这显然也适用于向量和超过 2 维的张量。[0:2, :]:

X[0:2, :] = 12
X
tensor([[12., 12., 12., 12.],
        [12., 12., 12., 12.],
        [ 8.,  9., 10., 11.]])

2.1.5 节省内存

运行操作可能会导致将新内存分配给主机结果。例如,如果我们写,我们将取消引用曾经指向的张量,而是指向新分配的内存。在下面的示例中,我们使用 Python 的函数来演示这一点,它为我们提供了被引用对象在内存中的确切地址。运行后,我们会发现指向不同的位置。这是因为 Python 首先计算,为结果分配新内存,然后指向内存中的这个新位置。

before = id(Y)
Y = Y + X
id(Y) == before
False

出于两个原因,这可能是不可取的。首先,我们不想一直跑来跑去分配不必要的内存。在机器学习中,我们可能有数百兆字节的参数,并且每秒更新所有参数多次。通常,我们会希望就地执行这些更新。其次,我们可能会从多个变量中指向相同的参数。如果我们没有就地更新,其他引用仍将指向旧的内存位置,这使得我们的部分代码可能无意中引用过时的参数。
幸运的是,执行就地操作很容易。我们可以使用切片符号将操作的结果分配给先前分配的数组,例如. 为了说明这个概念,我们首先创建一个与另一个形状相同的新矩阵,用于分配一块Y[:] =

Z = torch.zeros_like(Y)
print('id(Z):', id(Z))
Z[:] = X + Y
print('id(Z):', id(Z))
id(Z): 139729336478400
id(Z): 139729336478400

如果 的值X在后续计算中没有被重用,我们也可以使用or来减少操作的内存开销。X[:] = X + Y

before = id(X)
X += Y
id(X) == before
True

转换为 NumPy 张量 ( ndarray),反之亦然,很容易。torch 张量和 numpy 数组将共享它们的底层内存位置,通过就地操作更改一个也会更改另一个。

A = X.numpy()
B = torch.from_numpy(A)
type(A), type(B)
(numpy.ndarray, torch.Tensor)

要将 size-1 张量转换为 Python 标量,我们可以调用 item函数或 Python 的内置函数。

a = torch.tensor([3.5])
a, a.item(), float(a), int(a)
(tensor([3.5000]), 3.5, 3.5, 3)

2.1.7 总结

为深度学习存储和操作数据的主要接口是张量 ( 𝑛 维数组)。它提供了多种功能,包括基本数学运算、广播、索引、切片、内存节省以及转换为其他 Python 对象。

参考

https://d2l.ai/chapter_preliminaries/ndarray.html

以上是关于翻译: 2.1深度学习pytorch数据处理的主要内容,如果未能解决你的问题,请参考以下文章

(翻译)60分钟入门深度学习工具-PyTorch

Pytorch深度学习基础之Tensor

PyTorch学习3《PyTorch深度学习实践》——反向传播(Back Propagation)

动手学深度学习v2学习笔记02:线性代数矩阵计算自动求导

Four---pytorch学习---基本数据类型/标量/张量/dim值

深度学习pytorch使用tensorboard可视化实验数据