您的位置:

深入浅出torch.autograd

一、介绍autograd

torch.autograd 模块是 PyTorch 中的自动微分引擎。它支持任意数量的计算图,可以自动执行前向传递、后向传递和计算梯度,同时提供很多有用的函数来处理各种计算图。它与程序中的其它部分紧密结合使用,使得使用 PyTorch 构建神经网络得以简单而高效实现。

二、期望值反向传播

我们简单介绍一下期望值反向传播(REINFORCE)及其PyTorch实现。期望值反向传播是一种用于直接优化policy的算法。在许多强化学习任务中,我们需要优化一个策略,使其能够最大化累积奖励的期望值。在连续动作空间或高维状态空间下,直接优化该策略是非常困难的,因此我们通常使用一些策略梯度方法,它们依据一些目标构建策略的估计,并更新策略以优化该估计。 在期望值反向传播中,我们计算得到目标的期望值,然后对该期望值的对数化为一组权重,分别赋给每一步的回报。然后用一个对策略参数的公式求导。这将给出一组价值函数的梯度,此外,我们还可以使用Torch中的链式法则很容易地获得策略梯度。

import torch

def REINFORCE(policy, episodes, learning_rate):
    optimizer = torch.optim.Adam(policy.parameters(), lr=learning_rate) #定义一个优化器
    episode_rewards = []
    for i in range(episodes):
        log_probs = []
        rewards = []

        state = env.reset()
        episode_reward = 0

        while(True):
            action, log_prob = policy.get_action(state)
            new_state, reward, done, _ = env.step(action.item())
            log_probs.append(log_prob)
            rewards.append(reward)
            state = new_state
            episode_reward += reward
            if done:
                break

        episode_rewards.append(episode_reward)

        # 计算期望梯度,更新策略参数
        eps = torch.finfo(torch.float32).eps
        discounts = [np.power(0.99, i) for i in range(len(rewards))]
        discounts = torch.from_numpy(np.array(discounts, dtype=np.float32)).to(device)
        rewards = torch.stack(rewards).to(device)
        log_probs = torch.stack(log_probs).to(device)
        
        R = (rewards * discounts).sum(dim=0)
        R = (R - R.mean()) / (R.std() + eps) # 标准化策略梯度

        policy_loss = (-log_probs * R).sum(dim=0)

        optimizer.zero_grad()
        policy_loss.backward()
        optimizer.step()

    return episode_rewards

三、autograd.Function的使用

本节将详细介绍PyTorch中 autograd.Function 的使用,以及如何使用它们自定义您的操作。在PyTorch中,Variable的每个操作都是如何在计算图中回溯到其他变量,并且每个操作同样可以回溯到其他函数。为了允许用户实现自己的操作,PyTorch为我们提供了一个非常简单和强大的类”。该autograd.Function类被Pytorch用于允许我们定义随时可导的用户自定义运算。

class Exp(Function):

    @staticmethod
    def forward(ctx, i):
        result = i.exp()
        ctx.save_for_backward(result)
        return result

    @staticmethod
    def backward(ctx, grad_output):
        result, = ctx.saved_variables
        return grad_output * result

四、Variable以及在计算图中的应用

在PyTorch中,算子可以添加到计算图中,以实现自动求导。在计算图中,每一个节点都表示一个Tensor,其中一些Tensor节点是输入节点(InputNode),而其他节点是操作节点(FunctionNode)。在轻松编写深度学习模型时,我们很少手动添加节点和边,PyTorch很好地隐藏了这些内容并隐式执行了它。 Variable 是 PyTorch 中图计算的重要概念之一。它是具有梯度的张量,可以直接放入计算图中,并可以通过它的backward()函数产生梯度信号。 Variable是Tensor的一个封装,不同的是Variable有 tensor的一些属性和方法,比如shape,size()等, 同时它有一些附加的属性,比如grad( Variable的梯度)、requires_grad(是不是需要求 Variable的梯度)、data(保存 Variable的 tensor)等。 我们在需要求 Variable的梯度时才需要调用 backward() 函数.

import torch
from torch.autograd import Variable

x_data = [1.0, 2.0, 3.0]
y_data = [2.0, 4.0, 6.0]

w = Variable(torch.Tensor([1.0]), requires_grad=True)

def forward(x):
    return x * w

def loss(x, y):
    y_pred = forward(x)
    return (y_pred - y) * (y_pred - y)

for epoch in range(10):
    for x_val, y_val in zip(x_data, y_data):
        l = loss(x_val, y_val)
        l.backward()
        print("grad: ", x_val, y_val, w.grad.data[0])
        w.data = w.data - 0.01 * w.grad.data

        # Manually zero the gradients after updating weights
        w.grad.data.zero_()

五、tensor.detach()函数的应用

在PyTorch中, tensor.detach()函数是用于获得没有对原始变量的梯度的新张量的,这也就是一个detachdetensor。总之,当您需要获取不需要梯度的张量时, detach() 函数非常有用。在使用GPU时,您必须对张量调用detach(),以便在进行数据移动时清除存储,否则它会导致内存泄漏;因为我们需要计算二阶梯度。 没有从图中分离张量属性。因此, detach() 使您可以在不影响计算图的情况下使用张量。

import torch

tensor = torch.randn(3, requires_grad=True)
print('tensor:', tensor)

# 移动到GPU上
tensor_gpu = tensor.cuda()

# 加点于tensor在GPU上产生的梯度
result_gpu = (tensor_gpu ** 2).sum()
result_gpu.backward()

# TensorFlow,tensor与 tensor_gpu 的梯度不匹配。
# Do a slow transfer back to CPU memory
result = (tensor.detach().cpu() ** 2).sum()
result.backward()

print('tensor_grad:', tensor.grad)
print('tensor_gpu_grad:', tensor_gpu.grad)