view
改变维度
a=torch.arange(0,6)
#[[0, 1, 2], [3, 4, 5]]
a=a.view(2,3)
unsqueeze
#a=[[0, 1, 2], [3, 4, 5]]
#在第1维增加维度 下标从0开始
print(a.unsqueeze(0))
#在第2维增加维度
print(a.unsqueeze(1))
#在第3维增加维度
print(a.unsqueeze(2))
squeeze
c=a.unsqueeze(0)
print(c)
print(c.squeeze(0))
#可以看出维度并没有变化,仍然为(1,2,3),这是因为只有维度为1时才会去掉
print(c.squeeze(1))
print(c.squeeze(2))
gather
import torch
input=torch.arange(16).view(4,4)
"""
[[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11],
[12, 13, 14, 15]]
"""
"""
首先要指出的是input和index必须有相同的维度,但是各个维度的大小(shape)可以不一样。比如input是一个三维矩阵,则tensor也必须是一个三位矩阵。
对于d != dim的维度,有index.size(d) <= input.size(d)。
gather函数的输出的shape与index的shape一致, gather函数内部没有广播机制。
"""
index=torch.LongTensor([[0,2,2,3]])
#dim=0表示从每一行选 index中的具体数字0,2,2,3告诉我们分别选择第0,2,2,3行,
#然后0 2 2 3在index中分别位于第0 1 2 3列,因此选择的位置分别为input[0][0], input[2][1], input[2][2], input[3][3]。
output=input.gather(dim=0,index=index)#tensor([[ 0, 9, 10, 15]])
#dim=1表示从每一列选 index中的具体数字0,2,2,3告诉我们分别选择第0,2,2,3列,
#然后0 2 2 3在index中都位于第一行,因此选择的位置分别为input[0][0], input[0][2], input[0][2], input[0][3]。
output=input.gather(dim=1,index=index)#tensor([[0, 2, 2, 3]])
#比如要选出0 6 10 15四个元素,则
index = torch.LongTensor([[0], [2], [2],[3]])
output = input.gather(dim=0, index=index)
#首先分别选取第0 2 2 3列。对于行来说,因为这些数据全部位于index的第0行,因此全部选择input中的第0行的数据。
max
#torch.max
#1、torch.max(input)返回输入tensor中所有元素的最大值
print(torch.max(index))
#2、torch.max(input,dim)
#torch.max(a,0),按维度返回最大值,且返回相应的索引
#torch.max(a,1) ,返回每一行中最大的那个值,和相应的索引
Categorical
作用是创建以参数probs为标准的类别分布,样本是来自 “0 … K-1” 的整数,其中 K 是probs参数的长度。也就是说,按照传入的probs中给定的概率,在相应的位置处进行取样,取样返回的是该位置的整数索引。
m=Categorical(probs)
action=m.sample()
requires_grad
import torch
x = torch.tensor(3.) # 数字
w = torch.tensor(4., requires_grad=True) # 数字
b = torch.tensor(5., requires_grad=True) # 数字
y = w * x + b
y.backward()
#相当于函数变为 y = 3 * w + b
#求梯度:
# Display gradients
print('dy/dx:', x.grad) #dy/dx: None
print('dy/dw:', w.grad)#dy/dw: tensor(3.)
print('dy/db:', b.grad)#dy/db: tensor(1.)
解释:对于y = x * w + b,因为x为常数3, 对y = 3 * w + b,y对x求偏导数dy/dx=0, y对w求偏导数dy/dw = 3, y对b求偏导dy/db=1.
import torch
x=torch.tensor([1,2],dtype=torch.float32,requires_grad=True)
a=torch.tensor([3,4],dtype=torch.float32,requires_grad=True)
y=x*2+a
y.requires_grad_(True)
z=torch.mean(y)
z.backward()
print(x.grad, x.requires_grad)
print(a.grad, a.requires_grad)
print(y.grad, y.requires_grad)
print(z.grad, z.requires_grad)
这段会报一个警告:
UserWarning: The .grad attribute of a Tensor that is not a leaf Tensor is being accessed. Its .grad attribute won’t be populated during autograd.backward(). If you indeed want the .grad field to be populated for a non-leaf Tensor, use .retain_grad() on the non-leaf Tensor. If you access the non-leaf Tensor by mistake, make sure you access the leaf Tensor instead. See github.com/pytorch/pytorch/pull/30531 for more informations.(Triggered internally at C:\cb\pytorch_1000000000000\work\build\aten\src\ATen/core/TensorBody.h:485.) print(y.grad, y.requires_grad)
这个报错的大致翻译如下:
< input > : 1:UserWarning:正在访问非叶张量的张量的.grad属性。在autograd.backward()期间不会填充其.grad属性。如果确实需要非叶张量的梯度,请在非叶张量上使用.retain_grad()。如果您错误地访问了非叶张量,请确保您访问了叶张量。
一句话总结是:
并不是 requires_grad()为True就可以输出对应的梯度,还要看is_leaf属性,当is_leaf=false时,也即该变量是非叶张量,则会爆出上面的错误
- requires_grad=True 的作用是让 backward 可以追踪这个参数并且计算它的梯度。最开始定义你的输入是requires_grad=True ,那么后续对应的输出也自动具有 requires_grad=True ,如代码中的 y 和 z ,而与 z 对 x 求导无关联的 a ,其 requires_grad 仍等于 False。
- 如果在 z 对 x 求导的过程中需要用到 z 对 y 求导,而如果设置 y.requires_grad=False,则会报错。就是因为只有 requires_grad=True的参数才会参与求导,而在求导路径的中间设置相关参数不可求导,那么它就会报错。
- 那么如何取得参数的 grad :①如果你想取的参数是由 torch.tensor(requires_grad=True)定义的,可以直接取它的 grad ;②如果你的参数是如 y 和 z 这样计算出来的,那么根据编译器警告,需要定义 y.retain_grad() 就可以取得 y 的 grad;③还有一个方法是使用钩子可以保存计算的中间梯度,在上面文章中可见。由此可知梯度的计算会在计算完成后遗弃,并且requires_grad=False 的参数不计算它的梯度,如此可以减少内存使用和降低计算量。
- 当你在使用 Pytorch 的 nn.Module 建立网络时,其内部的参数都自动的设置为了 requires_grad=True ,故可以直接取梯度。而我们使用反向传播时,其实根据全连接层的偏导数计算公式,可知链式求导和 w , b的梯度无关,而与其中一个连接层的输出梯度有关,这也是为什么冻结了网络的参数,还是可以输出对输入求导。
detach()
(1)detach()与detach_()
在x->y->z传播中,如果我们对y进行detach(),梯度还是能正常传播的;
但如果我们对y进行detach_(),就把x->y->z切成两部分:x和y->z,x则无法接受到后面传过来的梯度。
(2)detach()和data
1)共同点:
x.data和x.detach()返回和x的相同数据 tensor,这个新的tensor和原来的tensor(即x)是共用数据的,一者改变,另一者也会跟着改变,但是x.data和x.detach() 的 require s_grad = False,即是不可求导的。
2)不同点:
①x.data 不能被 autograd 追踪求微分,即使被改了也能错误求导;而x.detach()也不能被 autograd 追踪求微分,被改了会直接报错,避免错误的产生。
②.data 是一个属性,detach()是一个方法;
③.data 是不安全的, .detach()是安全的。
叶张量和非叶张量
首先根据张量的属性判断,当张量的is_leaf为true时,该变量为叶张量
那么is_leaf是如何产生的:
由用户初始创建的变量,而不是程序中间产生的结果变量,那么该变量为叶变量。
x1 = torch.ones(2, 2) * 2
x1.requires_grad=True
print('x1是否是叶张量:', x1.is_leaf)
x2 = torch.ones(2, 2) * 3
x2.requires_grad = True
print('x2是否是叶张量:', x2.is_leaf)
x3 = torch.ones(2, 2) * 4
x3.requires_grad = True
y = x1 * x3 + x2
t_1 = y.sum()
print('y是否是叶张量:', y.is_leaf)
print('t_1是否是叶张量:', t_1.is_leaf)
三、为什么要区分叶张量和非叶张量?
从以下几个方面来讲:
1.从链式求导法则来讲:
对于一个可导函数,无论其多么复杂,一定可由若干可导函数的四则运算组成,那么在这个环节中任何一个运算都是可导的,最终得到函数的计算是需要这些环节的导数的传递。不知道讲清楚没有,,,例如:对上面的x1求导,那么自上而下要经过t_1,y,t_1和y的requires_grad()为True,否则梯度无法求解。
2.从求导的目的来讲
在深度学习或者机器学子中,求导或求梯度的根本原因是,对梯度进行反向传播,进而来更新相应的参数来达到学习的目的,在这个例子中可以看出y,t_1是前向传播计算得到的中间值,它们虽然需要求导但是不需要更新参数。
3.从实际需要出发
那些需要更新参数的变量,设置为叶张量,在计算梯度的时候,会保留它们的梯度信息,供反向传播更新参数的时候使用;那些不需要更新参数的中间变量,任然会计算与之相关联的梯度,但是程序不会保留该变量对应的梯度信息;这样有利于节约计算机的资源。