基础知识回顾
1.强化学习(Agent、Environment)
在 RL 中,代理通过不断与环境交互、以试错的方式进行学习,在不确定性下做出顺序决策,并在探索(新领域)和开发(使用从经验中学到的知识)之间取得平衡。 (探索利用问题)
已经使用stable_baseline3做过一些列实验,sb3这个库相对简单,但是训练起来感觉并不是很好;
ElegantRL 在 Actor-Critic 框架下实现 DRL 算法,其中 Agent(又名 DRL 算法)由 Actor 网络和 Critic 网络组成。由于代码结构的完整性和简单性,用户能够轻松自定义自己的代理。
该开源库的框架很清楚的描述了运行流程,Run.py来实现Agent和Environment的交互;
ElegantRL 的文件结构如图 1 所示:
- Env.py:它包含代理与之交互的环境。
- 用于健身房环境修改的 PreprocessEnv 类。
- 以自建股票交易环境为例,进行用户自定义。
2. Net.py:有三种类型的网络:
- Q-Net,
- Actor Network、
- 评论家网络,
每个 API 都包括一个用于继承的基本网络和一组用于不同算法的变体。
3. Agent.py:它包含不同 DRL 算法的代理。
4. Run.py:提供训练和评估过程的基本功能:
- 参数初始化 /
- 训练环 /
- 计算器。
作为高级概述,文件之间的关系如下。在 Env.py 中初始化环境,在 Agent.py 中初始化代理。该代理是使用 Net.py 中的 Actor 和 Critic 网络构建的。在 Run.py 的每个训练步骤中,代理与环境交互,生成存储到 Replay Buffer 中的转换。然后,代理从 Replay Buffer 获取转换以训练其网络。每次更新后,评估器都会评估代理的性能,如果性能良好,则会保存代理。
该库每个DRL算法代理都遵循其基类中的层次结构
如图 2 所示,DQN 系列算法的继承层次结构如下:
- AgentDQN:标准 DQN Agent。
- AgentDoubleDQN:继承自 AgentDQN 的双 DQN 代理,具有两个用于减少高估的 Q-Net。
- AgentDuelingDQN:继承自 AgentDQN 的 Q 值计算不同 DQN 代理。
- AgentD3QN:AgentDoubleDQN 和 AgentDuelingDQN 的组合,继承自 AgentDoubleDQN。
class AgentBase: def init(self); def select_action(states); # states = (state, …) def explore_env(env, buffer, target_step, reward_scale, gamma); def update_net(buffer, max_step, batch_size, repeat_times); def save_load_model(cwd, if_save); def soft_update(target_net, current_net); class AgentDQN: def init(net_dim, state_dim, action_dim); def select_action(states); # for discrete action space def explore_env(env, buffer, target_step, reward_scale, gamma); def update_net(buffer, max_step, batch_size, repeat_times); def save_or_load_model(cwd, if_save); class AgentDuelingDQN(AgentDQN): def init(net_dim, state_dim, action_dim);class AgentDoubleDQN(AgentDQN): def init(self, net_dim, state_dim, action_dim); def select_action(states); def update_net(buffer, max_step, batch_size, repeat_times); class AgentD3QN(AgentDoubleDQN): # D3QN: Dueling Double DQN def init(net_dim, state_dim, action_dim);
在构建 DRL 代理时应用这样的层次结构可以有效地提高轻量级和有效性。用户可以在类似的流程中轻松设计和实施新代理。
基本上,一个智能体有两个基本功能,数据流如图所示:
- explore_env():它允许代理与环境交互并为训练网络生成转换。
- update_net() :它首先从 Replay Buffer 中获取一批 transitions,然后使用反向传播训练网络。
训练piple
train代理的两个主要步骤:
- 初始化:
- hyper-parameters 参数 args 的 Json 参数。
- env = PreprocessEnv() :创建一个环境(以 OpenAI gym 格式)。
- agent = AgentXXX() :为 DRL 算法创建代理。
- evaluator = Evaluator() :评估并存储经过训练的模型。
- buffer = ReplayBuffer() :存储过渡。
2. 然后,训练过程由 while 循环控制:
- agent.explore_env(...):代理在 Target Steps 中探索环境,生成转换,并将其存储到 ReplayBuffer 中。
- agent.update_net(...):代理使用 ReplayBuffer 中的批处理来更新网络参数。
- evaluator.evaluate_save(...):评估代理的性能,并保持经过训练的模型获得最高分。
while 循环将在满足条件时终止,例如,达到目标分数、最大步数或手动中断。
阅读ElegantRL框架,从Helloworld开始
1.正如这段所说,可以自己重新创建一个folder,并将net/agent/config/env/run.py文件加入并加入tutorial_*.py,然后运行tutorial。在代码中如下
2.同时也可以运行,因为从代码发现实际上是一样的,不过是将多个模块的代码集成到了一个py文件中;
了解结构之后从第一种方式去看代码,因为分成了不同模块更符合之后编写自己的环境以及接入算法的工作;
从这个教程上可以看到基本流程就是1.初始化智能体2.初始化环境3.配置参数的设定使用Config
4.训练
因此针对环境这一块需要去再看看gym。目前的疑问在于如果env_class不是gym.make。
env_class(**kwargs_filter(env_class.__init__, env_args.copy()))
该怎么使用?
看了GPT之后明白了,如果是else情况这里的env_class直接就相当于自定义的环境类,或则是来自其他库的非gym.make环境类;
GYM:
然后这里也回顾一下gym
- import gym
- env = gym.make('CartPole-v0')
- env.reset()
- for _ in range(1000):
- env.render()
- env.step(env.action_space.sample()) # take a random action
- env.close()
因为基于gym创建环境后可以注册到gym服务器,因此使用的时候使用注册名即可生成;
关于Observation Space 和 Action Space, 根据自己的应用场景设定;
对于 step 与返回的 obs, reward, done 与 info;
继承gym写环境就相当于一套模板,__init__(self,args), step(),reset() 这三个必须的
- import gym
- env = gym.make('CartPole-v0')
- env.reset()
- for _ in range(10):
- env.render()
- observation, reward, done, info = env.step(env.action_space.sample()) # take a random action
- print('observation:{}, reward:{}, done:{}, info:{}'.format(observation, reward, done, info))
- env.close()
上面的就是一个经典的 agent-environment 循环。agent 选择一个 action,环境返回一个observation 和 reward。就是如下图所示。
Gym进阶使用
Wrappers其实就相当于装饰器,在不改变原来的功能基础上可能对某些功能做一些增加;
这里如何调用了def action(),在gym.ActionWrappr里面封装的,动作输入之后实际上调用的是 RandomActionWrapper
中封装的 step
方法,而 RandomActionWrapper
继承自 gym.ActionWrapper
。在这个过程中,动作会经过自定义的 action()
方法进行预处理。最后返回return self.env.step(final_action)
注册和删除:接上文代码中的部分,如何自定义环境后注册到gym
render():两种操作video和matplotlib
毕业设计按照后者做的,然后绘图代码基本上在render里面实现,test的时候并没有放到循环中实现。但是这里来开,plt.imshow()完全可以放到循环中,从而减少环境中显示的延迟;
整体看下来gym目前还是比较理解的,就是通过继承这个父类,然后自定义自己的环境,还可以通过wrapper,在自定义环境类生成示例后进行功能添加(装饰器的功能);
关于自定义环境注册,上述文本只提供了已有环境修改参数后的注册,至于如何针对自己自定义环境的注册,看下面的描述:
如何在 Gym 中注册自定义环境? - 知乎 (zhihu.com)
构建自己的gym训练环境 巨详细_gym自定义环境-CSDN博客
更加深入理解gym可以查看官方文档,已经更换团队维护,import gymnasium as gym
Basic Usage - Gymnasium Documentation (farama.org)
Basic Usage
Gymnasium is a project that provides an API for all single agent reinforcement learning environments, and includes implementations of common environments: cartpole, pendulum, mountain-car, mujoco, atari, and more.
这里已经说了这个API包括四个最关键的函数
gymnasium.envs.registry.keys() 就是一个字典查看键的操作,可以看到
dict_keys(['CartPole-v0', 'CartPole-v1', 'MountainCar-v0', 'MountainCarContinuous-v0', 'Pendulum-v1', 'Acrobot-v1', 'phys2d/CartPole-v0', 'phys2d/CartPole-v1', 'phys2d/Pendulum-v0', 'LunarLander-v2', 'LunarLanderContinuous-v2', 'BipedalWalker-v3', 'BipedalWalkerHardcore-v3', 'CarRacing-v2', 'Blackjack-v1', 'FrozenLake-v1', 'FrozenLake8x8-v1', 'CliffWalking-v0', 'Taxi-v3', 'tabular/Blackjack-v0', 'tabular/CliffWalking-v0', 'Reacher-v2', 'Reacher-v4', 'Pusher-v2', 'Pusher-v4', 'InvertedPendulum-v2', 'InvertedPendulum-v4', 'InvertedDoublePendulum-v2', 'InvertedDoublePendulum-v4', 'HalfCheetah-v2', 'HalfCheetah-v3', 'HalfCheetah-v4', 'Hopper-v2', 'Hopper-v3', 'Hopper-v4', 'Swimmer-v2', 'Swimmer-v3', 'Swimmer-v4', 'Walker2d-v2', 'Walker2d-v3', 'Walker2d-v4', 'Ant-v2', 'Ant-v3', 'Ant-v4', 'Humanoid-v2', 'Humanoid-v3', 'Humanoid-v4', 'HumanoidStandup-v2', 'HumanoidStandup-v4', 'GymV21Environment-v0', 'GymV26Environment-v0'])
环境构建完成之后;(插眼一下这个图很好可以用于答辩使用HH
但是存在的一个问题是,之前env.step返回的好像是4元组
看了一下版本用的是0.29.1,用的是5元组
我的调度任务中也是设置了终止状态和截断信号,到该就是跑完600个TTI算终止,然后在运行过程中不符合实际的调度时刻超过了600个TTI的50%可能就截止回合,然后reset重启环境;
这一部分也很重要,本身是MIMO系统,怎么把环境建模,
动作空间可以
1.建模成Discrete(如果考虑每次只选择单个用户)
2.MultiBinary(考虑一次性选出用户子集,目前使用)
3.看论文也发现存在连续动作空间采样离散值(在连续空间采样出多个用户,那么也可以使用Box)
4.如果能够做一个预分组,也可以使用MultiDiscrete (3.4两者非常一致,可看图片解释)
包装环境和获得原环境
2.兼容性:刚刚记得存在done的问题,下一篇就设计了环境的兼容性,大体意思就是说
1.以前的gym版本下注册的环境,可以通过特殊的这个环境名或者wrapper来实现;
2.对gymnasium低版本的环境,也有参数compatibility来实现;
这一节了解到了一个关键技巧,Reset这一步中表明info也已用来存储一些重要指标,和有效动作掩码。然后通过info中获取就可以比如说保存吞吐量或者公平性,因为本身step返回的是奖励,但是我们的奖励函数可能是多指标的组合,这个时候就将多个指标通过info返回,我们就可以用来分析训练的好坏。
同时这个动作掩码也提供了一种新思路:
obs, reward, terminated, truncated, info = env.step(action)
因为我只能根据obs_space给定的几个格式设置空间,那么如果现在我的环境状态时[H,J],空间该怎么设置呢?
我们可以只将H的shape设置为obs_space,对于j使用info来记录
day2.试验记录
遵循gym的custom环境创建格式,将自己的环境已经搭建完成,并实现了渲染。
同时也利用register将环境注册到了gym中;
实现过程参考了官方文档和【强化学习系列】Gym库使用——创建自己的强化学习环境2:拆解官方标准模型源码/规范自定义类+打包自定义环境_gym库 强化学习-CSDN博客
简化后的一版环境实现的效果如下;
day3.矢量化环境已经实现,但是目前碰到了一个bug,无法使用异步环境
问题追溯:
简述:仅仅在主线程里面注册了环境,注册表实例得到更新。 当创建异步向量env时,进程会生成/分叉(稍后会详细介绍),这基本上会创建一个新的解释器并重新运行一些代码。 似乎这不包括对注册表的更新,因此在子进程中,新环境不会注册。 因此,即使主线程看到所有内容,每个子进程也只看到内置的envs,因此它们崩溃。
solution: The temporary workaround could be monkey-patching the gym.vector.make
function to manually select the right start method, or accessing AsyncVectorEnv
directly.
最后这句抱怨GIL,个人认为也是主要原因导致了python无法并行化线程,不过高版本的python似乎已经解决了GIL。
后续发现:该问题已经closed,Fix async vectorization for custom registered envs by RedTachyon · Pull Request #810 · Farama-Foundation/Gymnasium (github.com)
然后还有一个问题就是注册以及打包的规范化,如果import不了,
示例:env = gymnasium.make('module:Env-v0')
- 其中
module
是包含注册代码的模块名称。 - 这样 Gym 会在导入
module
时执行注册,然后创建Env-v0
这个环境。
(硬解),直接不传注册名,而是传模块,模块里面实现了注册,gym.make使用;就跟我下面一样传入这个entry_point;
中午查看了一下文档,发现原来版本的gymnasium需要另外对envs进行wrapper,但是新版已经实现
但是查看之后还发现了一点,也就是更新后的版本已经去除了原有gymnasium.wrappers下的这个记录包装器支持矢量化环境
而重新在class gymnasium.wrappers.vector.RecordEpisodeStatistics 下面写了一个新的
并且也解决了异步的问题;时间上来看从原来的2h->40min
idea+1:根据目前的动作空间和环境空间来看,动作只要求输出调度的用户,环境是[h,j];
后续改进的话可能会考虑action=[user_group, coding_method],因为现在做的仅仅是在环境中设置不同的编码方式,每次训练固定一种方式,来调度用户,最后比较这三种方式在不同信噪比的情况下的优劣;
改造行为空间和状态空间;state=[H,J,SNR]
但其实想要让基站更加智能,就直接基于接收到的CSI以及估算的SNR,做调度并且自动切换编码方式。这样不仅实现了选择合适的用户,又能够自动选择编码方式,从而实现一个更优秀的智能体;