机器学习面试:RL中的策略梯度

1. 策略梯度

这是在看策略梯度算法时产生的一个困惑,假如我们有一个策略 θ \theta θ,通过这个策略可以跟环境交互,产生一系列的样本 τ = τ 1 , τ 2 . . . τ N \tau={\tau_{1},\tau_{2}...\tau_{N}} τ=τ1,τ2...τN,这个策略得到的分数为
R θ ‾ = ∑ τ R ( τ ) p θ ( τ ) = E τ ∼ p θ [ R ( τ ) ] \begin{aligned} \overline{R_\theta}&=\sum_{\tau}R(\tau)p_\theta(\tau)\\ &=E_{\tau \sim p_\theta}[R(\tau)] \end{aligned} Rθ=τR(τ)pθ(τ)=Eτpθ[R(τ)]

强化学习的核心目标是求取价值函数的最大值,因此有
θ ∗ = a r g m a x R θ ‾ \theta^{*}=argmax\overline{R_\theta} θ=argmaxRθ

这个是不可以直接求解的,所以通常我们使用梯度下降法进行策略优化
θ k + 1 = θ k + α ∇ R θ ‾ \theta_{k+1}=\theta_{k}+\alpha \nabla \overline{R_\theta} θk+1=θk+αRθ

策略一般非常的复杂,所以我们使用一个神经网络来代替这个策略,损失函数为 R θ ‾ \overline{R_\theta} Rθ,我们将损失函数丢入到神经网络中,使用tf或者torch可以自动帮助我们计算 θ \theta θ,将损失函数进行简化之后得到了一个新的梯度

∇ R θ ‾ = 1 N ∑ n = 1 N ∑ t = 1 T R ( τ ) ∇ l o g p θ ( a t n ∣ s t n ) \nabla \overline{R_{\theta}}=\frac{1}{N}\sum_{n=1}^{N}\sum_{t=1}^{T} R(\tau)\nabla{logp_{\theta}(a_{t}^{n}|s_{t}^{n})} Rθ=N1n=1Nt=1TR(τ)logpθ(atnstn)

看到这个梯度的时候我是非常懵逼的,主要有两点

  1. 一般情况下,我们会定义一个损失函数,然后将这个损失函数丢到神经网络中就可以训练了,这里为什么不去定义损失函数,而是去计算这个梯度呢,梯度不是神经网络自动帮我们计算的吗
  2. 这个梯度即使计算出来之后,难道要我们手动去进行更新吗,如果是简单的线性函数还可以,复杂的神经网络该怎么去更新呢?

其实这个问题非常的简单,只是我自己把这个问题给想复杂了。举一个例子,假如有一个损失函数
L θ = f θ 2 ( x ) L_\theta=f_{\theta}^{2}(x) Lθ=fθ2(x)

我们该如何求解 θ \theta θ,一种非常简单的方法就是直接使用 L θ L_\theta Lθ,丢入到神经网络中会自动帮我们计算梯度,并且更新参数,不过我们可以先做优化,化简一下梯度
∇ L = ∇ f θ 2 ( x ) = 2 ∇ f θ ( x ) \begin{aligned} \nabla{L}&=\nabla{f_{\theta}^{2}(x)}\\ &=2\nabla{f_{\theta}(x)} \end{aligned} L=fθ2(x)=2fθ(x)

你会发现原本求 L θ L_\theta Lθ的梯度,现在变成了求 f θ ( x ) f_{\theta}(x) fθ(x)的梯度了,怎么求 L θ L_\theta Lθ的梯度呢?直接把 L θ L_\theta Lθ丢到神经网络中,怎么求 f θ ( x ) f_{\theta}(x) fθ(x)的梯度呢?直接把 f θ ( x ) f_{\theta}(x) fθ(x)丢到神经网络中就行了,这其实就相当于说我们把损失函数给改变了。梯度符号后面是什么函数,那么我们的损失函数就是什么样子的,只要把梯度后的函数放到神经网络中,就可以自动求梯度了,不用纠结时 L L L还是 f f f,谁在梯度后面,谁就是损失函数。 这两种形式其实得到的结果是一样的。

pytoch中的梯度

之后我们看了策略梯度的代码

   optimizer.zero_grad()
   for i in reversed(range(len(reward_list))): # 从最后一步算起
       reward = reward_list[i]
       state = torch.tensor([state_list[i]], dtype=torch.float)
       action = torch.tensor([action_list[i]]).view(-1, 1)
       log_prob = torch.log(self.policy_net(state).gather(1, action))
       G = self.gamma * G + reward
       loss = - log_prob * G # 每一步的损失函数
       loss.backward() # 反向传播计算梯度
   self.optimizer.step() # 梯度下降

其中有两个地方非常的奇怪,

  1. 首先是为什么在pytorch中需要对梯度进行清除,在tensorflow中从未见过如此奇怪的函数。
  2. 为什么每一个steps都会有一个loss.backward(),这是pytoch计算需要,还是策略梯度算法需要?

1. SGD->BGD

google了一圈之后,算是找到了一些思路。首先看一下一般的使用方式

for idx, (batch_x, batch_y) in enumerate(data_loader): # 获取每个批次的样本
    output = model(batch_x) # 计算这些样本的预测结果,神经网络的输出
    loss = criterion(output, batch_y) #计算损失函数
    optimizer.zero_grad() # 梯度清零
    loss.backward() # 计算梯度
    optimizer.step() # 更新参数
  1. 调用optimizer.zero_grad()会将梯度清零 ∇ = 0 \nabla =0 =0
  2. 调用loss.backward()时pytoch会帮助我们自动计算每个参数的梯度 ∇ = ∂ L ∂ w \nabla =\frac{\partial{L}}{\partial{w}} =wL,并且会将这个梯度存储下来,如果没有对梯度清零,pytorch会自动将这些梯度累加的。
  3. 调用optimizer.step()会将参数进行更新 w = w − α ∂ L ∂ w w=w-\alpha \frac{\partial{L}}{\partial{w}} w=wαwL

首先看一下正常的SGD是输入一个样本就会进行一次参数更新
θ k + 1 = θ k − a k g ( x k ) \theta_{k+1}=\theta_{k} - a_kg(x_k) θk+1=θkakg(xk)

当我们不对梯度清零时,梯度会进行累加,参数更新变为
θ k + 1 = θ k − a k ∑ i = 1 N g ( x i ) \theta_{k+1}=\theta_{k} - a_k\sum_{i=1}^{N} g(x_i) θk+1=θkaki=1Ng(xi)

看一下BGD的参数更新,BGD是输入一批样本之后再去更新参数
θ k + 1 = θ k − a k 1 N ∑ i = 1 N g ( x i ) \theta_{k+1}=\theta_{k} - a_k\frac{1}{N}\sum_{i=1}^{N} g(x_i) θk+1=θkakN1i=1Ng(xi)

两个式子放在一起对比一下

{ θ k + 1 = θ k − a k ∑ i = 1 N g ( x i ) S G D 梯 度 未 清 零 θ k + 1 = θ k − a k N ∑ i = 1 N g ( x i ) B G D \left\{ \begin{aligned} \theta_{k+1}&=\theta_{k} - a_k\sum_{i=1}^{N} g(x_i) & & SGD梯度未清零\\ \theta_{k+1}&=\theta_{k} - \frac{a_k}{N}\sum_{i=1}^{N} g(x_i) & & BGD\\ \end{aligned} \right. θk+1θk+1=θkaki=1Ng(xi)=θkNaki=1Ng(xi)SGDBGD

发现一个非常有趣的现象,不清梯度的SGD和BGD竟然是一样的,如果我们不对梯度清零,就相当于是在使用BGD,只是在更新参数的时候需要缩小 1 N \frac{1}{N} N1倍,好像没什么问题。
去百度的时候发现很多人抄了抄去,说什么梯度不清零就相当于样本放大了N倍,参数更新时还需要缩小N倍,真的完全不知道在说什么,当把公式列出来的时候,就很清晰了。但是这么做有什么好处呢,既然是等价的,为什么pytoch不自动去做呢?
举一个有趣的例子,当我们资源很小模型很大的时候,该如何训练?直接使用BGD更新会导致资源不够,使用SGD收敛速度会变慢,这个时候我们就可以使用pytorch进行骚操作了,我们每次输入一个样本

for idx, (x, y) in enumerate(data_loader): # 输出一个样本
    output = model(x) # 计算神经网络输出
    loss = criterion(output, y) # 得到损失函数
    
    loss = loss / batch_size # loss进行
    loss.backward() # 求梯度
    
    if (idx % batch_size) == 0:
        optimizer.step() # 累积batch_size样本后的梯度,我们去做更新
        optimizer.zero_grad() # 再对梯度清零

我们依然使用神经网络去计算输出,每次都去计算梯度,但是不对梯度清零,这样梯度就会累加到一起,相当于我们使用了BGD。


[Pytorch]zero_grad()和backward()使用技巧

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值