Skip to content

zw007981/BasicRLAlgo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

112 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BasicRLAlgo

这个库使用 Python 实现了赵世钰老师的《Mathematical Foundations of Reinforcement Learning》,王树森、黎彧君和张志华老师合著的《Deep Reinforcement Learning》以及张伟楠、沈键和俞勇老师合著的《动手学强化学习》中的部分强化学习算法,包括:Value Iteration,Policy Iteration,SARSA,Q-Learning,Deep Q-Learning,Dueling Deep Q-Learning,Actor-Critic,REINFORCE,A2C(Advantage Actor-Critic),REINFORCE with Baseline,PPO(Proximal Policy Optimization),TD3(Twin Delayed Deep Deterministic Policy Gradient),SAC(Soft Actor-Critic),BC-SAC(Behavior Cloned Soft Actor-Critic),PETS(Probabilistic Ensembles with Trajectory Sampling)以及模仿学习中的Behavior Clone,GAIL(Generative Adversarial Imitation Learning)和SafeDAgger算法。

除做了特殊标识的代码外库中的绝大部分代码可在MIT License下自由使用,此外它们均在Python 3.12环境下测试通过。但是由于使用的Python版本较新兼容性可能存在问题,尝试了很久依然无法正确配置cuda环境¯\_ (ツ)_/¯ ¯\_ (ツ)_/¯ ¯\_ (ツ)_/¯,因此所有模型不得不在CPU上训练;加上个人平时使用Python的机会不多对这门语言的很多最佳实践都不清楚因而代码中肯定有不少可优化的地方。这两点原因导致了一些算法如REINFORCE with Baseline的训练速度非常慢,请多包涵。

1. Usage

    1. 安装requirements.txt中的依赖,可以使用如下命令:
      pip install -r requirements.txt
    1. src/algo目录下选择对应的算法文件运行即可。
    1. 运行日志将保存在log目录下,产生的图片和动画将保存在fig目录下,此外如果算法中使用了神经网络,模型将会以pth和onnx格式保存在model目录下。

2. Environment

本库使用了三个简单的环境,分别是栅格地图(GridMap)小车-推杆(CartPole)和摆锤(Pendulum)。

2.1. GridMap

与赵世钰老师书中的例子相似,这个环境中状态空间和动作空间都是离散的,我们需要训练agent使它尽快到达终点,并且在移动时避开障碍物。如下图所示,这是一个$5 \times 5$的栅格地图,其中绿色的栅格代表终点,红色的栅格代表障碍物,栅格上的箭头代表当前状态下的最优动作方向,如果栅格上的图形是圆形则代表最优动作是停止。如果想生成自己的地图,可以修改config/grid_map.json文件,但是请留意通常我们是从矩阵的左上角开始计数。

gridmap

2.2. CartPole

来源于gymnasium库的CartPole-v1环境,其状态空间是连续的而动作空间是离散的。在这个环境中我们需要通过左右移动小车,使得竖直的杆不倒下同时小车不要越过边界。与下面的Pendulum环境一样,我对原始的gymnasium环境进行了一定的封装以适配我自己的编程风格。

CartPole

2.3. Pendulum

来源于gymnasium库的Pendulum-v1环境,其状态空间和动作空间都是连续的。在这个环境中我们需要通过施加扭矩来控制摆的运动,尽可能使摆保持竖直向上。

Pendulum

3. Algorithm

3.1. Value Iteration

src/algo/value_iteration.py。训练过程如下图所示:

value_iteration

最终收敛得到的价值函数和最优策略如下:

value_iteration_result

训练过程中前后两帧之间的差异如下:

value_iteration_statistics

3.2. Policy Iteration

src/algo/policy_iteration.py。训练过程如下图所示:

policy_iteration

最终收敛得到的价值函数和最优策略如下:

policy_iteration_result

训练过程中前后两帧之间的差异如下:

policy_iteration_statistics

3.3. SARSA

src/algo/SARSA.py。从这里开始为了让Model-Free算法更好的收敛,我对栅格地图环境中的奖励函数做了一些调整,因此最后得到的价值函数的值与赵老师的书中的结果可能有所不同。训练过程如下图所示:

SARSA

最终收敛得到的价值函数和最优策略如下:

SARSA_result

在每一轮结束后,我都会对当前的策略进行评估,评估结果如下:

SARSA_statistics

3.4. Q-Learning

src/algo/Q_learning.py。训练过程如下图所示:

Q_learning

最终收敛得到的价值函数和最优策略如下:

Q_learning_result

对每一轮的评估结果如下:

Q_learning_statistics

3.5. Deep Q-Learning

src/algo/deep_Q_learning.py。训练过程如下图所示:

deep_Q_learning

最终收敛得到的价值函数和最优策略如下:

deep_Q_learning_result

对每一轮的评估结果如下:

deep_Q_learning_statistics

3.6. Dueling Deep Q-Learning

src/algo/dueling_deep_Q_learning.py。训练过程如下图所示:

dueling_deep_Q_learning

最终收敛得到的价值函数和最优策略如下:

dueling_deep_Q_learning_result

对每一轮的评估结果如下:

dueling_deep_Q_learning_statistics

3.7. Actor-Critic

src/algo/actor_critic.py。这里我们在CartPole环境中进行训练,训练过程中每一轮所获得的总奖励如下:

actor_critic

对训练得到的模型的评估结果如下:

actor_critic

3.8. REINFORCE

src/algo/REINFORCE.py。训练过程中每一轮所获得的总奖励如下:

REINFORCE

对训练得到的模型的评估结果如下:

REINFORCE

3.9. A2C

src/algo/A2C.py。训练过程中每一轮所获得的总奖励如下:

A2C

对训练得到的模型的评估结果如下:

A2C

3.10. REINFORCE with Baseline

src/algo/REINFORCE_with_baseline.py。从这里开始,我们将改用Pendulum环境,这使得动作空间从离散变为连续,增加了训练难度。训练过程中每一轮所获得的总奖励如下:

REINFORCE_with_baseline

对训练得到的模型的评估结果如下:

REINFORCE_with_baseline

3.11. PPO

src/algo/ppo.py。为了避免像之前的REINFORCE with Baseline算法那样出现训练时间过长的问题额外针对Pendulum-v1环境设置了随机数种子,如果想训练一个更通用的模型可删除对应代码并增加训练次数。训练过程中每一轮所获得的总奖励如下:

PPO

对训练得到的模型的评估结果如下:

PPO

3.12. TD3

src/algo/TD3.py。训练过程中每一轮所获得的总奖励如下:

TD3

对训练得到的模型的评估结果如下:

TD3

3.13. SAC

src/algo/SAC.py。训练过程中每一轮所获得的总奖励如下:

SAC

对训练得到的模型的评估结果如下:

SAC

3.14. BC-SAC

src/algo/BC_SAC.py。训练过程中每一轮所获得的总奖励如下:

BC_SAC

通过下图的对比实验可见(左:专家数据量EXPERT_DATA_SIZE=1000时的Behavior Clone算法训练曲线,右:标准SAC算法训练曲线),BC-SAC算法展现出以下特点:

    1. 策略鲁棒性:相较于传统模仿学习方法,BC-SAC通过强化学习机制有效解决了专家数据分布外状态下的动作选择问题。当智能体处于专家数据未覆盖的状态空间时,其基于Q函数的策略评估机制能够避免选择过差的动作,显著提升策略的泛化能力。
    1. 训练加速与梯度引导:相对于原始SAC算法,专家数据集为策略网络提供了高质量的初始梯度下降方向,能够在短时间内迭代到一个较好的策略,这一点也许在复杂系统中有着更为明显的优势。
    1. 值得特别指出的是,BC-SAC的架构设计理论上具备免于人工设计过于复杂的奖励的特性——通过专家轨迹隐含的最优性信息,可直接替代传统RL中复杂且敏感的奖励设计过程(原论文中RL部分的奖励非常简单,专注于安全性只惩罚了距离其它车辆过近和偏离道路两点)。但是遗憾的是当前实验环境(gymnasium/Pendulum-v1)直接提供了预设的奖励,未能验证该特性。

对训练得到的模型的评估结果如下:

BC_SAC

3.15. PETS

src/algo/PETS.py。训练过程中每一轮所获得的总奖励如下,可以看到借助训练所得模型的先验知识,相较于上面model-free的RL算法PETS算法能通过少得多的训练次数给出一个较好的策略。

PETS

对训练得到的模型的评估结果如下:

PETS

3.16. Imitation Learning

3.16.1. Behavior Clone

src/algo/behavior_clone.py。训练过程中每一轮所获得的总奖励如下:

behavior_clone

对训练得到的模型的评估结果如下:

behavior_clone

3.16.2. SafeDAgger

src/algo/SafeDAgger.py。训练过程中每一轮所获得的总奖励如下:

SafeDAgger

尤为值得注意的是,这里的专家数据集的大小只有上面Behavior Clone算法的十二分之一(EXPERT_DATA_SIZE = 1000),作为对比,我们把Behavior Clone算法的专家数据集大小也设置为1000,训练结果如下。可以看到在初始训练数据不足的情况下,Behavior Clone算法的总奖励在100轮左右迭代到-600附近,同时波动很大;而SafeDAgger算法的总奖励能收敛到-250附近。从这里我们可以看出DAgger类算法确实可以从一定程度上解决模仿学习中的distribution shift问题。

behavior_clone2000

对训练得到的模型的评估结果如下:

SafeDAgger

3.16.3. GAIL

src/algo/GAIL.py。训练过程中每一轮所获得的总奖励如下:

GAIL

对训练得到的模型的评估结果如下:

GAIL

4. Script

script文件夹中提供了两个脚本用于比较不同超参数下的算法性能。

4.1. hyperparameter_opt

基于optuna库实现,可用于自动寻找最优的超参数组合。在运行时会通过试验比较不同超参数下的算法性能,之后输出表现最好的几个超参数组合,如下所示(这里以PETS算法为例):

Found 444 completed trials.
Rank 1, Trial ID: 303, Reward: -197.5267, Params: {'num_sub_models': 4, 'hidden_dim': 113, 'num_samples': 30, 'num_cem_iterations': 3, 'prediction_horizon': 17, 'act_func': 'swish'}.
Rank 2, Trial ID: 39, Reward: -202.6504, Params: {'num_sub_models': 5, 'hidden_dim': 138, 'num_samples': 50, 'num_cem_iterations': 3, 'prediction_horizon': 18, 'act_func': 'swish'}.
Rank 3, Trial ID: 201, Reward: -211.6958, Params: {'num_sub_models': 7, 'hidden_dim': 116, 'num_samples': 50, 'num_cem_iterations': 3, 'prediction_horizon': 18, 'act_func': 'swish'}.
Rank 4, Trial ID: 171, Reward: -213.1675, Params: {'num_sub_models': 4, 'hidden_dim': 125, 'num_samples': 50, 'num_cem_iterations': 3, 'prediction_horizon': 17, 'act_func': 'swish'}.
Rank 5, Trial ID: 429, Reward: -213.5682, Params: {'num_sub_models': 6, 'hidden_dim': 136, 'num_samples': 60, 'num_cem_iterations': 3, 'prediction_horizon': 19, 'act_func': 'swish'}.
Rank 6, Trial ID: 313, Reward: -214.2942, Params: {'num_sub_models': 4, 'hidden_dim': 131, 'num_samples': 30, 'num_cem_iterations': 3, 'prediction_horizon': 19, 'act_func': 'swish'}.
Rank 7, Trial ID: 418, Reward: -218.7742, Params: {'num_sub_models': 4, 'hidden_dim': 120, 'num_samples': 60, 'num_cem_iterations': 3, 'prediction_horizon': 18, 'act_func': 'swish'}.
Rank 8, Trial ID: 375, Reward: -220.0506, Params: {'num_sub_models': 3, 'hidden_dim': 117, 'num_samples': 50, 'num_cem_iterations': 3, 'prediction_horizon': 18, 'act_func': 'swish'}.

同时会在fig/hyperparameter_opt文件夹下生成两张图片,第一张是优化历史,如下所示:

optimization_history

第二张是各个参数的重要性,可以看到在这个例子中激活函数的选取最重要。结合一开始的输出可以看到在表现最好的几个超参数组合中激活函数都选择了"swish",因此至少在这个例子中要尽量选择"swish"作为激活函数,否则可能会大幅降低算法性能。

param_importance

但是需要注意的是在优化模仿学习中的算法时最好不要开多线程计算(保持参数n_jobs=1),因为那几个算法默认会读取同一份专家数据同时在生成新的专家数据后会存储到同一个地方,多线程读取和写入会产生不可控的错误。

4.2. log_miner

上面的脚本虽然实现了自动寻优,但是运行所需时间较长。由于GIL的存在optuna的原生多线程方法无法在CPU密集型任务上实现真正的提速,在上面一个例子中花了九个小时比较了444个超参数组合,因此这里还提供了一个轻量级的脚本用于人工选择超参数进行精调。在运行这个脚本之前需要先手动运行不同超参数下的算法并保存对应的日志到同一个文件夹下(注意日志文件最好要重命名以避免被覆盖),然后在脚本中输入对应的文件夹路径运行即可。脚本会自动从日志中提取超参数和训练数据并输出对比图,如下图所示(同样以PETS算法为例):

log_miner

可以看到这里比较了在PETS算法中MPC的预测步数对每轮的总奖励以及运行时间的影响。把预测步数从20增加到30后,每轮的总奖励没有明显提升,但是运行时间却增加了很多。所以至少在这个例子中,把预测步数增加到30并不是一个好的选择。

5. Test

test文件夹中提供了几个单元测试用例用于验证环境和常用容器能否正常工作,但是没有提供用于验证算法正确性的场景测试。想要运行这些测试需要先安装pytest库。

Releases

No releases published

Packages

 
 
 

Contributors

Languages