计算图是用来描述运算的有向无环图。计算图有两个主要元素:结点(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 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中计算结果。
tensor复制可以使用clone()函数和detach()函数
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 Falsedetach()
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=) 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:指示是否是叶子结点(张量)
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.gradgrad
该Tensor的梯度值,默认情况下是None,但是当第一次为当前张量自身self计算梯度调用backward()方法时,
该属性grad将变成一个Tensor张量类型. 该属性将包含计算所得的梯度,
在这之后如果再次调用backward()方法,将会对这个grad属性进行累加.
叶子节点通常为None,只有结果节点的grad_fn才有效,用于指示梯度函数是哪种类型。
print(tensor.grad_fn)
设置为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)is_leaf>>> 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= ) >>> data_in.requires_grad True >>> data_mean.requires_grad True >>> data_1 = data_mean * 20200910.0 >>> data_1 tensor(1182591., grad_fn= ) >>> data_2 = data_1 * 15.0 >>> data_2 tensor(17738864., grad_fn= ) >>> data_2.retain_grad() >>> data_3 = 2 * (data_2 + 55.0) >>> loss = data_3 / 2.0 +89.2 >>> loss tensor(17739010., grad_fn=) >>> >>> 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 >>> >>> >>>
判断是否是叶子节点
对于自己定义的变量,我们称之为叶子节点(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=) 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(原地 *** 作)
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)