前言
这个是RLHF系列中的策略梯度部分,在看了Hands-on-RL和parl两者实现后,感觉整体难度并不是很高,但是当自己从零实现时还是会莫名其妙多一些问题,相比深度学习来讲,还是有蛮多小细节是需要额外注意的。
注意点
1. log平滑
这里是指learn阶段中的获取最大期望阶段,如下代码所示:
1 | output = self.model(obs_bs) |
在最开始自己实现时,我没有加log进行平滑,发现模型没法收敛(CarPole-v0 reward最大得分为200),一直是8,9徘徊。后来我看了上述实现,发现这里多了个log,这里让我觉得很困惑,因为我觉得这一步是不必要的,原因有以下几个方面:
- model.forward部分,已经用softmax做归一化了,已经避免了差异较大的情况。
- 从某种角度来讲,我甚至觉得model.forward中的softmax部分也不应该添加。因为本质来讲就是希望期望最大嘛。
但是呢,如果不加log这一步,模型就无法收敛。
2. 折扣因子
这里是指在每一次done,产生了一批state、reward、action之后,在进行计算loss时,下一步的reward还要考虑当前步reward的结果,即下一步的reward一定要小于当前步的reward。也就是calc_reward_to_go
函数这里。
这里同样也会觉得很困惑,因为如果希望期望最大,那就reward * prob使其概率最大即可。
如果看Hands-on-RL他的实现,不会感到任何困惑,因为他是用for来做的。但是呢,parl的实现在考虑了reward * prob使其概率最大
这一步之后,又添加了calc_reward_to_go
函数,从而本来是个离散的东西,强生生的给变成了一个连续状态的事情,关键呢,怎么看都不像是连续的,因为当前步在计算loss时也并没有跟上一步扯上直接的关系。
这一步还好,从理解角度来讲,我会更倾向Hands-on-RL的实现,容易理解。
总结
关于第一点,我的感觉是步子不能迈太大,宁可慢慢收敛,也要比无法收敛更强,例如model部分我尝试改成如下:
1 | class PolicyNet(torch.nn.Module): |
即forward部分不用softmax,剩下代码保持不变,也同样处于无法收敛状态,似乎来看,softmax+log才是这个算法成功的关键。
这里让我想到一个事情,在深度学习Layer参数初始化的过程,比如:
1 | a = nn.Linear(2222, 11) |
我们会是这么写,基本不会关心a这个linear的参数是如何初始化的。是因为框架内部已经考虑了kaiming、xavier、uniform等各种初始化技术。如果不加这些参数初始化技术,模型基本也很难收敛。
所以如果应用的话,可以采用现成的实现,如果研究的话,其中一些细节可以慢慢调整。
源码
1 | import random |