【pytorch】|tensor grad

计算图与动态图机制

计算图是用来描述运算的有向无环图。计算图有两个主要元素:结点(Node)和边(Edge)。结点表示数据,如向量,矩阵,张量;边表示运算,如加减乘除卷积等。
下面用计算图表示:y = ( x + w ) ∗ ( w + 1 )

在这里插入图片描述
采用计算图描述运算的好处:不仅使得运算更加简洁,而且使得梯度求导更加方便。下面用代码展示上述计算图梯度求导过程:

import torch

# 需要计算梯度-requires_grad=True
w = torch.tensor([1.], requires_grad=True)
x = torch.tensor([2.], requires_grad=True)

# 前向传播
a = torch.add(w, x)     # retain_grad()
b = torch.add(w, 1)
y = torch.mul(a, b)

# 反向传播-自动求导
y.backward()
print(w.grad)
# 5

# 查看叶子结点
print("is_leaf:\n", w.is_leaf, x.is_leaf, a.is_leaf, b.is_leaf, y.is_leaf)

# 查看梯度
print("gradient:\n", w.grad, x.grad, a.grad, b.grad, y.grad)

is_leaf:
 True True False False False
gradient:
 tensor([5.]) tensor([2.]) None None None

如果我们想要保存非叶子节点的梯度,那么应该怎么做呢?

import torch

# 需要计算梯度-requires_grad=True
w = torch.tensor([1.], requires_grad=True)
x = torch.tensor([2.], requires_grad=True)

# 前向传播
a = torch.add(w, x)     
# 保存非叶子节点a的梯度
a.retain_grad()#############
b = torch.add(w, 1)
y = torch.mul(a, b)

# 反向传播-自动求导
y.backward()
print(w.grad)

# 查看叶子结点
print("is_leaf:\n", w.is_leaf, x.is_leaf, a.is_leaf, b.is_leaf, y.is_leaf)

# 查看梯度
print("gradient:\n", w.grad, x.grad, a.grad, b.grad, y.grad)

tensor([5.])
is_leaf:
 True True False False False
gradient:
 tensor([5.]) tensor([2.]) tensor([2.]) None None

grad_fn:grad_fn:记录创建该张量时所用的方法(函数),是自动求导的关键

# 查看创建张良所使用的函数
print("grad_fn:\n", w.grad_fn, x.grad_fn, a.grad_fn, b.grad_fn, y.grad_fn)

根据计算图搭建方式,可将计算图分为静态图与动态图。静态图是先搭建图后运算。静态图的特点:高效不灵活。TensorFlow是静态图。动态图是运算与搭建同时进行。动态图的特点:灵活易调节。PyTorch是动态图。

tensor 数据类型

系统默认是torch.FloatTensor类型。
在这里插入图片描述

类型转换

数据类型的转换

在Tensor后加long(), int(), double(),float(),byte()等函数就能将Tensor进行类型转换

Torch.LongTensor—>Torch.FloatTensor, 直接使用data.float()

CPU-GPU

在cpu上tensor的数据类型:torch.FloatTensor
在GPU上tensor的数据类型:torch.cuda.FloatTensor
CPU张量和GPU张量之间的转换
CPU -> GPU: data.cuda()
GPU -> CPU: data.cpu()

tensor-numpy

Tensor与Numpy Array之间的转换

Tensor---->Numpy 可以使用 data.numpy(),data为Tensor变量

Numpy ----> Tensor 可以使用torch.from_numpy(data),data为numpy变量

tensor 操作

初始化

一个张量tensor可以从Python的list或序列构建:

a=torch.FloatTensor([[1, 2, 3], [4, 5, 6]])
a.shape
>>torch.Size([2, 3])


torch.FloatTensor([1, 2, 3]).shape

>>torch.Size([3])

一个空张量tensor可以通过规定其大小来构建:

torch.IntTensor(2, 4).zero_()
torch.FloatTensor(2, 4).zero_()

a = torch.ones(2, 2, requires_grad=True)
b = torch.rand_like(a, requires_grad=True)


in-place操作 会改变操作数tensor的函数操作,用一个下划线后缀来标示。
不带下划线将会在一个新的tensor中计算结果。

clone() detach()

tensor复制可以使用clone()函数和detach()函数
在这里插入图片描述

clone()

clone()函数可以返回一个完全相同的tensor,新的tensor开辟新的内存,但是仍然留在计算图中。

将计算图中参与运算tensor变为clone()后的tensor。此时梯度仍然只流向了原始的tensor。

x= torch.tensor([1., 2., 3.], requires_grad=True)
clone_x = x.clone()
detach_x = x.detach()
clone_detach_x = x.detach().clone()

f = torch.nn.Linear(3, 1)
y = f(clone_x)
y.backward()

print(x.grad)
print(clone_x.grad)
print(detach_x.requires_grad)
print(clone_detach_x.requires_grad)

tensor([-0.0043,  0.3097, -0.4752])
None
False
False

将原始tensor设为requires_grad=False,clone()后的梯度设为.requires_grad_(),clone()后的tensor参与计算图的运算,则梯度穿向clone()后的tensor。

x= torch.tensor([1., 2., 3.], requires_grad=False)
clone_x = x.clone().requires_grad_()
detach_x = x.detach()
clone_detach_x = x.detach().clone()

f = torch.nn.Linear(3, 1)
y = f(clone_x)
y.backward()

print(x.grad)
print(clone_x.grad)
print(detach_x.requires_grad)
print(clone_detach_x.requires_grad)

None
tensor([-0.0043,  0.3097, -0.4752])
False
False

detach()

import torch
 
a = torch.tensor([1,2,3.], requires_grad = True)
out = a.sigmoid()
c = out.detach()  # 通过.detach() “分离”得到的的变量也会与原张量使用同一数据,而且新分离得到的张量是不可求导的
c.zero_()         # 改变c的值,原来的out也会改变
print(c.requires_grad)    #false
print(c)          #tensor([0., 0., 0.])
print(out.requires_grad)  #true
print(out)        #tensor([0., 0., 0.], grad_fn=<SigmoidBackward>)
print("----------------------------------------------")
 
out.sum().backward()      # 对原来的out求导,
print(a.grad)     # 此时会报错,监测到梯度计算所需要的张量已经被“原位操作inplace”所更改了

detach()函数可以返回一个完全相同的tensor,新的tensor开辟与旧的tensor共享内存,新的tensor会脱离计算图,不会牵扯梯度计算。此外,一些原地操作(in-place, such as resize_ / resize_as_ / set_ / transpose_) 在两者任意一个执行都会引发错误。

detach()后的tensor由于与原始tensor共享内存,所以原始tensor在计算图中数值反向传播更新之后,detach()的tensor值也发生了改变。

x = torch.tensor([1., 2., 3.], requires_grad=True)
f = torch.nn.Linear(3, 1)
w = f.weight.detach()
print(f.weight)
print(w)

y = f(x)
y.backward()

optimizer = torch.optim.SGD(f.parameters(), 0.1)
optimizer.step()

print(f.weight)
print(w)

Parameter containing:
tensor([[-0.0043,  0.3097, -0.4752]], requires_grad=True)
tensor([[-0.0043,  0.3097, -0.4752]])
Parameter containing:
tensor([[-0.1043,  0.1097, -0.7752]], requires_grad=True)
tensor([[-0.1043,  0.1097, -0.7752]])

tensor属性

https://blog.csdn.net/weixin_46649052/article/details/118694624

Variable是PyTorch0.4.0之前的重要数据类型,在PyTorch0.4.0之后已经并入到Tensor中。但是我们还要了解Variable这一数据类型,因为了解Variable对了解Tensor是十分有帮助的。Variable是torch.autograd中的数据类型,进行自动求导。

在这里插入图片描述
data:被包装的Tensor
grad: data的梯度
grad_fn:创建Tensor的Function,是自动求导的关键
requires_grad:指示是否需要梯度
is_lea f:指示是否是叶子结点(张量)

PyTorch0.4.0版开始,Variable并入Tensor。在并入之后,Tensor有8个属性:
在这里插入图片描述
data:被包装的Tensor
dtype:张量的数据类型,如torch.FloatTensor, torch.cuda.FloatTensor(表示数据放到了GPU上)
shape:张量的形状,如(64,3,224,224)
device:张量所在设备,GPU/CPU,是加速的关键
grad: data的梯度
grad_fn:创建Tensor的Function,是自动求导的关键
requires_grad:指示是否需要梯度
is_lea f:指示是否是叶子结点(张量)

dtype shape device

在这里插入图片描述

tensor = torch.rand(3,4)

print(f"Shape of tensor: {tensor.shape}")
print(f"Datatype of tensor: {tensor.dtype}")
print(f"Device tensor is stored on: {tensor.device}")

import torch
a = torch.randn(3,5,requires_grad=True)
b = a.sum()
c = a.mean()
b.backward()
a.grad
c.backward()
a.grad # 不清零的话梯度将会累加
 a.grad.zero_()
 c.backward()
 a.grad

grad

该Tensor的梯度值,默认情况下是None,但是当第一次为当前张量自身self计算梯度调用backward()方法时,
该属性grad将变成一个Tensor张量类型. 该属性将包含计算所得的梯度,
在这之后如果再次调用backward()方法,将会对这个grad属性进行累加.

tensor.grad_fn

叶子节点通常为None,只有结果节点的grad_fn才有效,用于指示梯度函数是哪种类型。
print(tensor.grad_fn)

requires_grad

设置为True则表示该Tensor需要求导

print(tensor.requires_grad)

x.requires_grad_()

 a = torch.randn(3,5)
 a.requires_grad_()
 a

.只有对于 requires_grad=True的叶子张量,我们才会将梯度一直保存在该叶子张量的grad属性中,对于非叶子节点, 即中间节点的张量,我们在计算完梯度之后为了更高效地利用内存,我们会将梯度grad的内存释放掉.)


>>> import torch
>>> torch.manual_seed(seed=20200910)
<torch._C.Generator object at 0x000001CDB4A5D330>
>>> data_in = torch.randn(3,5,requires_grad=True)
>>> data_in
tensor([[ 0.2824, -0.3715,  0.9088, -1.7601, -0.1806],
        [ 2.0937,  1.0406, -1.7651,  1.1216,  0.8440],
        [ 0.1783,  0.6859, -1.5942, -0.2006, -0.4050]], requires_grad=True)
>>> data_mean = data_in.mean()
>>> data_mean
tensor(0.0585, grad_fn=<MeanBackward0>)
>>> data_in.requires_grad
True
>>> data_mean.requires_grad
True
>>> data_1 = data_mean * 20200910.0
>>> data_1
tensor(1182591., grad_fn=<MulBackward0>)
>>> data_2 = data_1 * 15.0
>>> data_2
tensor(17738864., grad_fn=<MulBackward0>)
>>> data_2.retain_grad()
>>> data_3 = 2 * (data_2 + 55.0)
>>> loss = data_3 / 2.0 +89.2
>>> loss
tensor(17739010., grad_fn=<AddBackward0>)
>>>
>>> data_in.grad
>>> data_mean.grad
>>> data_1.grad
>>> data_2.grad
>>> data_3.grad
>>> loss.grad
>>> print(data_in.grad, data_mean.grad, data_1.grad, data_2.grad, data_3.grad, loss.grad)
None None None None None None
>>>
>>> loss.backward()
>>> data_in.grad
tensor([[20200910., 20200910., 20200910., 20200910., 20200910.],
        [20200910., 20200910., 20200910., 20200910., 20200910.],
        [20200910., 20200910., 20200910., 20200910., 20200910.]])
>>> data_mean.grad
>>> data_mean.grad
>>> data_1.grad
>>> data_2.grad
tensor(1.)
>>> data_3.grad
>>> loss.grad
>>>
>>>
>>> print(data_in.grad, data_mean.grad, data_1.grad, data_2.grad, data_3.grad, loss.grad)
tensor([[20200910., 20200910., 20200910., 20200910., 20200910.],
        [20200910., 20200910., 20200910., 20200910., 20200910.],
        [20200910., 20200910., 20200910., 20200910., 20200910.]]) None None tensor(1.) None None
>>>
>>>
>>> print(data_in.is_leaf, data_mean.is_leaf, data_1.is_leaf, data_2.is_leaf, data_3.is_leaf, loss.is_leaf)
True False False False False False
>>>
>>>
>>>

is_leaf

判断是否是叶子节点

对于自己定义的变量,我们称之为叶子节点(leaf nodes),而基于叶子节点得到的中间或最终变量则可称之为结果节点。

torch.autograd.backward(z) = z.backward()

深度学习模型的训练就是不断更新权值。权值的更新需要求解梯度。PyTorch提供自动求导系统解决这一问题。自动求导系统autograd只需要搭建前向传播的计算图,然后通过torch.autograd就可以得到每个张量的梯度。

默认为NONE ,当使用backward()后计算梯度,得到tensor
再次使用backward将会累计梯度(你可能需要在调用此函数之前将leaf variable的梯度置零)

torch.autograd.backward(
		tensors, 
		grad_tensors=None, 
		retain_graph=None, 
		create_graph=False, 
		grad_variables=None)
tensor: 用于计算梯度的tensor。也就是说这两种方式是等价的:torch.autograd.backward(z) == z.backward()
grad_tensors: 在计算矩阵的梯度时会用到。多梯度权重(用于多个梯度权重的设置),他其实也是一个tensor,shape一般需要和前面的tensor保持一致。
retain_graph: 通常在调用一次backward后,pytorch会自动把计算图销毁,所以要想对某个变量重复调用backward,则需要将该参数设置为True
create_graph: 当设置为True的时候创建导数计算图,用于高阶求导
grad_variables: 这个官方说法是grad_variables' is deprecated. Use 'grad_tensors' instead.也就是说这个参数后面版本中应该会丢弃,直接使用grad_tensors就好了

注意:函数必须是求得的一个值,即标量。而求一个矩阵对另一矩阵的导数束手无策。

w = torch.tensor([1.], requires_grad=True)
x = torch.tensor([2.], requires_grad=True)

a = torch.add(w, x)     # retain_grad()
b = torch.add(w, 1)

y0 = torch.mul(a, b)    # y0 = (x+w) * (w+1)    dy0/dw = 5
y1 = torch.add(a, b)    # y1 = (x+w) + (w+1)    dy1/dw = 2

loss = torch.cat([y0, y1], dim=0)       # [y0, y1]
grad_tensors = torch.tensor([1., 2.])

loss.backward(gradient=grad_tensors)    ###### gradient 传入 torch.autograd.backward()中的grad_tensors

print(w.grad)

## 输出
# tensor([9.])
### grad_tensors = torch.tensor([1., 1.])  #或 grad_tensors = torch.ones_like(loss)
## 输出 tensor([7.])

举例:

x = torch.ones(2,requires_grad=True)
z = x + 2
z.backward()

>>> ...
RuntimeError: grad can be implicitly created only for scalar outputs

在这里插入图片描述那么我们只要想办法把矩阵转变成一个标量不就好了?比如我们可以对z求和,然后用求和得到的标量在对x求导,这样不会对结果有影响,例如:
在这里插入图片描述

进一步,对z求和不就是等价于z点乘一个一样维度的全为1的矩阵吗?
在这里插入图片描述而这个I也就是我们需要传入的grad_tensors参数。(点乘只是相对于一维向量而言的,对于矩阵或更高为的张量,可以看做是对每一个维度做点乘)

x = torch.ones(2,requires_grad=True)
z = x + 2
z.backward(torch.ones_like(z)) # grad_tensors需要与输入tensor大小一致
print(x.grad)

>>> tensor([1., 1.])
x = torch.ones(2,requires_grad=True)
z = x + 2
z.sum().backward()
print(x.grad)

>>> tensor([1., 1.])
import torch

x = torch.ones(5)  # input tensor
y = torch.zeros(3)  # expected output
w = torch.randn(5, 3, requires_grad=True)
b = torch.randn(3, requires_grad=True)
z = torch.matmul(x, w)+b
loss = torch.nn.functional.binary_cross_entropy_with_logits(z, y)

loss.backward()
print(w.grad)
print(b.grad)

If we need to do several backward calls on the same graph, we need to pass retain_graph=True to the backward call.



```c
x = torch.tensor([2., 1.], requires_grad=True)
y = torch.tensor([[1., 2.], [3., 4.]], requires_grad=True)

z = torch.mm(x.view(1, 2), y)
print(f"z:{z}")
z.backward(torch.Tensor([[1., 0]]), retain_graph=True)
print(f"x.grad: {x.grad}")
print(f"y.grad: {y.grad}")

>>> z:tensor([[5., 8.]], grad_fn=<MmBackward>)
x.grad: tensor([[1., 3.]])
y.grad: tensor([[2., 0.],
        [1., 0.]])

在这里插入图片描述


### torch.no_grad()
用来禁止梯度的计算,常用在网络推断。
被with torch.no_grad包起来的操作,仍会运行或计算,但是他们的requires_grad属性会被赋为False。从而在计算图中关闭这些操作的梯度计算。
![在这里插入图片描述](https://img-blog.csdnimg.cn/4b899e8266024a73a3c6dad2fabb785a.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAcnJyMg==,size_20,color_FFFFFF,t_70,g_se,x_16)
#### 梯度不自动清零,需要手动清零

```c
w = torch.tensor([1.], requires_grad=True)
x = torch.tensor([2.], requires_grad=True)

for i in range(4):
    a = torch.add(w, x)
    b = torch.add(w, 1)
    y = torch.mul(a, b)

    y.backward()
    print(w.grad)
    # 梯度清零
    # w.grad.zero_()

依赖于叶子节点的节点,requires_grad默认为True,叶子节点不可执行in-place(原地操作)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值