├── .gitignore ├── LICENSE ├── README.md ├── client.py ├── dqn-cartpole.py ├── general ├── __init__.py ├── agents │ ├── a2c.py │ ├── ddpg.py │ └── dqn.py ├── core.py ├── memory.py ├── policy.py └── trainer.py ├── gui.py ├── proxy.py ├── setup.py └── urldemo.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Daniel Augusto Rizzi Salvadori 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 前言 2 | 3 |    一直以来我都在思考如何将强化学习低门槛的引入到工业领域,现在我终于找到了实现这一目标的途径,那就是自己写一个强化学习应用编程框架。在研究了国外一个优秀的强化学框架(huskarl)之后,我决定基于该框架来写一个对国内开发者更友好的强化学习应用编程框架。框架取名general(将军),因为将军都是经过身经百战,在一次次的生死场景中训练成长出来的,广义上是一个强化学习的过程。 4 | # general特性以及可视化演示 5 | ## general特性 6 |   general项目原计划至少实现四个方面的特性:支持可视化操作、集成主流强化学习算法、支持非gym环境交互、提供工业应用项目案例集,在1.0版本中实现了前三个特性的支持,工业应用项目案例集需要随着项目的推广和实践来积累。 7 | 8 | ### 支持可视化操作 9 |   对于编程能力稍弱或者对于强化学习初学者来说,直接上手敲代码来实现强化学习是有困难的。如果能够通过界面配置就可以实现一个强化学习应用并能够直观的感受到其训练的过程,那么可以极大的提高初学者的兴趣和信心。因此,我用wxpython进行了Gui的开发,虽然界面做的比较丑但是基本上实现了想要达到的目的。在可视化模块,还有一些欠缺优化的部分,会在后面的迭代中持续进行更新,也欢迎wxpython的高手加入一起维护。可视化配置界面如图1,完全可视化配置后就可以搭建一个强化学习应用。 10 | ![图1:general配置页面](https://images.gitbook.cn/cc7b1450-5ada-11ea-aae1-d9c8e5f0a61f) 11 | ### 集成主流强化学习算法 12 |   按照项目设计,在general中会集成主流的强化学习算法包括DQN、DDPG、PPO等,同时会关注强化学习的研究动态,及时将最新的强化学习算法进行集成。集成主流强化学习算法的目的降低强化学习算法应用的门槛,因此会对实现的过程和原理进行详细中文代码注释,以便让更多的人理解和掌握强化学习算法原理和实现过程。目前项目实现了DQN强化学习算法,具体的介绍会放在第三章节中。 13 | 14 | - [x] DQN:代码实现和详细注释,原理图对照讲解。 15 | - [ ] DDPG:待实现 16 | - [ ] PPO:待实现 17 | - [ ] ... 18 | 19 | ### 支持非gym环境交互 20 |   当前的强化学习在训练过程中主流的是使用gym模拟实际的真实的环境进行交互,但是在工业生产有太多的场景是没有办法或者需要付出很大的成本才能进行抽象和模拟的。因此,在本框架中加了与非gym环境交互的模块,能够通过http或者grpc与真实环境交互。当然这里说的与真实环境交互是受控的交互,就是通过数据控制和真实环境的措施来避免训练过程造成实际的损失和危害,阿里每年双十一的全链路线上压测就是这样的思路,通过数据流量控制保证测试数据不会影响正常的交易数据。如果你想要尝试通过http的方式来与非gym环境交互,那么可以通过可视化界面配置项目或者直接调用client接口就可以。非gym环境交互配置如图2: 21 | ![非gym环境交互项目配置](https://images.gitbook.cn/bba08570-5af8-11ea-a8f4-7b4742511f3f) 22 | ### 工业应用项目集 23 |   当前强化学习最缺的不是前沿的理论研究,而是在工业领域应用的实践。现在很多很先进的算法的应用往往都是在与游戏环境的交互,哪怕是将DQN应用到工业环境中都是非常有价值的。因此我在这里也向各位读者征集在工作中遇到的难题,你有应用场景和数据,我有算法框架和编程技术,那么我们就可以一起搞点事情出来。如果能够搞成了,算法和代码你免费拿走,案例留下就可以。 24 | 25 | - [ ] 强化学习在燃气轮机自动调节优化中的应用:待应用实践 26 | - [ ] 强化学习在量化交易中的应用:尽管实用价值不大,但是后面会实现一下,因为环境和数据都比较充足。 27 | - [ ] ... 28 | 29 | ## general可视化配置演示 30 | 31 | ### gym交互环境配置演示 32 |   在本框架中集成了gym,因此可以直接通过可视化界面完成项目的配置。 33 | #### gym环境项目配置 34 | ![gym环境项目配置](https://images.gitbook.cn/12009330-5b6a-11ea-9cd5-8967f4762932) 35 | #### gym交互环境训练过程 36 | ![gym交互环境训练](https://images.gitbook.cn/81555100-5b68-11ea-9cd5-8967f4762932) 37 | 38 | ### 非gym交互环境配置演示 39 |   在非gym交互环境下,我们采用http的方式与环境进行交互,因此需要先配置神经网络模型以及训练的超参数,然后配置环境交付url和环境的初始化状态。在本示例中,我使用gym的游戏后台写了一个模拟服务(urldemo.py),因此在使用前需要先启动该模拟服务。 40 | #### 配置神经网络和训练的超参数 41 | ![配置神经网络和训练的超参](https://images.gitbook.cn/b57bd760-5b68-11ea-93b5-3993d9eab61e) 42 | #### 启动模拟服务 43 | ![启动模拟服务urldemo.py](https://images.gitbook.cn/8a6f2e50-5b68-11ea-bd6c-43eeac1b3938) 44 | #### 将服务地址配置到项目中 45 | ![将服务地址配置到项目中](https://images.gitbook.cn/951d1920-5b68-11ea-9cd5-8967f4762932) 46 | #### 非gyn交互环境的训练过程 47 | ![非gym交互环境的训练过程](https://images.gitbook.cn/ab207960-5b68-11ea-bd6c-43eeac1b3938) 48 | # general项目结构和模块功能介绍 49 | 50 | ## general项目结构 51 | 52 | ### 项目工程结构 53 |   general项目工程架构采用我一直推荐的“文件与文件夹”结构,就是基础稳定的文件放入文件中,动态的需要调试的放在根目录下。在general文件中包含了项目的核心模块,这些模块包括core、memory、policy、trainer以及一个算法包。在项目的根目录下放了代理层的client(代理终端)、proxy(服务代理)、urldemo(模拟服务)以及展现层的gui(可视化展现)、dqn-catpole(命令行展现)。 54 | ![项目工程结构](https://images.gitbook.cn/0ac01ee0-5b6c-11ea-bd6c-43eeac1b3938) 55 | 56 | ### 项目功能结构 57 |   general从功能逻辑上分为四层,分别是核心层、算法层、代理层、展现层。核心层主要是实现框架中的ABC(抽象基类)的定义以及核心模块的实现,算法层主要是DQN、DDPG等算法智能体的实现、代理层主要是对外部非gym环境的交互代理,展现层包括基于wxpython的界面化交互和命令行的交互示例。 58 | 59 | ![general功能架构](https://images.gitbook.cn/c5e94460-5b7d-11ea-a695-8f4c079b036d) 60 | ## general功能介绍 61 | ### 核心层 62 | * **memory:** memory是用来存储在强化学习训练过程的记录,以便能够通过记忆回放的方式训练Q网络,可以类比成人类的大脑记忆存储区域,里面包含了训练记录的存储、读取方法以及记忆回放策略(包括纯随机回放、有限随机回放、优先级回放)。 63 | * **core:** core是一个定义智能体的抽象基类文件,在文件中定义了一个智能体在实现过程中所必须具备的属性,比如模型文件保存、训练记录存储、做出行为指令预测等,这些抽象基类方法如果在实例化一个智能体过程中没有被实现则会宝未实现错误。 64 | * **policy:** policy是动作指令评估选择策略,对于深度神经网络模型会根据输入的环境状态预测出多个可执行动作,但是我们需要按照一定的选择策略算法对这些动作进行评价选择,最终输出动作指令。当前版本中实现的选择算法策略算法包括贪婪算法、随机贪婪算法、高斯随机贪婪算法。之所以会有不同算法是为了改进纯贪婪算法带来的局部最优解的困扰,通过随机贪婪算法、高斯随机贪婪算法能够在一定程度能够缓解或者解决贪婪算法带来的问题。 65 | * **trainer:** trainer可以理解为训练模拟器,是一个有单进程或者多进程构成的“沙箱”。智能体与外界环境的交互、智能体的训练、智能体的记录存储等等这些过程都是在这个沙箱中。trainer可以构建包括单进程单实例、单进程多实例、多进程多实例的训练沙箱,单进程单实例和单进程多实例实现和训练过程相对比较简单,但是多进程多实例涉及到进程间通信、rewards同步、sates同步等过程非常的复杂。trainer的进程间通信和多实例训练同步的实现没有使用tensorflow的分布式多进程训练机制,而是采用消息队列基于python控制流来实现的。在后面的版本中会进行重构,采用tensorflow的架构和机制。 66 | 67 | ### 算法层 68 | * **DQN:** 当前在1.0版本中只实现了DQN算法智能体,不过根据core对算法智能的定义每个算法智能体中包含的方法都是一样的。在DQN智能体中包含loss函数定义、神经网络模型编译、train训练方法、save模型文件存储、push记录存储、act执行指令预测和选择。 69 | ### 代理层 70 | 71 | * **client:** client其实是可视化模块的后端服务,其主要作用是根据在gui可视化界面的超参配置构建一个训练实例并进行训练,将训练过程数据返回给可视化界面进行动态展示。在client中包括模型的定义、模型的训练、模型的保存、模型的测试等模块,实现从训练到测试的全过程。 72 | * **proxy:** proxy是一个服务代理模块,因为我们在对接非gym环境时需要通过http或者grpc的方式来进行,因此需要在服务代理模块中定义一些数据传输、数据处理的方法。目前在该模块中只实现了http的服务代理,后续版本中会增加grpc的代理。 73 | * **urldemo:** urldemo一个非gym环境模拟模块,也可以作为与非gym环境进行交互的客户端。在当前模块中,我们使用gym的cartpole游戏模拟作为外部环境的模拟,urldemo中实现了在环境本地端与环境的数据交互和调用,这也模拟在正常情况的场景。urldemo模块中包含对htrp传输数据的获取和序列化处理,对环境接口的交互和调用、对环境返回数据的json化处理以及返回。 74 | 75 | ### 展现层 76 | * **gui:** gui完全是基于wxpython编写的一个可视化模块,包含两个页面分别是gym环境配置页面和非gym环境配置页面,以实现对gym环境训练实例的配置和非gym环境训练实例的配置。在当前的模块中,受限于对wxpython的使用还不够娴熟,因此功能实现还不够丰富。在后续的版本中会对可视化页面进行改版以更加的符合工业应用使用体验和功能要求。比如后面会增加功能导航、训练模型实例的管理等功能,在页面的布局也会进行优化。 77 | * **dqn-cartpole:** dqn-cartpole是一个示例模块,展示如何使用general框架的api来完成自己所需的强化学习应用的编程。 78 | 79 | # 使用说明 80 | 81 | ## 安装依赖 82 | 83 | ``` 84 | python setup.py install 85 | ``` 86 | ## 运行可视化配置 87 | 88 | ``` 89 | pythonw gui.py 90 | ``` 91 | 92 | ## 使用dqn-cartpole示例 93 | 94 | ``` 95 | pythonw dqn-cartpole 96 | ``` 97 | 98 | # 使用框架实现一个dqn-cartpole应用教程 99 | 100 | ## 需要实现的功能模块 101 |   在本示例中,使用DQN网络和gym的托扁担游戏来作为示例,同时我们还会将gym的托扁担游戏来作为真实环境来演示如何通过http的方式与真实环境交互。我们需要实现以下的功能模块以搭建一个从训练和测试基本完整的强化学习应用: 102 | 103 | - 检验模型文件夹model_dir是否存在。 104 | - 托扁担(CartPole)游戏环境搭建。 105 | - 深度神经网络模型构建。 106 | - rewards反馈图形化展示。 107 | - 训练函数,如果是多次训练,则每次的新的训练是在之前训练成果的基础进行训练。 108 | - 模型测试函数,测试检验模型的训练效果。 109 | 110 | ## 代码实现详细过程 111 | ### 第一步需要导入各种依赖 112 | ``` 113 | import tensorflow as tf 114 | import matplotlib 115 | matplotlib.use("macOSX")#在使用macOSX系统时需要该行 116 | import matplotlib.pyplot as plt 117 | import gym 118 | import general as gr 119 | import os 120 | ``` 121 | ### 第二步初始化gym环境 122 | ``` 123 | #初始化gym环境,使用CartPole-v0环境,就是托扁担游戏 124 | create_env = lambda: gym.make('CartPole-v0').unwrapped 125 | dummy_env = create_env() 126 | ``` 127 | ### 第三步检验模型文件夹是否存在,如果不存在自动创建 128 | 129 | ``` 130 | if not os.path.exists("model_dir"): 131 | os.makedirs("model_dir") 132 | ``` 133 | ### 第四步构建网络模型 134 |   我们使用TensorFlow2.0中的高阶API Sequential来构建神经网络模型,Sequential既以数组的方式来搭建网络模型也可以使用add方法搭建网络模型。下面代码示例是以数组的方式来搭建神经网络模型。 135 | ``` 136 | def create_model(): 137 | # 我们搭建的神经网络模型一共三层,每层16个神经元,使用relu作为激活函数。 138 | model = tf.keras.Sequential([ 139 | tf.keras.layers.Dense(16, activation='relu', input_shape=dummy_env.observation_space.shape), 140 | tf.keras.layers.Dense(16, activation='relu'), 141 | tf.keras.layers.Dense(16, activation='relu'), 142 | ]) 143 | return model 144 | ``` 145 | ### 第五步我们使用matplotlib来实现对反馈reward的图形化展示 146 | 147 | ``` 148 | #定义反馈画图函数,这是为了能够图形化展示训练过程中rewards的变化走势,rewards是用来反馈对智能体的行为的评价。 149 | def plot_rewards(episode_rewards, episode_steps, done=False): 150 | #初始化一块画布 151 | plt.clf() 152 | #设置XY坐标轴名称 153 | plt.xlabel('Step') 154 | plt.ylabel('Reward') 155 | #将反馈数据和训练步数全部画在画布中 156 | for ed, steps in zip(episode_rewards, episode_steps): 157 | plt.plot(steps, ed) 158 | plt.show() if done else plt.pause(0.001) 159 | ``` 160 | ### 第六步我们定义一个训练函数来循环的训练模型 161 | 162 | ``` 163 | def train(): 164 | #初始化神经网络模型 165 | model=create_model() 166 | #将定义好的网络作为参数传入general框架的API中,构成一个完成DQN 智能体,用于接下来的强化学习训练。 167 | agent = gr.DQN(model, actions=dummy_env.action_space.n, nsteps=2) 168 | cpkt=tf.io.gfile.listdir("model_dir") 169 | if cpkt: 170 | agent.model.load_weights("model_dir/dqn.h5") 171 | #将智能体和gym环境放入训练器中开始训练深度神经网络模型 172 | tra = gr.Trainer(dummy_env, agent) 173 | tra.train(max_steps=3000, visualize=True, plot=plot_rewards) 174 | agent.save(filename='model_dir/dqn.h5',overwrite=True,save_format='h5') 175 | ``` 176 | 177 | ### 第七步我们定义一个测试函数来检验模型训练的效果 178 | 179 | ``` 180 | def test(): 181 | #初始化神经网络模型 182 | model=create_model() 183 | #将定义好的网络作为参数传入general框架的API中,构建一个含有DQN神经网络的智能体。 184 | agent = gr.DQN(model, actions=dummy_env.action_space.n, nsteps=2) 185 | #将之前训练的模型参数导入的新初始化的神经网络中 186 | agent.model.load_weights("model_dir/dqn.h5") 187 | #将智能体和gym环境放入训练器中开始测试模型的效果 188 | tra = gr.Trainer(create_env, agent) 189 | tra.test(max_steps=1000) 190 | ``` 191 | ### 第八步我们定义一个主函数并设置一些交互选项,可以实现训练模式与测试模式的切换。 192 | 193 | ``` 194 | if __name__ == '__main__': 195 | print("请准确输入train或者test") 196 | #获取键盘输入 197 | mode=input() 198 | #如果是训练模式,则调用训练函数 199 | if mode=="train": 200 | train() 201 | #如果是测试模式,则调用测试函数 202 | elif mode=="test": 203 | test() 204 | else: 205 | print("请重新执行程序并准确输入train或者test") 206 | ``` 207 | 208 | 209 | ## 最终效果展示 210 |   本示例的代码也会包含在general项目中,可以直接下载下来进行调试。 211 | 212 | 213 | ### 示例的启动如下图 214 | 215 | 216 | ![启动示例](https://images.gitbook.cn/462df5d0-5b06-11ea-a695-8f4c079b036d) 217 | 218 | 219 | ### 模型训练过程如下图,随着训练的进行智能体越来越能够保持木杆的垂直状态。 220 | 221 | 222 | 223 | ![模型训练过程](https://images.gitbook.cn/666b9ab0-5b0a-11ea-b7a0-3967fecd90d4) 224 | 225 | ### 模型的训练效果如下图,可以看到智能体可以很好的将木杆保持垂直状态。 226 | 227 | 228 | 229 | ![测试模型](https://images.gitbook.cn/b1809a10-5b09-11ea-9cd5-8967f4762932) 230 | 231 | 232 | # 结语 233 |   当前的版本还比较的基础,很多设计的功能和特性还没有来得及实现,如果你发现了一个bug或者希望加入到贡献者中一起维护该项目,我是非常欢迎的。同时,如果你在工作中有需要使用强化学习来解决问题的场景,还是那句话:你有场景需求和数据,我有框架和技术,我们为什么不一起搞点事情。最后,如果你对于强化学习感兴趣,那么请关注该项目[github地址](https://github.com/zhaoyingjun/general),并顺手给个star哈。 234 | 235 | # 版本迭代路线图 236 | ## 1.1版本 237 | - [ ] DDPG、PPO算法实现 238 | 239 | - [ ] 可视化界面布局优化 240 | 241 | - [ ] grpc代理模块实现 242 | 243 | - [ ] 量化投资应用案例 244 | 245 | ## 1.2版本 246 | 247 | - [ ] pytorch版本发布 248 | 249 | - [ ] 底层性能优化 250 | 251 | 252 | 253 | 254 | 255 | ## 使用许可 256 | 257 | [MIT](LICENSE) @zhaoyingjun 258 | -------------------------------------------------------------------------------- /client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020-02-01 20:40 3 | # @Author : Enjoy Zhao 4 | 5 | import tensorflow as tf 6 | import general as gr 7 | import os 8 | import configparser 9 | if not os.path.exists("model_dir"): 10 | os.makedirs("model_dir") 11 | 12 | class client(object): 13 | 14 | def __init__(self,dense_num=None,cell_num=None,activation=None,action_space=0,train_steps=3000,run_steps=1000,dummy_env=None,model_name=None,algorithm_type=None): 15 | 16 | self.dense_num=dense_num 17 | self.cell_num=cell_num 18 | self.activation=activation 19 | self.train_steps=train_steps 20 | self.run_steps=run_steps 21 | self.dummy_env=dummy_env 22 | self.model_name=model_name 23 | self.file_path="model_dir/"+model_name 24 | self.config_path="model_dir/"+model_name+".cfg" 25 | self.algorithm_type=algorithm_type 26 | self.run_steps=run_steps 27 | self.action_space=action_space 28 | self.ep_reward=0 29 | self.ep_step=0 30 | self.done=False 31 | 32 | 33 | def create_model(self): 34 | model = tf.keras.Sequential() 35 | model.add(tf.keras.layers.Dense(16, activation='relu', input_shape=self.dummy_env.observation_space.shape)) 36 | for i in range(self.dense_num): 37 | model.add(tf.keras.layers.Dense(self.cell_num,activation=self.activation )) 38 | return model 39 | 40 | 41 | def plot_rewards(self,episode_rewards, episode_steps, done=False): 42 | 43 | self.ep_reward=episode_rewards[0][-1] 44 | 45 | self.ep_step=episode_steps[0][-1] 46 | self.done=done 47 | 48 | def create_agent(self): 49 | model = self.create_model() 50 | if self.action_space ==0: 51 | self.action_space=self.dummy_env.action_space.n 52 | if self.algorithm_type=='dqn': 53 | agent = gr.DQN(model, actions=self.action_space, nsteps=2) 54 | elif self.algorithm_type=='ddpg': 55 | agent = gr.DDPG(model, actions=self.action_space, nsteps=2) 56 | 57 | elif self.algorithm_type=='ppo': 58 | agent = gr.PPO(model, actions=self.action_space, nsteps=2) 59 | 60 | return agent 61 | 62 | 63 | def train(self): 64 | 65 | # 将定义好的网络作为参数传入框架的API中,构成一个完成智能体,用于接下来的强化学习训练。 66 | agent =self.create_agent() 67 | cpkt = tf.io.gfile.listdir("model_dir") 68 | if cpkt==self.file_path: 69 | agent.model.load_weights(self.file_path) 70 | 71 | 72 | # 使用general框架的trainer来创建一个训练模拟器,在模拟器中进行训练。 73 | sim = gr.Trainer(self.dummy_env, agent) 74 | sim.train(max_steps=self.train_steps, visualize=True, plot=self.plot_rewards) 75 | agent.save(filename=self.file_path,overwrite=True,save_format='h5') 76 | cf = configparser.ConfigParser(allow_no_value=True) 77 | cf.add_section('ints') 78 | cf.add_section('strings') 79 | cf.set('ints', 'dense_num', "%s"%self.dense_num) 80 | cf.set('ints', 'cell_num', "%s"%self.cell_num) 81 | cf.set('ints', 'action_space', "%s" % self.action_space) 82 | cf.set('strings', 'activation',"%s"%self.activation) 83 | cf.set('strings', 'algorithm_type', "%s"%self.algorithm_type) 84 | with open(self.config_path, 'w') as f: 85 | cf.write(f) 86 | 87 | def run_model(self): 88 | agent = self.create_agent() 89 | agent.model.load_weights(filepath=self.file_path) 90 | sim = gr.Trainer(self.dummy_env, agent) 91 | sim.test(max_steps=self.run_steps,plot=self.plot_rewards) 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /dqn-cartpole.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020-03-01 00:06 3 | # @Author : Enjoy Zhao 4 | 5 | import tensorflow as tf 6 | import matplotlib 7 | matplotlib.use("macOSX")#在使用macOSX系统时需要该行 8 | import matplotlib.pyplot as plt 9 | import gym 10 | import general as gr 11 | import os 12 | #初始化gym环境,使用CartPole-v0环境,就是托扁担游戏 13 | create_env = lambda: gym.make('CartPole-v0').unwrapped 14 | dummy_env = create_env() 15 | if not os.path.exists("model_dir"): 16 | os.makedirs("model_dir") 17 | def create_model(): 18 | # 我们使用tensorflow2.0中的高阶API keras定义一个全连接神经网络,用于学习预测。一共三层,每层16个神经元,激活函数使用relu。 19 | model = tf.keras.Sequential([ 20 | tf.keras.layers.Dense(16, activation='relu', input_shape=dummy_env.observation_space.shape), 21 | tf.keras.layers.Dense(16, activation='relu'), 22 | tf.keras.layers.Dense(16, activation='relu'), 23 | ]) 24 | return model 25 | #定义反馈画图函数,这是为了能够图形化展示训练过程中rewards的变化走势,rewards是用来反馈对智能体的行为的评价。 26 | def plot_rewards(episode_rewards, episode_steps, done=False): 27 | plt.clf() 28 | plt.xlabel('Step') 29 | plt.ylabel('Reward') 30 | for ed, steps in zip(episode_rewards, episode_steps): 31 | plt.plot(steps, ed) 32 | plt.show() if done else plt.pause(0.001) # Pause a bit so that the graph is updated 33 | def train(): 34 | #初始化神经网络模型 35 | model=create_model() 36 | #将定义好的网络作为参数传入general框架的API中,构成一个完成DQN 智能体,用于接下来的强化学习训练。 37 | agent = gr.DQN(model, actions=dummy_env.action_space.n, nsteps=2) 38 | cpkt=tf.io.gfile.listdir("model_dir") 39 | if cpkt: 40 | agent.model.load_weights("model_dir/dqn") 41 | #将智能体和gym环境放入训练器中开始训练深度神经网络模型 42 | tra = gr.Trainer(dummy_env, agent) 43 | tra.train(max_steps=300, visualize=True, plot=plot_rewards) 44 | agent.save(filename='model_dir/dqn',overwrite=True,save_format='h5') 45 | def test(): 46 | #初始化神经网络模型 47 | model=create_model() 48 | #将定义好的网络作为参数传入general框架的API中,构建一个含有DQN神经网络的智能体。 49 | agent = gr.DQN(model, actions=dummy_env.action_space.n, nsteps=2) 50 | #将之前训练的模型参数导入的新初始化的神经网络中 51 | agent.model.load_weights("model_dir/dqn") 52 | #将智能体和gym环境放入训练器中开始测试模型的效果 53 | tra = gr.Trainer(create_env, agent) 54 | tra.test(max_steps=300) 55 | if __name__ == '__main__': 56 | print("请准确输入train或者test") 57 | mode=input() 58 | 59 | if mode=="train": 60 | train() 61 | elif mode=="test": 62 | test() 63 | else: 64 | print("请重新执行程序并准确输入train或者test") 65 | 66 | -------------------------------------------------------------------------------- /general/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020-02-01 21:51 3 | # @Author : Enjoy Zhao 4 | 5 | from general.core import GrEexception 6 | from general.trainer import Trainer 7 | from general.agents.dqn import DQN 8 | -------------------------------------------------------------------------------- /general/agents/a2c.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020-01-31 22:19 3 | # @Author : Enjoy Zhao -------------------------------------------------------------------------------- /general/agents/ddpg.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020-01-31 22:19 3 | # @Author : Enjoy Zhao -------------------------------------------------------------------------------- /general/agents/dqn.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020-01-31 22:19 3 | # @Author : Enjoy Zhao 4 | # @Describe :在本文件是基于DQN算法的智能体agent,具备模型的训练、保存、预测等功能。 5 | 6 | import tensorflow as tf 7 | import numpy as np 8 | 9 | from general.core import Agent,GrEexception 10 | from general.policy import EpsGreedy,Greedy 11 | from general import memory 12 | 13 | class DQN(Agent): 14 | 15 | """ 16 | 实现一个基于dqn网络的智能体 17 | """ 18 | def __init__(self,model, actions, optimizer=None, policy=None, test_policy=None, 19 | memsize=10_000, target_update=10, gamma=0.99, batch_size=64, nsteps=1, 20 | enable_double_dqn=True, enable_dueling_network=False, dueling_type='avg'): 21 | 22 | """ 23 | 24 | :param model:是我们定义的智能体的神经网络 25 | :param actions:是需要执行的动作的空间,其实就是智能体输出的动作集中需要包含动作的数量 26 | :param optimizer:优化器 27 | :param policy:是配置训练时action的选择策略 28 | :param test_policy:配置测试时action的选择策略 29 | :param memsize:记忆存储器的大小 30 | :param target_update:配置eval网络训练多少次之后更新一次target网络 31 | :param gamma:是一个系数 32 | :param batch_size:批量训练每批次的大小 33 | :param nsteps:可以理解为向前看的深度,比如nsteps=2,表示模型取的前两个action步骤的reward 34 | :param enable_double_dqn:配置是否使用double_dqn 35 | :param enable_dueling_network:配置是否使用dueling网络 36 | :param dueling_type:配置dueling网络的策略 37 | """ 38 | #以下是进行参数的初始化,首先是对动作种类的数量进行初始化 39 | self.actions=actions 40 | #对神经网络的优化器进行初始化,默认使用adam 41 | self.optimizer = tf.keras.optimizers.Adam(lr=3e-3) if optimizer is None else optimizer 42 | #初始化训练状态下的action选择策略,默认是随机贪婪算法。 43 | self.policy = EpsGreedy(0.1) if policy is None else policy 44 | #初始化测试状态下的action选择策略,默认是原始贪婪算法。 45 | self.test_policy = Greedy() if test_policy is None else test_policy 46 | #初始化记忆器的存储空间大小 47 | self.memsize = memsize 48 | #初始化记忆回放策略,采用优先级经验回放 49 | self.memory = memory.PrioritizedExperienceReplay(memsize, nsteps) 50 | 51 | self.target_update = target_update 52 | self.gamma = gamma 53 | self.batch_size = batch_size 54 | self.nsteps = nsteps 55 | self.training = True 56 | 57 | self.enable_double_dqn = enable_double_dqn 58 | self.enable_dueling_network = enable_dueling_network 59 | self.dueling_type = dueling_type 60 | #初始化输出向量 61 | raw_output=model.layers[-1].output 62 | #如果配置使用dueling网络,则在输出层上一分为二,分为两个中间输出层网络,分别是价值网络和优势网络,然后将两个中间输出层的结果线性组合输出。 63 | #所谓价值网络就是输出结果与状态S有关,与执行动作action无关,而优势网络与状态和执行动作都有关系,其实就是朴素dqn的输出。 64 | if self.enable_dueling_network: 65 | #使用Dnese构建中间输出层,与dqn相比其输出维度加1,其中输出的结果a[:,0]是价值网络的输出,a[:,1:]是优势网络的输出。 66 | dueling_layer=tf.keras.layers.Dense(self.actions+1,activation='liner')(raw_output) 67 | #如果dueling策略选择均值策略,则在进行两个网络输出线性组合前,对优势网络按照均值策略进行优化处理。 68 | if self.dueling_type == 'avg': 69 | f = lambda a: tf.expand_dims(a[:, 0], -1) + (a[:, 1:] - tf.reduce_mean(a[:, 1:],axis=1,keepdims=True)) 70 | elif self.dueling_type == 'max': 71 | # 如果dueling策略选择最大策略,则在进行两个网络输出线性组合前,对优势网络按照最大值策略进行优化处理。 72 | f = lambda a: tf.expand_dims(a[:, 0], -1) + (a[:, 1:] - tf.reduce_max(a[:, 1:],axis=1,keepdims=True)) 73 | elif self.dueling_type == 'naive': 74 | # 如果dueling策略选择原生策略,则在进行两个网络输出线性组合前,不对优势网络进行任何处理。 75 | f = lambda a: tf.expand_dims(a[:, 0], -1) + a[:, 1:] 76 | else: 77 | raise GrEexception("dueling_type must be one of {'avg','max','naive'}") 78 | 79 | output_layer=tf.keras.layers.Lambda(f,output_shape=(self.actions,))(dueling_layer) 80 | 81 | 82 | else: 83 | #如果不是dueling网络,则按照dqn的标准网络输出。 84 | output_layer=tf.keras.layers.Dense(self.actions,activation='linear')(raw_output) 85 | #使用tf.keras.Model构建神经网络模型 86 | self.model=tf.keras.Model(inputs=model.input,outputs=output_layer) 87 | 88 | #定义一个Loss函数,用于计算模型预测输出结果的loss 89 | def masked_q_loss(data,y_pred): 90 | 91 | """ 92 | :param data:其中包含action和其对应reward数据 93 | :param y_pred:预测的action数据 94 | 95 | """ 96 | #从data中取出action和reward 97 | action_batch,target_qvals=data[:,0],data[:,1] 98 | 99 | #接下来需要构建一个特殊数组action_idxs,能够根据action_idxs gather出在y_pred中与action_idx对应的q值。 100 | #首先根据action_batch的长度构建一个递增数组,比如[0,1,2,3] 101 | seq = tf.cast(tf.range(0, tf.shape(action_batch)[0]), tf.int32) 102 | #然后通过将seq与action_batch(批量取出的动作)进行堆叠组合多维数组,对多维数组进行转置,这样处理是为了能够与y_pred进行gather_nd运算 103 | action_idxs=tf.transpose(tf.stack([seq,tf.cast(action_batch,tf.int32)])) 104 | #最后通过gather_nd运算得到我们需要的qvals,也就是预测的动作的q值。 105 | qvals=tf.gather_nd(y_pred,action_idxs) 106 | #这里是判断一下是否使用PrioritizedExperienceReplay策略进行记忆回放,如果是则对记忆回放按照如下处理 107 | if isinstance(self.memory, memory.PrioritizedExperienceReplay): 108 | #定义一个优先级更新函数,更新记录的优先级 109 | def update_priorities(_qvals,_target_qvals,_traces_idxs): 110 | 111 | """ 112 | 这里需要更新优先级的计算是计算target_qvals和预测得到qvals的差的绝对值,差值越大优先级越高,这也是引导在记忆回放时能够 113 | 覆盖到陌生领域。以下是算法的实现过程。 114 | 115 | """ 116 | #计算得到优先级的值 117 | td_error=tf.abs((_target_qvals-_qvals).numpy()) 118 | #得到记录对应的idx 119 | _traces_idxs=(tf.cast(_traces_idxs,tf.int32)).numpy() 120 | #调用memory中PrioritizedExperienceReplay的优先度更新方法进行更新 121 | self.memory.update_priorities(_traces_idxs,td_error) 122 | 123 | return _qvals 124 | #使用tf.py_function调用update_priorities更新所需要更新的优先度 125 | qvals=tf.py_function(func=update_priorities,inp=[qvals,target_qvals,data[:,2]],Tout=tf.float32) 126 | #使用mes作为loss计算损失值 127 | return tf.keras.losses.mse(qvals,target_qvals) 128 | #最后编译网络模型。 129 | self.model.compile(optimizer=self.optimizer,loss=masked_q_loss) 130 | 131 | # q_eval 网络和target网络是完全一样的网络结构,因此我们直接复制q_eval即可 132 | #复制网络模型 133 | self.target_model=tf.keras.models.clone_model(self.model) 134 | #配置网络参数 135 | self.target_model.set_weights(self.model.get_weights()) 136 | 137 | def save(self,filename,overwrite=False,save_format='h5'): 138 | 139 | """ 140 | save方法是用于保存网络模型到特定文件 141 | 142 | """ 143 | self.model.save_weights(filename,overwrite=overwrite,save_format=save_format) 144 | 145 | 146 | def act(self,state,instance=0): 147 | 148 | """ 149 | act 方法是根据网络模型和输入状态数据,预测最有的执行动作 150 | 151 | """ 152 | qvals=self.model.predict(np.array([state]))[0] 153 | #使用动作选择算法对输出的结果选出最终的动作 154 | return self.policy.act(qvals) if self.training else self.test_policy.act(qvals) 155 | 156 | 157 | def push(self,transition,instance=0): 158 | 159 | """ 160 | push 方法是将记录存入到记忆存储器中。 161 | """ 162 | self.memory.put(transition) 163 | 164 | def train(self,step): 165 | 166 | """ 167 | train方法是实现了对智能体神经网络的训练过程 168 | 169 | """ 170 | #首先判断记忆存储器中的记录是否存在,如果存在则继续进行训练。 171 | if len(self.memory)==0: 172 | return 173 | #判断target网络是否需要更新,如果达到更新条件则进行更新。 174 | if self.target_update>=1 and step % self.target_update==0: 175 | 176 | self.target_model.set_weights(self.model.get_weights()) 177 | #如果target_update小于1,则直接将target_update作为系数,组合target_model和model的参数进行更新。 178 | elif self.target_update<1: 179 | 180 | mw=np.array(self.model.get_weights()) 181 | 182 | tmw=np.array(self.target_model.get_weights()) 183 | 184 | self.target_model.set_weights(self.target_update*mw+(1-self.target_update)*tmw) 185 | 186 | #将batch_size取最小范围,以保证在记忆存储器的记录数量不小于batch_size 187 | batch_size=min(len(self.memory),self.batch_size) 188 | #从记忆存储器中批量的取数据,大小是batch_size 189 | state_batch,action_batch,reward_batches,end_state_batch,not_done_mask=self.memory.get(batch_size) 190 | #使用全零数组初始化target_qvals 191 | target_qvals=np.zeros(batch_size) 192 | #取出非终止状态的下一个状态 193 | non_final_last_next_states=[es for es in end_state_batch if es is not None] 194 | 195 | if len(non_final_last_next_states)>0: 196 | #如果设置的是double_dqn,则进行如下的过程计算selected_target_q_vals 197 | if self.enable_double_dqn: 198 | #首先使用q_eval网络将non_final_last_next_states中的状态作为输入,进而预测q_values 199 | q_values=self.model.predict_on_batch(np.array(non_final_last_next_states)) 200 | #使用argmax获得q_values最大值对应的actions。 201 | actions=tf.cast(tf.argmax(q_values,axis=1),tf.int32) 202 | #使用target网络将non_final_last_next_states中的状态作为输入,进而预测target_q_values 203 | target_q_values=self.target_model.predict_on_batch(np.array(non_final_last_next_states)) 204 | #接下来也是一个gather_nd的过程,现实组合一个特殊的数组,目的是找到在target_q_values中actions对应的值 205 | selected_target_q_vals=tf.gather_nd(target_q_values,tf.transpose(tf.stack([tf.range(len(target_q_values)),actions]))) 206 | 207 | else: 208 | #如果不是使用double_dqn,则直接使用target网络将non_final_last_next_states中的状态作为输入,进而预测target_q_values,并取最大值。 209 | 210 | selected_target_q_vals=self.target_model.predict_on_batch(np.array(non_final_last_next_states)).max(1) 211 | 212 | #将end_state_batch为非None的数值取出 213 | non_final_mask=list(map(lambda s:s is not None,end_state_batch)) 214 | #将selected_target_q_vals值更新到状态非None对应的target_qvals中 215 | target_qvals[non_final_mask]=selected_target_q_vals 216 | 217 | #下面是根据nsteps的配置,取将前n个状态的reward引入到target_qvals中 218 | for n in reversed(range(self.nsteps)): 219 | #取reward_batches中的前n个reward 220 | rewards=np.array([b[n] for b in reward_batches]) 221 | #将target_qvals与not_done_mask中前n个值组成的数组乘机得到target_qvals 222 | target_qvals*=np.array([t[n] for t in not_done_mask]) 223 | #将前面两步计算得到的rewards和target_qvals与一个系数的乘积相加得到最终的target_qvals 224 | target_qvals=rewards+(self.gamma*target_qvals) 225 | #将action_batch、target_qvals组合成lossdata,计算loss 226 | loss_data=[action_batch,target_qvals] 227 | 228 | #如果是使用优先级记忆回放策略loss_data还需要加上记忆存储器的最后记录的indexs 229 | if isinstance(self.memory, memory.PrioritizedExperienceReplay): 230 | 231 | loss_data.append(self.memory.last_traces_idxs()) 232 | #将数据灌入模型,进行训练。 233 | self.model.train_on_batch(np.array(state_batch),tf.transpose(tf.stack(loss_data))) 234 | 235 | 236 | 237 | 238 | 239 | 240 | -------------------------------------------------------------------------------- /general/core.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020-01-31 20:54 3 | # @Author : Enjoy Zhao 4 | """ 5 | Abstract base class for exception and agent and proxy 6 | 在本文件中是定义exception和client的抽象基类,注意抽象基类并不能直接使用,需要根据抽象基类的定义具体实现之后才能使用 7 | """ 8 | class GrEexception(Exception): 9 | """ 10 | Basic exception for errors raised by General. 11 | 抛出的异常方法基于本抽象基类来实现 12 | """ 13 | 14 | class Agent: 15 | """ 16 | Abstract base class for all implemented agents. 17 | agent是指在强化学习中的训练的智能体,在本抽象基类中包括agent所有需要具备的属性. 18 | """ 19 | def save(self,filename,overwrite=False): 20 | """ 21 | Saves the model parameters to the specified file. 22 | save属性将智能体中的预测模型参数保存到模型文件中 23 | """ 24 | raise NotImplementedError() 25 | 26 | def act(self,state,instance=0): 27 | """ 28 | Returns the action to be taken given a state. 29 | act属性是需要实现根据输入的环境状态输出执行动作指令 30 | """ 31 | raise NotImplementedError() 32 | 33 | def push(self,transition,instance=0): 34 | """ 35 | Stores the transition in memory. 36 | push 属性是将智能体与环境交互的经验存储到记忆器中 37 | """ 38 | raise NotImplementedError() 39 | 40 | def train(self,step): 41 | """ 42 | Trains the agent for one step. 43 | train属性是完成对智能体神经网络的训练。 44 | """ 45 | raise NotImplementedError() 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /general/memory.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020-01-31 21:48 3 | # @Author : Enjoy Zhao 4 | # @Describe :在本文件中主要存储器基类和不同的记忆回放算法的存储器,存储器的作用是存储在强化学习中的当前状态、执行动作、环境反馈、下一个状态,用于智能体预测网络的训练。 5 | # 目前已经实现OnPolicy、ExperienceReplay、PrioritizedExperienceReplay 6 | 7 | from collections import namedtuple,deque 8 | import random 9 | import numpy as np 10 | import abc 11 | 12 | """ 定义所存储的记录格式,以元组的方式存储当前状态、执行动作、环境反馈、下一个状态 """ 13 | Transition=namedtuple('Transition',['state','action','reward','next_state']) 14 | """实现一个Memory基类,并要求后续的继承该基类的方法类必须包括put、get、 __len__方法。""" 15 | class Memory: 16 | 17 | @abc.abstractmethod 18 | def put(self,*args,**kwargs): 19 | 20 | raise NotImplementedError() 21 | 22 | @abc.abstractmethod 23 | def get(self,*args,**kwargs): 24 | 25 | raise NotImplementedError() 26 | 27 | @abc.abstractmethod 28 | def __len__(self): 29 | 30 | raise NotImplementedError() 31 | """定义一个unpack方法,作用是把从存储器取出的记录给拆解,然后返回。""" 32 | def unpack(traces): 33 | #拆解出记录中的states 34 | states=[t[0].state for t in traces] 35 | # 拆解出记录中的actions 36 | actions=[t[0].action for t in traces] 37 | # 拆解出记录中的rewards 38 | rewards=[[e.reward for e in t] for t in traces] 39 | #拆解出记录中的end_states 40 | end_states=[t[-1].next_state for t in traces] 41 | #根据下一个状态是否存在来判断是否记录终止了,返回相应的标志位 42 | not_done_mask=[[1 if n.next_state is not None else 0 for n in t]for t in traces] 43 | #返回拆解出的值 44 | return states,actions,rewards,end_states,not_done_mask 45 | 46 | """实现一个原始的存储器方法类,实现记录的存储put、获取get、以及存储的记录数量,存储和读取都是全存全取的,没有优化算法。""" 47 | class OnPolicy(Memory): 48 | """ 49 | 初始化参数,包括steps和instances,steps是指存储记录的步数,instances是指实例的数量,也就是智能体的数量。 50 | """ 51 | def __init__(self,steps=1,instances=1): 52 | self.bufffers=[[] for _ in range(instances)] 53 | self.steps=steps 54 | self.instances=instances 55 | def put(self,transition,instance=0): 56 | """ 57 | 因为初始化instances=1,也就是说只有一个智能体,那么我们就将记录全部存储在bufffers[0]中,transition就是记录数据 58 | """ 59 | self.bufffers[instance].append(transition) 60 | 61 | def get(self): 62 | """ 63 | 将所有的记录从bufffer中取出,清空存储器,然后对记录进行拆解返回。 64 | """ 65 | traces=[list(tb) for tb in self.bufffers] 66 | 67 | self.bufffers=[[] for _ in range(self.instances)] 68 | 69 | return unpack(traces) 70 | 71 | def __len__(self): 72 | """ 73 | 统计存储器中的记录数量并返回 74 | """ 75 | 76 | return sum([len(b)-self.steps+1 for b in self.bufffers]) 77 | 78 | """实现一个带有经验回放的存储器方法类,同样实现记录的存储put、获取get、以及存储的记录数量的统计,不过在存储记录和获取记录时并非全存全取的,使用了算法以打破记录的连续性和关联性, 79 | 因为如果不这样做,算法在连续一段时间内基本朝着同一个方向做gradient descent,那么同样的步长下这样直接计算gradient就有可能不收敛,因此这样做可以有助于模型收敛。同时打破连续性和关联性还可以增加网络的泛化性。 80 | """ 81 | class ExperienceReplay(Memory): 82 | """ 83 | 初始化参数,capacity是存储器的存储容量,steps存储记录的步数,exclude_boundaries是标记是否存储边界值,traces是存储队列 84 | """ 85 | def __init__(self,capacity,steps=1,exclude_boundaries=False): 86 | self.traces=deque(maxlen=capacity) 87 | self.buffer=[] 88 | self.steps=steps 89 | self.exclude_boundaries=exclude_boundaries 90 | 91 | def put(self,transition): 92 | 93 | self.buffer.append(transition) 94 | #达到一定的记录步数之后,将存储buffer存入到存储队列trances中去 95 | if len(self.buffer)self.eps: 33 | return tf.argmax(qvals) 34 | return random.randrange(len(qvals)) 35 | #实现一个高斯随机贪婪算法,是在随机贪婪算法的基础上对随机开关的取值eps进行了改进,将固定值eps修改为根据高斯分布来取值,这样可以更好的增加随机性,以具备更好的全局视野。 36 | class GaussianEpsGreedy(Policy): 37 | 38 | def __init__(self,eps_mean,eps_std): 39 | 40 | self.eps_mean=eps_mean 41 | 42 | self.eps_std=eps_std 43 | 44 | def act(self,qvals): 45 | #构建一个高斯分布来取值,以增加全局随机性。 46 | eps=truncnorm.rvs((0-self.eps_mean)/self.eps_std, (1-self.eps_mean)/self.eps_std) 47 | if random.random() >eps: 48 | return tf.argmax(qvals) 49 | return random.randrange(len(qvals)) 50 | 51 | #实现一个透传策略,其实就是把输入原封不动的输出 52 | class PassThrough(Policy): 53 | 54 | def act(self,action): 55 | return action -------------------------------------------------------------------------------- /general/trainer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020-01-31 22:18 3 | # @Author : Enjoy Zhao 4 | # @Describe :本文件是定义了一个训练器方法类,在训练器中定义了训练函数,包括单实例和多实例训练,强化学习的训练是在训练模拟器中完成的。 5 | import multiprocessing as mp 6 | from collections import namedtuple 7 | import tensorflow as tf 8 | import cloudpickle 9 | from general.core import GrEexception 10 | from general.memory import Transition 11 | 12 | #定义全局变量RewardState 13 | RewardState=namedtuple('RewardState',['reward','state']) 14 | #定义Trainer方法类,实现在训练器的训练功能 15 | class Trainer: 16 | 17 | #定义初始化方法,将create_env、agent、mapping进行初始化 18 | def __init__(self,create_env,agent,mapping=None): 19 | 20 | self.create_env=create_env 21 | self.agent=agent 22 | self.mapping=mapping 23 | #定义train方法,根据配置对智能体进行训练 24 | def train(self,max_steps=1000,instances=1,visualize=False,plot=None,max_subprocesses=0): 25 | 26 | #将智能体设置为训练状态 27 | self.agent.training=True 28 | #如果是单进程训练,则调用_sp_train方法,如果是多进程训练则调用_mp_train方法 29 | if max_subprocesses==0: 30 | self._sp_train(max_steps,instances,visualize,plot) 31 | elif max_subprocesses is None or max_subprocesses>0: 32 | 33 | self._mp_train(max_steps,instances,visualize,plot,max_subprocesses) 34 | else: 35 | raise GrEexception(f"Invalid max_subprocesses setting: {max_subprocesses}") 36 | #定义单进程训练方法 37 | def _sp_train(self, max_steps, instances, visualize=False, plot=None): 38 | 39 | """ 40 | :param max_steps:最大训练步数 41 | :param instances:训练智能体的数量 42 | :param visualize:配置是否图形可视化,针对与gym适用 43 | :param plot:画图函数,对训练步数和rewards进行画图 44 | """ 45 | #根据设置的instances的数量也就是智能体的数量,分别初始化reward、step、envs、states,用于训练过程的图形化展示 46 | episode_reward_sequences=[[0] for _ in range(instances)] 47 | episode_step_sequences=[[0] for _ in range(instances)] 48 | episode_rewards=[0]*instances 49 | 50 | envs=[self.create_env for _ in range(instances)] 51 | states=[env.reset() for env in envs] 52 | 53 | #训练步数在最大步数范围内开始循环训练 54 | for step in range(max_steps): 55 | #根据智能体的数量和是否进行图形可视化,进行环境可视化,这里只适用于gym环境 56 | for i in range(instances): 57 | if visualize:envs[i].render() 58 | #将预测得到的action从Tensor转换为数值 59 | action = tf.keras.backend.eval(self.agent.act(states[i], i)) 60 | #将预测得到的action输入给环境,获得环境反馈的下一个状态、reward、和是否结束的标记 61 | next_state,reward,done, _=envs[i].step(action=action) 62 | print(reward) 63 | #将环境返回的数据、action作为一条记录保存到记忆存储器中 64 | self.agent.push(Transition(states[i],action,reward,None if done else next_state),i) 65 | #将reward进行累加 66 | episode_rewards[i]+=reward 67 | #如果环境给予结束的状态则将episode_rewards、训练步数给保存,episode_rewards清零后进行图形展示,如果不是结束状态则将状态更新为下一个状态。 68 | episode_reward_sequences[i].append(episode_rewards[i]) 69 | episode_step_sequences[i].append(step) 70 | if plot: 71 | plot(episode_reward_sequences, episode_step_sequences) 72 | if done: 73 | 74 | episode_rewards[i]=0 75 | 76 | states[i]=envs[i].reset() 77 | else: 78 | states[i]=next_state 79 | #训练智能体,完成一步的训练 80 | self.agent.train(step) 81 | # 最后图形化展示整个训练过程的reward 82 | 83 | if plot:plot(episode_reward_sequences,episode_step_sequences,done=True) 84 | #定义多进程训练方法,涉及到多进程调度、通信等过程,相对实现起来比较复杂。 85 | def _mp_train(self,max_steps,instances,visualize,plot,max_subprocesses): 86 | 87 | #如果最大子进程数量没有设置,则根据cpu的数量来作为最大子进程数量 88 | if max_subprocesses is None: 89 | 90 | max_subprocesses=mp.cpu_count() 91 | #智能体的数量和最大子进程的数量取最小值 92 | nprocesses=min(instances,max_subprocesses) 93 | 94 | #计算每一个进程中智能体的数量 95 | instances_per_process=[instances//nprocesses] * nprocesses 96 | #计算左侧溢出的智能体,也就是排队的智能体 97 | leftover=instances%nprocesses 98 | #如果有智能体排队,则均匀的分配排队智能体的数量 99 | if leftover>0: 100 | for i in range(leftover): 101 | instances_per_process[i]+=1 102 | 103 | #计算智能体的id,通过枚举的方式逐个获得智能体的id 104 | instance_ids=[list(range(i,instances,nprocesses))[:ipp] for i ,ipp in enumerate(instances_per_process)] 105 | #初始化一个pipes和processes数组,用于存储等待状态或者中间状态的数据 106 | pipes=[] 107 | processes=[] 108 | #逐个将智能体实例调度到不同的子进程中,等待训练。 109 | for i in range(nprocesses): 110 | child_pipes=[] 111 | #以下过程是将不同的智能体装入到子进程中的过程。 112 | for j in range(instances_per_process[i]): 113 | #获得父进程和子进程的pipe 114 | parent,child=mp.Pipe() 115 | #将进程和子进程的pipe保存到各自的数组中 116 | pipes.append(parent) 117 | child_pipes.append(child) 118 | #将多个智能体以及训练参数装入到子进程中,这里的训练函数使用的是专门为多进程训练编写的函数,具体过程见_train 119 | pargs=(cloudpickle.dumps(self.create_env),instance_ids[i],max_steps,child_pipes,visualize) 120 | processes.append(mp.Process(target=_train,args=pargs)) 121 | 122 | print(f"Starting {nprocesses} process(es) for {instances} environment instance(s)... {instance_ids}") 123 | #启动所有的进程开始训练 124 | for p in processes:p.start() 125 | # 根据设置的instances的数量也就是智能体的数量,分别初始化reward、step、envs、states,用于训练过程的图形化展示 126 | episode_reward_sequences = [[] for _ in range(instances)] 127 | episode_step_sequences = [[] for _ in range(instances)] 128 | episode_rewards = [0] * instances 129 | 130 | #初始化rss和last_actions 131 | rss=[None]*instances 132 | 133 | last_actions=[None]*instances 134 | 135 | 136 | #开始在最大训练步数范围进行循环训练 137 | for step in range(max_steps): 138 | #初始化完成训练的数量,全部初始化为未完成训练 139 | step_done=[False]*instances 140 | #如果没有全部完成训练,则需要进行等待,直到全部完成训练。 141 | while sum(step_done)