本文发表于 658 天前,其中的信息可能已经事过境迁

简介

Pytorch是一个开源的深度学习框架,由Facebook开发,提供了动态计算图的机制,使得模型的构建和调试更加灵活和方便。本文将介绍Pytorch的基础知识,包括张量(Tensor)、自动求导(Autograd)和模型构建等内容。

Paper Implementations grouped by frameworkPaper Implementations grouped by framework

截止目前,Pytorch在占有率上已经超过了Tensorflow,成为了最受欢迎的深度学习框架之一。

优势:

  • 简洁,易于使用,封装了很多高级API,方便用户快速构建模型。

  • 强大的社区及生态支持,有大量的开源项目和模型可供使用。

  • 使用CUDA加速,支持NVIDIA GPU的CUDA加速,训练速度快。

特性:

  • 动态计算图(Dynamic Computation Graphs): PyTorch 的计算图是动态的,这意味着它们在运行时构建,并且可以随时改变。这为实验和调试提供了极大的灵活性,因为开发者可以逐行执行代码,查看中间结果。

  • 自动微分(Automatic Differentiation): PyTorch 的自动微分系统允许开发者轻松地计算梯度,这对于训练深度学习模型至关重要。它通过反向传播算法自动计算出损失函数对模型参数的梯度。

  • 张量计算(Tensor Computation): PyTorch 提供了类似于 NumPy 的张量操作,这些操作可以在 CPU 和 GPU 上执行,从而加速计算过程。张量是 PyTorch 中的基本数据结构,用于存储和操作数据。

  • 丰富的 API: PyTorch 提供了大量的预定义层、损失函数和优化算法,这些都是构建深度学习模型的常用组件。

  • 多语言支持: PyTorch 虽然以 Python 为主要接口,但也提供了 C++ 接口,允许更底层的集成和控制。

对于环境配置,可以参考官方文档

张量(Tensor)

张量是 PyTorch 中的基本数据结构,类似于 NumPy 中的数组。张量可以存储和操作多维数据,支持各种数学运算和线性代数操作。在 PyTorch 中,张量可以在 CPU 和 GPU 上运行,从而加速计算过程。

张量的概念

在数学中:

  • 标量(Scalar):只有一个数值的张量,如 1、2、3 等。

  • 向量(Vector):有多个数值组成的张量,如 [1, 2, 3]。

  • 矩阵(Matrix):二维张量,如 [[1, 2], [3, 4]]。

  • 张量(Tensor):多维张量,如 [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]。

但在 PyTorch 中,张量是一个更加通用的概念,可以是任意维度的数组。

  • 0 维张量:标量(Scalar)。

  • 1 维张量:向量(Vector)。

  • 2 维张量:矩阵(Matrix)。

  • 3 维张量:三维张量,时间序列数据、RGB 图像等。

例:

  • 3维 = 时间序列

  • 4维 = RGB 图像

  • 5维 = 视频

在图像中,一个图像可以用三个维度表示:高度、宽度和通道数。例如,一张 224x224 的 RGB 图像可以表示为一个 3x224x224 的张量。

python
(width, height, channel) = 3D

但是,在机器学习中,我们需要处理大量图片或者文档,也就是说我们需要处理一个集合,这个集合就是一个张量。

python
(batch_size, width, height, channel) = 4D

其中,batch_size 表示一次处理的图片数量。

创建张量

在 PyTorch 中,我们可以使用 torch.Tensor 类来创建张量。张量可以是标量、向量、矩阵或多维张量。

python
import torch # 导入 PyTorch 库, 如果你不知道这是什么含义,请自行学习Python基础知识

# 创建一个标量(0 维张量)
scalar = torch.tensor(1.0)

# 创建一个向量(1 维张量)
vector = torch.tensor([1.0, 2.0, 3.0])

# 创建一个矩阵(2 维张量)
matrix = torch.tensor([[1.0, 2.0], [3.0, 4.0]])

# 创建一个三维张量
tensor = torch.tensor([[[1.0, 2.0], [3.0, 4.0]], [[5.0, 6.0], [7.0, 8.0]]])

print(scalar)
print(vector)
print(matrix)
print(tensor)

可以看到,我们使用 torch.tensor 函数来创建张量,然后打印出来。在 PyTorch 中,张量的数据类型是 torch.Tensor,可以通过 dtype 属相来查看。

类似的,我们还可以使用 torch.zerostorch.onestorch.randn 等函数来创建全 0 张量、全 1 张量和随机张量。

python
import torch

# 创建全 0 张量
zeros = torch.zeros(2, 3) # 2x3 的全 0 张量

# 创建全 1 张量
ones = torch.ones(2, 3)

# 创建随机张量
randn = torch.randn(2, 3)

print(zeros)
print(ones)
print(randn)

结果:

shell
tensor([[0., 0., 0.],
        [0., 0., 0.]])
tensor([[1., 1., 1.],
        [1., 1., 1.]])
tensor([[ 1.0898, -0.8992,  1.2634],
        [-0.2780,  1.1508, -2.3810]])

类似于Matlab和Numpy,Pytorch的Tensor有多种构造方法,以下是常见的方法:

python
import torch


# 1. 直接构造
a = torch.tensor([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])

# 2. 全1构造
b = torch.ones(3, 4)

# 3. 全0构造
c = torch.zeros(3, 4)

# 4. 随机构造 rand/randn
# rand是从0到1的均匀分布,randn是标准正态分布
d = torch.randn(3, 4)

# 5. 从numpy构造
import numpy as np
e = torch.from_numpy(np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]))

# 6. 对角为1的构造,其他为0
f = torch.eye(3, 4)

# 7. linspace构造
# torch.linspace(start, end, steps=100) 也就是从start到end,均匀分成step份
g = torch.linspace(1, 10, 10)

# 8. arange构造
# torch.arange(start, end, step=1) 也就是从start到end,间隔为step
h = torch.arange(1, 10, 1)

# 9. normal构造
# torch.normal(mean, std, size) 也就是从均值为mean,标准差为std的正态分布中采样,size是采样的数量
i = torch.normal(0, 1, (3, 4))

# 10. randperm构造
# torch.randperm(n) 也就是生成一个0到n-1的随机排列
j = torch.randperm(10)


print(a)
print(b)
print(c)
print(d)
print(e)
print(f)
print(g)
print(h)
print(i)
print(j)

张量的操作

  • 基础数学运算,在 PyTorch 中,我们可以对张量进行各种数学运算和线性代数操作,如加法、减法、乘法、除法、矩阵乘法等。
python
import torch

# 创建两个张量
a = torch.tensor([[1.0, 2.0]
                , [3.0, 4.0]])
b = torch.tensor([[5.0, 6.0]
                , [7.0, 8.0]])

# 加法
c = a + b

# 减法
d = a - b

# 乘法
e = a * b

# 除法
f = a / b

# 矩阵乘法
g = torch.matmul(a, b)

print(c)
print(d)
print(e)
print(f)
print(g)

结果:

shell
tensor([[ 6.,  8.],
        [10., 12.]])
tensor([[-4., -4.],
        [-4., -4.]])
tensor([[ 5., 12.],
        [21., 32.]])
tensor([[0.2000, 0.3333],
        [0.4286, 0.5000]])
tensor([[19., 22.],
        [43., 50.]])
  • 索引操作,我们可以使用索引操作来访问张量的元素,类似于 NumPy 中的索引操作。

注意:索引操作结果与原张量共享内存,修改索引操作结果会影响原张量。

例子:

python
import torch

# 创建一个张量
a = torch.tensor([[1.0, 2.0, 3.0]
                , [4.0, 5.0, 6.0]
                , [7.0, 8.0, 9.0]])

# 访问第一行第一列元素
print(a[0, 0])

# 访问第一行
print(a[0])

# 修改第一行第一列元素
a[0, 0] = 0.0

# 现在,我们来查看原张量
print(a)

结果:

shell
tensor(1.)
tensor([1., 2., 3.])
tensor([[0., 2., 3.],
        [4., 5., 6.],
        [7., 8., 9.]])
  • 形状操作,我们可以使用 size() 函数来查看张量的形状,使用 view() 函数来改变张量的形状。

注意:view() 函数返回的张量与原张量本质上是一个张量,只是形状不同,共享内存,相当于同一个数据的不同观察方式,因此操作view()返回的张量会影响原张量。

python
import torch

# 创建一个张量
a = torch.tensor([[1.0, 2.0, 3.0]
                , [4.0, 5.0, 6.0]
                , [7.0, 8.0, 9.0]])

# 查看张量的形状
print(a.size())

# 改变张量的形状
b = a.view(1, 9) # 改变为 1x9 的张量

print(b)

# 现在,我们试着来修改 b 的值
b[0, 0] = 0.0

# 我们来查看原张量
print(b)
print(a)

结果:

shell
torch.Size([3, 3]) // 这里表示3行3列
tensor([[1., 2., 3., 4., 5., 6., 7., 8., 9.]])
tensor([[0., 2., 3., 4., 5., 6., 7., 8., 9.]])
tensor([[0., 2., 3.],
        [4., 5., 6.],
        [7., 8., 9.]])

可以看到,以上操作修改了 b 的值,也影响了原张量 a 的值,所以要注意这一点,我们如果要保留原张量的值,可以使用 clone() 函数来复制一个张量。

也可以使用 reshape() 函数来改变张量的形状,reshape() 函数返回的张量与原张量不共享内存,相当于复制了一个张量,但是此函数并不能保证返回的是其拷贝值,所以官方不推荐使用。

注:使用 clone() 还有一个好处是会被记录在计算图中,即梯度回传到副本时也会传到源 Tensor 。 3. 取值操作 如果我们有一个元素 tensor ,我们可以使用 .item() 来获得这个 value,而不获得其他性质。

python
import torch

# 创建一个张量
a = torch.tensor([[1.0, 2.0, 3.0]
                , [4.0, 5.0, 6.0]
                , [7.0, 8.0, 9.0]])

# 复制一个张量
b = a.clone()

# 改变 b 的值
b[0, 0] = 0.0

# 查看 a 和 b 的值
print(a)
print(b)
  • 广播机制,PyTorch 支持广播操作,可以对不同形状的张量进行运算,如果你感觉到广播操作有些难以理解,可以简单地将其理解为对不同形状的张量进行适当的复制,使得它们的形状相同,然后再进行运算。看以下例子:
python
import torch

# 创建两个张量
a = torch.tensor([[1.0, 2.0, 3.0]
                , [4.0, 5.0, 6.0]
                , [7.0, 8.0, 9.0]])
b = torch.tensor([1.0, 2.0, 3.0])

# 广播操作
c = a + b

print(c)

结果:

shell
tensor([[ 2.,  4.,  6.],
        [ 5.,  7.,  9.],
        [ 8., 10., 12.]])

可以看到,我们对一个 3x3 的张量 a 和一个 1x3 的张量 b 进行了广播操作,得到了一个 3x3 的张量 c

  • 其他操作,PyTorch 还提供了很多其他操作,如转置、拼接、切片、矩阵分解等,这些操作可以帮助我们更方便地处理张量。
python
import torch

# 创建一个张量
a = torch.tensor([[1.0, 2.0, 3.0]
                , [4.0, 5.0, 6.0]
                , [7.0, 8.0, 9.0]])

# 转置
b = a.t()

# 拼接
c = torch.cat((a, b), dim=0) # 按行拼接

# 切片
d = a[0:2, 0:2] # 取第 0 行到第 1 行,第 0 列到第 1 列

# 矩阵分解
u, s, v = torch.svd(a)

# 条件筛选
e = a[a > 5]

# 上、下三角,对角矩阵获取
f = torch.triu(a) # 上三角
g = torch.tril(a) # 下三角
h = torch.diag(a) # 获取对角线元素

# 张量分块
i = torch.chunk(a, 2, dim=0) # 按行分块,分成 2 块,dim=0表示按行分块,dim=1表示按列分块

print(b)
print(c)
print(d)
print(u)
print(s)
print(v)
print(e)
print(f)
print(g)
print(h)
print(i)

除开上述操作外,PyTorch 还提供了很多其他操作,很多类似于Matlab中的矩阵操作,可参考官方文档 torch.Tensor 方法

自动求导(Autograd)

在深度学习中,自动求导是一个非常重要的功能,它可以帮助我们自动计算梯度,从而实现反向传播算法,训练深度学习模型。PyTorch 提供了自动求导功能,称为 Autograd。

Autograd 的概念

Autograd 是 PyTorch 中的自动求导模块,它可以自动计算张量的梯度,从而实现反向传播算法。在 PyTorch 中,所有的张量都有一个 requires_grad 属性,当 requires_grad=True 时,PyTorch 会自动追踪所有对该张量的操作,并自动计算梯度。

在数学中,微分是一个非常重要的概念,它可以帮助我们计算函数局部变化率,从而实现优化算法。在深度学习中,我们需要计算损失函数对模型参数的梯度,从而实现模型的训练。

Autograd开始之前,我们需要先了解计算图的概念。 计算图是一个有向无环图,它由节点和边组成,每个节点表示一个操作,每条边表示一个张量。在 PyTorch 中,计算图是动态的,它在运行时构建,并且可以随时改变。

过于抽象?

引用自PyTorch 的 Autograd--知乎

我们把它想象成一个错综复杂的管道结构,不同的管道之间通过节点连接起来,我们有一个注水口,一个出水口。我们在入口注入数据的之后,数据就沿着设定好的管道路线缓缓流动到出水口,这时候我们就完成了一次正向传播。想象一下输入的 tensor 数据在管道中缓缓流动的场景。

在这个过程中,我们可以通过管道的每个节点,看到数据的变化,这个变化就是梯度的变化。我们可以通过这个变化来计算梯度,这就是反向传播。

Autograd就是这个管道的设计者,他知道每个节点的作用,知道数据的流动方向,知道数据的变化,知道梯度的变化。所以他可以帮我们计算梯度。

Autograd 的使用

在 PyTorch 中,我们可以使用 torch.Tensor 是包的核心类,如果一个张量的 requires_grad=True,那么它将会追踪对其的所有操作,并自动计算梯度。

python
import torch

# 创建一个张量,并设置 requires_grad=True
x = torch.tensor(1.0, requires_grad=True)

# 创建一个函数 y = x^2
y = x ** 2

# 计算 y 对 x 的梯度,backward() 函数会自动计算梯度
y.backward()

# 查看 x 的梯度
print(x.grad)

结果:

shell
tensor(2.)

可以看到,我们创建了一个张量 x,并设置 requires_grad=True,然后创建了一个函数 y = x^2,最后计算了 yx 的梯度,得到了 x 的梯度为 2.0

注意:backward() 函数会自动计算梯度,但是只能对标量进行求导,如果要对非标量进行求导,需要传入一个和 y 同形状的张量作为参数。

python
import torch

# 创建一个张量,并设置 requires_grad=True
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)

# 创建一个函数 y = x^2
y = x ** 2

# 计算 y 对 x 的梯度,backward() 函数会自动计算梯度
y.backward(torch.tensor([1.0, 1.0, 1.0]))

# 查看 x 的梯度
print(x.grad)

结果:

shell
tensor([2., 4., 6.])

要组织一个张量被跟踪历史,可以使用 detach() 函数,这样就可以防止梯度的传播。

python
import torch

# 创建一个张量,并设置 requires_grad=True
x = torch.tensor(1.0, requires_grad=True)

# 创建一个函数 y = x^2
y = x ** 2

# 使用 detach() 函数防止梯度传播
z = y.detach()

# 计算 z 对 x 的梯度
z.backward()

# 查看 x 的梯度
print(x.grad)

此时运行会报错,因为我们使用了 detach() 函数,防止了梯度的传播,所以无法计算梯度。

还有一个非常重要的类 torch.autograd.FunctionTensorFunction 是相互关联的,Tensor 记录了创建它的 Function,也就是创建了一个无环图,它编码了完整的计算历史。每个张量都有一个.grad_fn属性,该属性引用了创建 Tensor 自身的Function(除非这个张量是用户手动创建的,即这个张量的grad_fn是 None )。

python
import torch

# 创建一个张量,并设置 requires_grad=True
x = torch.tensor(1.0, requires_grad=True)

# 创建一个函数 y = x^2
y = x ** 2

print(x.grad_fn)
print(y.grad_fn)

结果:

shell
None
<PowBackward0 object at 0x00000201998CF460>

可以看到,xgrad_fnNone,因为它是用户手动创建的,而 ygrad_fnPowBackward0,因为它是通过 x 计算得到的。

如果需要计算导数,需要使用 backward() 函数,该函数会计算当前张量的梯度,然后将梯度累加到 .grad 属性中。

梯度

在 PyTorch 中,我们可以使用 backward() 函数来计算梯度,然后使用 .grad 属性来查看梯度。

数学上,梯度是一个向量,它包含了函数在每个维度上的变化率,可以帮助我们找到函数的最小值或最大值。若有向量函数y=f(x),那么 y 关于 x 的梯度就是一个雅可比矩阵:

J=(y1x1y1xnymx1ymxn)

torch.autograd 这个包就是用来计算一些雅可比矩阵的乘积的。例如,如果 v 是一个标量函数 l=g(y) 的梯度:

v=(ly1lym)

由链式法则,我们可以得到:

vJ=(ly1lym)(y1x1y1xnymx1ymxn)=(lx1lxn)

注意:grad在反向传播过程中是累加的(accumulated),这意味着每次运行反向传播,梯度都会累加,所以在反向传播之前需要把梯度清零。

一个简单的求梯度的例子:

python
import torch

# 创建一个张量,并设置 requires_grad=True
x = torch.tensor(1.0, requires_grad=True)

# 创建一个函数 y = x^2
y = x ** 2

# 计算 y 对 x 的梯度
y.backward()

# 查看 x 的梯度
print(x.grad)

结果:

shell
tensor(2.)

可以看到,我们创建了一个张量 x,并设置 requires_grad=True,然后创建了一个函数 y = x^2,最后计算了 yx 的梯度,得到了 x 的梯度为 2.0

现在,我们来看一个更复杂的例子,计算一个函数 y=x3+2x2+3x+4 的梯度。

python
import torch

# 创建一个张量,并设置 requires_grad=True
x = torch.tensor(1.0, requires_grad=True)

# 创建一个函数 y = x^3 + 2x^2 + 3x + 4
y = x ** 3 + 2 * x ** 2 + 3 * x + 4

# 计算 y 对 x 的梯度
y.backward()

# 查看 x 的梯度
print(x.grad)

结果:

shell
tensor(10.)

可以看到,我们创建了一个张量 x,并设置 requires_grad=True,然后创建了一个函数 y = x^3 + 2x^2 + 3x + 4,最后计算了 yx 的梯度,得到了 x 的梯度为 10.0

现在我们来看一个雅可比向量积的例子:

python
import torch

# 创建一个张量,并设置 requires_grad=True
x = torch.randn(3, requires_grad=True)
print(x)

y = x * 2
i = 0
while y.data.norm() < 1000:
    y = y * 2
    i = i + 1
print(y)
print(i)

结果:

shell
tensor([ 0.1115, -1.1299,  1.0451], requires_grad=True)
tensor([  114.1972, -1157.0621,  1070.1959], grad_fn=<MulBackward0>)
9

在这个例子中,我们创建了一个张量 x,并设置 requires_grad=True,然后创建了一个函数 y = x * 2,然后不断地乘以 2,直到 y 的范数大于 1000,最后得到了 y 的值和乘法的次数。

不过,在这种情况下,y 不再是一个标量,所以我们不能直接计算雅可比矩阵,但是如果我们想要计算雅可比向量积,我们可以传递一个相同形状的张量作为参数。

python
import torch

# 创建一个张量,并设置 requires_grad=True
x = torch.randn(3, requires_grad=True)

# 创建一个函数 y = x * 2
y = x * 2

# 计算雅可比向量积
v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)

# 查看 x 的梯度
print(x.grad)

结果:

shell
tensor([2.0000e-01, 2.0000e+00, 2.0000e-04])

可以看到,我们创建了一个张量 x,并设置 requires_grad=True,然后创建了一个函数 y = x * 2,然后计算了雅可比向量积,得到了 x 的梯度。

模型构建

评论 隐私政策