Python实现强化学习PPO算法详解(第5部分)
这篇是PPO算法代码分析的最后一篇,如果要去跑代码,就将我这几篇挨着复制到运行软件中即可,要注意好缩进!
五、定义环境
import gym
import os
import numpy as np
def all_seed(env, seed=2):
''' 万能的seed函数
'''
if seed == 0:
return
env.seed(seed) # env config
np.random.seed(seed)
random.seed(seed)
torch.manual_seed(seed) # config for CPU
torch.cuda.manual_seed(seed) # config for GPU
os.environ['PYTHONISTA'] = str(seed) # config for python scripts
# config for cudnn
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False
torch.backends.cudnn.enabled = False
def env_agent_config(cfg):
env = gym.make(cfg.env_name) # 创建环境
all_seed(env, seed=cfg.seed)
n_states = env.observation_space.shape[0]
n_actions = env.action_space.n
print(f"状态空间维度:{n_states},动作空间维度:{n_actions}")
# 更新n_states和n_actions到cfg参数中
setattr(cfg, 'n_states', n_states)
setattr(cfg, 'n_actions', n_actions)
agent = Agent(cfg)
return env, agent
Gym是环境,os是操作系统相关内容,numpy是数值操作相关。
env.seed(seed):设置环境的随机种子为指定的 seed 值。
之后设置其他随机种子:设置环境的、numpy 库、Python 内置的随机数生成器random的、PyTorch 在CPU上的、PyTorch 在GPU上随机种子。os.environ['PYTHONHASHSEED'] = str(seed):设置 Python 脚本的哈希种子。确保在不同的运行环境中相同的输入数据生成相同的哈希值。
针对 cudnn 进行一些配置,torch.backends.cudnn.deterministic = True: 这一行代码告诉 cuDNN 库在执行卷积等操作时使用确定性算法,即每次运行时相同输入会产生相同输出。这有助于确保在相同条件下运行时的结果一致性,尤其是在使用随机数种子控制的情况下。torch.backends.cudnn.benchmark = False: 将此参数设置为 False 可以禁用 cuDNN 的自动寻找最适合当前配置的卷积算法功能。关闭 benchmark 模式可能会增加一些计算时间,但有助于确保结果的可重复性。torch.backends.cudnn.enabled = False: 这行代码可以显式地禁用 cuDNN 加速。在某些情况下,可能需要禁用 cuDNN 加速以避免一些特定问题,或者用于调试目的。
之后定义一个新的函数,先创建环境env,之后设置所有需要的随机种子。获取环境的状态空间维度和动作空间维度(获取方式不同,获取状态是连续空间,所以需要用shape[0],对于动作的离散空间,用n就行了)。接着使用 setattr 函数将 n_states 和 n_actions 更新到配置参数 cfg 中(setattr 的作用是将 n_states 和 n_actions 这两个变量的值分别设置到对象 cfg 中)。最后创建智能体对象 agent,并返回环境和智能体对象。
注意:
(1)"种子"通常指的是一个用来初始化随机数生成器的数值。随机数生成器根据种子生成一个伪随机数序列。如果使用相同的种子,那么生成的随机数序列将是确定性的,也就是说每次生成的随机数都是一样的。(简单来说,我们对很多东西进行初始化时,需要给一个初始值,但是每次随机的初始值都不一样的话,结果就很难复现出来,所以我们加入一个种子值,这里设定的是seed=1,所以每次选择的随机数序列也就确定了,随机就变成了伪随机。这种机制确保了实验的可重现性,使得在同样的条件下,每次运行得到的结果都是相同的,有助于调试和验证算法的正确性。)
六、设置参数
import matplotlib.pyplot as plt
import seaborn as sns
class Config:
def __init__(self) -> None:
self.env_name = "CartPole-v1" # 环境名字
self.new_step_api = True # 是否用gym的新api
self.algo_name = "PPO" # 算法名字
self.mode = "train" # train or test
self.seed = 2 # 随机种子
self.device = "cuda" # device to use
self.train_eps = 200 # 训练的回合数
self.test_eps = 20 # 测试的回合数
self.max_steps = 200 # 每个回合的最大步数
self.eval_eps = 5 # 评估的回合数
self.eval_per_episode = 10 # 评估的频率
self.gamma = 0.99 # 折扣因子
self.k_epochs = 4 # 更新策略网络的次数
self.actor_lr = 0.0003 # actor网络的学习率
self.critic_lr = 0.0003 # critic网络的学习率
self.eps_clip = 0.2 # epsilon-clip
self.entropy_coefficient = 0.01 # entropy的系数
self.update_freq = 100 # 更新频率
self.actor_hidden_dim = 256 # actor网络的隐藏层维度
self.critic_hidden_dim = 256 # critic网络的隐藏层维度
def smooth(data, weight=0.9):
'''用于平滑曲线,类似于Tensorboard中的smooth曲线
'''
last = data[0]
smoothed = []
for point in data:
smoothed_val = last * weight + (1 - weight) * point # 计算平滑值
smoothed.append(smoothed_val)
last = smoothed_val
return smoothed
def plot_rewards(rewards, cfg, tag='train'):
''' 画图
'''
sns.set()
plt.figure() # 创建一个图形实例,方便同时多画几个图
plt.title(f"{tag}ing curve on {cfg.device} of {cfg.algo_name} for {cfg.env_name}")
plt.xlabel('episodes')
plt.plot(rewards, label='rewards')
plt.plot(smooth(rewards), label='smoothed')
plt.legend()
plt.show() # 显示绘制的图形
用config类创造实例,然后进行初始化,设置好参数。
定义smooth函数,对数据进行平滑处理。首先将数据的第一个点作为初始值,然后创建一个空列表用于存放平滑后的数值,并遍历原始数据中的每个数据点,之后使用指数加权移动平均计算平滑值,将平滑值添加到平滑列表中,最后更新last为当前的平滑值,以便下一次循环使用,循环结束后就返回平滑后的数据。
plot_rewards 函数用于绘制奖励曲线图,其中包括了原始奖励曲线和经过平滑处理后的奖励曲线。函数内部使用了 seaborn 库设置样式,并调用了 plt 对象来创建图形、设置标题、标签,并绘制曲线。
注意:
(1)self.new_step_api = False # 是否用gym的新api,对于这句而言,self.new_step_api 可能是用来控制是否使用 Gym 最新版本中引入的新的环境交互方法(新的 API)。不使用可能是为了兼容之前的代码或特定的需求。
(2)import matplotlib.pyplot as plt和import seaborn as sns是给画图设置样式,并让其可视化的。
七、开始训练
# 获取参数
cfg = Config()
# 训练
env, agent = env_agent_config(cfg)
best_agent, res_dic = train(cfg, env, agent)
plot_rewards(res_dic['rewards'], cfg, tag="train")
# 测试
res_dic = test(cfg, env, best_agent)
plot_rewards(res_dic['rewards'], cfg, tag="test") # 画出结果
创建一个 Config 类的实例,用于存储参数配置,
根据参数配置创建环境和智能体。
使用训练函数 train 对代理进行训练,并返回最佳智能体和训练结果奖励。调用 plot_rewards 函数绘制训练奖励曲线。
使用测试函数 test 对最佳智能体进行测试。再次调用 plot_rewards 函数绘制测试奖励曲线。
当然,代码最好再整体看一下,理解一下各部分之间的关系
(以上代码分析均为个人理解,如果有误,欢迎指正讨论!)
作者:沈念辰