目录

  • 说明
  • Tensor
  • Tensor的创建
  • Tensor(张量)基本数据类型与常用属性
  • Tensor的自动微分
  • 设置不可积分计算
  • pytorch 计算图
  • backward一些细节


该文章解决问题如下:

  • 对于tensor计算梯度,需设置requires_grad=True
  • 为什么需要tensor.zero_grad()
  • tensor.backward()中两个参数gradientretain_graph介绍

说明

pytorch旧版本中有两个基本对象:Tensor(张量),Variable(变量)。其中tensor不能进行反向传播;Variable是一种不断变化的类型,符合参数更新的属性,因此可以反向传播。

其实Variable是对tensor的一种封装,操作和tensor基本一致,但是每个Variable都多了三个属性

  • .data: tensor本身的数据
  • .grad: 对应tensor的梯度
  • .grad_fn: 获得.grad的方式(这个可能不是很好理解,后面有说明。表示该tensor的梯度值的计算来自于哪,如果没有,则是None。这里可以简单类比于函数中的自变量和因变量,如果是自变量,那么该属性值就是None;如果是因变量,其梯度来自于其自变量的计算)

每次创建了tensor张量,如果需要计算梯度,需要先将其转换为Variable变量后计算,并且属性requires_grad要设置为True。

from torch.autograd import Variable
import torch

# 创建一个torch.Size([2, 3])的tensor
x_tensor = torch.randn(2, 3)

# 将tensor封装成Variable类型,用于计算梯度,这里的requires_grad要设置为True
x = Variable(x_tensor, requires_grad=True)

y = 3 * x ** 2 + 1

print(y.type())  # torch.FloatTensor
print(y.grad_fn)  # <AddBackward0 object at 0x0000021679AB9700>

# 梯度参数grad_variables形状必须与Variable一致
grad_variables = torch.FloatTensor([[1, 2, 3],
                                    [1, 1, 1]])

# 求函数y对x的梯度,这里需要输入参数,否则会报错grad can be implicitly created only for scalar outputs
y.backward(grad_variables)
print(x.grad)  # 得到x的梯度

在新版本的pytorch中,torch.autograd.Variabletorch.Tensor同属于一类。以下所有的都是操作都是在新版本的pytorch下执行的。

Tensor

Tensor的创建

Tensor是n维的数组,在概念上与numpy数组是一样的,不同的是Tensor可以跟踪计算图和计算梯度。其创建可以来自于python的内置数据类型,也可以来自于其他(numpy)等。以下给出几种创建tensor的方法:

import torch
import numpy as np

# 从numpy中创建
numpy_array = np.array([2, 1, 2])
torch_tensor1 = torch.from_numpy(numpy_array)
torch_tensor2 = torch.Tensor(numpy_array)
torch_tensor3 = torch.tensor(numpy_array)

# 将tensor转换回numpy类型
numpy_array = torch_tensor1.numpy()  # 如果tensor在CPU上
numpy_array = torch_tensor1.cpu.numpy()  # 如果tensor在GPU上
print(type(numpy_array))  # 输出 : <class 'numpy.ndarray'>

torch.Tensor()是默认张量类型torch.FloatTensor()的别名,也就是说torch.Tensor()返回的是Float数据类型。可以修改其默认的数据类型:

torch.set_default_tensor_type(torch.DoubleTensor)

torch.tensor()则会根据输入数据类型生成相应的torch.LongTensortorch.FloatTensortorch.DoubleTensor

# 创建形状为size的全为fill_value的Tensor
a = torch.full(size=[2, 3], fill_value=2)
# 创建形状为size的全为1的Tensor
b = torch.ones(size=[2, 3])
# 创建形状为size的全0的Tensor
c = torch.zeros(size=[2, 3])
# 创建对角阵的Tensor
d = torch.eye(3)
# 在区间[low,high]中随机创建形状为size的Tensor
e = torch.randint(low=1, high=10, size=[2, 2])

在使用torch.Tensor()方法创建Tensor的时候可以设置数据类型(dtype)与设备(device)

a = torch.tensor(data=[[1, 3, 1], # 数据
                       [4, 5, 4]],
                 dtype=torch.float64,# 数据类型
                 device=torch.device('cuda:0')) # 设备

# 可以通过Tensor的属性查看
print(a.dtype)  # torch.float64
print(a.device)  # cuda:0
print(a.is_cuda)  # True

在使用tensor的时候,如果将tensor放在GPU中,则可以加速tensor的计算。

# 1,定义cuda数据类型
# 把Tensor转换为cuda数据类型
dtype = torch.cuda.FloatTensor
gpu_tensor = torch.tensor(data=[[1, 3, 1],
                                [4, 5, 4]]).type(dtype)

# 2.直接将Tensor放到GPU上
a = torch.tensor(data=[[1, 3, 1],
                       [4, 5, 4]])
gpu_tensor = a.cuda(0)  # 把Tensor直接放在第一个GPU上
gpu_tensor = a.cuda(1)  # 把Tensor直接放在第二个GPU上

# 如果将GPU上tensor放回CPU也简单
a_cpu = gpu_tensor.cpu()

Tensor(张量)基本数据类型与常用属性

Tensor的最基本数据类型:

  • 32位浮点型:torch.float32 (最常用)
  • 64位浮点型:torch.float64 (最常用)
  • 32位整型:torch.int32
  • 16位整型:torch.int16
  • 64位整型:torch.int64

a.type 可以重新生成一个数据类型的相同数据的tensor。

import torch

# 创建一个tensor
a = torch.tensor(data=[[1, 3, 1],
                       [4, 5, 4]])
# 查看Tensor类型
print(a.dtype)  # torch.int64

# 如果不传入参数,则默认转换为torch.LongTensor类型
b = a.type(torch.float64)
print(a.dtype)  # torch.int64
print(b.dtype)  # torch.float64

# 查看Tensor尺寸
print(a.shape)  # torch.Size([2, 3])

# 查看Tensor是否在GPU上
print(a.is_cuda)  # False

# 查看Tensor存储设备
print(a.device)  # cpu

# 查看Tensor梯度计算,如果没有,则是None
print(a.grad)  # None

# 如果要计算梯度,requires_grad需要设置为True
print(a.requires_grad)  # False

Tensor的自动微分

Torch.Tensorrequires_grad 属性设置为True,那么pytorch将开始跟踪对此张量的所有操作。完成计算后,可以调用.backward()方法自动计算所有梯度。并且该张量的梯度计算值将累加到.grad属性中。

  • .data: tensor本身的数据
  • .grad: 对应张量的梯度累加和
  • .grad_fn: 获得.grad的方式(来自于什么函数,如果是自己创建的,则是None
# 创建tensor
x = torch.tensor(data=[[1, 2],
                       [2, 1]],
                 dtype=torch.float64,
                 requires_grad=True)
print(x.requires_grad)  # True

y = x ** 2 + 2
y = y.mean()

# 在计算backward的时候,y必须是标量,否则需要设置一下backward函数的输入参数。
y.backward()
print(x.grad)
"""
tensor([[0.5000, 1.0000],
        [1.0000, 0.5000]], dtype=torch.float64)
"""

为了说明上述的梯度计算原理,这边需要定义一些函数,设 pytorch模型backward部分计算速度慢 pytorch backward参数_整型,并且pytorch模型backward部分计算速度慢 pytorch backward参数_数据_02

因此,求 pytorch模型backward部分计算速度慢 pytorch backward参数_数据类型_03pytorch模型backward部分计算速度慢 pytorch backward参数_数据_04 的梯度,有:
pytorch模型backward部分计算速度慢 pytorch backward参数_数据_05
因为pytorch模型backward部分计算速度慢 pytorch backward参数_pytorch_06 带入梯度计算公式,于是其梯度pytorch模型backward部分计算速度慢 pytorch backward参数_python_07

但是这里有个问题,由于梯度是累加到.grad中的,如果存在多个函数对同一个数据x求解梯度,其梯度是累加之和。可以通过Tensor.grad.data.zero_().grad进行清零操作。

# 创建tensor
x = torch.tensor(data=[[1, 2],
                       [2, 1]],
                 dtype=torch.float64,
                 requires_grad=True)
print(x.requires_grad)  # True

z = 4 * x + 2
z = z.mean()

z.backward()
print(x.grad)
"""
tensor([[1., 1.],
        [1., 1.]], dtype=torch.float64)
"""

# 对x的梯度清0操作,否则会由于x的不同梯度进行叠加操作。
# x.grad.data.zero_()

y = x ** 2 + 2
y = y.mean()

# 在计算backward的时候,y必须是标量,否则需要设置一下backward函数的输入参数。
y.backward()
print(x.grad)
"""
tensor([[1.5000, 2.0000],
        [2.0000, 1.5000]], dtype=torch.float64)
"""

该实例中x的梯度由两个函数累加而成 pytorch模型backward部分计算速度慢 pytorch backward参数_python_08 。其中,pytorch模型backward部分计算速度慢 pytorch backward参数_整型_09pytorch模型backward部分计算速度慢 pytorch backward参数_python_10

开始是x的.grad属性来自于 pytorch模型backward部分计算速度慢 pytorch backward参数_整型_11 ,于是有
pytorch模型backward部分计算速度慢 pytorch backward参数_数据类型_12
此时,pytorch模型backward部分计算速度慢 pytorch backward参数_pytorch_06 带入梯度计算公式,于是其梯度pytorch模型backward部分计算速度慢 pytorch backward参数_数据_14

由于没有对x的梯度进行清零操作,那么在计算求 pytorch模型backward部分计算速度慢 pytorch backward参数_数据类型_03pytorch模型backward部分计算速度慢 pytorch backward参数_数据_04 的梯度后,其x.grad由两部分组成,之前梯度+新增梯度,即pytorch模型backward部分计算速度慢 pytorch backward参数_数据类型_17

设置不可积分计算

有时候不需要去跟踪计算。例如,用于测试集的测试数据(全设置为不跟踪计算),或者冻结权重信息(一部分跟踪计算)。

实现该方法的原理是将不需要计算梯度的tensor的.requires_grad属性设置为False即可。

在pytorch中,tensor的requires_grad参数如果设置为True,则反向传播时,该tensor就会自动求导。当requires_grad设置为False时,反向传播时就不会自动求导了,因此大大节约了显存或者说内存。tensor的requires_grad的默认属性为False。

改变Tensor(张量)变量的requires_grad值语句:

# 就地改变Tensor变量a的requires_grad属性为True
a.requires_grad = True 
a.requires_grad_(True)  # 与上面等价,注意下划线

用于测试集,使用with torch.no_grad(),在该模块下的所有计算得出的tensor的.requires_grad属性将自动设置为False。

import torch

x = torch.randn(3, 4, requires_grad=True)
y = torch.randn(3, 4, requires_grad=True)

u = x + y
print(u.requires_grad)  # True
print(u.grad_fn)  # <AddBackward0 object at 0x000001D0DC709700>

with torch.no_grad():
    w = x + y
    print(w.requires_grad)  # False
    print(w.grad_fn)  # None
print(w.requires_grad)  # False

with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源。

语句:with open("1.txt") as file:

with工作原理:

  1. 紧跟with后面的语句被求值后,返回对象的 __enter__ 方法被调用,返回值将被赋值给 as 后面的变量;
  2. 当 with 语句体全部被执行完之后,将调用前面返回对象的 __exit__ 方法。

用于冻结权重,对网络中参数进行遍历,将需要冻结参数的tensor设置为True,其他设置为False

# 遍历模型权重信息
for name, para in model.named_parameters():
    # 按照名字将其进行冻结,这里冻结最后一层的fc,和fc.bias
    if "fc" not in name and "fc.bias" not in name:
        para.requires_grad_(False)

pytorch 计算图

说明,pytorch是动态图机制,所以在训练模型时候,每迭代一次都会构建一个新的计算图。而计算图其实就是代表程序中变量之间的关系。

动态计算意味着程序将按照我们编写命令的顺序进行执行。这种机制将使得调试更加容易,并且也使得我们将大脑中的想法转化为实际代码变得更加容易。而静态计算则意味着程序在编译执行时将先生成神经网络的结构,然后再执行相应操作。从理论上讲,静态计算这样的机制允许编译器进行更大程度的优化,但是这也意味着你所期望的程序与编译器实际执行之间存在着更多的代沟。这也意味着,代码中的错误将更加难以发现(比如,如果计算图的结构出现问题,你可能只有在代码执行到相应操作的时候才能发现它)。尽管理论上而言,静态计算图比动态计算图具有更好的性能,但是在实践中我们经常发现并不是这样的。

计算图是用来描述运算的有向无环图,主要由结点(Node)和边(Edge)构成,其中结点表示数据,而边表示运算。

pytorch模型backward部分计算速度慢 pytorch backward参数_pytorch_18 为例, 其计算图如下图所示,其中 pytorch模型backward部分计算速度慢 pytorch backward参数_python_19

pytorch模型backward部分计算速度慢 pytorch backward参数_python_20

由链式法则,可以知道,如果要计算某个叶子结点对其根结点的导数,就是根结点到该叶子结点所有路径上的导数求和。(也进一步说明为什么.grad是累加的。)

比如求解 pytorch模型backward部分计算速度慢 pytorch backward参数_数据类型_03pytorch模型backward部分计算速度慢 pytorch backward参数_pytorch_22 的导数,存在两条路径,因此,
pytorch模型backward部分计算速度慢 pytorch backward参数_整型_23
pytorch模型backward部分计算速度慢 pytorch backward参数_数据_24,这时候计算出来的pytorch模型backward部分计算速度慢 pytorch backward参数_数据_25 满足:
pytorch模型backward部分计算速度慢 pytorch backward参数_整型_26

import torch

# 创建tensor
x_1 = torch.tensor(data=[1.], requires_grad=True)
x_2 = torch.tensor(data=[2.], requires_grad=True)
x_3 = torch.tensor(data=[3.], requires_grad=True)
x_4 = torch.tensor(data=[4.], requires_grad=True)

f = x_1 + x_2
g = x_2 - x_3 - x_4

y = f * g
# y.retain_grad()

y.backward()
print(x_2.grad)

# 查看叶子结点
print(x_1.is_leaf, x_2.is_leaf, x_3.is_leaf, x_4.is_leaf, f.is_leaf, g.is_leaf, y.is_leaf)
# 输出:True True True True False False False

# 查看梯度
print(x_1.grad, x_2.grad, x_3.grad, x_4.grad, f.grad, g.grad, y.grad)
# 输出:tensor([-5.]) tensor([-2.]) tensor([-3.]) tensor([-3.]) None None None

torch.tensor中有个属性is_leaf属性用于指示张量是否为叶子结点。叶子节点是整个计算图的根基,无论是求导计算图还是反向传播过程中,所有的梯度计算都依赖于叶子结点。设置叶子节点主要是为了节省内存,在梯度反向传播结束之后,非叶子节点的梯度都会被释放掉。

在运行过程中,可能会报错:UserWarning: The .grad attribute of a Tensor that is not a leaf Tensor is being accessed.

表示:正在访问非叶张量的张量的.grad属性,在autograd.backward() 期间不会填充其.grad属性。如果确实需要非叶张量的梯度,请在非叶张量上使用.retain_grad()。如果您错误地访问了非叶张量,请确保您访问了叶张量。

对于非叶子结点 pytorch模型backward部分计算速度慢 pytorch backward参数_pytorch_27 三个张量,如果要使用其.grad属性,可以在backward之前使用.retain_grad()将其梯度保存下来。

backward一些细节

形式:

tensor.backward(gradient, retain_graph)

pytoch构建的计算图是动态图,为了节约内存,所以每次一轮迭代完之后计算图就被在内存释放。如果使用多次backward就会报错。可以通过设置标识retain_graph=True来保存计算图,使其不被释放。

import torch

x = torch.randn(4, 4, requires_grad=True)
y = 3 * x + 2
y = torch.sum(y)

y.backward(retain_graph=True)  # 添加retain_graph=True标识,让计算图不被立即释放
y.backward()  # 不报错
y.backward()  # 报错

还有,以上所有的计算梯度都是基于y是标量的情况下,如果不是标量的情况下,需要对backward传入参数gradient 也就是说当y不再是标量,而是多维张量的情况下,需要设置gradient。那么为什么需要使用该参数,并且该参数如何使用?

参考:Automatic Differentiation with torch.autograd

这里举个例子,设,pytorch模型backward部分计算速度慢 pytorch backward参数_pytorch_28pytorch模型backward部分计算速度慢 pytorch backward参数_数据_29pytorch模型backward部分计算速度慢 pytorch backward参数_数据类型_30,且满足:pytorch模型backward部分计算速度慢 pytorch backward参数_数据_31,其中,函数pytorch模型backward部分计算速度慢 pytorch backward参数_数据_32的具体定义未知。
pytorch模型backward部分计算速度慢 pytorch backward参数_整型_33
改成标量形式:
pytorch模型backward部分计算速度慢 pytorch backward参数_pytorch_34
基于此,我们需要计算出 pytorch模型backward部分计算速度慢 pytorch backward参数_整型_35pytorch模型backward部分计算速度慢 pytorch backward参数_整型_36的偏导,即需要求解pytorch模型backward部分计算速度慢 pytorch backward参数_数据_37pytorch模型backward部分计算速度慢 pytorch backward参数_整型_38,根据复合函数链式求解法则,有
pytorch模型backward部分计算速度慢 pytorch backward参数_python_39
上面 2个等式可以写成矩阵相乘的形式,如下:
pytorch模型backward部分计算速度慢 pytorch backward参数_数据类型_40
其中
pytorch模型backward部分计算速度慢 pytorch backward参数_数据_41
叫作雅可比( Jacobian) 式。雅可比式可以根据已知条件求出。
现在只要知道 pytorch模型backward部分计算速度慢 pytorch backward参数_数据_42 的值哪怕不知道 pytorch模型backward部分计算速度慢 pytorch backward参数_数据类型_43 的具体形式也能求出来 pytorch模型backward部分计算速度慢 pytorch backward参数_数据_42 。 那现在的问题是怎么样才能求出pytorch模型backward部分计算速度慢 pytorch backward参数_数据_42

该部分由pytorchbackward函数中的gradient参数提供。

这里提供一种计算方式,设pytorch模型backward部分计算速度慢 pytorch backward参数_数据_46,则雅可比( Jacobian) 式pytorch模型backward部分计算速度慢 pytorch backward参数_pytorch_47 (这里凑巧等于W了,而且该雅可比的计算和pytorch模型backward部分计算速度慢 pytorch backward参数_整型_36无关,在其他情况下不一定这么凑巧)。

于是无论pytorch模型backward部分计算速度慢 pytorch backward参数_整型_36 取何值,其雅可比式固定,然后通过gradient传入参数torch.tensor([0.1, 0.2], dtype=torch.float)即:
pytorch模型backward部分计算速度慢 pytorch backward参数_pytorch_50
代码实现如下:

# 在实现的时候,取了巧,所以计算的梯度和理论上存在转置
import torch

x = torch.tensor(data=[[1], [2]], dtype=torch.float64, requires_grad=True)  # 定义一个输入变量

w = torch.tensor([[1, 2],
                  [3, 4]], dtype=torch.float64)

y = torch.mm(w, x)  # 矩阵相乘
y.backward(gradient=torch.FloatTensor([[0.1], [0.2]]))
print(x.grad.data)

# 输出:
"""
tensor([[0.7000],
        [1.0000]], dtype=torch.float64)
"""

当然,这里以一个三维的输入和输出例子举例:
pytorch模型backward部分计算速度慢 pytorch backward参数_pytorch_51
那么其多元复合函数求导的矩阵形式如下:
pytorch模型backward部分计算速度慢 pytorch backward参数_数据类型_52
假设传入gradient参数为torch.tensor([0.1, 0.2, 0.3], dtype=torch.float),并且假定pytorch模型backward部分计算速度慢 pytorch backward参数_整型_53,理论上的梯度有:
pytorch模型backward部分计算速度慢 pytorch backward参数_数据_54
对应代码如下:

import torch

x = torch.tensor([1, 2, 3], requires_grad=True, dtype=torch.float)

y = torch.randn(3)

y[0] = x[0] + x[1] + x[2]
y[1] = x[0] * x[1] + x[2]
y[2] = x[0] - x[1] * x[2]

y.backward(torch.tensor([0.1, 0.2, 0.3], dtype=torch.float))

print(x.grad)
# 输出:
"""
tensor([ 0.8000, -0.6000, -0.3000])
"""