pytorch 中的 .detach() .clone()

  • pytorch tensor 中的 .clone().detach()

    detach() 的用法

    在写代码时经常能见到通过 tensor.detach().clone() 操作生成一个和原本 tensor 值相同的新 tensor

    为什么需要同时使用 .clone().detach() ,接下来通过代码进行说明

    1. 生成两个 tensor,并且求梯度

      a = torch.tensor([1.0, 1.0], requires_grad=True)
      b = torch.tensor([2.0, 2.0], requires_grad=True)
      loss = a@b
      loss.backward()
      print(a, b)
      print(a.grad, b.grad)
      

      输出结果:

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

      可以看到 a, b 的梯度分别为 [2., 2.],[1., 1.]

    2. 使用 a_=a.detch() 脱离计算图

      在上面的代码中加上 a_=a.detch() 并且使用 a_ 计算和 backward()

      a = torch.tensor([1.0, 1.0], requires_grad=True)
      b = torch.tensor([2.0, 2.0], requires_grad=True)
      a_ = a.detach()
      loss = a_@b
      loss.backward()
      print(a, b)
      print(a.grad, b.grad)
      

      输出结果:

      tensor([1., 1.], requires_grad=True) tensor([2., 2.], requires_grad=True)
      None tensor([1., 1.])

      此时 a 的梯度为 none,因为.detach() 生成了一个新的 tensor 并且从计算图中脱离。a_ 运算后产生的梯度并不会传回 a

      关于 detach()具体细节可以查询官方文档

      生成的 a_ 是不会计算梯度的:

      print(a_.requires_grad)
      # out: False
      
    3. 需要注意的是 .detach() 生成的 tensor 和原本的 tensor 共享内存

      a = torch.tensor([1.0, 1.0], requires_grad=True)
      b = torch.tensor([2.0, 2.0], requires_grad=True)
      a_ = a.detach()
      # 对 a_ 修改
      a_[0] +=2 
      loss = a@b
      loss.backward()
      print(a, b)
      print(a.grad, b.grad)
      

      输出:

      tensor([3., 1.], requires_grad=True) tensor([2., 2.], requires_grad=True)
      tensor([2., 2.]) tensor([3., 1.])

      可以看到 a, b 的梯度分别为 [2., 2.],[3., 1.],原本a, b 的梯度分别为 [2., 2.],[1., 1.],但是因为改变了 a_[0],导致 a[0] 也变了,所以梯度也发生了变化。

      上面的代码中,通过.detach() 生成了新的 tensor,然后修改新生成的 tensor。在计算原本 a@b 的backward() 发现 b 的梯度发生了变化。这是因为修改了 a_ 的同时 a 也发生了变换。所以需要 .clone()

    4. 使用 .clone() 生成新的 tensor

      a = torch.tensor([1.0, 1.0], requires_grad=True)
      b = torch.tensor([2.0, 2.0], requires_grad=True)
      a_ = a.clone()
      a_[0] +=2 
      loss = a@b
      loss.backward()
      print(a, b)
      print(a.grad, b.grad)
      

      输出:

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

      上述代码中,a_a.clone() 生成。对a_ 进行修改并不会影响原本的a

    5. .clone().detach()

      .clone() 生成的 tensor 是可微的,在 backward 时候会将梯度回传

      官方文档-clone() 中提到

      This function is differentiable, so gradients will flow back from the result of this operation to input. To create a tensor without an autograd relationship to input see detach().

      a = torch.tensor([1.0, 1.0], requires_grad=True)
      b = torch.tensor([2.0, 2.0], requires_grad=True)
      a_ = a.clone()
      a_[0] +=2 
      loss = a_@b
      loss.backward()
      print(a, b)
      print(a.grad, b.grad)
      

      tensor([1., 1.], requires_grad=True) tensor([2., 2.], requires_grad=True)
      tensor([2., 2.]) tensor([3., 1.])

      可以看到,b 的梯度是 [3., 1.] 而不是 [1., 1.]

      这是因为使用 .clone() 生成的 tensor 进行运算,反向求导产生的梯度会传回输入节点。

      所以一般.clone().detach() 需要配合使用

def forward(self, l, ab, y, idx=None): K = int(self.params[0].item()) T = self.params[1].item() Z_l = self.params[2].item() Z_ab = self.params[3].item() momentum = self.params[4].item() batchSize = l.size(0) outputSize = self.memory_l.size(0) # the number of sample of memory bank inputSize = self.memory_l.size(1) # the feature dimensionality # score computation if idx is None: # 用 AliasMethod 为 batch 里的每个样本都采样 4096 个负样本的 idx idx = self.multinomial.draw(batchSize * (self.K + 1)).view(batchSize, -1) # sample positives and negatives idx.select(1, 0).copy_(y.data) # sample weight_l = torch.index_select(self.memory_l, 0, idx.view(-1)).detach() weight_l = weight_l.view(batchSize, K + 1, inputSize) out_ab = torch.bmm(weight_l, ab.view(batchSize, inputSize, 1)) # sample weight_ab = torch.index_select(self.memory_ab, 0, idx.view(-1)).detach() weight_ab = weight_ab.view(batchSize, K + 1, inputSize) out_l = torch.bmm(weight_ab, l.view(batchSize, inputSize, 1)) if self.use_softmax: out_ab = torch.div(out_ab, T) out_l = torch.div(out_l, T) out_l = out_l.contiguous() out_ab = out_ab.contiguous() else: out_ab = torch.exp(torch.div(out_ab, T)) out_l = torch.exp(torch.div(out_l, T)) # set Z_0 if haven't been set yet, # Z_0 is used as a constant approximation of Z, to scale the probs if Z_l < 0: self.params[2] = out_l.mean() * outputSize Z_l = self.params[2].clone().detach().item() print("normalization constant Z_l is set to {:.1f}".format(Z_l)) if Z_ab < 0: self.params[3] = out_ab.mean() * outputSize Z_ab = self.params[3].clone().detach().item() print("normalization constant Z_ab is set to {:.1f}".format(Z_ab)) # compute out_l, out_ab out_l = torch.div(out_l, Z_l).contiguous() out_ab = torch.div(out_ab, Z_ab).contiguous() # # update memory with torch.no_grad(): l_pos = torch.index_select(self.memory_l, 0, y.view(-1)) l_pos.mul_(momentum) l_pos.add_(torch.mul(l, 1 - momentum)) l_norm = l_pos.pow(2).sum(1, keepdim=True).pow(0.5) updated_l = l_pos.div(l_norm) self.memory_l.index_copy_(0, y, updated_l) ab_pos = torch.index_select(self.memory_ab, 0, y.view(-1)) ab_pos.mul_(momentum) ab_pos.add_(torch.mul(ab, 1 - momentum)) ab_norm = ab_pos.pow(2).sum(1, keepdim=True).pow(0.5) updated_ab = ab_pos.div(ab_norm) self.memory_ab.index_copy_(0, y, updated_ab) return out_l, out_ab
04-19
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值