梯度如何通过随机样本反向传播?
How does a gradient backpropagates through random samples?
我正在学习策略梯度,但我很难理解梯度是如何通过随机操作的。来自 here:It is not possible to directly backpropagate through random samples. However, there are two main methods for creating surrogate functions that can be backpropagated through
.
他们有一个例子 score function
:
probs = policy_network(state)
# Note that this is equivalent to what used to be called multinomial
m = Categorical(probs)
action = m.sample()
next_state, reward = env.step(action)
loss = -m.log_prob(action) * reward
loss.backward()
我试图创建一个示例:
import torch
import torch.nn as nn
import torch.optim as optim
from torch.distributions import Normal
import matplotlib.pyplot as plt
from tqdm import tqdm
softplus = torch.nn.Softplus()
class Model_RL(nn.Module):
def __init__(self):
super(Model_RL, self).__init__()
self.fc1 = nn.Linear(1, 20)
self.fc2 = nn.Linear(20, 30)
self.fc3 = nn.Linear(30, 2)
def forward(self, x):
x1 = self.fc1(x)
x = torch.relu(x1)
x2 = self.fc2(x)
x = torch.relu(x2)
x3 = softplus(self.fc3(x))
return x3, x2, x1
# basic
net_RL = Model_RL()
features = torch.tensor([1.0])
x = torch.tensor([1.0])
y = torch.tensor(3.0)
baseline = 0
baseline_lr = 0.1
epochs = 3
opt_RL = optim.Adam(net_RL.parameters(), lr=1e-3)
losses = []
xs = []
for _ in tqdm(range(epochs)):
out_RL = net_RL(x)
mu, std = out_RL[0]
dist = Normal(mu, std)
print(dist)
a = dist.sample()
log_p = dist.log_prob(a)
out = features * a
reward = -torch.square((y - out))
baseline = (1-baseline_lr)*baseline + baseline_lr*reward
loss = -(reward-baseline)*log_p
opt_RL.zero_grad()
loss.backward()
opt_RL.step()
losses.append(loss.item())
这似乎神奇地工作得很好,我不明白梯度是如何通过的,因为他们提到它不能通过随机操作(但不知何故它确实如此)。
现在由于梯度无法通过随机操作流动我尝试替换
mu, std = out_RL[0]
与 mu, std = out_RL[0].detach()
并导致错误:
RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn
。如果梯度不通过随机操作,我不明白为什么在操作之前分离张量很重要。
采样确实不是可微分的操作本身。但是,有两种(广泛的)方法可以缓解这种情况 - [1] REINFORCE 方法和 [2] reparameterization 方法。由于您的示例与 [1] 相关,因此我将回答 REINFORCE。
REINFORCE 所做的是完全摆脱计算图中的采样操作。但是,采样操作仍然在图形之外。所以,你的声明
.. how does the gradient passes through a random operation ..
不正确。它不通过任何随机操作。让我们看看你的例子
mu, std = out_RL[0]
dist = Normal(mu, std)
a = dist.sample()
log_p = dist.log_prob(a)
a
的计算不涉及创建计算图。它在技术上等同于从数据集中插入一些离线数据(如在监督学习中)
mu, std = out_RL[0]
dist = Normal(mu, std)
# a = dist.sample()
a = torch.tensor([1.23, 4.01, -1.2, ...], device='cuda')
log_p = dist.log_prob(a)
因为我们事先没有离线数据,所以我们即时创建它们,而.sample()
方法只做这些。
所以,图上没有随机操作。 log_p
确定性地依赖于 mu
和 std
,就像任何标准计算图一样。如果你这样切断连接
mu, std = out_RL[0].detach()
..当然要投诉了
还有,不要被这个操作搞糊涂了
dist = Normal(mu, std)
log_p = dist.log_prob(a)
因为它本身不包含任何随机性。这只是为 Normal
分发编写乏味的 log-likelihood formula 的捷径。
@ayandas 很好地解释了第一种方式。
第二种方法,重新参数化方法,完全不同。
与 sample()
相比,使用 rsample()
returns 样本的重新参数化 维持 计算图。
这是通过添加一个随机值(但保持模型的参数)来完成的。
用简单的代码检查this explanation。
我正在学习策略梯度,但我很难理解梯度是如何通过随机操作的。来自 here:It is not possible to directly backpropagate through random samples. However, there are two main methods for creating surrogate functions that can be backpropagated through
.
他们有一个例子 score function
:
probs = policy_network(state)
# Note that this is equivalent to what used to be called multinomial
m = Categorical(probs)
action = m.sample()
next_state, reward = env.step(action)
loss = -m.log_prob(action) * reward
loss.backward()
我试图创建一个示例:
import torch
import torch.nn as nn
import torch.optim as optim
from torch.distributions import Normal
import matplotlib.pyplot as plt
from tqdm import tqdm
softplus = torch.nn.Softplus()
class Model_RL(nn.Module):
def __init__(self):
super(Model_RL, self).__init__()
self.fc1 = nn.Linear(1, 20)
self.fc2 = nn.Linear(20, 30)
self.fc3 = nn.Linear(30, 2)
def forward(self, x):
x1 = self.fc1(x)
x = torch.relu(x1)
x2 = self.fc2(x)
x = torch.relu(x2)
x3 = softplus(self.fc3(x))
return x3, x2, x1
# basic
net_RL = Model_RL()
features = torch.tensor([1.0])
x = torch.tensor([1.0])
y = torch.tensor(3.0)
baseline = 0
baseline_lr = 0.1
epochs = 3
opt_RL = optim.Adam(net_RL.parameters(), lr=1e-3)
losses = []
xs = []
for _ in tqdm(range(epochs)):
out_RL = net_RL(x)
mu, std = out_RL[0]
dist = Normal(mu, std)
print(dist)
a = dist.sample()
log_p = dist.log_prob(a)
out = features * a
reward = -torch.square((y - out))
baseline = (1-baseline_lr)*baseline + baseline_lr*reward
loss = -(reward-baseline)*log_p
opt_RL.zero_grad()
loss.backward()
opt_RL.step()
losses.append(loss.item())
这似乎神奇地工作得很好,我不明白梯度是如何通过的,因为他们提到它不能通过随机操作(但不知何故它确实如此)。
现在由于梯度无法通过随机操作流动我尝试替换
mu, std = out_RL[0]
与 mu, std = out_RL[0].detach()
并导致错误:
RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn
。如果梯度不通过随机操作,我不明白为什么在操作之前分离张量很重要。
采样确实不是可微分的操作本身。但是,有两种(广泛的)方法可以缓解这种情况 - [1] REINFORCE 方法和 [2] reparameterization 方法。由于您的示例与 [1] 相关,因此我将回答 REINFORCE。
REINFORCE 所做的是完全摆脱计算图中的采样操作。但是,采样操作仍然在图形之外。所以,你的声明
.. how does the gradient passes through a random operation ..
不正确。它不通过任何随机操作。让我们看看你的例子
mu, std = out_RL[0]
dist = Normal(mu, std)
a = dist.sample()
log_p = dist.log_prob(a)
a
的计算不涉及创建计算图。它在技术上等同于从数据集中插入一些离线数据(如在监督学习中)
mu, std = out_RL[0]
dist = Normal(mu, std)
# a = dist.sample()
a = torch.tensor([1.23, 4.01, -1.2, ...], device='cuda')
log_p = dist.log_prob(a)
因为我们事先没有离线数据,所以我们即时创建它们,而.sample()
方法只做这些。
所以,图上没有随机操作。 log_p
确定性地依赖于 mu
和 std
,就像任何标准计算图一样。如果你这样切断连接
mu, std = out_RL[0].detach()
..当然要投诉了
还有,不要被这个操作搞糊涂了
dist = Normal(mu, std)
log_p = dist.log_prob(a)
因为它本身不包含任何随机性。这只是为 Normal
分发编写乏味的 log-likelihood formula 的捷径。
@ayandas 很好地解释了第一种方式。
第二种方法,重新参数化方法,完全不同。
与 sample()
相比,使用 rsample()
returns 样本的重新参数化 维持 计算图。
这是通过添加一个随机值(但保持模型的参数)来完成的。
用简单的代码检查this explanation。