PyTorch学习(基础)—— Tensor & autograd

几乎所有的深度学习框架背后的设计核心都是:张量计算图

一、Tensor

        在pytorch中,Tensor(一般可译作“张量”)是重要的数据结构,torch.Tensor是存储和变换数据的主要工具,可认为是一个高维数组,它可以是一个数(标量)、一维张量(向量)、二维张量(矩阵)或更高维的张量。Tensor和numpy中的多维数组ndarray很类似,但Tensor可以使用GPU加速。

        Tensor的接口设计与numpy类似,从接口的角度讲,对Tensor的操作可分为两类:(1)torch.function,如:torch.save等。(2)tensor.function,如:tensor.view等。为方便使用,对tensor的大部分操作同时支持这两种接口,如:torch.sum(a,b)与a.sum(b)功能等价;从存储的角度讲,对Tensor的操作又可分为两类:(1)不会修改自身的数据,如 a.add(b),加法的结果会返回一个新的Tensor。(2)会修改自身的数据,如 a.add_(b),加法的结果仍存储在a中,a被修改了。函数名以 _ 结尾的都是inplace的方式,即:会修改调用者自己的数据,在实际应用中需要加以区分。

1、创建Tensor

在pytorch中新建tensor的方法有很多,具体如下:

       其中,使用Tensor函数新建tensor是最复杂多变的方式,它既可以接收一个list并根据list的数据新建tensor,也能根据指定的形状新建tensor,还能传入其他的tensor。注意:torch.Tensor(*sizes)创建tensor时,系统不会马上分配空间,只会计算剩余的内存是否足够使用,使用到tensor时才会分配,而其他操作都是在创建完tensor后马上进行空间分配

import torch

# 1、用list的数据创建tensor
a = torch.Tensor([[1, 2, 3], [4, 5, 6]])
print("tensor_a:\n", a)
list_a = a.tolist()
print("list_a:\n", list_a)

# 2、指定Tensor形状
b = torch.Tensor(2, 3)  # 数值取决于内存空间的状态
print("tensor_b:\n", b)
print("b_size:", b.size(), b.shape)  # tensor.size()或tensor.shape获取tensor的形状
print("b_num:", b.numel(), b.nelement())  # 返回b中元素总个数,b.numel()与b.nelement()等价

# 3、创建一个和b形状一样的tensor
c = torch.Tensor(b.size())
# 创建一个元素为2和3的tensor
d = torch.Tensor((2, 3))
print("tensor_c:\n", c)
print("tensor_d:\n", d)

# 4、利用一些自带函数创建tensor
x = torch.empty(4, 3)  # 创建一个4x3的未初始化的Tensor
print("no_init_tensor:\n", x)
y = torch.rand(2, 3)   # 创建一个2x3的随机初始化的Tensor
print("init_tensor:\n", y)
z = torch.zero(2, 3)    # 创建一个2x3的全0的tensor
m = torch.arange(1, 6, 2)  # 创建一个从1到6步长为2的tensor
n = torch.eye(2, 3)  # 对角线为1,不要求行列数一致的tensor
print("zero_tensor:\n", z)
print("arange|_tensor:\n", m)
print("diagonal_tensor:\n", n)

############## 运行结果 ########################
tensor_a:
 tensor([[1., 2., 3.],
        [4., 5., 6.]])
list_a:
 [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]
tensor_b:
 tensor([[3.1375e-31, 6.1237e-43, 7.1086e-31],
        [6.1237e-43, 1.7725e-31, 6.1237e-43]])
b_size: torch.Size([2, 3]) torch.Size([2, 3])
b_num: 6 6
tensor_c:
 tensor([[7.1941e-31, 6.1237e-43, 7.1941e-31],
        [6.1237e-43, 1.8578e-31, 6.1237e-43]])
tensor_d:
 tensor([2., 3.])
no_init_tensor:
 tensor([[9.9184e-39, 9.0000e-39, 1.0561e-38],
        [1.0653e-38, 4.1327e-39, 8.9082e-39],
        [9.8265e-39, 9.4592e-39, 1.0561e-38],
        [1.0653e-38, 1.0469e-38, 9.5510e-39]])
init_tensor:
 tensor([[0.7474, 0.6727, 0.4569],
        [0.6518, 0.8314, 0.1763]])
zero_tensor:
 tensor([[0., 0., 0.],
        [0., 0., 0.]])
arange_tensor:
 tensor([1, 3, 5])
diagonal_tensor:
 tensor([[1., 0., 0.],
        [0., 1., 0.]])

2、常用操作

        通过tensor.view方法可以调整tensor的形状,但必须保证调整前后元素总数一致view不会修改自身的数据,返回的新tensor和源tensor共享内存,即:更改其中一个另外一个也会跟着改变。在实际应用中可能经常需要增加或减少某一维度。这时syueeze和unsqueeze两个函数就排上用场了。resize是另一种可用来调整size的方法,它可以修改tensor的尺寸,如果新尺寸超过了原尺寸,会自动分配新的内存空间,而如果新尺寸小于原尺寸,则之前的数据依旧会被保存

import torch
# 1、tensor.view调整tensor的形状
a = torch.arange(0, 6)
b1 = a.view(2, 3)
b2 = a.view(-1, 2)  # 当某一维为-1时,回自动计算它的大小
print(b1)
print(b2)
b3 = b1.unsqueeze(1)
print(b3)
print(b3.size())  # 注意形状,在第1维(下标从0开始)上增加“1”
b4 = b1.unsqueeze(-2)  # -2表示倒数第二个维度
print(b4)
print(b4.shape)

c = b1.view(1, 1, 1, 2, 3)
c1 = c.squeeze(0)   # 压缩第0维的“1”
print(c)
print(c1.shape)

# 2、resize调整tensor的形状
d1 = b1.resize_(1, 3)
d2 = b2.resize_(3, 3)   # 旧数据依旧保存,多出的数据会分配新空间
print(d1)
print(d2)

# 运行结果
tensor([[0, 1, 2],
        [3, 4, 5]])
tensor([[0, 1],
        [2, 3],
        [4, 5]])
tensor([[[0, 1, 2]],

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

        [[3, 4, 5]]])
torch.Size([2, 1, 3])
tensor([[[[[0, 1, 2],
           [3, 4, 5]]]]])
torch.Size([1, 1, 2, 3])
tensor([[0, 1, 2]])
tensor([[                0,                 1,                 2],
        [                3,                 4,                 5],
        [29555370778493020, 28147892815593580, 25896114476810337]])

(1)算术操作

         函数名后面带下划线 _ 的函数会修改Tensor本身,如:x.add_(y)和x.t_()会改变x,但x.add(y)和x.t()会返回一个新的Tensor,而x不变

import torch
# 算术操作
x = torch.ones(2, 3)
y = torch.rand(2, 3)
print("x:\n", x)
print("y:\n", y)
print("x+y:\n", x+y)  # 加法形式一
print("add(x+y):\n", torch.add(x, y))  # 加法形式二
# 指定输出结果
result = torch.Tensor(5, 3)  # 预先分配空间
torch.add(x, y, out=result)  # 结果输入到result
print("result:\n", result)

# 加法形式三,inplace
y.add_(x)
print("y.add_(x):\n", y)

# 运行结果
x:
 tensor([[1., 1., 1.],
        [1., 1., 1.]])
y:
 tensor([[0.8108, 0.0669, 0.0516],
        [0.9076, 0.1393, 0.8508]])
x+y:
 tensor([[1.8108, 1.0669, 1.0516],
        [1.9076, 1.1393, 1.8508]])
add(x+y):
 tensor([[1.8108, 1.0669, 1.0516],
        [1.9076, 1.1393, 1.8508]])
result:
 tensor([[1.8108, 1.0669, 1.0516],
        [1.9076, 1.1393, 1.8508]])
y.add_(x):
 tensor([[1.8108, 1.0669, 1.0516],
        [1.9076, 1.1393, 1.8508]])

(2)索引

       Tensor支持与numpy.ndarray类似的索引操作,语法也类似,需要注意的是:索引出来的结果与原数据共享内存,也即修改一个,另一个会跟着修改

import torch
a = torch.randn(3, 4)
print("a:\n", a)
print("第0行:\n", a[0])
print("第0列:\n", a[:, 0])
print("第0行第2个元素:\n", a[0][2])
print(a > 0)  # 返回一个ByteTensor
print(a[a > 0])  # 等价于a.masked_select(a>0),选择结果与原Tensor不共享内存空间
print(a[torch.LongTensor([0, 1])])  # 第0行和第1行

# 运行结果
a:
 tensor([[ 0.7942, -0.8665,  1.4201, -0.6606],
        [ 1.3923, -1.0253, -0.9995,  0.9130],
        [ 1.3881,  0.0780, -0.9674,  0.5636]])
第0行:
 tensor([ 0.7942, -0.8665,  1.4201, -0.6606])
第0列:
 tensor([0.7942, 1.3923, 1.3881])
第0行第2个元素:
 tensor(1.4201)
tensor([[ True, False,  True, False],
        [ True, False, False,  True],
        [ True,  True, False,  True]])
tensor([0.7942, 1.4201, 1.3923, 0.9130, 1.3881, 0.0780, 0.5636])
tensor([[ 0.7942, -0.8665,  1.4201, -0.6606],
        [ 1.3923, -1.0253, -0.9995,  0.9130]])

除了常用的索引选择数据之外,PyTorch还提供了一些高级的选择函数:

 

与gather相对应的逆操作的是scatter_,gather把数据从input中按index取出,而scatter_是把取出的数据再放回去,注意,scatter_函数是inplace操作。

import torch
# gather与scatter_
a = torch.arange(0, 16).view(4, 4)
print("a:\n", a)
index1 = torch.LongTensor([[0, 1, 2, 3]])
b = a.gather(0, index1)
print("选取对角线元素:\n", b)
index2 = torch.LongTensor([[3, 2, 1, 0]]).t()
c = a.gather(1, index2)
print("选取反对角线元素:\n", c)
index3 = torch.LongTensor([[3, 2, 1, 0]])
d = a.gather(0, index3)
print("选取反对角线元素:\n", d)
index4 = torch.LongTensor([[0, 1, 2, 3], [3, 2, 1, 0]]).t()
e = a.gather(1, index4)
print("选取两个对角线元素:\n", e)

"""
out = input.gather(dim, index)
-->近似逆操作
out = Tensor()
out.scatter_(dim, index)
"""
# 把两个对角线元素放回到指定位置
f = torch.zeros(4, 4, dtype=torch.long)
f.scatter_(1, index4, e)
print("把两个对角线元素放回到指定位置:\n", f)

# 运行结果
a:
 tensor([[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11],
        [12, 13, 14, 15]])
选取对角线元素:
 tensor([[ 0,  5, 10, 15]])
选取反对角线元素:
 tensor([[ 3],
        [ 6],
        [ 9],
        [12]])
选取反对角线元素:
 tensor([[12,  9,  6,  3]])
选取两个对角线元素:
 tensor([[ 0,  3],
        [ 5,  6],
        [10,  9],
        [15, 12]])
把两个对角线元素放回到指定位置:
 tensor([[ 0,  0,  0,  3],
        [ 0,  5,  6,  0],
        [ 0,  9, 10,  0],
        [12,  0,  0, 15]])

(3)Tensor类型

      Tensor由不同的数据类型,每种类型分别对应由CPU和GPU版本,默认的tensor是FloatTensor,可通过torch.set_default_tensor_type修改默认tensor类型(如果默认类型为GPU Tensor,则所有操作都将在GPU上进行)。Tensor类型对分析内存占用很有帮助,如:一个size为(1000,1000,1000)的FloatTensor,它有10^9个元素,每个元素占用32bit/8=4Byte内存,所以共占大约4GB内存/显存。HalfTensor是专门为GPU版本设计,同样的元素个数,显存占用只有FloatTensor的一半,可以极大缓解GPU显存的问题,但由于HalfTensor所能表示的数值大小和精度有限,可能出现溢出问题。

       各类型之间可以相互转换,type(new_type)是通用的做法,CPU Tensor和GPU Tensor之间的转换通过tensor.cuda和tensor.cpu的方法实现。Tensor还有一个new方法,用法和torch.Tensor一样,会调用该Tensor对应类型的构造函数,生成与当前类型一致的Tensor。

import torch
# tensor类型
torch.set_default_tensor_type('torch.FloatTensor')  # 设置默认tensor,注意参数是字符串
a = torch.Tensor(2, 3)
print("a:\n", a, a.type())
b = a.int()  # 把a转换成IntTensor
print("b:\n", b)
d = a.new(2, 3)
print("d:\n", d)

# 运行结果
a:
 tensor([[1.0561e-38, 1.0653e-38, 9.1837e-39],
        [8.4490e-39, 8.7245e-39, 1.0102e-38]]) torch.FloatTensor
b:
 tensor([[0, 0, 0],
        [0, 0, 0]], dtype=torch.int32)
d:
 tensor([[-1.0414e-37,  6.9645e-43, -1.0414e-37],
        [ 6.9645e-43, -2.5224e-38,  6.9645e-43]])

(4)线性代数

pytorch的线性函数主要封装了Blas和Lapack,其用法和接口都与之类似。常用的线性代数函数如下表:

 

import torch
# 线性代数
a = torch.Tensor(2, 3)
b = a.t()  # 转置
print("转置前:\n", a)
print("转置后:\n", b)
print(b.is_contiguous())
c = b.contiguous()  # 转置导致存储空间不连续,需要调用它的contiguous方法将其转为连续
print(c.is_contiguous())

# 运行结果
转置前:
 tensor([[1.8200e-23, 8.3798e-43, 7.9399e-24],
        [8.3798e-43, 1.8362e-23, 8.3798e-43]])
转置后:
 tensor([[1.8200e-23, 8.3798e-43],
        [8.3798e-43, 1.8362e-23],
        [7.9399e-24, 8.3798e-43]])
False
True

需要注意:矩阵转置后会导致存储空间不连续,需要调用它的contiguous方法将其转换为连续。 

3、广播(boardcast)机制

       广播法则(Boardcast)是科学运算中常用的一个技巧,它在快速执行向量化的同时不会占用额外的内存/显存。Numpy的广播法则定义如下:

(1)让所有输入数组都向其中shape最长的数组看齐,shape中不足的部分通过在其前面加1补齐;

(2)两个数组要么在某一维度的长度一致,要么其中一个为1,否则不能计算;

(3)当输入数组的某个维度长度为1时,计算时沿此维度复制扩充成一样的形状。

      pytorch当前已经支持自动广播法则,可以通过以下两个函数的组合手动实现广播法则,

(1) unsqueeze或view为数据某一维的形状补1,实现法则1;

(2)expand或expand_as,重复数组,实现法则3,该操作不会复制数组,所以不会占用额外的空间。

import torch
# boardcast
a = torch.ones(3, 2)
b = torch.zeros(2, 3, 1)

"""
自动广播法则,第一步:a是2维,b是3维,所以先在较小的a前面补1,
即:a.unsqueeze(0),a的形状变成(1,3,2),b的形状是(2,3,1)
第二步,a和b在第一维和第三维的形状都不一样,其中一个为1,可以利用广播法则扩展,
两个形状都变成了(2,3,2),可以进行加法操作
"""
print(a+b)

# 手动广播法则
# expand不会占用额外的空间,只会在需要的时候才扩充,可极大节省内存
c = a.unsqueeze(0).expand(2, 3, 2) + b.expand(2, 3, 2)
d = a.view(1, 3, 2).expand(2, 3, 2) + b.expand(2, 3, 2)

print(c)
print(d)

# 运行结果
tensor([[[1., 1.],
         [1., 1.],
         [1., 1.]],

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

4、运行内存开销

        索引、view不会开辟新内存的,而像y = x + y这样的运算是会新开内存的,然后将y指向新内存。为了演示这一点,我们可以使用Python自带的id函数:如果两个实例的ID一致,那么它们所对应的内存地址相同;反之则不同。

import torch
# 运算的内存开销
x = torch.tensor([1, 2])
y = torch.tensor([3, 4])
id_before = id(y)
y = y + x   # y开辟新内存
print(id(y) == id_before)

# 指定结果到原来y的内存,使用索引来替换
# 把 x+y 的结果通过[:]写进y对应的内存中
x = torch.tensor([1, 2])
y = torch.tensor([3, 4])
id_before = id(y)
y[:] = y+x
print(id(y) == id_before)

# 使用运算符全名函数中的out参数或者自加运算符 += (即:add_())达到效果
x = torch.tensor([1, 2])
y = torch.tensor([3, 4])
id_before = id(y)
torch.add(x, y, out=y)
# y += x
# y.add_(x)
print(id(y) == id_before)

# 运行结果
False
True
True

5、Tensor与numpy的相互转换

        Tensor和Numpy数组之间具有很高的相似性,彼此之间的操作非常简单高效。很容易用numpy()from_numpy()将Tensor和Numpy中的数组相互转换。所有在CPU上的Tensor(除了CharTensor)都支持与NumPy数组相互转换。但是需要注意的一点是: 这两个函数所产生的Tensor和NumPy中的数组共享相同的内存(所以他们之间的转换很快),改变其中一个时另一个也会改变!!!所以当遇到Tensor不支持的操作时,可先转成Numpy数组,处理后再转回tensor,其转换开销很小。

        还有一个常用的将NumPy中的array转换成Tensor的方法就是torch.tensor(), 需要注意的是,此方法总是会进行数据拷贝(就会消耗更多的时间和空间),所以返回的Tensor和原来的数据不再共享内存

(1)Tensor转NumPy

         使用numpy()Tensor转换成NumPy数组

(2)NumPy数组转Tensor

        使用from_numpy()NumPy数组转换成Tensor

import torch
# Tensor和Numpy转换
# 1、Tensor转Numpy
a = torch.ones(5)
b = a.numpy()
print(a, b)
# 内存共享
a += 1
print(a, b)
b += 1
print(a, b)

# 2、Numpy转Tensor
import numpy as np
m = np.zeros(5)
n = torch.from_numpy(m)
print(m, n)
# 内存共享
m += 1
print(m, n)
n += 1
print(m, n)

# 该方法数据拷贝,内存不再共享
z = np.ones(3)
x = torch.tensor(z)
z += 1
print(z, x)

# 运行结果
tensor([1., 1., 1., 1., 1.]) [1. 1. 1. 1. 1.]
tensor([2., 2., 2., 2., 2.]) [2. 2. 2. 2. 2.]
tensor([3., 3., 3., 3., 3.]) [3. 3. 3. 3. 3.]
[0. 0. 0. 0. 0.] tensor([0., 0., 0., 0., 0.], dtype=torch.float64)
[1. 1. 1. 1. 1.] tensor([1., 1., 1., 1., 1.], dtype=torch.float64)
[2. 2. 2. 2. 2.] tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
[2. 2. 2.] tensor([1., 1., 1.], dtype=torch.float64)

6、Tensor on GPU

用方法to()可以将Tensor在CPU和GPU(需要硬件支持)之间相互移动

# 以下代码只有在PyTorch GPU版本上才会执行
if torch.cuda.is_available():
    device = torch.device("cuda")          # GPU
    y = torch.ones_like(x, device=device)  # 直接创建一个在GPU上的Tensor
    x = x.to(device)                       # 等价于 .to("cuda")
    z = x + y
    print(z)
    print(z.to("cpu", torch.double))       # to()还可以同时更改数据类型

二、autograd

        torch.autogard就是为方便用户使用,专门开发的一套自动求导引擎,它能够根据输入和前向传播过程自动构建计算图,并执行反向传播。计算图(computation graph)是现代深度学习框架(pytorch和tensorflow等)的核心,它为自动求导算法——反向传播(back propogation)提供了理论支持。

1、Variable

pytorch在autograd模块中实现了计算图的相关功能,autogard中的核心数据结构是VariableVariable封装了tensor,并记录对tensor的操作记录来构建计算图。Variable的数据结构如下图所示,主要包含三个属性:

                                                                          

(1)data保存Variable所包含的tensor

(2)grad保存data对应的梯度grad也是Variable,并非tensor,它与data形状一致

(3)grad_fn指向一个Function记录Variable的操作历史,即它是什么操作的输出,用来构建计算图。如果某一个变量是用户创建的,则它为叶子节点,对应的grad_fn等于None。

Variable的构造函数需要传入tensor,同时有两个可选的参数:

(1)requires_grad (bool) : 是否需要对该Variable进行求导

(2)volatile (bool) :意为“挥发”,设置为True,构建在该Variable上的图都不会求导,专为推理阶段设计。

Variable支持大部分tensor支持的函数,但其不支持部分inplace函数,因为这些函数会修改tensor自身,在反向传播中,Variable需要缓存原来的tensor来计算梯度。如果想要计算各个Variable的梯度,只需要调用根节点Variable的backward方法,autograd会自动沿着反向计算,计算每一个叶子节点的梯度。

variable.backward(grad_variables=None,retain_graph=None,create_graph=None)主要有如下参数:

(1)grad_variables :形状与variable一致,对于y.backward(),grad_variables相当于链式法则中的

grad_variables也可以是tensor或序列。

(2)retain_graph :反向传播需要缓存一些中间结果,反向传播之后这些缓存就被清空,可通过指定这个参数不清空缓存,用来多次反向传播。

(3)create_graph :对于反向传播中再次构建计算图,可通过backward of backward 实现求高阶导数。

import torch
from torch.autograd import Variable

# 1、Variable相关
# 从tensor中创建Variable并指定需要求导
a = Variable(torch.ones(2, 3), requires_grad=True)
print("a:\n", a)
b = Variable(torch.zeros(2, 3))
print("b:\n", b)

c = a.add(b)
print("c:\n", c)

# c.data.sum() 在取data后变为tensor,从tensor计算sum得到float
# c.sum() 计算后仍是Variable
d = c.sum()
print("d:\n", d)
d.backward()

print("a.grad:\n", a.grad)
# 此处虽没有指定c需要求导,但c依赖于a,而a需要求导
print(a.requires_grad, b.requires_grad, c.requires_grad)
# 由用户创建的variable属于叶子节点,对应的grad_fn是None
print(a.is_leaf, b.is_leaf, c.is_leaf)
# c.grad是None,c不是叶子节点,它的梯度是用来计算a的梯度,
# 虽然c.requires_grad=True,但梯度计算完之后即被释放
print(c.grad is None)

# 运行结果
a:
 tensor([[1., 1., 1.],
        [1., 1., 1.]], requires_grad=True)
b:
 tensor([[0., 0., 0.],
        [0., 0., 0.]])
c:
 tensor([[1., 1., 1.],
        [1., 1., 1.]], grad_fn=<AddBackward0>)
d:
 tensor(6., grad_fn=<SumBackward0>)
a.grad:
 tensor([[1., 1., 1.],
        [1., 1., 1.]])
True False True
True True False
True

2、计算图(computation graph)

pytorch中autograd的底层采用了计算图,计算图是一种特殊的有向无环图(DAG),用于记录算子与变量之间的关系。一般用矩形表示算子,椭圆形表示变量。表达式 z=wx+b 可分解为 y=wx 和 z=y+b,其中计算图如下图所示,图中的MUL和ADD都是算子,w、x、b为变量。

                                                               

上面的有向无环图中,X和b是叶子节点(leaf node),这些节点通常由用户自己创建,不依赖于其他变量。z称为根节点,是计算图的最终目标。利用链式法则很容易求得各个叶子节点的梯度。

                                       

有了计算图,链式求导即可利用计算图的反向传播自动完成,过程如下图:

                                                    

在pytorch实现中,autograd会随着用户的操作,记录生成当前variable的所有操作,并由此建立一个有向无环图。用户每进行一次操作,相应的计算图就会发生改变。更底层的实现中,图中记录了操作function,每一个变量在图中的位置可通过其grad_fn属性在图中的位置推测到。在反向传播过程中,autograd沿着这个图从当前变量(根节点z)溯源,可以利用链式求导法则计算所有叶子节点的梯度。每一个前向传播操作的函数都有与之对应的反向传播函数用来计算输入各个variable的梯度,这些函数的函数名通常以Backward结尾。

import torch
from torch.autograd import Variable

x = Variable(torch.ones(1))
b = Variable(torch.rand(1), requires_grad=True)
w = Variable(torch.rand(1), requires_grad=True)

y = w.mul(x)    # y = w * x
z = y.add(b)    # z = y + b

# 虽然未指定y.requires_grad为True,但由于依赖于需要求导的w,所以y.requires_grad为True
print(x.requires_grad, b.requires_grad, w.requires_grad, y.requires_grad)
print(x.is_leaf, w.is_leaf, b.is_leaf, y.is_leaf, z.is_leaf)

# grad_fn可以查看这个Variable的反向传播函数,z是add函数的输出,所以它的反向传播函数是AddBackward
print(z.grad_fn)

# next_functions保存grad_fn的输入,grad_fn的输入是一个tuple
# 第一个是y,它的乘法(mul)的输出,对应的反向传播函数y.grad_fn是MulBackward
# 第一个是b,它是叶子节点,由用户创建,grad_fn为None,但是有
print(z.grad_fn.next_functions)
# variable的grad_fn对应图中的function
print(z.grad_fn.next_functions[0][0] == y.grad_fn)

# 第一个是w,叶子节点,需要求导,梯度是累加的;第二个是x,叶子节点,不需要求导,所以为None
print(y.grad_fn.next_functions)
# 叶子节点的gard_fn是None
print(w.grad_fn, x.grad_fn)

# 使用retain_graph保存buffer
z.backward(retain_graph=True)
print(w.grad)

# 运行结果
False True True True
True True True False False
<AddBackward0 object at 0x000001E127D6D320>
((<MulBackward0 object at 0x000001E127D6DE48>, 0), (<AccumulateGrad object at 0x000001E127D70160>, 0))
True
((<AccumulateGrad object at 0x000001E127D6D320>, 0), (None, 0))
None None
tensor([1.])

                    

import torch
from torch.autograd import Variable

x = Variable(torch.ones(1))
w = Variable(torch.rand(1), requires_grad=True)
y = x * w
# y依赖于w,而w.requires_grad=True
print(x.requires_grad, w.requires_grad, y.requires_grad)

with torch.no_grad():   # torch.no_grad()为1.2版本,作用等价于volatile=True(0.2版本)
    a = Variable(torch.ones(1))
    b = Variable(torch.rand(1), requires_grad=True)
    c = a * b
# c依赖于a和b,但a.volatile=True, b.requires_grad=True
print(a.requires_grad, b.requires_grad, c.requires_grad)

# 运行结果
False True True
False True False
False False False

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值