diff --git a/codes/DDPG/agent.py b/codes/DDPG/agent.py index f2860b7..b080c15 100644 --- a/codes/DDPG/agent.py +++ b/codes/DDPG/agent.py @@ -5,7 +5,7 @@ @Email: johnjim0816@gmail.com @Date: 2020-06-09 20:25:52 @LastEditor: John -LastEditTime: 2021-03-17 20:43:25 +LastEditTime: 2021-03-31 00:56:32 @Discription: @Environment: python 3.7.7 ''' @@ -58,9 +58,7 @@ class DDPG: done = torch.FloatTensor(np.float32(done)).unsqueeze(1).to(self.device) # 注意critic将(s_t,a)作为输入 policy_loss = self.critic(state, self.actor(state)) - policy_loss = -policy_loss.mean() - next_action = self.target_actor(next_state) target_value = self.target_critic(next_state, next_action.detach()) expected_value = reward + (1.0 - done) * self.gamma * target_value @@ -87,7 +85,7 @@ class DDPG: param.data * self.soft_tau ) def save(self,path): - torch.save(self.target_net.state_dict(), path+'DDPG_checkpoint.pth') + torch.save(self.actor.state_dict(), path+'checkpoint.pt') def load(self,path): - self.actor.load_state_dict(torch.load(path+'DDPG_checkpoint.pth')) \ No newline at end of file + self.actor.load_state_dict(torch.load(path+'checkpoint.pt')) \ No newline at end of file diff --git a/codes/DDPG/main.py b/codes/DDPG/main.py index bee9d21..a3f6eef 100644 --- a/codes/DDPG/main.py +++ b/codes/DDPG/main.py @@ -5,12 +5,17 @@ @Email: johnjim0816@gmail.com @Date: 2020-06-11 20:58:21 @LastEditor: John -LastEditTime: 2021-03-19 19:57:00 +LastEditTime: 2021-03-31 01:04:48 @Discription: @Environment: python 3.7.7 ''' import sys,os -sys.path.append(os.getcwd()) # 添加当前终端路径 +from pathlib import Path +import sys,os +curr_path = os.path.dirname(__file__) +parent_path=os.path.dirname(curr_path) +sys.path.append(parent_path) # add current terminal path to sys.path + import torch import gym import numpy as np @@ -20,27 +25,23 @@ from DDPG.env import NormalizedActions,OUNoise from common.plot import plot_rewards from common.utils import save_results -SEQUENCE = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") # 获取当前时间 -SAVED_MODEL_PATH = os.path.split(os.path.abspath(__file__))[0]+"/saved_model/"+SEQUENCE+'/' # 生成保存的模型路径 -if not os.path.exists(os.path.split(os.path.abspath(__file__))[0]+"/saved_model/"): # 检测是否存在文件夹 - os.mkdir(os.path.split(os.path.abspath(__file__))[0]+"/saved_model/") -if not os.path.exists(SAVED_MODEL_PATH): # 检测是否存在文件夹 - os.mkdir(SAVED_MODEL_PATH) -RESULT_PATH = os.path.split(os.path.abspath(__file__))[0]+"/results/"+SEQUENCE+'/' # 存储reward的路径 -if not os.path.exists(os.path.split(os.path.abspath(__file__))[0]+"/results/"): # 检测是否存在文件夹 - os.mkdir(os.path.split(os.path.abspath(__file__))[0]+"/results/") -if not os.path.exists(RESULT_PATH): # 检测是否存在文件夹 - os.mkdir(RESULT_PATH) +SEQUENCE = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") # obtain current time +SAVED_MODEL_PATH = curr_path+"/saved_model/"+SEQUENCE+'/' # path to save model +if not os.path.exists(curr_path+"/saved_model/"): os.mkdir(curr_path+"/saved_model/") +if not os.path.exists(SAVED_MODEL_PATH): os.mkdir(SAVED_MODEL_PATH) +RESULT_PATH = curr_path+"/results/"+SEQUENCE+'/' # path to save rewards +if not os.path.exists(curr_path+"/results/"): os.mkdir(curr_path+"/results/") +if not os.path.exists(RESULT_PATH): os.mkdir(RESULT_PATH) class DDPGConfig: def __init__(self): + self.algo = 'DDPG' self.gamma = 0.99 self.critic_lr = 1e-3 self.actor_lr = 1e-4 self.memory_capacity = 10000 self.batch_size = 128 self.train_eps =300 - self.train_steps = 200 self.eval_eps = 200 self.eval_steps = 200 self.target_update = 4 @@ -56,19 +57,19 @@ def train(cfg,env,agent): for i_episode in range(cfg.train_eps): state = env.reset() ou_noise.reset() + done = False ep_reward = 0 - for i_step in range(cfg.train_steps): + i_step = 0 + while not done: + i_step += 1 action = agent.choose_action(state) - action = ou_noise.get_action( - action, i_step) # 即paper中的random process + action = ou_noise.get_action(action, i_step) # 即paper中的random process next_state, reward, done, _ = env.step(action) ep_reward += reward agent.memory.push(state, action, reward, next_state, done) agent.update() state = next_state - if done: - break - print('Episode:{}/{}, Reward:{}, Steps:{}, Done:{}'.format(i_episode+1,cfg.train_eps,ep_reward,i_step+1,done)) + print('Episode:{}/{}, Reward:{}'.format(i_episode+1,cfg.train_eps,ep_reward)) ep_steps.append(i_step) rewards.append(ep_reward) if ma_rewards: diff --git a/codes/DDPG/results/20210331-010047/ma_rewards_train.npy b/codes/DDPG/results/20210331-010047/ma_rewards_train.npy new file mode 100644 index 0000000..6d3572e Binary files /dev/null and b/codes/DDPG/results/20210331-010047/ma_rewards_train.npy differ diff --git a/codes/DDPG/results/20210331-010047/rewards_curve_train.png b/codes/DDPG/results/20210331-010047/rewards_curve_train.png new file mode 100644 index 0000000..f2046a5 Binary files /dev/null and b/codes/DDPG/results/20210331-010047/rewards_curve_train.png differ diff --git a/codes/DDPG/results/20210331-010047/rewards_train.npy b/codes/DDPG/results/20210331-010047/rewards_train.npy new file mode 100644 index 0000000..72a95cc Binary files /dev/null and b/codes/DDPG/results/20210331-010047/rewards_train.npy differ diff --git a/codes/DDPG/saved_model/20210331-010047/checkpoint.pt b/codes/DDPG/saved_model/20210331-010047/checkpoint.pt new file mode 100644 index 0000000..85ddc28 Binary files /dev/null and b/codes/DDPG/saved_model/20210331-010047/checkpoint.pt differ diff --git a/codes/DQN/README.md b/codes/DQN/README.md index 9eb2246..530d666 100644 --- a/codes/DQN/README.md +++ b/codes/DQN/README.md @@ -1,7 +1,7 @@ # DQN ## 原理简介 -DQN是Q-leanning算法的优化和延伸,Q-leaning中使用有限的Q表存储值的信息,而DQN中则用神经网络替代Q表存储信息,这样更适用于高维的情况,相关知识基础可参考[datawhale李宏毅笔记-Q学习](https://datawhalechina.github.io/leedeeprl-notes/#/chapter6/chapter6)。 +DQN是Q-leanning算法的优化和延伸,Q-leaning中使用有限的Q表存储值的信息,而DQN中则用神经网络替代Q表存储信息,这样更适用于高维的情况,相关知识基础可参考[datawhale李宏毅笔记-Q学习](https://datawhalechina.github.io/easy-rl/#/chapter6/chapter6)。 论文方面主要可以参考两篇,一篇就是2013年谷歌DeepMind团队的[Playing Atari with Deep Reinforcement Learning](https://www.cs.toronto.edu/~vmnih/docs/dqn.pdf),一篇是也是他们团队后来在Nature杂志上发表的[Human-level control through deep reinforcement learning](https://web.stanford.edu/class/psych209/Readings/MnihEtAlHassibis15NatureControlDeepRL.pdf)。后者在算法层面增加target q-net,也可以叫做Nature DQN。 diff --git a/codes/DQN/agent.py b/codes/DQN/agent.py index 2b56175..7890270 100644 --- a/codes/DQN/agent.py +++ b/codes/DQN/agent.py @@ -5,7 +5,7 @@ @Email: johnjim0816@gmail.com @Date: 2020-06-12 00:50:49 @LastEditor: John -LastEditTime: 2021-03-13 14:56:23 +LastEditTime: 2021-03-30 17:01:26 @Discription: @Environment: python 3.7.7 ''' @@ -13,6 +13,8 @@ LastEditTime: 2021-03-13 14:56:23 ''' + + import torch import torch.nn as nn import torch.optim as optim @@ -23,61 +25,44 @@ from common.memory import ReplayBuffer from common.model import MLP class DQN: def __init__(self, state_dim, action_dim, cfg): - + self.action_dim = action_dim # 总的动作个数 self.device = cfg.device # 设备,cpu或gpu等 - self.gamma = cfg.gamma # 奖励的折扣因子 + self.gamma = cfg.gamma # 奖励的折扣因子 # e-greedy策略相关参数 - self.sample_count = 0 # 用于epsilon的衰减计数 - self.epsilon = 0 - self.epsilon_start = cfg.epsilon_start - self.epsilon_end = cfg.epsilon_end - self.epsilon_decay = cfg.epsilon_decay + self.frame_idx = 0 # 用于epsilon的衰减计数 + self.epsilon = lambda frame_idx: cfg.epsilon_end + \ + (cfg.epsilon_start - cfg.epsilon_end) * \ + math.exp(-1. * frame_idx / cfg.epsilon_decay) self.batch_size = cfg.batch_size - self.policy_net = MLP(state_dim, action_dim,hidden_dim=cfg.hidden_dim).to(self.device) - self.target_net = MLP(state_dim, action_dim,hidden_dim=cfg.hidden_dim).to(self.device) - # target_net的初始模型参数完全复制policy_net - self.target_net.load_state_dict(self.policy_net.state_dict()) - self.target_net.eval() # 不启用 BatchNormalization 和 Dropout - # 可查parameters()与state_dict()的区别,前者require_grad=True + self.policy_net = MLP(state_dim, action_dim, + hidden_dim=cfg.hidden_dim).to(self.device) + self.target_net = MLP(state_dim, action_dim, + hidden_dim=cfg.hidden_dim).to(self.device) self.optimizer = optim.Adam(self.policy_net.parameters(), lr=cfg.lr) self.loss = 0 self.memory = ReplayBuffer(cfg.memory_capacity) - def choose_action(self, state, train=True): + def choose_action(self, state): '''选择动作 ''' - if train: - self.epsilon = self.epsilon_end + (self.epsilon_start - self.epsilon_end) * \ - math.exp(-1. * self.sample_count / self.epsilon_decay) - self.sample_count += 1 - if random.random() > self.epsilon: - with torch.no_grad(): - # 先转为张量便于丢给神经网络,state元素数据原本为float64 - # 注意state=torch.tensor(state).unsqueeze(0)跟state=torch.tensor([state])等价 - state = torch.tensor( - [state], device=self.device, dtype=torch.float32) - # 如tensor([[-0.0798, -0.0079]], grad_fn=) - q_value = self.policy_net(state) - # tensor.max(1)返回每行的最大值以及对应的下标, - # 如torch.return_types.max(values=tensor([10.3587]),indices=tensor([0])) - # 所以tensor.max(1)[1]返回最大值对应的下标,即action - action = q_value.max(1)[1].item() - else: - action = random.randrange(self.action_dim) - return action - else: - with torch.no_grad(): # 取消保存梯度 - # 先转为张量便于丢给神经网络,state元素数据原本为float64 - # 注意state=torch.tensor(state).unsqueeze(0)跟state=torch.tensor([state])等价 - state = torch.tensor( - [state], device='cpu', dtype=torch.float32) # 如tensor([[-0.0798, -0.0079]], grad_fn=) - q_value = self.target_net(state) - # tensor.max(1)返回每行的最大值以及对应的下标, - # 如torch.return_types.max(values=tensor([10.3587]),indices=tensor([0])) - # 所以tensor.max(1)[1]返回最大值对应的下标,即action - action = q_value.max(1)[1].item() - return action + self.frame_idx += 1 + if random.random() > self.epsilon(self.frame_idx): + with torch.no_grad(): + # 先转为张量便于丢给神经网络,state元素数据原本为float64 + # 注意state=torch.tensor(state).unsqueeze(0)跟state=torch.tensor([state])等价 + state = torch.tensor( + [state], device=self.device, dtype=torch.float32) + # 如tensor([[-0.0798, -0.0079]], grad_fn=) + q_value = self.policy_net(state) + # tensor.max(1)返回每行的最大值以及对应的下标, + # 如torch.return_types.max(values=tensor([10.3587]),indices=tensor([0])) + # 所以tensor.max(1)[1]返回最大值对应的下标,即action + action = q_value.max(1)[1].item() + else: + action = random.randrange(self.action_dim) + return action + def update(self): if len(self.memory) < self.batch_size: @@ -96,32 +81,31 @@ class DQN: next_state_batch = torch.tensor( next_state_batch, device=self.device, dtype=torch.float) done_batch = torch.tensor(np.float32( - done_batch), device=self.device).unsqueeze(1) # 将bool转为float然后转为张量 + done_batch), device=self.device) '''计算当前(s_t,a)对应的Q(s_t, a)''' '''torch.gather:对于a=torch.Tensor([[1,2],[3,4]]),那么a.gather(1,torch.Tensor([[0],[1]]))=torch.Tensor([[1],[3]])''' q_values = self.policy_net(state_batch).gather( dim=1, index=action_batch) # 等价于self.forward # 计算所有next states的V(s_{t+1}),即通过target_net中选取reward最大的对应states - next_state_values = self.target_net( - next_state_batch).max(1)[0].detach() # 比如tensor([ 0.0060, -0.0171,...,]) + next_q_values = self.target_net(next_state_batch).max( + 1)[0].detach() # 比如tensor([ 0.0060, -0.0171,...,]) # 计算 expected_q_value # 对于终止状态,此时done_batch[0]=1, 对应的expected_q_value等于reward - expected_q_values = reward_batch + self.gamma * \ - next_state_values * (1-done_batch[0]) + expected_q_values = reward_batch + \ + self.gamma * next_q_values * (1-done_batch) # self.loss = F.smooth_l1_loss(q_values,expected_q_values.unsqueeze(1)) # 计算 Huber loss self.loss = nn.MSELoss()(q_values, expected_q_values.unsqueeze(1)) # 计算 均方误差loss # 优化模型 self.optimizer.zero_grad() # zero_grad清除上一步所有旧的gradients from the last step # loss.backward()使用backpropagation计算loss相对于所有parameters(需要gradients)的微分 self.loss.backward() - for param in self.policy_net.parameters(): # clip防止梯度爆炸 - param.grad.data.clamp_(-1, 1) - + # for param in self.policy_net.parameters(): # clip防止梯度爆炸 + # param.grad.data.clamp_(-1, 1) self.optimizer.step() # 更新模型 - def save(self,path): + def save(self, path): torch.save(self.target_net.state_dict(), path+'dqn_checkpoint.pth') - def load(self,path): - self.target_net.load_state_dict(torch.load(path+'dqn_checkpoint.pth')) + def load(self, path): + self.target_net.load_state_dict(torch.load(path+'dqn_checkpoint.pth')) diff --git a/codes/DQN/main.ipynb b/codes/DQN/main.ipynb new file mode 100644 index 0000000..e21c74c --- /dev/null +++ b/codes/DQN/main.ipynb @@ -0,0 +1,467 @@ +{ + "metadata": { + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.10-final" + }, + "orig_nbformat": 2, + "kernelspec": { + "name": "python3", + "display_name": "Python 3", + "language": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2, + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import sys,os\n", + "from pathlib import Path\n", + "curr_path = str(Path().absolute())\n", + "parent_path = str(Path().absolute().parent)\n", + "sys.path.append(parent_path) # add current terminal path to sys.path\n", + "import gym\n", + "import torch\n", + "import datetime\n", + "from DQN.agent import DQN\n", + "from common.plot import plot_rewards\n", + "from common.utils import save_results" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "SEQUENCE = datetime.datetime.now().strftime(\"%Y%m%d-%H%M%S\") # 获取当前时间\n", + "SAVED_MODEL_PATH = curr_path+\"/saved_model/\"+SEQUENCE+'/' # 生成保存的模型路径\n", + "if not os.path.exists(curr_path+\"/saved_model/\"): # 检测是否存在文件夹\n", + " os.mkdir(curr_path+\"/saved_model/\")\n", + "if not os.path.exists(SAVED_MODEL_PATH): # 检测是否存在文件夹\n", + " os.mkdir(SAVED_MODEL_PATH)\n", + "RESULT_PATH = curr_path+\"/results/\"+SEQUENCE+'/' # 存储reward的路径\n", + "if not os.path.exists(curr_path+\"/results/\"): # 检测是否存在文件夹\n", + " os.mkdir(curr_path+\"/results/\")\n", + "if not os.path.exists(RESULT_PATH): # 检测是否存在文件夹\n", + " os.mkdir(RESULT_PATH)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "class DQNConfig:\n", + " def __init__(self):\n", + " self.algo = \"DQN\" # 算法名称\n", + " self.gamma = 0.99\n", + " self.epsilon_start = 0.95 # e-greedy策略的初始epsilon\n", + " self.epsilon_end = 0.01\n", + " self.epsilon_decay = 200\n", + " self.lr = 0.01 # 学习率\n", + " self.memory_capacity = 800 # Replay Memory容量\n", + " self.batch_size = 64\n", + " self.train_eps = 300 # 训练的episode数目\n", + " self.train_steps = 200 # 训练每个episode的最大长度\n", + " self.target_update = 2 # target net的更新频率\n", + " self.eval_eps = 20 # 测试的episode数目\n", + " self.eval_steps = 200 # 测试每个episode的最大长度\n", + " self.device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\") # 检测gpu\n", + " self.hidden_dim = 128 # 神经网络隐藏层维度" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "def train(cfg,env,agent):\n", + " print('Start to train !')\n", + " rewards = []\n", + " ma_rewards = [] # 滑动平均的reward\n", + " ep_steps = []\n", + " for i_episode in range(cfg.train_eps):\n", + " state = env.reset() # reset环境状态\n", + " ep_reward = 0\n", + " for i_step in range(cfg.train_steps):\n", + " action = agent.choose_action(state) # 根据当前环境state选择action\n", + " next_state, reward, done, _ = env.step(action) # 更新环境参数\n", + " ep_reward += reward\n", + " agent.memory.push(state, action, reward, next_state, done) # 将state等这些transition存入memory\n", + " state = next_state # 跳转到下一个状态\n", + " agent.update() # 每步更新网络\n", + " if done:\n", + " break\n", + " # 更新target network,复制DQN中的所有weights and biases\n", + " if i_episode % cfg.target_update == 0:\n", + " agent.target_net.load_state_dict(agent.policy_net.state_dict())\n", + " print('Episode:{}/{}, Reward:{}, Steps:{}, Done:{}'.format(i_episode+1,cfg.train_eps,ep_reward,i_step+1,done))\n", + " ep_steps.append(i_step)\n", + " rewards.append(ep_reward)\n", + " # 计算滑动窗口的reward\n", + " if ma_rewards:\n", + " ma_rewards.append(\n", + " 0.9*ma_rewards[-1]+0.1*ep_reward)\n", + " else:\n", + " ma_rewards.append(ep_reward) \n", + " print('Complete training!')\n", + " return rewards,ma_rewards" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Start to train !\n", + "Episode:1/300, Reward:41.0, Steps:41, Done:True\n", + "Episode:2/300, Reward:23.0, Steps:23, Done:True\n", + "Episode:3/300, Reward:19.0, Steps:19, Done:True\n", + "Episode:4/300, Reward:17.0, Steps:17, Done:True\n", + "Episode:5/300, Reward:14.0, Steps:14, Done:True\n", + "Episode:6/300, Reward:15.0, Steps:15, Done:True\n", + "Episode:7/300, Reward:10.0, Steps:10, Done:True\n", + "Episode:8/300, Reward:23.0, Steps:23, Done:True\n", + "Episode:9/300, Reward:14.0, Steps:14, Done:True\n", + "Episode:10/300, Reward:9.0, Steps:9, Done:True\n", + "Episode:11/300, Reward:9.0, Steps:9, Done:True\n", + "Episode:12/300, Reward:9.0, Steps:9, Done:True\n", + "Episode:13/300, Reward:9.0, Steps:9, Done:True\n", + "Episode:14/300, Reward:10.0, Steps:10, Done:True\n", + "Episode:15/300, Reward:10.0, Steps:10, Done:True\n", + "Episode:16/300, Reward:12.0, Steps:12, Done:True\n", + "Episode:17/300, Reward:10.0, Steps:10, Done:True\n", + "Episode:18/300, Reward:10.0, Steps:10, Done:True\n", + "Episode:19/300, Reward:9.0, Steps:9, Done:True\n", + "Episode:20/300, Reward:10.0, Steps:10, Done:True\n", + "Episode:21/300, Reward:8.0, Steps:8, Done:True\n", + "Episode:22/300, Reward:10.0, Steps:10, Done:True\n", + "Episode:23/300, Reward:10.0, Steps:10, Done:True\n", + "Episode:24/300, Reward:13.0, Steps:13, Done:True\n", + "Episode:25/300, Reward:10.0, Steps:10, Done:True\n", + "Episode:26/300, Reward:9.0, Steps:9, Done:True\n", + "Episode:27/300, Reward:9.0, Steps:9, Done:True\n", + "Episode:28/300, Reward:9.0, Steps:9, Done:True\n", + "Episode:29/300, Reward:12.0, Steps:12, Done:True\n", + "Episode:30/300, Reward:9.0, Steps:9, Done:True\n", + "Episode:31/300, Reward:9.0, Steps:9, Done:True\n", + "Episode:32/300, Reward:10.0, Steps:10, Done:True\n", + "Episode:33/300, Reward:11.0, Steps:11, Done:True\n", + "Episode:34/300, Reward:12.0, Steps:12, Done:True\n", + "Episode:35/300, Reward:8.0, Steps:8, Done:True\n", + "Episode:36/300, Reward:10.0, Steps:10, Done:True\n", + "Episode:37/300, Reward:9.0, Steps:9, Done:True\n", + "Episode:38/300, Reward:10.0, Steps:10, Done:True\n", + "Episode:39/300, Reward:10.0, Steps:10, Done:True\n", + "Episode:40/300, Reward:10.0, Steps:10, Done:True\n", + "Episode:41/300, Reward:9.0, Steps:9, Done:True\n", + "Episode:42/300, Reward:10.0, Steps:10, Done:True\n", + "Episode:43/300, Reward:10.0, Steps:10, Done:True\n", + "Episode:44/300, Reward:10.0, Steps:10, Done:True\n", + "Episode:45/300, Reward:9.0, Steps:9, Done:True\n", + "Episode:46/300, Reward:22.0, Steps:22, Done:True\n", + "Episode:47/300, Reward:74.0, Steps:74, Done:True\n", + "Episode:48/300, Reward:13.0, Steps:13, Done:True\n", + "Episode:49/300, Reward:29.0, Steps:29, Done:True\n", + "Episode:50/300, Reward:56.0, Steps:56, Done:True\n", + "Episode:51/300, Reward:74.0, Steps:74, Done:True\n", + "Episode:52/300, Reward:85.0, Steps:85, Done:True\n", + "Episode:53/300, Reward:72.0, Steps:72, Done:True\n", + "Episode:54/300, Reward:114.0, Steps:114, Done:True\n", + "Episode:55/300, Reward:97.0, Steps:97, Done:True\n", + "Episode:56/300, Reward:101.0, Steps:101, Done:True\n", + "Episode:57/300, Reward:104.0, Steps:104, Done:True\n", + "Episode:58/300, Reward:58.0, Steps:58, Done:True\n", + "Episode:59/300, Reward:11.0, Steps:11, Done:True\n", + "Episode:60/300, Reward:56.0, Steps:56, Done:True\n", + "Episode:61/300, Reward:74.0, Steps:74, Done:True\n", + "Episode:62/300, Reward:51.0, Steps:51, Done:True\n", + "Episode:63/300, Reward:113.0, Steps:113, Done:True\n", + "Episode:64/300, Reward:48.0, Steps:48, Done:True\n", + "Episode:65/300, Reward:97.0, Steps:97, Done:True\n", + "Episode:66/300, Reward:59.0, Steps:59, Done:True\n", + "Episode:67/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:68/300, Reward:67.0, Steps:67, Done:True\n", + "Episode:69/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:70/300, Reward:45.0, Steps:45, Done:True\n", + "Episode:71/300, Reward:48.0, Steps:48, Done:True\n", + "Episode:72/300, Reward:90.0, Steps:90, Done:True\n", + "Episode:73/300, Reward:47.0, Steps:47, Done:True\n", + "Episode:74/300, Reward:94.0, Steps:94, Done:True\n", + "Episode:75/300, Reward:107.0, Steps:107, Done:True\n", + "Episode:76/300, Reward:12.0, Steps:12, Done:True\n", + "Episode:77/300, Reward:30.0, Steps:30, Done:True\n", + "Episode:78/300, Reward:62.0, Steps:62, Done:True\n", + "Episode:79/300, Reward:64.0, Steps:64, Done:True\n", + "Episode:80/300, Reward:41.0, Steps:41, Done:True\n", + "Episode:81/300, Reward:67.0, Steps:67, Done:True\n", + "Episode:82/300, Reward:45.0, Steps:45, Done:True\n", + "Episode:83/300, Reward:130.0, Steps:130, Done:True\n", + "Episode:84/300, Reward:50.0, Steps:50, Done:True\n", + "Episode:85/300, Reward:51.0, Steps:51, Done:True\n", + "Episode:86/300, Reward:67.0, Steps:67, Done:True\n", + "Episode:87/300, Reward:37.0, Steps:37, Done:True\n", + "Episode:88/300, Reward:41.0, Steps:41, Done:True\n", + "Episode:89/300, Reward:54.0, Steps:54, Done:True\n", + "Episode:90/300, Reward:93.0, Steps:93, Done:True\n", + "Episode:91/300, Reward:71.0, Steps:71, Done:True\n", + "Episode:92/300, Reward:102.0, Steps:102, Done:True\n", + "Episode:93/300, Reward:55.0, Steps:55, Done:True\n", + "Episode:94/300, Reward:73.0, Steps:73, Done:True\n", + "Episode:95/300, Reward:61.0, Steps:61, Done:True\n", + "Episode:96/300, Reward:16.0, Steps:16, Done:True\n", + "Episode:97/300, Reward:61.0, Steps:61, Done:True\n", + "Episode:98/300, Reward:79.0, Steps:79, Done:True\n", + "Episode:99/300, Reward:76.0, Steps:76, Done:True\n", + "Episode:100/300, Reward:32.0, Steps:32, Done:True\n", + "Episode:101/300, Reward:95.0, Steps:95, Done:True\n", + "Episode:102/300, Reward:83.0, Steps:83, Done:True\n", + "Episode:103/300, Reward:41.0, Steps:41, Done:True\n", + "Episode:104/300, Reward:30.0, Steps:30, Done:True\n", + "Episode:105/300, Reward:83.0, Steps:83, Done:True\n", + "Episode:106/300, Reward:95.0, Steps:95, Done:True\n", + "Episode:107/300, Reward:104.0, Steps:104, Done:True\n", + "Episode:108/300, Reward:98.0, Steps:98, Done:True\n", + "Episode:109/300, Reward:109.0, Steps:109, Done:True\n", + "Episode:110/300, Reward:63.0, Steps:63, Done:True\n", + "Episode:111/300, Reward:98.0, Steps:98, Done:True\n", + "Episode:112/300, Reward:105.0, Steps:105, Done:True\n", + "Episode:113/300, Reward:99.0, Steps:99, Done:True\n", + "Episode:114/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:115/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:116/300, Reward:47.0, Steps:47, Done:True\n", + "Episode:117/300, Reward:98.0, Steps:98, Done:True\n", + "Episode:118/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:119/300, Reward:52.0, Steps:52, Done:True\n", + "Episode:120/300, Reward:55.0, Steps:55, Done:True\n", + "Episode:121/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:122/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:123/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:124/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:125/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:126/300, Reward:40.0, Steps:40, Done:True\n", + "Episode:127/300, Reward:42.0, Steps:42, Done:True\n", + "Episode:128/300, Reward:101.0, Steps:101, Done:True\n", + "Episode:129/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:130/300, Reward:70.0, Steps:70, Done:True\n", + "Episode:131/300, Reward:175.0, Steps:175, Done:True\n", + "Episode:132/300, Reward:90.0, Steps:90, Done:True\n", + "Episode:133/300, Reward:81.0, Steps:81, Done:True\n", + "Episode:134/300, Reward:61.0, Steps:61, Done:True\n", + "Episode:135/300, Reward:74.0, Steps:74, Done:True\n", + "Episode:136/300, Reward:68.0, Steps:68, Done:True\n", + "Episode:137/300, Reward:50.0, Steps:50, Done:True\n", + "Episode:138/300, Reward:51.0, Steps:51, Done:True\n", + "Episode:139/300, Reward:99.0, Steps:99, Done:True\n", + "Episode:140/300, Reward:87.0, Steps:87, Done:True\n", + "Episode:141/300, Reward:94.0, Steps:94, Done:True\n", + "Episode:142/300, Reward:51.0, Steps:51, Done:True\n", + "Episode:143/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:144/300, Reward:55.0, Steps:55, Done:True\n", + "Episode:145/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:146/300, Reward:57.0, Steps:57, Done:True\n", + "Episode:147/300, Reward:129.0, Steps:129, Done:True\n", + "Episode:148/300, Reward:74.0, Steps:74, Done:True\n", + "Episode:149/300, Reward:108.0, Steps:108, Done:True\n", + "Episode:150/300, Reward:63.0, Steps:63, Done:True\n", + "Episode:151/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:152/300, Reward:103.0, Steps:103, Done:True\n", + "Episode:153/300, Reward:129.0, Steps:129, Done:True\n", + "Episode:154/300, Reward:77.0, Steps:77, Done:True\n", + "Episode:155/300, Reward:129.0, Steps:129, Done:True\n", + "Episode:156/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:157/300, Reward:181.0, Steps:181, Done:True\n", + "Episode:158/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:159/300, Reward:136.0, Steps:136, Done:True\n", + "Episode:160/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:161/300, Reward:181.0, Steps:181, Done:True\n", + "Episode:162/300, Reward:120.0, Steps:120, Done:True\n", + "Episode:163/300, Reward:190.0, Steps:190, Done:True\n", + "Episode:164/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:165/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:166/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:167/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:168/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:169/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:170/300, Reward:89.0, Steps:89, Done:True\n", + "Episode:171/300, Reward:74.0, Steps:74, Done:True\n", + "Episode:172/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:173/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:174/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:175/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:176/300, Reward:93.0, Steps:93, Done:True\n", + "Episode:177/300, Reward:139.0, Steps:139, Done:True\n", + "Episode:178/300, Reward:78.0, Steps:78, Done:True\n", + "Episode:179/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:180/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:181/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:182/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:183/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:184/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:185/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:186/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:187/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:188/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:189/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:190/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:191/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:192/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:193/300, Reward:190.0, Steps:190, Done:True\n", + "Episode:194/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:195/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:196/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:197/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:198/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:199/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:200/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:201/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:202/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:203/300, Reward:67.0, Steps:67, Done:True\n", + "Episode:204/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:205/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:206/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:207/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:208/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:209/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:210/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:211/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:212/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:213/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:214/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:215/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:216/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:217/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:218/300, Reward:44.0, Steps:44, Done:True\n", + "Episode:219/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:220/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:221/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:222/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:223/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:224/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:225/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:226/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:227/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:228/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:229/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:230/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:231/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:232/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:233/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:234/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:235/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:236/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:237/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:238/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:239/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:240/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:241/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:242/300, Reward:126.0, Steps:126, Done:True\n", + "Episode:243/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:244/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:245/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:246/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:247/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:248/300, Reward:118.0, Steps:118, Done:True\n", + "Episode:249/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:250/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:251/300, Reward:99.0, Steps:99, Done:True\n", + "Episode:252/300, Reward:145.0, Steps:145, Done:True\n", + "Episode:253/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:254/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:255/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:256/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:257/300, Reward:130.0, Steps:130, Done:True\n", + "Episode:258/300, Reward:170.0, Steps:170, Done:True\n", + "Episode:259/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:260/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:261/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:262/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:263/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:264/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:265/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:266/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:267/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:268/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:269/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:270/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:271/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:272/300, Reward:135.0, Steps:135, Done:True\n", + "Episode:273/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:274/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:275/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:276/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:277/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:278/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:279/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:280/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:281/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:282/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:283/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:284/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:285/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:286/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:287/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:288/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:289/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:290/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:291/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:292/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:293/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:294/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:295/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:296/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:297/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:298/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:299/300, Reward:200.0, Steps:200, Done:True\n", + "Episode:300/300, Reward:200.0, Steps:200, Done:True\n", + "Complete training!\n", + "results saved!\n" + ] + }, + { + "output_type": "display_data", + "data": { + "text/plain": "
", + "image/svg+xml": "\n\n\n \n \n \n \n 2021-03-29T19:53:51.889101\n image/svg+xml\n \n \n Matplotlib v3.4.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXsAAAEcCAYAAAAmzxTpAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8+yak3AAAACXBIWXMAAAsTAAALEwEAmpwYAACVh0lEQVR4nO2dd5wU9f3/nzOz5XrlgKMXBRGUdtgAC2gsWOPXhK+JRo1JTKKYGE2MEjWWRNSYRGK+amLMz0jUJFZs2EssICooooB0uON63z4zvz9mZ3Zmd/Zud2+PK8zr8YC9nfIps7uvec/r/f6834KqqioOHDhw4GBQQ+zrAThw4MCBg96HQ/YOHDhwcADAIXsHDhw4OADgkL0DBw4cHABwyN6BAwcODgA4ZO/AgQMHBwAcsnfQL7F8+XKuvvrqPul70aJFrF69uk/67m8IBAJcdtllzJ49myVLlvT1cBz0AK6+HoADB/0Nzz//fF8Pod/gpZdeoqGhgdWrV+NyJdLF8uXLue+++/B4PAAMHTqUuXPnctlllzF06FDjuLa2Nn73u9/x6quv0tHRwZgxY7jkkks455xzjGMWLFiA3+/ntddeIy8vD4B///vfPPvss/zjH//o5ZkOfjiWvYOUEIlE+noIWcFgmMf+nEN1dTXjxo2zJXodp556Kp988glr1qzhT3/6Ew0NDXz961+nrq4OgFAoxEUXXUR1dTWPPfYYa9eu5ZprruHOO+/k4YcftrSlKErCNgfZgUP2gwAPPPAAJ554IjNnzuS0007jlVdeAbQfWVVVFZs3bzaObWpq4vDDD6exsRGAN954g7POOouqqioWL17Ml19+aRy7YMECHnjgAc444wxmzJhBJBJJ2heALMvcfvvtHHnkkSxYsIBHHnmEyZMnG+TU3t7Oddddx7x585g/fz6///3vkWU5pTmuW7eOxYsXU1VVxZlnnmmRWZ544glOPfVUZs6cycKFC3nssceMfatXr+bYY4/lgQceYO7cufzyl79k+fLlXHnllfz85z9n5syZLFq0iM8++8wy7/feew+g22M///xzzj77bGbOnMmSJUv4yU9+wu9///uk8/jXv/5ljPW0007j888/B2Dy5Mns3LnTOO7aa6812rGbw6mnnsobb7xhHB+JRDjqqKOM9rq6XvHYunUrF1xwAVVVVSxatIjXXnsNgHvuuYc///nPvPjii8ycOZN///vfXXxC4Ha7Ofjgg/n9739PWVkZDz30EADPPPMMNTU1/PGPf2T06NG43W6OPfZYli5dyh/+8Ac6OzuNNr773e/yt7/9jba2ti77cpA+HLIfBBg9ejQrVqzgo48+4vLLL+eaa66hrq4Oj8fDSSedZJElXnzxRebMmUN5eTkbN27kuuuu4+abb2b16tV885vf5Ec/+hGhUMg4/vnnn+eBBx5g7dq1uFyupH2BRmRvv/02zzzzDE899RSvvvqqZZzXXnstLpeLl19+maeffpp33323WwIBqK2t5Qc/+AE//OEPWbNmDb/4xS9YsmQJTU1NAJSXl3P//ffz8ccf89vf/pbf/va3BukBNDQ00NrayhtvvMEtt9wCwOuvv86iRYtYu3YtCxYsMLbbIdmxoVCIyy+/nHPOOYc1a9Zw+umnJ8zZjBdffJHly5ezbNkyPv74Y/7v//6PkpKSbudvN4dFixbx3HPPGfv/+9//UlpaytSpU7u9XmaEw2Euu+wy5s6dy3vvvcfSpUu5+uqr2bZtG0uWLOEHP/iBYbmfd955KY1VkiQWLlzI2rVrAXjvvfc49thjDWlGx9e+9jUCgQDr1q0ztk2bNo0jjjiCBx98MKW+HKQOh+wHAU499VSGDRuGKIqcdtppjB07lk8//RSAM844w0L2K1eu5IwzzgDg8ccf55vf/CbTp09HkiTOOecc3G635cd3wQUXUFlZSU5OTrd9vfjii1x44YUMHz6c4uJivv/97xvtNDQ08NZbb3HdddeRl5dHeXk5F110UUr6+DPPPMOxxx7LcccdhyiKzJ07l2nTpvHWW28BcPzxxzNmzBgEQeCII45g7ty5BtEAiKLIkiVL8Hg8xjxmz57NcccdhyRJnHXWWZYnmngkO3b9+vVEIhEuvPBC3G43X/va1zjssMOStvOf//yHSy+9lMMPPxxBEBg7diwjR47sdv52czjjjDN4/fXX8fv9gPa5Llq0KKXrZcb69evx+Xx8//vfx+PxcPTRR3PCCSf02G8xdOhQWltbAWhubqaioiLhGJfLRWlpacJNaMmSJTzyyCO2NycHmcNx0A4CPP300zz00EPs3bsXAJ/PR3NzMwBHHnkkgUCA9evXU15ezpdffsmJJ54IaHrs008/zSOPPGK0FQ6HDUsdoLKyMuW+6urqLMcPHz7c+Lu6uppIJMK8efOMbYqiJLRvh+rqal566aUE2eLII48E4K233uLee+9lx44dKIpCIBBg0qRJxrGlpaV4vV5Lm0OGDDH+zsnJIRgMEolEbLXpZMfW1dUxbNgwBEEw9nc1n5qaGsaMGdPtfO0QP4exY8cyceJE3njjDU444QRef/11nn76aaD762VGXV0dw4cPRxRjdt+IESOora3NaJw6amtrKS4uNsZeX1+fcEwkEqG5uZnS0lLL9kmTJnH88cfzwAMPMHHixB6Nw0EMDtkPcOzdu5elS5fy97//nZkzZxrWpw5JkjjllFN47rnnGDJkCMcffzwFBQWARkyXXXYZP/zhD5O2byay7vqqqKhg3759xnvz38OHD8fj8fDBBx906eyzQ2VlJWeddRa33nprwr5QKMSSJUtYtmwZCxcuxO1286Mf/QhzMlfzHLKJiooKamtrUVXV6KOmpobRo0cnnceuXbts9+Xm5hpWOkB9fT3Dhg0z3tvN4fTTT+e5555DURQOOuggxo4da/ST7HrFY+jQoezbtw9FUQzCr6mpYdy4cd2emwyKovDGG29wzDHHAHDMMcdw99134/P5LFLOyy+/jNvtZvr06QltLFmyhHPOOYdLLrkk43E4sMKRcQY4/H4/giBQVlYGaM7KLVu2WI4544wzePHFF1m5ciWnn366sf28887jscceY/369aiqis/n480336SjoyOjvk499VQefvhhamtraWtr4y9/+YuxTw/Ju/322+no6EBRFHbt2sWaNWu6neOZZ57JG2+8wTvvvIMsywSDQVavXs2+ffsIhUKEQiHKyspwuVy89dZbvPvuu6lfwB5gxowZSJLEI488QiQS4dVXX7U4b+PxP//zP/ztb39jw4YNqKrKzp07jSekQw45hOeeew5Zlnn77bf58MMPu+3/tNNO49133+XRRx+1fK5dXa94HH744eTk5PDXv/6VcDjM6tWref311znttNPSvh6RSIStW7dy1VVX0dDQwEUXXQTAWWedxfDhw7nyyivZs2cP4XCYd955h1tvvZXvfve7FBYWJrQ1duxYTjvtNCfkMotwyH6A46CDDuKSSy5h8eLFHHPMMWzevJlZs2ZZjpk+fTq5ubnU1dVx7LHHGtsPO+wwbrnlFm6++WbmzJnD1772NZ588smM+/rGN77B3LlzOfPMMzn77LM57rjjcLlc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AwAO2Ttw4MDBAQCH7B04cODgAIBD9g4cOHBwAKBfp0tIFwM9pfKCBQvweDx4vVqt26uvvpr58+ezbt06brjhBoLBICNHjuTOO++kvLy8j0cbw7Jly1i1ahV79+5l5cqVTJo0Cej68+ivn1WyuST7bIB++/k0Nzfz85//nF27duHxeBg7diw333wzZWVlXY65P86nq7lMnjyZSZMmGUXR77jjDiZPngzA66+/zh133IEsy0ydOpXf/va35Obm9uVUAPjRj37Enj17EEWRvLw8fvWrXzFlypTe/c2ogwgXXHCB+vTTT6uqqqpPP/20esEFF/TxiNLDCSecoG7atMmyTZZl9cQTT1Q//PBDVVVV9d5771WvvfbavhheUnz44YdqdXV1wvi7+jz662eVbC52n42q9u/Pp7m5Wf3ggw+M97fffrv6y1/+sssx99f5JJuLqqrqpEmT1I6OjoRzOjo61GOOOUbdvn27qqqqet1116nLly/fL+PtDm1tbcbfr7zyinr22Werqtq7v5lBI+MM1pTKGzZswOv1UlVVBcDixYt56aWX+nhUVlRVVSXkPOrq8+jPn5XdXLpCf/58SkpKOPLII433M2bMoLq6ussx99f5JJtLV3j77beZNm2aYf0uXryYF198sTeHmTIKC2NJzjo6OhAEodd/M4NGxukqpfJAysdz9dVXo6oqs2fP5qqrrqKmpoYRI0YY+8vKylAUxXiU66/o6vNQVXVAflbxn01RUdGA+XwUReHRRx9lwYIFXY55IMzHPBcdF1xwAbIsc+yxx3LFFVfg8XgS5jJixIh+lWL9+uuv591330VVVf7617/2+m9m0Fj2gwErVqzg2Wef5YknnkBVVW6++ea+HpKDKAb6Z3PLLbeQl5fHt7/97b4eSo8RP5c333yTJ598khUrVvDVV19x77339vEIU8Ntt93Gm2++yU9/+lPuuOOOXu9v0JD9YEiprI/V4/Fw/vnn8/HHH1NZWWl5XG1qakIUxX5jZSVDV5/HQPys7D4bfXt//3yWLVvGzp07+cMf/oAoil2Oub/PJ34uEPtsCgoKOO+885J+NtXV1f3yO3b22WezevVqhg8f3qu/mUFD9gM9pbLP56O9Xas5qaoqL7zwAlOmTGHatGkEAgHWrl0LwGOPPcYpp5zSl0NNCV19HgPts0r22QD9/vO5++672bBhA/fe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\n" + }, + "metadata": {} + } + ], + "source": [ + "cfg = DQNConfig()\n", + "env = gym.make('CartPole-v0')\n", + "env.seed(1)\n", + "state_dim = env.observation_space.shape[0]\n", + "action_dim = env.action_space.n\n", + "agent = DQN(state_dim,action_dim,cfg)\n", + "rewards,ma_rewards = train(cfg,env,agent)\n", + "agent.save(path=SAVED_MODEL_PATH)\n", + "save_results(rewards,ma_rewards,tag='train',path=RESULT_PATH)\n", + "plot_rewards(rewards,ma_rewards,tag=\"train\",algo = cfg.algo,path=RESULT_PATH)" + ] + } + ] +} \ No newline at end of file diff --git a/codes/DQN/main.py b/codes/DQN/main.py index a6a998e..afc2f5f 100644 --- a/codes/DQN/main.py +++ b/codes/DQN/main.py @@ -5,12 +5,17 @@ @Email: johnjim0816@gmail.com @Date: 2020-06-12 00:48:57 @LastEditor: John -LastEditTime: 2021-03-26 17:17:17 +LastEditTime: 2021-03-30 16:59:19 @Discription: @Environment: python 3.7.7 ''' import sys,os -sys.path.append(os.getcwd()) # 添加当前终端路径 +from pathlib import Path +import sys,os +curr_path = os.path.dirname(__file__) +parent_path=os.path.dirname(curr_path) +sys.path.append(parent_path) # add current terminal path to sys.path + import gym import torch import datetime @@ -18,58 +23,52 @@ from DQN.agent import DQN from common.plot import plot_rewards from common.utils import save_results -SEQUENCE = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") # 获取当前时间 -SAVED_MODEL_PATH = os.path.split(os.path.abspath(__file__))[0]+"/saved_model/"+SEQUENCE+'/' # 生成保存的模型路径 -if not os.path.exists(os.path.split(os.path.abspath(__file__))[0]+"/saved_model/"): # 检测是否存在文件夹 - os.mkdir(os.path.split(os.path.abspath(__file__))[0]+"/saved_model/") -if not os.path.exists(SAVED_MODEL_PATH): # 检测是否存在文件夹 +SEQUENCE = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") # obtain current time +SAVED_MODEL_PATH = curr_path+"/saved_model/"+SEQUENCE+'/' # path to save model +if not os.path.exists(curr_path+"/saved_model/"): + os.mkdir(curr_path+"/saved_model/") +if not os.path.exists(SAVED_MODEL_PATH): os.mkdir(SAVED_MODEL_PATH) -RESULT_PATH = os.path.split(os.path.abspath(__file__))[0]+"/results/"+SEQUENCE+'/' # 存储reward的路径 -if not os.path.exists(os.path.split(os.path.abspath(__file__))[0]+"/results/"): # 检测是否存在文件夹 - os.mkdir(os.path.split(os.path.abspath(__file__))[0]+"/results/") -if not os.path.exists(RESULT_PATH): # 检测是否存在文件夹 +RESULT_PATH = curr_path+"/results/"+SEQUENCE+'/' # path to save rewards +if not os.path.exists(curr_path+"/results/"): + os.mkdir(curr_path+"/results/") +if not os.path.exists(RESULT_PATH): os.mkdir(RESULT_PATH) class DQNConfig: def __init__(self): - self.algo = "DQN" # 算法名称 - self.gamma = 0.99 - self.epsilon_start = 0.95 # e-greedy策略的初始epsilon + self.algo = "DQN" # name of algo + self.gamma = 0.95 + self.epsilon_start = 1 # e-greedy策略的初始epsilon self.epsilon_end = 0.01 - self.epsilon_decay = 200 - self.lr = 0.01 # 学习率 - self.memory_capacity = 800 # Replay Memory容量 - self.batch_size = 64 + self.epsilon_decay = 500 + self.lr = 0.0001 # learning rate + self.memory_capacity = 10000 # Replay Memory容量 + self.batch_size = 32 self.train_eps = 300 # 训练的episode数目 - self.train_steps = 200 # 训练每个episode的最大长度 self.target_update = 2 # target net的更新频率 self.eval_eps = 20 # 测试的episode数目 - self.eval_steps = 200 # 测试每个episode的最大长度 self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 检测gpu - self.hidden_dim = 128 # 神经网络隐藏层维度 + self.hidden_dim = 256 # 神经网络隐藏层维度 def train(cfg,env,agent): print('Start to train !') rewards = [] - ma_rewards = [] # 滑动平均的reward - ep_steps = [] + ma_rewards = [] # moveing average reward for i_episode in range(cfg.train_eps): - state = env.reset() # reset环境状态 + state = env.reset() + done = False ep_reward = 0 - for i_step in range(cfg.train_steps): - action = agent.choose_action(state) # 根据当前环境state选择action - next_state, reward, done, _ = env.step(action) # 更新环境参数 + while not done: + action = agent.choose_action(state) + next_state, reward, done, _ = env.step(action) ep_reward += reward - agent.memory.push(state, action, reward, next_state, done) # 将state等这些transition存入memory - state = next_state # 跳转到下一个状态 - agent.update() # 每步更新网络 - if done: - break - # 更新target network,复制DQN中的所有weights and biases + agent.memory.push(state, action, reward, next_state, done) + state = next_state + agent.update() if i_episode % cfg.target_update == 0: agent.target_net.load_state_dict(agent.policy_net.state_dict()) - print('Episode:{}/{}, Reward:{}, Steps:{}, Done:{}'.format(i_episode+1,cfg.train_eps,ep_reward,i_step+1,done)) - ep_steps.append(i_step) + print('Episode:{}/{}, Reward:{}'.format(i_episode+1,cfg.train_eps,ep_reward)) rewards.append(ep_reward) # 计算滑动窗口的reward if ma_rewards: @@ -82,8 +81,8 @@ def train(cfg,env,agent): if __name__ == "__main__": cfg = DQNConfig() - env = gym.make('CartPole-v0').unwrapped # 可google为什么unwrapped gym,此处一般不需要 - env.seed(1) # 设置env随机种子 + env = gym.make('CartPole-v0') + env.seed(1) state_dim = env.observation_space.shape[0] action_dim = env.action_space.n agent = DQN(state_dim,action_dim,cfg) diff --git a/codes/DQN/results/20210313-140409/ma_rewards_train.npy b/codes/DQN/results/20210313-140409/ma_rewards_train.npy deleted file mode 100644 index 4790db2..0000000 Binary files a/codes/DQN/results/20210313-140409/ma_rewards_train.npy and /dev/null differ diff --git a/codes/DQN/results/20210313-140409/rewards_curve_train.png b/codes/DQN/results/20210313-140409/rewards_curve_train.png deleted file mode 100644 index a077d9d..0000000 Binary files a/codes/DQN/results/20210313-140409/rewards_curve_train.png and /dev/null differ diff --git a/codes/DQN/results/20210313-140409/rewards_train.npy b/codes/DQN/results/20210313-140409/rewards_train.npy deleted file mode 100644 index 19992a9..0000000 Binary files a/codes/DQN/results/20210313-140409/rewards_train.npy and /dev/null differ diff --git a/codes/DQN/results/20210326-171704/ma_rewards_train.npy b/codes/DQN/results/20210326-171704/ma_rewards_train.npy deleted file mode 100644 index 2f231bb..0000000 Binary files a/codes/DQN/results/20210326-171704/ma_rewards_train.npy and /dev/null differ diff --git a/codes/DQN/results/20210326-171704/rewards_curve_train.png b/codes/DQN/results/20210326-171704/rewards_curve_train.png deleted file mode 100644 index 0f289b2..0000000 Binary files a/codes/DQN/results/20210326-171704/rewards_curve_train.png and /dev/null differ diff --git a/codes/DQN/results/20210326-171704/rewards_train.npy b/codes/DQN/results/20210326-171704/rewards_train.npy deleted file mode 100644 index 9933915..0000000 Binary files a/codes/DQN/results/20210326-171704/rewards_train.npy and /dev/null differ diff --git a/codes/DQN/results/20210326-171722/ma_rewards_train.npy b/codes/DQN/results/20210326-171722/ma_rewards_train.npy deleted file mode 100644 index 1d9ea32..0000000 Binary files a/codes/DQN/results/20210326-171722/ma_rewards_train.npy and /dev/null differ diff --git a/codes/DQN/results/20210326-171722/rewards_curve_train.png b/codes/DQN/results/20210326-171722/rewards_curve_train.png deleted file mode 100644 index e900e9c..0000000 Binary files a/codes/DQN/results/20210326-171722/rewards_curve_train.png and /dev/null differ diff --git a/codes/DQN/results/20210326-171722/rewards_train.npy b/codes/DQN/results/20210326-171722/rewards_train.npy deleted file mode 100644 index 0351d73..0000000 Binary files a/codes/DQN/results/20210326-171722/rewards_train.npy and /dev/null differ diff --git a/codes/DQN/results/20210330-150205/ma_rewards_train.npy b/codes/DQN/results/20210330-150205/ma_rewards_train.npy new file mode 100644 index 0000000..5005888 Binary files /dev/null and b/codes/DQN/results/20210330-150205/ma_rewards_train.npy differ diff --git a/codes/DQN/results/20210330-150205/rewards_curve_train.png b/codes/DQN/results/20210330-150205/rewards_curve_train.png new file mode 100644 index 0000000..0b596c3 Binary files /dev/null and b/codes/DQN/results/20210330-150205/rewards_curve_train.png differ diff --git a/codes/DQN/results/20210330-150205/rewards_train.npy b/codes/DQN/results/20210330-150205/rewards_train.npy new file mode 100644 index 0000000..df7e676 Binary files /dev/null and b/codes/DQN/results/20210330-150205/rewards_train.npy differ diff --git a/codes/DQN/results/20210330-165925/ma_rewards_train.npy b/codes/DQN/results/20210330-165925/ma_rewards_train.npy new file mode 100644 index 0000000..af251bc Binary files /dev/null and b/codes/DQN/results/20210330-165925/ma_rewards_train.npy differ diff --git a/codes/DQN/results/20210330-165925/rewards_curve_train.png b/codes/DQN/results/20210330-165925/rewards_curve_train.png new file mode 100644 index 0000000..bcee48d Binary files /dev/null and b/codes/DQN/results/20210330-165925/rewards_curve_train.png differ diff --git a/codes/DQN/results/20210330-165925/rewards_train.npy b/codes/DQN/results/20210330-165925/rewards_train.npy new file mode 100644 index 0000000..cc301db Binary files /dev/null and b/codes/DQN/results/20210330-165925/rewards_train.npy differ diff --git a/codes/DQN/saved_model/20210313-140409/dqn_checkpoint.pth b/codes/DQN/saved_model/20210313-140409/dqn_checkpoint.pth deleted file mode 100644 index c685cbc..0000000 Binary files a/codes/DQN/saved_model/20210313-140409/dqn_checkpoint.pth and /dev/null differ diff --git a/codes/DQN/saved_model/20210326-171704/dqn_checkpoint.pth b/codes/DQN/saved_model/20210326-171704/dqn_checkpoint.pth deleted file mode 100644 index 567518a..0000000 Binary files a/codes/DQN/saved_model/20210326-171704/dqn_checkpoint.pth and /dev/null differ diff --git a/codes/DQN/saved_model/20210326-171722/dqn_checkpoint.pth b/codes/DQN/saved_model/20210326-171722/dqn_checkpoint.pth deleted file mode 100644 index b460976..0000000 Binary files a/codes/DQN/saved_model/20210326-171722/dqn_checkpoint.pth and /dev/null differ diff --git a/codes/DQN/saved_model/20210330-150205/dqn_checkpoint.pth b/codes/DQN/saved_model/20210330-150205/dqn_checkpoint.pth new file mode 100644 index 0000000..fe647f9 Binary files /dev/null and b/codes/DQN/saved_model/20210330-150205/dqn_checkpoint.pth differ diff --git a/codes/DQN/saved_model/20210330-165925/dqn_checkpoint.pth b/codes/DQN/saved_model/20210330-165925/dqn_checkpoint.pth new file mode 100644 index 0000000..b5ee3f6 Binary files /dev/null and b/codes/DQN/saved_model/20210330-165925/dqn_checkpoint.pth differ diff --git a/codes/DQN_cnn/main.py b/codes/DQN_cnn/main.py index 6e94e25..d25b2a0 100644 --- a/codes/DQN_cnn/main.py +++ b/codes/DQN_cnn/main.py @@ -5,12 +5,17 @@ @Email: johnjim0816@gmail.com @Date: 2020-06-11 10:01:09 @LastEditor: John -LastEditTime: 2021-03-23 20:43:28 +LastEditTime: 2021-03-29 20:23:48 @Discription: @Environment: python 3.7.7 ''' import sys,os -sys.path.append(os.getcwd()) # add current terminal path to sys.path +from pathlib import Path +import sys,os +curr_path = os.path.dirname(__file__) +parent_path=os.path.dirname(curr_path) +sys.path.append(parent_path) # add current terminal path to sys.path + import gym import torch import datetime @@ -19,17 +24,15 @@ from DQN_cnn.agent import DQNcnn from common.plot import plot_rewards from common.utils import save_results -sys.path.append(os.getcwd()) # add current terminal path to sys.path - SEQUENCE = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") # obtain current time -SAVED_MODEL_PATH = os.path.split(os.path.abspath(__file__))[0]+"/saved_model/"+SEQUENCE+'/' # path to save model -if not os.path.exists(os.path.split(os.path.abspath(__file__))[0]+"/saved_model/"): - os.mkdir(os.path.split(os.path.abspath(__file__))[0]+"/saved_model/") +SAVED_MODEL_PATH = curr_path+"/saved_model/"+SEQUENCE+'/' # path to save model +if not os.path.exists(curr_path+"/saved_model/"): + os.mkdir(curr_path+"/saved_model/") if not os.path.exists(SAVED_MODEL_PATH): os.mkdir(SAVED_MODEL_PATH) -RESULT_PATH = os.path.split(os.path.abspath(__file__))[0]+"/results/"+SEQUENCE+'/' # path to save rewards -if not os.path.exists(os.path.split(os.path.abspath(__file__))[0]+"/results/"): - os.mkdir(os.path.split(os.path.abspath(__file__))[0]+"/results/") +RESULT_PATH = curr_path+"/results/"+SEQUENCE+'/' # path to save rewards +if not os.path.exists(curr_path+"/results/"): + os.mkdir(curr_path+"/results/") if not os.path.exists(RESULT_PATH): os.mkdir(RESULT_PATH) diff --git a/codes/DoubleDQN/memory.py b/codes/DoubleDQN/memory.py deleted file mode 100644 index 52394a5..0000000 --- a/codes/DoubleDQN/memory.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python -# coding=utf-8 -''' -@Author: John -@Email: johnjim0816@gmail.com -@Date: 2020-06-10 15:27:16 -@LastEditor: John -LastEditTime: 2021-01-20 18:58:37 -@Discription: -@Environment: python 3.7.7 -''' -import random - -class ReplayBuffer: - - def __init__(self, capacity): - self.capacity = capacity # buffer的最大容量 - self.buffer = [] - self.position = 0 - - def push(self, state, action, reward, next_state, done): - '''以队列的方式将样本填入buffer中 - ''' - if len(self.buffer) < self.capacity: - self.buffer.append(None) - self.buffer[self.position] = (state, action, reward, next_state, done) - self.position = (self.position + 1) % self.capacity - - def sample(self, batch_size): - '''随机采样batch_size个样本 - ''' - batch = random.sample(self.buffer, batch_size) - state, action, reward, next_state, done = zip(*batch) - return state, action, reward, next_state, done - - def __len__(self): - '''返回buffer的长度 - ''' - return len(self.buffer) - diff --git a/codes/DoubleDQN/model.py b/codes/DoubleDQN/model.py deleted file mode 100644 index 282fa83..0000000 --- a/codes/DoubleDQN/model.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env python -# coding=utf-8 -''' -@Author: John -@Email: johnjim0816@gmail.com -@Date: 2020-06-12 00:47:02 -@LastEditor: John -LastEditTime: 2020-08-19 16:55:54 -@Discription: -@Environment: python 3.7.7 -''' -import torch.nn as nn -import torch.nn.functional as F - -class MLP(nn.Module): - def __init__(self, n_states=4, n_actions=18): - """ 初始化q网络,为全连接网络 - n_states: 输入的feature即环境的state数目 - n_actions: 输出的action总个数 - """ - super(MLP, self).__init__() - self.fc1 = nn.Linear(n_states, 128) # 输入层 - self.fc2 = nn.Linear(128, 128) # 隐藏层 - self.fc3 = nn.Linear(128, n_actions) # 输出层 - - def forward(self, x): - # 各层对应的激活函数 - x = F.relu(self.fc1(x)) - x = F.relu(self.fc2(x)) - return self.fc3(x) \ No newline at end of file diff --git a/codes/DoubleDQN/params.py b/codes/DoubleDQN/params.py deleted file mode 100644 index 75b9f24..0000000 --- a/codes/DoubleDQN/params.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env python -# coding=utf-8 -''' -Author: John -Email: johnjim0816@gmail.com -Date: 2020-12-22 15:22:17 -LastEditor: John -LastEditTime: 2021-01-21 14:30:38 -Discription: -Environment: -''' -import datetime -import os -import argparse - -ALGO_NAME = 'Double DQN' -SEQUENCE = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") -SAVED_MODEL_PATH = os.path.split(os.path.abspath(__file__))[0]+"/saved_model/"+SEQUENCE+'/' -RESULT_PATH = os.path.split(os.path.abspath(__file__))[0]+"/results/"+SEQUENCE+'/' - -TRAIN_LOG_DIR=os.path.split(os.path.abspath(__file__))[0]+"/logs/train/" + SEQUENCE -EVAL_LOG_DIR=os.path.split(os.path.abspath(__file__))[0]+"/logs/eval/" + SEQUENCE - -def get_args(): - '''模型参数 - ''' - parser = argparse.ArgumentParser() - parser.add_argument("--train", default=1, type=int) # 1 表示训练,0表示只进行eval - parser.add_argument("--gamma", default=0.99, - type=float) # q-learning中的gamma - parser.add_argument("--epsilon_start", default=0.95, - type=float) # 基于贪心选择action对应的参数epsilon - parser.add_argument("--epsilon_end", default=0.01, type=float) - parser.add_argument("--epsilon_decay", default=500, type=float) - parser.add_argument("--policy_lr", default=0.01, type=float) - parser.add_argument("--memory_capacity", default=1000, - type=int, help="capacity of Replay Memory") - - parser.add_argument("--batch_size", default=32, type=int, - help="batch size of memory sampling") - parser.add_argument("--train_eps", default=200, type=int) # 训练的最大episode数目 - parser.add_argument("--train_steps", default=200, type=int) - parser.add_argument("--target_update", default=2, type=int, - help="when(every default 2 eisodes) to update target net ") # 更新频率 - - parser.add_argument("--eval_eps", default=100, type=int) # 训练的最大episode数目 - parser.add_argument("--eval_steps", default=200, - type=int) # 训练每个episode的长度 - config = parser.parse_args() - - return config \ No newline at end of file diff --git a/codes/DoubleDQN/plot.py b/codes/DoubleDQN/plot.py deleted file mode 100644 index 1004285..0000000 --- a/codes/DoubleDQN/plot.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python -# coding=utf-8 -''' -@Author: John -@Email: johnjim0816@gmail.com -@Date: 2020-06-11 16:30:09 -@LastEditor: John -LastEditTime: 2020-12-22 15:24:31 -@Discription: -@Environment: python 3.7.7 -''' -import matplotlib.pyplot as plt -import seaborn as sns -import numpy as np -import os -from params import ALGO_NAME -def plot(item,ylabel='rewards_train', save_fig = True): - '''plot using searborn to plot - ''' - sns.set() - plt.figure() - plt.plot(np.arange(len(item)), item) - plt.title(ylabel+' of '+ALGO_NAME) - plt.ylabel(ylabel) - plt.xlabel('episodes') - if save_fig: - plt.savefig(os.path.dirname(__file__)+"/results/"+ylabel+".png") - plt.show() - - - # plt.show() -if __name__ == "__main__": - - output_path = os.path.split(os.path.abspath(__file__))[0]+"/results/" - tag = 'train' - rewards=np.load(output_path+"rewards_"+tag+".npy", ) - moving_average_rewards=np.load(output_path+"moving_average_rewards_"+tag+".npy",) - steps=np.load(output_path+"steps_"+tag+".npy") - plot(rewards) - plot(moving_average_rewards,ylabel='moving_average_rewards_'+tag) - plot(steps,ylabel='steps_'+tag) - tag = 'eval' - rewards=np.load(output_path+"rewards_"+tag+".npy", ) - moving_average_rewards=np.load(output_path+"moving_average_rewards_"+tag+".npy",) - steps=np.load(output_path+"steps_"+tag+".npy") - plot(rewards,ylabel='rewards_'+tag) - plot(moving_average_rewards,ylabel='moving_average_rewards_'+tag) - plot(steps,ylabel='steps_'+tag) diff --git a/codes/DoubleDQN/results/20210317-010120/ma_rewards_train.npy b/codes/DoubleDQN/results/20210317-010120/ma_rewards_train.npy deleted file mode 100644 index a4e7516..0000000 Binary files a/codes/DoubleDQN/results/20210317-010120/ma_rewards_train.npy and /dev/null differ diff --git a/codes/DoubleDQN/results/20210317-010120/rewards_curve_train.png b/codes/DoubleDQN/results/20210317-010120/rewards_curve_train.png deleted file mode 100644 index a776580..0000000 Binary files a/codes/DoubleDQN/results/20210317-010120/rewards_curve_train.png and /dev/null differ diff --git a/codes/DoubleDQN/results/20210317-010120/rewards_train.npy b/codes/DoubleDQN/results/20210317-010120/rewards_train.npy deleted file mode 100644 index c788230..0000000 Binary files a/codes/DoubleDQN/results/20210317-010120/rewards_train.npy and /dev/null differ diff --git a/codes/DoubleDQN/saved_model/20210317-010120/DoubleDQN_checkpoint.pth b/codes/DoubleDQN/saved_model/20210317-010120/DoubleDQN_checkpoint.pth deleted file mode 100644 index 8a43c12..0000000 Binary files a/codes/DoubleDQN/saved_model/20210317-010120/DoubleDQN_checkpoint.pth and /dev/null differ diff --git a/codes/HierarchicalDQN/README.md b/codes/HierarchicalDQN/README.md new file mode 100644 index 0000000..383cdd0 --- /dev/null +++ b/codes/HierarchicalDQN/README.md @@ -0,0 +1,13 @@ +# Hierarchical DQN + +## 原理简介 + +Hierarchical DQN是一种分层强化学习方法,与DQN相比增加了一个meta controller, + +![image-20210331153115575](assets/image-20210331153115575.png) + +即学习时,meta controller每次会生成一个goal,然后controller或者说下面的actor就会达到这个goal,直到done为止。这就相当于给agent增加了一个队长,队长擅长制定局部目标,指导agent前行,这样应对一些每回合步数较长或者稀疏奖励的问题会有所帮助。 + +## 伪代码 + +![image-20210331153542314](assets/image-20210331153542314.png) \ No newline at end of file diff --git a/codes/HierarchicalDQN/agent.py b/codes/HierarchicalDQN/agent.py index 84e79e0..bcfe1fa 100644 --- a/codes/HierarchicalDQN/agent.py +++ b/codes/HierarchicalDQN/agent.py @@ -5,7 +5,7 @@ Author: John Email: johnjim0816@gmail.com Date: 2021-03-24 22:18:18 LastEditor: John -LastEditTime: 2021-03-27 04:24:30 +LastEditTime: 2021-03-31 14:51:09 Discription: Environment: ''' @@ -13,90 +13,103 @@ import torch import torch.nn as nn import numpy as np import random,math -from HierarchicalDQN.model import MLP -from common.memory import ReplayBuffer import torch.optim as optim +from common.model import MLP +from common.memory import ReplayBuffer + class HierarchicalDQN: def __init__(self,state_dim,action_dim,cfg): + self.state_dim = state_dim self.action_dim = action_dim + self.gamma = cfg.gamma self.device = cfg.device self.batch_size = cfg.batch_size - self.sample_count = 0 - self.epsilon = 0 - self.epsilon_start = cfg.epsilon_start - self.epsilon_end = cfg.epsilon_end - self.epsilon_decay = cfg.epsilon_decay - self.batch_size = cfg.batch_size + self.frame_idx = 0 + self.epsilon = lambda frame_idx: cfg.epsilon_end + (cfg.epsilon_start - cfg.epsilon_end ) * math.exp(-1. * frame_idx / cfg.epsilon_decay) self.policy_net = MLP(2*state_dim, action_dim,cfg.hidden_dim).to(self.device) - self.target_net = MLP(2*state_dim, action_dim,cfg.hidden_dim).to(self.device) - self.meta_policy_net = MLP(state_dim, state_dim,cfg.hidden_dim).to(self.device) - self.meta_target_net = MLP(state_dim, state_dim,cfg.hidden_dim).to(self.device) + self.meta_policy_net = MLP(state_dim, state_dim,cfg.hidden_dim).to(self.device) self.optimizer = optim.Adam(self.policy_net.parameters(),lr=cfg.lr) self.meta_optimizer = optim.Adam(self.meta_policy_net.parameters(),lr=cfg.lr) self.memory = ReplayBuffer(cfg.memory_capacity) self.meta_memory = ReplayBuffer(cfg.memory_capacity) - def to_onehot(x): - oh = np.zeros(6) + self.loss_numpy = 0 + self.meta_loss_numpy = 0 + self.losses = [] + self.meta_losses = [] + def to_onehot(self,x): + oh = np.zeros(self.state_dim) oh[x - 1] = 1. return oh - def set_goal(self,meta_state): - self.epsilon = self.epsilon_end + (self.epsilon_start - self.epsilon_end) * math.exp(-1. * self.sample_count / self.epsilon_decay) - self.sample_count += 1 - if random.random() > self.epsilon: + def set_goal(self,state): + if random.random() > self.epsilon(self.frame_idx): with torch.no_grad(): - meta_state = torch.tensor([meta_state], device=self.device, dtype=torch.float32) - q_value = self.policy_net(meta_state) - goal = q_value.max(1)[1].item() + state = torch.tensor(state, device=self.device, dtype=torch.float32).unsqueeze(0) + goal = self.meta_policy_net(state).max(1)[1].item() else: - goal = random.randrange(self.action_dim) - goal = self.meta_policy_net(meta_state) - onehot_goal = self.to_onehot(goal) - return onehot_goal + goal = random.randrange(self.state_dim) + return goal def choose_action(self,state): - self.epsilon = self.epsilon_end + (self.epsilon_start - self.epsilon_end) * math.exp(-1. * self.sample_count / self.epsilon_decay) - self.sample_count += 1 - if random.random() > self.epsilon: + self.frame_idx += 1 + if random.random() > self.epsilon(self.frame_idx): with torch.no_grad(): - state = torch.tensor([state], device=self.device, dtype=torch.float32) + state = torch.tensor(state, device=self.device, dtype=torch.float32).unsqueeze(0) q_value = self.policy_net(state) action = q_value.max(1)[1].item() else: action = random.randrange(self.action_dim) return action def update(self): + self.update_policy() + self.update_meta() + def update_policy(self): if self.batch_size > len(self.memory): - state_batch, action_batch, reward_batch, next_state_batch, done_batch = self.memory.sample(self.batch_size) - state_batch = torch.tensor( - state_batch, device=self.device, dtype=torch.float) - action_batch = torch.tensor(action_batch, device=self.device).unsqueeze(1) - reward_batch = torch.tensor(reward_batch, device=self.device, dtype=torch.float) - next_state_batch = torch.tensor(next_state_batch, device=self.device, dtype=torch.float) - done_batch = torch.tensor(np.float32(done_batch), device=self.device).unsqueeze(1) - q_values = self.policy_net(state_batch).gather(dim=1, index=action_batch) - next_state_values = self.target_net(next_state_batch).max(1)[0].detach() - expected_q_values = reward_batch + self.gamma * next_state_values * (1-done_batch[0]) - loss = nn.MSELoss()(q_values, expected_q_values.unsqueeze(1)) + return + state_batch, action_batch, reward_batch, next_state_batch, done_batch = self.memory.sample(self.batch_size) + state_batch = torch.tensor(state_batch,dtype=torch.float) + action_batch = torch.tensor(action_batch,dtype=torch.int64).unsqueeze(1) + reward_batch = torch.tensor(reward_batch,dtype=torch.float) + next_state_batch = torch.tensor(next_state_batch, dtype=torch.float) + done_batch = torch.tensor(np.float32(done_batch)) + q_values = self.policy_net(state_batch).gather(dim=1, index=action_batch).squeeze(1) + next_state_values = self.policy_net(next_state_batch).max(1)[0].detach() + expected_q_values = reward_batch + 0.99 * next_state_values * (1-done_batch) + loss = nn.MSELoss()(q_values, expected_q_values) self.optimizer.zero_grad() loss.backward() - for param in self.policy_net.parameters(): + for param in self.policy_net.parameters(): # clip防止梯度爆炸 param.grad.data.clamp_(-1, 1) - self.optimizer.step() - + self.optimizer.step() + self.loss_numpy = loss.detach().numpy() + self.losses.append(self.loss_numpy) + def update_meta(self): if self.batch_size > len(self.meta_memory): - meta_state_batch, meta_action_batch, meta_reward_batch, next_meta_state_batch, meta_done_batch = self.memory.sample(self.batch_size) - meta_state_batch = torch.tensor(meta_state_batch, device=self.device, dtype=torch.float) - meta_action_batch = torch.tensor(meta_action_batch, device=self.device).unsqueeze(1) - meta_reward_batch = torch.tensor(meta_reward_batch, device=self.device, dtype=torch.float) - next_meta_state_batch = torch.tensor(next_meta_state_batch, device=self.device, dtype=torch.float) - meta_done_batch = torch.tensor(np.float32(meta_done_batch), device=self.device).unsqueeze(1) - meta_q_values = self.meta_policy_net(meta_state_batch).gather(dim=1, index=meta_action_batch) - next_state_values = self.target_net(next_meta_state_batch).max(1)[0].detach() - expected_meta_q_values = meta_reward_batch + self.gamma * next_state_values * (1-meta_done_batch[0]) - meta_loss = nn.MSEmeta_loss()(meta_q_values, expected_meta_q_values.unsqueeze(1)) + return + state_batch, action_batch, reward_batch, next_state_batch, done_batch = self.meta_memory.sample(self.batch_size) + state_batch = torch.tensor(state_batch,dtype=torch.float) + action_batch = torch.tensor(action_batch,dtype=torch.int64).unsqueeze(1) + reward_batch = torch.tensor(reward_batch,dtype=torch.float) + next_state_batch = torch.tensor(next_state_batch, dtype=torch.float) + done_batch = torch.tensor(np.float32(done_batch)) + q_values = self.meta_policy_net(state_batch).gather(dim=1, index=action_batch).squeeze(1) + next_state_values = self.meta_policy_net(next_state_batch).max(1)[0].detach() + expected_q_values = reward_batch + 0.99 * next_state_values * (1-done_batch) + meta_loss = nn.MSELoss()(q_values, expected_q_values) self.meta_optimizer.zero_grad() meta_loss.backward() - for param in self.meta_policy_net.parameters(): + for param in self.meta_policy_net.parameters(): # clip防止梯度爆炸 param.grad.data.clamp_(-1, 1) self.meta_optimizer.step() + self.meta_loss_numpy = meta_loss.detach().numpy() + self.meta_losses.append(self.meta_loss_numpy) + + def save(self, path): + torch.save(self.policy_net.state_dict(), path+'policy_checkpoint.pth') + torch.save(self.meta_policy_net.state_dict(), path+'meta_checkpoint.pth') + + def load(self, path): + self.policy_net.load_state_dict(torch.load(path+'policy_checkpoint.pth')) + self.meta_policy_net.load_state_dict(torch.load(path+'meta_checkpoint.pth')) + + \ No newline at end of file diff --git a/codes/HierarchicalDQN/assets/image-20210331153115575.png b/codes/HierarchicalDQN/assets/image-20210331153115575.png new file mode 100644 index 0000000..5bb9251 Binary files /dev/null and b/codes/HierarchicalDQN/assets/image-20210331153115575.png differ diff --git a/codes/HierarchicalDQN/assets/image-20210331153542314.png b/codes/HierarchicalDQN/assets/image-20210331153542314.png new file mode 100644 index 0000000..6db2d82 Binary files /dev/null and b/codes/HierarchicalDQN/assets/image-20210331153542314.png differ diff --git a/codes/HierarchicalDQN/main.ipynb b/codes/HierarchicalDQN/main.ipynb new file mode 100644 index 0000000..c63e950 --- /dev/null +++ b/codes/HierarchicalDQN/main.ipynb @@ -0,0 +1,477 @@ +{ + "metadata": { + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.10-final" + }, + "orig_nbformat": 2, + "kernelspec": { + "name": "python3", + "display_name": "Python 3.7.10 64-bit ('py37': conda)", + "metadata": { + "interpreter": { + "hash": "fbea1422c2cf61ed9c0cfc03f38f71cc9083cc288606edc4170b5309b352ce27" + } + } + } + }, + "nbformat": 4, + "nbformat_minor": 2, + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import sys,os\n", + "from pathlib import Path\n", + "curr_path = str(Path().absolute())\n", + "parent_path = str(Path().absolute().parent)\n", + "sys.path.append(parent_path) # add current terminal path to sys.path\n", + "\n", + "import gym\n", + "import torch\n", + "import numpy as np\n", + "import datetime\n", + "\n", + "from HierarchicalDQN.agent import HierarchicalDQN\n", + "from common.plot import plot_rewards\n", + "from common.utils import save_results" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "SEQUENCE = datetime.datetime.now().strftime(\n", + " \"%Y%m%d-%H%M%S\") # obtain current time\n", + "SAVED_MODEL_PATH = curr_path+\"/saved_model/\"+SEQUENCE+'/' # path to save model\n", + "if not os.path.exists(curr_path+\"/saved_model/\"):\n", + " os.mkdir(curr_path+\"/saved_model/\")\n", + "if not os.path.exists(SAVED_MODEL_PATH):\n", + " os.mkdir(SAVED_MODEL_PATH)\n", + "RESULT_PATH = curr_path+\"/results/\"+SEQUENCE+'/' # path to save rewards\n", + "if not os.path.exists(curr_path+\"/results/\"):\n", + " os.mkdir(curr_path+\"/results/\")\n", + "if not os.path.exists(RESULT_PATH):\n", + " os.mkdir(RESULT_PATH)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "class HierarchicalDQNConfig:\n", + " def __init__(self):\n", + " self.algo = \"H-DQN\" # name of algo\n", + " self.gamma = 0.95\n", + " self.epsilon_start = 1 # start epsilon of e-greedy policy\n", + " self.epsilon_end = 0.01\n", + " self.epsilon_decay = 500\n", + " self.lr = 0.0001 # learning rate\n", + " self.memory_capacity = 20000 # Replay Memory capacity\n", + " self.batch_size = 64\n", + " self.train_eps = 300 # 训练的episode数目\n", + " self.target_update = 2 # target net的更新频率\n", + " self.eval_eps = 20 # 测试的episode数目\n", + " self.device = torch.device(\n", + " \"cuda\" if torch.cuda.is_available() else \"cpu\") # 检测gpu\n", + " self.hidden_dim = 256 # dimension of hidden layer" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "def train(cfg, env, agent):\n", + " print('Start to train !')\n", + " rewards = []\n", + " ma_rewards = [] # moveing average reward\n", + " for i_episode in range(cfg.train_eps):\n", + " state = env.reset()\n", + " done = False\n", + " ep_reward = 0\n", + " while not done:\n", + " goal = agent.set_goal(state)\n", + " onehot_goal = agent.to_onehot(goal)\n", + " meta_state = state\n", + " extrinsic_reward = 0\n", + " while not done and goal != np.argmax(state):\n", + " goal_state = np.concatenate([state, onehot_goal])\n", + " action = agent.choose_action(goal_state)\n", + " next_state, reward, done, _ = env.step(action)\n", + " ep_reward += reward\n", + " extrinsic_reward += reward\n", + " intrinsic_reward = 1.0 if goal == np.argmax(\n", + " next_state) else 0.0\n", + " agent.memory.push(goal_state, action, intrinsic_reward, np.concatenate(\n", + " [next_state, onehot_goal]), done)\n", + " state = next_state\n", + " agent.update()\n", + " agent.meta_memory.push(meta_state, goal, extrinsic_reward, state, done)\n", + " print('Episode:{}/{}, Reward:{}'.format(i_episode+1, cfg.train_eps, ep_reward))\n", + " rewards.append(ep_reward)\n", + " if ma_rewards:\n", + " ma_rewards.append(\n", + " 0.9*ma_rewards[-1]+0.1*ep_reward)\n", + " else:\n", + " ma_rewards.append(ep_reward)\n", + " print('Complete training!')\n", + " return rewards, ma_rewards" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Start to train !\n", + "Episode:1/300, Reward:25.0\n", + "Episode:2/300, Reward:26.0\n", + "Episode:3/300, Reward:23.0\n", + "Episode:4/300, Reward:19.0\n", + "Episode:5/300, Reward:23.0\n", + "Episode:6/300, Reward:21.0\n", + "Episode:7/300, Reward:21.0\n", + "Episode:8/300, Reward:22.0\n", + "Episode:9/300, Reward:15.0\n", + "Episode:10/300, Reward:12.0\n", + "Episode:11/300, Reward:39.0\n", + "Episode:12/300, Reward:42.0\n", + "Episode:13/300, Reward:79.0\n", + "Episode:14/300, Reward:54.0\n", + "Episode:15/300, Reward:28.0\n", + "Episode:16/300, Reward:85.0\n", + "Episode:17/300, Reward:46.0\n", + "Episode:18/300, Reward:37.0\n", + "Episode:19/300, Reward:45.0\n", + "Episode:20/300, Reward:79.0\n", + "Episode:21/300, Reward:80.0\n", + "Episode:22/300, Reward:154.0\n", + "Episode:23/300, Reward:74.0\n", + "Episode:24/300, Reward:129.0\n", + "Episode:25/300, Reward:185.0\n", + "Episode:26/300, Reward:200.0\n", + "Episode:27/300, Reward:115.0\n", + "Episode:28/300, Reward:104.0\n", + "Episode:29/300, Reward:200.0\n", + "Episode:30/300, Reward:118.0\n", + "Episode:31/300, Reward:200.0\n", + "Episode:32/300, Reward:200.0\n", + "Episode:33/300, Reward:83.0\n", + "Episode:34/300, Reward:75.0\n", + "Episode:35/300, Reward:46.0\n", + "Episode:36/300, Reward:96.0\n", + "Episode:37/300, Reward:78.0\n", + "Episode:38/300, Reward:150.0\n", + "Episode:39/300, Reward:147.0\n", + "Episode:40/300, Reward:74.0\n", + "Episode:41/300, Reward:137.0\n", + "Episode:42/300, Reward:182.0\n", + "Episode:43/300, Reward:200.0\n", + "Episode:44/300, Reward:200.0\n", + "Episode:45/300, Reward:200.0\n", + "Episode:46/300, Reward:184.0\n", + "Episode:47/300, Reward:200.0\n", + "Episode:48/300, Reward:200.0\n", + "Episode:49/300, Reward:200.0\n", + "Episode:50/300, Reward:61.0\n", + "Episode:51/300, Reward:9.0\n", + "Episode:52/300, Reward:9.0\n", + "Episode:53/300, Reward:200.0\n", + "Episode:54/300, Reward:200.0\n", + "Episode:55/300, Reward:200.0\n", + "Episode:56/300, Reward:200.0\n", + "Episode:57/300, Reward:200.0\n", + "Episode:58/300, Reward:200.0\n", + "Episode:59/300, Reward:200.0\n", + "Episode:60/300, Reward:167.0\n", + "Episode:61/300, Reward:200.0\n", + "Episode:62/300, Reward:200.0\n", + "Episode:63/300, Reward:200.0\n", + "Episode:64/300, Reward:200.0\n", + "Episode:65/300, Reward:200.0\n", + "Episode:66/300, Reward:200.0\n", + "Episode:67/300, Reward:200.0\n", + "Episode:68/300, Reward:200.0\n", + "Episode:69/300, Reward:197.0\n", + "Episode:70/300, Reward:200.0\n", + "Episode:71/300, Reward:200.0\n", + "Episode:72/300, Reward:200.0\n", + "Episode:73/300, Reward:200.0\n", + "Episode:74/300, Reward:200.0\n", + "Episode:75/300, Reward:200.0\n", + "Episode:76/300, Reward:200.0\n", + "Episode:77/300, Reward:200.0\n", + "Episode:78/300, Reward:200.0\n", + "Episode:79/300, Reward:200.0\n", + "Episode:80/300, Reward:200.0\n", + "Episode:81/300, Reward:181.0\n", + "Episode:82/300, Reward:200.0\n", + "Episode:83/300, Reward:200.0\n", + "Episode:84/300, Reward:200.0\n", + "Episode:85/300, Reward:200.0\n", + "Episode:86/300, Reward:200.0\n", + "Episode:87/300, Reward:200.0\n", + "Episode:88/300, Reward:200.0\n", + "Episode:89/300, Reward:200.0\n", + "Episode:90/300, Reward:200.0\n", + "Episode:91/300, Reward:200.0\n", + "Episode:92/300, Reward:200.0\n", + "Episode:93/300, Reward:200.0\n", + "Episode:94/300, Reward:200.0\n", + "Episode:95/300, Reward:200.0\n", + "Episode:96/300, Reward:200.0\n", + "Episode:97/300, Reward:200.0\n", + "Episode:98/300, Reward:200.0\n", + "Episode:99/300, Reward:192.0\n", + "Episode:100/300, Reward:183.0\n", + "Episode:101/300, Reward:200.0\n", + "Episode:102/300, Reward:200.0\n", + "Episode:103/300, Reward:200.0\n", + "Episode:104/300, Reward:200.0\n", + "Episode:105/300, Reward:200.0\n", + "Episode:106/300, Reward:200.0\n", + "Episode:107/300, Reward:200.0\n", + "Episode:108/300, Reward:200.0\n", + "Episode:109/300, Reward:200.0\n", + "Episode:110/300, Reward:200.0\n", + "Episode:111/300, Reward:200.0\n", + "Episode:112/300, Reward:200.0\n", + "Episode:113/300, Reward:200.0\n", + "Episode:114/300, Reward:200.0\n", + "Episode:115/300, Reward:200.0\n", + "Episode:116/300, Reward:200.0\n", + "Episode:117/300, Reward:200.0\n", + "Episode:118/300, Reward:200.0\n", + "Episode:119/300, Reward:200.0\n", + "Episode:120/300, Reward:196.0\n", + "Episode:121/300, Reward:200.0\n", + "Episode:122/300, Reward:200.0\n", + "Episode:123/300, Reward:200.0\n", + "Episode:124/300, Reward:200.0\n", + "Episode:125/300, Reward:200.0\n", + "Episode:126/300, Reward:189.0\n", + "Episode:127/300, Reward:193.0\n", + "Episode:128/300, Reward:200.0\n", + "Episode:129/300, Reward:200.0\n", + "Episode:130/300, Reward:193.0\n", + "Episode:131/300, Reward:183.0\n", + "Episode:132/300, Reward:183.0\n", + "Episode:133/300, Reward:200.0\n", + "Episode:134/300, Reward:200.0\n", + "Episode:135/300, Reward:200.0\n", + "Episode:136/300, Reward:200.0\n", + "Episode:137/300, Reward:200.0\n", + "Episode:138/300, Reward:200.0\n", + "Episode:139/300, Reward:100.0\n", + "Episode:140/300, Reward:118.0\n", + "Episode:141/300, Reward:99.0\n", + "Episode:142/300, Reward:185.0\n", + "Episode:143/300, Reward:41.0\n", + "Episode:144/300, Reward:11.0\n", + "Episode:145/300, Reward:9.0\n", + "Episode:146/300, Reward:152.0\n", + "Episode:147/300, Reward:155.0\n", + "Episode:148/300, Reward:181.0\n", + "Episode:149/300, Reward:197.0\n", + "Episode:150/300, Reward:200.0\n", + "Episode:151/300, Reward:200.0\n", + "Episode:152/300, Reward:200.0\n", + "Episode:153/300, Reward:200.0\n", + "Episode:154/300, Reward:200.0\n", + "Episode:155/300, Reward:200.0\n", + "Episode:156/300, Reward:123.0\n", + "Episode:157/300, Reward:11.0\n", + "Episode:158/300, Reward:8.0\n", + "Episode:159/300, Reward:9.0\n", + "Episode:160/300, Reward:10.0\n", + "Episode:161/300, Reward:9.0\n", + "Episode:162/300, Reward:10.0\n", + "Episode:163/300, Reward:9.0\n", + "Episode:164/300, Reward:9.0\n", + "Episode:165/300, Reward:10.0\n", + "Episode:166/300, Reward:9.0\n", + "Episode:167/300, Reward:9.0\n", + "Episode:168/300, Reward:9.0\n", + "Episode:169/300, Reward:9.0\n", + "Episode:170/300, Reward:10.0\n", + "Episode:171/300, Reward:9.0\n", + "Episode:172/300, Reward:9.0\n", + "Episode:173/300, Reward:11.0\n", + "Episode:174/300, Reward:11.0\n", + "Episode:175/300, Reward:10.0\n", + "Episode:176/300, Reward:9.0\n", + "Episode:177/300, Reward:10.0\n", + "Episode:178/300, Reward:8.0\n", + "Episode:179/300, Reward:9.0\n", + "Episode:180/300, Reward:9.0\n", + "Episode:181/300, Reward:10.0\n", + "Episode:182/300, Reward:10.0\n", + "Episode:183/300, Reward:9.0\n", + "Episode:184/300, Reward:10.0\n", + "Episode:185/300, Reward:10.0\n", + "Episode:186/300, Reward:13.0\n", + "Episode:187/300, Reward:16.0\n", + "Episode:188/300, Reward:117.0\n", + "Episode:189/300, Reward:13.0\n", + "Episode:190/300, Reward:16.0\n", + "Episode:191/300, Reward:11.0\n", + "Episode:192/300, Reward:11.0\n", + "Episode:193/300, Reward:13.0\n", + "Episode:194/300, Reward:13.0\n", + "Episode:195/300, Reward:9.0\n", + "Episode:196/300, Reward:20.0\n", + "Episode:197/300, Reward:12.0\n", + "Episode:198/300, Reward:10.0\n", + "Episode:199/300, Reward:14.0\n", + "Episode:200/300, Reward:12.0\n", + "Episode:201/300, Reward:14.0\n", + "Episode:202/300, Reward:12.0\n", + "Episode:203/300, Reward:11.0\n", + "Episode:204/300, Reward:10.0\n", + "Episode:205/300, Reward:13.0\n", + "Episode:206/300, Reward:10.0\n", + "Episode:207/300, Reward:10.0\n", + "Episode:208/300, Reward:13.0\n", + "Episode:209/300, Reward:9.0\n", + "Episode:210/300, Reward:11.0\n", + "Episode:211/300, Reward:14.0\n", + "Episode:212/300, Reward:10.0\n", + "Episode:213/300, Reward:20.0\n", + "Episode:214/300, Reward:12.0\n", + "Episode:215/300, Reward:13.0\n", + "Episode:216/300, Reward:17.0\n", + "Episode:217/300, Reward:17.0\n", + "Episode:218/300, Reward:11.0\n", + "Episode:219/300, Reward:15.0\n", + "Episode:220/300, Reward:26.0\n", + "Episode:221/300, Reward:73.0\n", + "Episode:222/300, Reward:44.0\n", + "Episode:223/300, Reward:48.0\n", + "Episode:224/300, Reward:102.0\n", + "Episode:225/300, Reward:162.0\n", + "Episode:226/300, Reward:123.0\n", + "Episode:227/300, Reward:200.0\n", + "Episode:228/300, Reward:200.0\n", + "Episode:229/300, Reward:120.0\n", + "Episode:230/300, Reward:173.0\n", + "Episode:231/300, Reward:138.0\n", + "Episode:232/300, Reward:106.0\n", + "Episode:233/300, Reward:193.0\n", + "Episode:234/300, Reward:117.0\n", + "Episode:235/300, Reward:120.0\n", + "Episode:236/300, Reward:98.0\n", + "Episode:237/300, Reward:98.0\n", + "Episode:238/300, Reward:200.0\n", + "Episode:239/300, Reward:96.0\n", + "Episode:240/300, Reward:170.0\n", + "Episode:241/300, Reward:107.0\n", + "Episode:242/300, Reward:107.0\n", + "Episode:243/300, Reward:200.0\n", + "Episode:244/300, Reward:128.0\n", + "Episode:245/300, Reward:165.0\n", + "Episode:246/300, Reward:168.0\n", + "Episode:247/300, Reward:200.0\n", + "Episode:248/300, Reward:200.0\n", + "Episode:249/300, Reward:200.0\n", + "Episode:250/300, Reward:200.0\n", + "Episode:251/300, Reward:200.0\n", + "Episode:252/300, Reward:200.0\n", + "Episode:253/300, Reward:200.0\n", + "Episode:254/300, Reward:200.0\n", + "Episode:255/300, Reward:200.0\n", + "Episode:256/300, Reward:200.0\n", + "Episode:257/300, Reward:164.0\n", + "Episode:258/300, Reward:200.0\n", + "Episode:259/300, Reward:190.0\n", + "Episode:260/300, Reward:185.0\n", + "Episode:261/300, Reward:200.0\n", + "Episode:262/300, Reward:200.0\n", + "Episode:263/300, Reward:200.0\n", + "Episode:264/300, Reward:200.0\n", + "Episode:265/300, Reward:168.0\n", + "Episode:266/300, Reward:200.0\n", + "Episode:267/300, Reward:200.0\n", + "Episode:268/300, Reward:200.0\n", + "Episode:269/300, Reward:200.0\n", + "Episode:270/300, Reward:200.0\n", + "Episode:271/300, Reward:200.0\n", + "Episode:272/300, Reward:200.0\n", + "Episode:273/300, Reward:200.0\n", + "Episode:274/300, Reward:200.0\n", + "Episode:275/300, Reward:188.0\n", + "Episode:276/300, Reward:200.0\n", + "Episode:277/300, Reward:177.0\n", + "Episode:278/300, Reward:200.0\n", + "Episode:279/300, Reward:200.0\n", + "Episode:280/300, Reward:200.0\n", + "Episode:281/300, Reward:200.0\n", + "Episode:282/300, Reward:200.0\n", + "Episode:283/300, Reward:200.0\n", + "Episode:284/300, Reward:189.0\n", + "Episode:285/300, Reward:200.0\n", + "Episode:286/300, Reward:200.0\n", + "Episode:287/300, Reward:200.0\n", + "Episode:288/300, Reward:200.0\n", + "Episode:289/300, Reward:200.0\n", + "Episode:290/300, Reward:200.0\n", + "Episode:291/300, Reward:200.0\n", + "Episode:292/300, Reward:200.0\n", + "Episode:293/300, Reward:200.0\n", + "Episode:294/300, Reward:200.0\n", + "Episode:295/300, Reward:200.0\n", + "Episode:296/300, Reward:200.0\n", + "Episode:297/300, Reward:200.0\n", + "Episode:298/300, Reward:200.0\n", + "Episode:299/300, Reward:200.0\n", + "Episode:300/300, Reward:200.0\n", + "Complete training!\n", + "results saved!\n" + ] + }, + { + "output_type": "display_data", + "data": { + "text/plain": "
", + "image/svg+xml": "\n\n\n\n \n \n \n \n 2021-03-31T14:01:15.395751\n image/svg+xml\n \n \n Matplotlib v3.3.4, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXkAAAEXCAYAAABI/TQXAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAACOjUlEQVR4nO2dd5jc1NWHX0nTt3qbe8HdBlwwxaaZ5oIL1XRwgBBIQiCQfHyAMYQ4MSHAB4EQICFAEkqAmBIghB4IYKoBG4Mxxr1ur7NTpfv9oZFGMzuzO9ub3ufx41mNpLlX5ejod889RxJCCGxsbGxs+iRydzfAxsbGxqbzsI28jY2NTR/GNvI2NjY2fRjbyNvY2Nj0YWwjb2NjY9OHsY28jY2NTR/GNvI23crvf/97VqxY0SW/9YMf/IDvvvuuS36ru7n//vs55phjuP766xOW79q1i+nTpzdZv7nzcN1113HUUUdx8sknc/LJJ7NgwQJ+/vOfU15enrDe008/zWmnncaiRYtYuHAh11xzDbt3707Yz/z582lsbEzYbvr06ezatautXbVpAUd3N8DGpqt48MEHu7sJXcaqVau44447OPjggztkfxdeeCHf//73ARBC8Mc//pFLLrmEZ599FkVRuP3221m3bh333XcfgwYNQtM0XnjhBc466yz+8Y9/MHjwYAB2797NypUrWblyZYe0y6ZlbCPfB9A0jVtuuYW1a9fi9/sRQvDrX/+a8ePHM3v2bF599VWKi4sBOPPMM7n88suZNWsWd9xxB5988gmqqjJ58mSWL19OdnY2xx13HFOmTGHjxo387Gc/w+Fw8Mc//pFwOExVVRWnnHIKV111FQB/+tOfWLVqFVlZWRx88MG8+eabvPXWW4TD4bT7T0dpaSkrVqxg7969RCIRFi5cyA9/+EMAHnjgAd544w1CoRCBQIBrr72WOXPm8Pvf/54vvviCsrIyJkyYwMiRI9m9ezfl5eXs3r2bgoIC7rrrLgYOHMhxxx3H3XffTWNjI3fddRfDhw9n06ZNhMNhbrrpJmbOnElVVRXXX389O3bsID8/n+LiYsaNG8cVV1yR0Fa/38+vf/1rPvvsMxRF4YQTTuDqq6/m+uuvZ9y4caZBvO6668y/rcf1iiuu4P777+fFF18EoK6ujuOPP5433niDYDCY9jhY2bdvHzfffDO7d+9GCMEpp5zCJZdcwlVXXUVpaSk33HADP/3pT1mwYEG7rzErkiTxwx/+kOeee47333+fiRMn8vjjj/Paa69RUlICgCzLnHLKKaxfv54//vGP3HzzzQAsXbqUf/7zn7z66qvMmzevQ9tlkxpbrukDrF27lrKyMp566ilefvllTj31VB588EFycnKYM2cOL7zwAgCbN2+mvLyco446ij/96U8oisKzzz7LCy+8QElJCXfccYe5z3HjxvHvf/+bE044gYcffphbb72VZ599lqeeeoo//elPVFVV8e677/Lss8+yatUqnn32Wfx+v7l9S/tPxTXXXMPpp59u7nP16tW8/PLL7N69m9WrV/PYY4/x4osvcvXVV3PPPfeY2+3evZvnnnvO3P+nn37K3XffzSuvvEJubi5PPfVUk99at24dF198Mc8//zxLlizh3nvvBeDXv/41Y8eO5d///jd33303n332Wcq23nPPPYRCIV5++WWef/55PvvsMz7++OMWz5VxXE888UT8fj9ffvklAC+99BKzZ88mLy8v7XFI5n/+53847LDDePHFF/n73//OCy+8wL/+9S9+97vfmcc7lYEPBoOm9GL8e/LJJ1tsezITJkzg22+/Ze3atQwfPtw08FaOOOKIhGNYUFDArbfeyk033cTevXtb/Zs2rcf25PsA06dPJy8vjyeffJKdO3fy0UcfkZWVBcAZZ5zBL3/5S77//e/zzDPPcNpppyHLMm+//Tb19fWsXr0agEgkQmFhoblP4zVfkiQeeOAB3n77bV566SU2b96MEIJAIMA777zD/Pnzyc3NBeC8887jww8/BGhx/8k0NjbyySefUFtby913320u++abb1iwYAG//e1vefHFF9m+fbv5xmIwbdo0HI74pXzooYeabwyTJ0+mtra2ye8NGTKESZMmmes899xzALzzzjvm55KSEubPn5+yvatXr+b6669HURQUReGxxx4DMLdNh/W4LlmyhOeee44DDzyQZ599lmuuuabF42A9Xp999hkPP/wwADk5OZx22mn897//ZeHChc22wePx8M9//jNh2e9//3uqq6ub3S4ZSZLwer0trqdpWsLfRx55JKeeeirXXHMNf/vb31r1mzatxzbyfYC3336blStXctFFF3H88cczevRo03s/+OCDiUajrFu3jpdeesn02DRNY9myZcyePRvQ5YdQKGTu0+fzAboxOfXUUznhhBM4+OCDOf3003njjTcQQuBwOLCmPlIUxfzc0v6T0TQNIQRPPvmkaTiqqqpwu9189dVX/PjHP+bCCy/kiCOO4JBDDuGXv/xlk7YaeDwe87MkSaRKz5RuneQ+yXLql12Hw4EkSebfe/fuxePxNPm9SCSSsJ21raeffjqnnHIKZ5xxBvX19Rx22GE0NDSkPQ6pjlfysmg0mrK9mVJaWsqll15q/v2nP/0p5XpCCL766ivOP/98Ro4cyc6dOykrKzO9+dLSUgYOHMiHH37ItGnTmmz/s5/9jLPOOosHHnigXe21aRlbrukDvP/++xx77LGce+65HHjggbzxxhuoqmp+f8YZZ/CrX/2KCRMmMGTIEED3ph5//HHC4TCapnHjjTdy5513Ntn39u3baWho4KqrruK4447j448/NreZPXs2r732GvX19YA+2GeQ6f4NsrOzmTZtGo888giga9TnnHMOb775Jp988gkHHHAAF110EYceeihvvvlmQv86ktmzZ5v9qK6u5o033kgw5gazZs3iueeeQ9M0wuEwV155JZ988gkDBgxg/fr1gG6cP/3007S/NXDgQKZOncpNN93EkiVLgOaPg5Xs7GymTp3K448/DkB9fT3PP/88hx9+eLv6P3DgQP75z3+a/wYOHNhkHVVV+cMf/sCAAQM45JBDKCkp4YILLuDnP/85paWlVFdX88Mf/pDLL7+cf/3rX1x22WVN9uFyufi///s/Hn74YYLBYLvabNM8tpHvA5x99tl88sknLF68mLPOOovhw4eza9cu8zX5lFNOYcOGDZxxxhnmNj/+8Y8ZOnQop556KgsWLEAIwXXXXddk3xMmTOCYY47hxBNP5NRTT+Wtt95i7NixbN++nVmzZnHmmWdy1llncdppp1FfX296n5nu38odd9zB2rVrWbx4MWeccQaLFi3ipJNOYtGiRVRXV7NgwQJOO+00fD4ftbW1NDQ0dOBR1Ln++uvZsmULixcv5sorr2TIkCEJXr/BT37yE5xOJyeffDKnnHIKs2fPZu7cuVxwwQWUl5czb948rrnmGg499NBmf++MM85gw4YNnHrqqS0eh2TuuOMOPvjgAxYvXsySJUuYO3cup512WvsPQgr+8pe/mH095ZRT2LNnT4KX//Of/5zFixfzox/9iPPPP988N0VFRbz22msp9zl6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wdChQ3ttn9Kdj+Tl5eXlPb5PGzdu5NVXXzX/FkLgcDh6xTW3Zs0aLrzwQn7+859z6qmn9urzktyX3npeNm/ezIYNGwDwer3MnTuXjz76qNPPS6838r09tXF9fT233XYboVCIhoYGnnvuOW6//fZe26epU6eydetWtm/fjqqqvPTSSxx99NEMHToUt9vNmjVrAHj++ed7fJ+EENxyyy3U1tYSiUR46qmnmDNnTto+9hT27t3L5Zdfzh133MHChQuB3nteUvWlt56XXbt2sXz5csLhMOFwmDfffJOzzz67089Lr9fke3tq42OPPZa1a9dyyimnoGka5557LjNmzOi1fXK73dx6661cccUVhEIhZs+ezfz58wG44447WL58OX6/n8mTJ7N06dJubm3zTJw4kUsvvZRzzjmHaDTK3LlzWbRoEUDaPvYEHnroIUKhELfeequ57Oyzz+6V5yVdX3rjeZk9e7Z5ryuKwty5c1m4cCEFBQWdel7sVMM2NjY2fZheL9fY2NjY2KTHNvI2NjY2fRjbyNvY2Nj0YWwjb2NjY9OHsY28jY2NTR/GNvI2Nin4wQ9+wHfffdeqbS677DKeffbZTmqRjU3b6PVx8jY2ncGDDz7Y3U2wsekQbCNv06d46623uP/++4lEIng8Hq699lree+89Nm3aREVFBZWVlUycOJGVK1eSnZ3NE088wZNPPonT6cTtdrNixQrGjh3Lcccdx913382BBx7IU089xaOPPoosyxQVFXHjjTey3377UVpaynXXXUdZWRlDhgyhsrLSbMfmzZtZuXIlNTU1qKrKBRdcwJIlS/D7/Vx//fVs374dWZbZf//9WbFiBbJsv1TbdBJtz45sY9Oz2Lp1q1i0aJGoqqoSQgjx7bffiiOOOELceuut4uijjxbl5eVCVVXxs5/9TNx6660iGo2K/fff38xL/txzz4knn3xSCCHEscceK9atWydWr14tTjjhBFFZWSmEEOKZZ54RJ554otA0Tfz4xz8Wd911lxBCiG3btolp06aJZ555RkQiEbFgwQKxfv16IYQQdXV14sQTTxSff/65eO6558TFF18shBAiGo2KG264QWzbtq0rD5NNP8P25G36DO+//z5lZWVceOGF5jJJktixYwfz58+nqKgIgCVLlnDLLbdw7bXXMn/+fM4++2yOOeYYjjjiCBYvXpywz3fffZcFCxZQUFAA6Hm9V65cya5du1i9ejXXXnstACNHjuSwww4D9JS4O3bsYNmyZeZ+gsEgX3/9NUcddRR33XUXF1xwAYcffjjf+973GDlyZGceFpt+jm3kbfoMmqYxa9Ysfve735nL9u7dy1NPPUU4HE5Yz5BH7rjjDr799ltWr17Ngw8+yKpVq7j//vvNdUWKrB9CCKLRaJOUsA6Hfjupqkpubi7//Oc/ze8qKirIycnB7Xbz+uuv89FHH/Hhhx9y0UUXsXz58h6VY8Wmb2ELgTZ9hpkzZ/L++++zefNmAN555x1OOukkQqEQb775JvX19WiaxtNPP82xxx5LVVUVs2fPJj8/nwsvvJCrrrqKjRs3JuzzyCOP5OWXX6aqqgqAZ555hvz8fEaOHMlRRx3FU089BcCePXv46KOPANhvv/1wu92mkd+7dy+LFi1i/fr1PPHEE1x//fUceeSRXHPNNRx55JFs2rSpqw6RTT/ETlBm06f497//zQMPPGDmGF+2bBkffPABH374IaqqUl1dzSGHHMLy5cvxeDw8+eST/O1vf8Pj8aAoCldffTWHH354wsDr448/zpNPPommaRQUFHDTTTcxbtw4qqqquP7669mxYweDBg0iGo1y6qmnctppp/HNN9+YA6/RaJSlS5dyzjnn0NjYyLJly9i4cSNer5chQ4awcuVK8vLyuvvQ2fRRbCNv0+f5/e9/T3V1NTfddFN3N8XGpsux5RobGxubPoztydvY2Nj0YWxP3sbGxqYPYxt5Gxsbmz6MbeRtbGxs+jC2kbexsbHpw9hG3sbGxqYPYxt5Gxsbmz7M/wMcC4nelkepKAAAAABJRU5ErkJggg==\n" + }, + "metadata": {} + } + ], + "source": [ + "env = gym.make('CartPole-v0')\n", + "env.seed(1)\n", + "cfg = HierarchicalDQNConfig()\n", + "state_dim = env.observation_space.shape[0]\n", + "action_dim = env.action_space.n\n", + "agent = HierarchicalDQN(state_dim, action_dim, cfg)\n", + "rewards, ma_rewards = train(cfg, env, agent)\n", + "agent.save(path=SAVED_MODEL_PATH)\n", + "save_results(rewards, ma_rewards, tag='train', path=RESULT_PATH)\n", + "plot_rewards(rewards, ma_rewards, tag=\"train\",\n", + " algo=cfg.algo, path=RESULT_PATH)" + ] + } + ] +} \ No newline at end of file diff --git a/codes/HierarchicalDQN/main.py b/codes/HierarchicalDQN/main.py index 5ecd02f..ea6dfdc 100644 --- a/codes/HierarchicalDQN/main.py +++ b/codes/HierarchicalDQN/main.py @@ -3,95 +3,108 @@ ''' Author: John Email: johnjim0816@gmail.com -Date: 2021-03-24 22:14:04 +Date: 2021-03-29 10:37:32 LastEditor: John -LastEditTime: 2021-03-27 04:23:43 +LastEditTime: 2021-03-31 14:58:49 Discription: Environment: ''' + + import sys,os -sys.path.append(os.getcwd()) # add current terminal path to sys.path -import gym +curr_path = os.path.dirname(__file__) +parent_path = os.path.dirname(curr_path) +sys.path.append(parent_path) # add current terminal path to sys.path + +import datetime import numpy as np import torch -import datetime -from HierarchicalDQN.agent import HierarchicalDQN -from common.plot import plot_rewards -from common.utils import save_results +import gym -SEQUENCE = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") # obtain current time -SAVED_MODEL_PATH = os.path.split(os.path.abspath(__file__))[0]+"/saved_model/"+SEQUENCE+'/' # path to save model -if not os.path.exists(os.path.split(os.path.abspath(__file__))[0]+"/saved_model/"): - os.mkdir(os.path.split(os.path.abspath(__file__))[0]+"/saved_model/") +from common.utils import save_results +from common.plot import plot_rewards,plot_losses +from HierarchicalDQN.agent import HierarchicalDQN + +SEQUENCE = datetime.datetime.now().strftime( + "%Y%m%d-%H%M%S") # obtain current time +SAVED_MODEL_PATH = curr_path+"/saved_model/"+SEQUENCE+'/' # path to save model +if not os.path.exists(curr_path+"/saved_model/"): + os.mkdir(curr_path+"/saved_model/") if not os.path.exists(SAVED_MODEL_PATH): os.mkdir(SAVED_MODEL_PATH) -RESULT_PATH = os.path.split(os.path.abspath(__file__))[0]+"/results/"+SEQUENCE+'/' # path to save rewards -if not os.path.exists(os.path.split(os.path.abspath(__file__))[0]+"/results/"): - os.mkdir(os.path.split(os.path.abspath(__file__))[0]+"/results/") -if not os.path.exists(RESULT_PATH): +RESULT_PATH = curr_path+"/results/"+SEQUENCE+'/' # path to save rewards +if not os.path.exists(curr_path+"/results/"): + os.mkdir(curr_path+"/results/") +if not os.path.exists(RESULT_PATH): os.mkdir(RESULT_PATH) + class HierarchicalDQNConfig: def __init__(self): - self.algo = "DQN" # name of algo + self.algo = "H-DQN" # name of algo self.gamma = 0.99 - self.epsilon_start = 0.95 # start epsilon of e-greedy policy + self.epsilon_start = 1 # start epsilon of e-greedy policy self.epsilon_end = 0.01 self.epsilon_decay = 200 - self.lr = 0.01 # learning rate - self.memory_capacity = 800 # Replay Memory capacity - self.batch_size = 64 - self.train_eps = 250 # 训练的episode数目 - self.train_steps = 200 # 训练每个episode的最大长度 - self.target_update = 2 # target net的更新频率 - self.eval_eps = 20 # 测试的episode数目 - self.eval_steps = 200 # 测试每个episode的最大长度 - self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 检测gpu - self.hidden_dim = 256 # dimension of hidden layer + self.lr = 0.0001 # learning rate + self.memory_capacity = 10000 # Replay Memory capacity + self.batch_size = 32 + self.train_eps = 300 # 训练的episode数目 + self.target_update = 2 # target net的更新频率 + self.eval_eps = 20 # 测试的episode数目 + self.device = torch.device( + "cuda" if torch.cuda.is_available() else "cpu") # 检测gpu + self.hidden_dim = 256 # dimension of hidden layer -def train(cfg,env,agent): + +def train(cfg, env, agent): print('Start to train !') rewards = [] - ma_rewards = [] # moving average reward - ep_steps = [] + ma_rewards = [] # moveing average reward for i_episode in range(cfg.train_eps): - state = env.reset() - extrinsic_reward = 0 - for i_step in range(cfg.train_steps): - goal= agent.set_goal(state) + state = env.reset() + done = False + ep_reward = 0 + while not done: + goal = agent.set_goal(state) + onehot_goal = agent.to_onehot(goal) meta_state = state - goal_state = np.concatenate([state, goal]) - action = agent.choose_action(state) - next_state, reward, done, _ = env.step(action) - extrinsic_reward += reward - intrinsic_reward = 1.0 if goal == np.argmax(next_state) else 0.0 - agent.memory.push(goal_state, action, intrinsic_reward, np.concatenate([next_state, goal]), done) - state = next_state - agent.update() - if done: - break - if i_episode % cfg.target_update == 0: - agent.target_net.load_state_dict(agent.policy_net.state_dict()) - print('Episode:{}/{}, Reward:{}, Steps:{}, Done:{}'.format(i_episode+1,cfg.train_eps,extrinsic_reward,i_step+1,done)) - ep_steps.append(i_step) - rewards.append(extrinsic_reward) + extrinsic_reward = 0 + while not done and goal != np.argmax(state): + goal_state = np.concatenate([state, onehot_goal]) + action = agent.choose_action(goal_state) + next_state, reward, done, _ = env.step(action) + ep_reward += reward + extrinsic_reward += reward + intrinsic_reward = 1.0 if goal == np.argmax( + next_state) else 0.0 + agent.memory.push(goal_state, action, intrinsic_reward, np.concatenate( + [next_state, onehot_goal]), done) + state = next_state + agent.update() + agent.meta_memory.push(meta_state, goal, extrinsic_reward, state, done) + print('Episode:{}/{}, Reward:{}, Loss:{:.2f}, Meta_Loss:{:.2f}'.format(i_episode+1, cfg.train_eps, ep_reward,agent.loss_numpy ,agent.meta_loss_numpy )) + rewards.append(ep_reward) if ma_rewards: ma_rewards.append( - 0.9*ma_rewards[-1]+0.1*extrinsic_reward) + 0.9*ma_rewards[-1]+0.1*ep_reward) else: - ma_rewards.append(extrinsic_reward) - agent.meta_memory.push(meta_state, goal, extrinsic_reward, state, done) + ma_rewards.append(ep_reward) print('Complete training!') - return rewards,ma_rewards + return rewards, ma_rewards + if __name__ == "__main__": - cfg = HierarchicalDQNConfig() env = gym.make('CartPole-v0') - env.seed(1) + env.seed(1) + cfg = HierarchicalDQNConfig() state_dim = env.observation_space.shape[0] action_dim = env.action_space.n - agent = HierarchicalDQN(state_dim,action_dim,cfg) - rewards,ma_rewards = train(cfg,env,agent) + agent = HierarchicalDQN(state_dim, action_dim, cfg) + rewards, ma_rewards = train(cfg, env, agent) agent.save(path=SAVED_MODEL_PATH) - save_results(rewards,ma_rewards,tag='train',path=RESULT_PATH) - plot_rewards(rewards,ma_rewards,tag="train",algo = cfg.algo,path=RESULT_PATH) \ No newline at end of file + save_results(rewards, ma_rewards, tag='train', path=RESULT_PATH) + plot_rewards(rewards, ma_rewards, tag="train", + algo=cfg.algo, path=RESULT_PATH) + plot_losses(agent.losses,algo=cfg.algo, path=RESULT_PATH) + diff --git a/codes/HierarchicalDQN/model.py b/codes/HierarchicalDQN/model.py deleted file mode 100644 index 0bf0584..0000000 --- a/codes/HierarchicalDQN/model.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python -# coding=utf-8 -''' -Author: John -Email: johnjim0816@gmail.com -Date: 2021-03-24 22:14:12 -LastEditor: John -LastEditTime: 2021-03-24 22:17:09 -Discription: -Environment: -''' -import torch.nn as nn -import torch.nn.functional as F -class MLP(nn.Module): - def __init__(self, state_dim,action_dim,hidden_dim=128): - super(MLP, self).__init__() - self.fc1 = nn.Linear(state_dim, hidden_dim) - self.fc2 = nn.Linear(hidden_dim,hidden_dim) - self.fc3 = nn.Linear(hidden_dim, action_dim) - - def forward(self, x): - x = F.relu(self.fc1(x)) - x = F.relu(self.fc2(x)) - return self.fc3(x) \ No newline at end of file diff --git a/codes/HierarchicalDQN/results/20210331-134559/ma_rewards_train.npy b/codes/HierarchicalDQN/results/20210331-134559/ma_rewards_train.npy new file mode 100644 index 0000000..daab87d Binary files /dev/null and b/codes/HierarchicalDQN/results/20210331-134559/ma_rewards_train.npy differ diff --git a/codes/HierarchicalDQN/results/20210331-134559/rewards_curve_train.png b/codes/HierarchicalDQN/results/20210331-134559/rewards_curve_train.png new file mode 100644 index 0000000..77555ad Binary files /dev/null and b/codes/HierarchicalDQN/results/20210331-134559/rewards_curve_train.png differ diff --git a/codes/HierarchicalDQN/results/20210331-134559/rewards_train.npy b/codes/HierarchicalDQN/results/20210331-134559/rewards_train.npy new file mode 100644 index 0000000..5a1ad82 Binary files /dev/null and b/codes/HierarchicalDQN/results/20210331-134559/rewards_train.npy differ diff --git a/codes/HierarchicalDQN/results/20210331-145852/losses_curve.png b/codes/HierarchicalDQN/results/20210331-145852/losses_curve.png new file mode 100644 index 0000000..4f962ea Binary files /dev/null and b/codes/HierarchicalDQN/results/20210331-145852/losses_curve.png differ diff --git a/codes/HierarchicalDQN/results/20210331-145852/ma_rewards_train.npy b/codes/HierarchicalDQN/results/20210331-145852/ma_rewards_train.npy new file mode 100644 index 0000000..523bdb4 Binary files /dev/null and b/codes/HierarchicalDQN/results/20210331-145852/ma_rewards_train.npy differ diff --git a/codes/HierarchicalDQN/results/20210331-145852/rewards_curve_train.png b/codes/HierarchicalDQN/results/20210331-145852/rewards_curve_train.png new file mode 100644 index 0000000..97443e5 Binary files /dev/null and b/codes/HierarchicalDQN/results/20210331-145852/rewards_curve_train.png differ diff --git a/codes/HierarchicalDQN/results/20210331-145852/rewards_train.npy b/codes/HierarchicalDQN/results/20210331-145852/rewards_train.npy new file mode 100644 index 0000000..99cf87a Binary files /dev/null and b/codes/HierarchicalDQN/results/20210331-145852/rewards_train.npy differ diff --git a/codes/HierarchicalDQN/saved_model/20210331-134559/meta_checkpoint.pth b/codes/HierarchicalDQN/saved_model/20210331-134559/meta_checkpoint.pth new file mode 100644 index 0000000..873b3ef Binary files /dev/null and b/codes/HierarchicalDQN/saved_model/20210331-134559/meta_checkpoint.pth differ diff --git a/codes/HierarchicalDQN/saved_model/20210331-134559/policy_checkpoint.pth b/codes/HierarchicalDQN/saved_model/20210331-134559/policy_checkpoint.pth new file mode 100644 index 0000000..be8ea8a Binary files /dev/null and b/codes/HierarchicalDQN/saved_model/20210331-134559/policy_checkpoint.pth differ diff --git a/codes/HierarchicalDQN/saved_model/20210331-145852/meta_checkpoint.pth b/codes/HierarchicalDQN/saved_model/20210331-145852/meta_checkpoint.pth new file mode 100644 index 0000000..e3f7c38 Binary files /dev/null and b/codes/HierarchicalDQN/saved_model/20210331-145852/meta_checkpoint.pth differ diff --git a/codes/HierarchicalDQN/saved_model/20210331-145852/policy_checkpoint.pth b/codes/HierarchicalDQN/saved_model/20210331-145852/policy_checkpoint.pth new file mode 100644 index 0000000..6be6ea3 Binary files /dev/null and b/codes/HierarchicalDQN/saved_model/20210331-145852/policy_checkpoint.pth differ diff --git a/codes/QLearning/results/20210313-110213/ma_rewards_train.npy b/codes/QLearning/results/20210313-110213/ma_rewards_train.npy deleted file mode 100644 index 4f05a73..0000000 Binary files a/codes/QLearning/results/20210313-110213/ma_rewards_train.npy and /dev/null differ diff --git a/codes/QLearning/results/20210313-110213/rewards_curve_train.png b/codes/QLearning/results/20210313-110213/rewards_curve_train.png deleted file mode 100644 index d6bbc01..0000000 Binary files a/codes/QLearning/results/20210313-110213/rewards_curve_train.png and /dev/null differ diff --git a/codes/QLearning/results/20210313-110213/rewards_train.npy b/codes/QLearning/results/20210313-110213/rewards_train.npy deleted file mode 100644 index f1e8ba9..0000000 Binary files a/codes/QLearning/results/20210313-110213/rewards_train.npy and /dev/null differ diff --git a/codes/QLearning/saved_model/20210313-110213/Qleaning_model.pkl b/codes/QLearning/saved_model/20210313-110213/Qleaning_model.pkl deleted file mode 100644 index 9f71ab0..0000000 Binary files a/codes/QLearning/saved_model/20210313-110213/Qleaning_model.pkl and /dev/null differ diff --git a/codes/README.md b/codes/README.md index 7591c98..d3dc6ef 100644 --- a/codes/README.md +++ b/codes/README.md @@ -19,9 +19,10 @@ ## 运行环境 python 3.7、pytorch 1.6.0-1.7.1、gym 0.17.0-0.18.0 + ## 使用说明 -对应算法文件夹下运行```main.py```即可 +运行```main.py```或者```main.ipynb``` ## 算法进度 | 算法名称 | 相关论文材料 | 环境 | 备注 | @@ -29,17 +30,17 @@ python 3.7、pytorch 1.6.0-1.7.1、gym 0.17.0-0.18.0 | [On-Policy First-Visit MC](./MonteCarlo) | | [Racetrack](./envs/racetrack_env.md) | | | [Q-Learning](./QLearning) | | [CliffWalking-v0](./envs/gym_info.md) | | | [Sarsa](./Sarsa) | | [Racetrack](./envs/racetrack_env.md) | | -| [DQN](./DQN) | [DQN-paper](https://www.cs.toronto.edu/~vmnih/docs/dqn.pdf) | [CartPole-v0](./envs/gym_info.md) | | -| [DQN-cnn](./DQN_cnn) | [DQN-paper](https://www.cs.toronto.edu/~vmnih/docs/dqn.pdf) | [CartPole-v0](./envs/gym_info.md) | 与DQN相比使用了CNN而不是全链接网络 | +| [DQN](./DQN) | [DQN Paper](https://www.cs.toronto.edu/~vmnih/docs/dqn.pdf) | [CartPole-v0](./envs/gym_info.md) | | +| [DQN-cnn](./DQN_cnn) | [DQN Paper](https://www.cs.toronto.edu/~vmnih/docs/dqn.pdf) | [CartPole-v0](./envs/gym_info.md) | 与DQN相比使用了CNN而不是全链接网络 | | [DoubleDQN](./DoubleDQN) | | [CartPole-v0](./envs/gym_info.md) | 效果不好,待改进 | -| Hierarchical DQN | [Hierarchical DQN](https://arxiv.org/abs/1604.06057) | | | +| Hierarchical DQN | [H-DQN Paper](https://arxiv.org/abs/1604.06057) | | | | [PolicyGradient](./PolicyGradient) | | [CartPole-v0](./envs/gym_info.md) | | | A2C | | [CartPole-v0](./envs/gym_info.md) | | | A3C | | | | | SAC | | | | | [PPO](./PPO) | [PPO paper](https://arxiv.org/abs/1707.06347) | [CartPole-v0](./envs/gym_info.md) | | | DDPG | [DDPG Paper](https://arxiv.org/abs/1509.02971) | [Pendulum-v0](./envs/gym_info.md) | | -| TD3 | [Twin Dueling DDPG Paper](https://arxiv.org/abs/1802.09477) | | | +| TD3 | [TD3 Paper](https://arxiv.org/abs/1802.09477) | | | | GAIL | | | | diff --git a/codes/README_en.md b/codes/README_en.md index c931b6a..31c3d1e 100644 --- a/codes/README_en.md +++ b/codes/README_en.md @@ -24,7 +24,7 @@ Note that ```model.py```,```memory.py```,```plot.py``` shall be utilized in diff python 3.7.9、pytorch 1.6.0、gym 0.18.0 ## Usage -Environment infomations see [环境说明](https://github.com/JohnJim0816/reinforcement-learning-tutorials/blob/master/env_info.md) +run ```main.py``` or ```main.ipynb``` ## Schedule diff --git a/codes/common/model.py b/codes/common/model.py index e02e3c1..41785fd 100644 --- a/codes/common/model.py +++ b/codes/common/model.py @@ -5,7 +5,7 @@ Author: John Email: johnjim0816@gmail.com Date: 2021-03-12 21:14:12 LastEditor: John -LastEditTime: 2021-03-24 22:15:00 +LastEditTime: 2021-03-31 13:49:06 Discription: Environment: ''' @@ -15,15 +15,15 @@ import torch.nn.functional as F from torch.distributions import Categorical class MLP(nn.Module): - def __init__(self, state_dim,action_dim,hidden_dim=128): + def __init__(self, input_dim,output_dim,hidden_dim=128): """ 初始化q网络,为全连接网络 - state_dim: 输入的feature即环境的state数目 - action_dim: 输出的action总个数 + input_dim: 输入的feature即环境的state数目 + output_dim: 输出的action总个数 """ super(MLP, self).__init__() - self.fc1 = nn.Linear(state_dim, hidden_dim) # 输入层 + self.fc1 = nn.Linear(input_dim, hidden_dim) # 输入层 self.fc2 = nn.Linear(hidden_dim,hidden_dim) # 隐藏层 - self.fc3 = nn.Linear(hidden_dim, action_dim) # 输出层 + self.fc3 = nn.Linear(hidden_dim, output_dim) # 输出层 def forward(self, x): # 各层对应的激活函数 @@ -32,10 +32,10 @@ class MLP(nn.Module): return self.fc3(x) class Critic(nn.Module): - def __init__(self, n_obs, action_dim, hidden_size, init_w=3e-3): + def __init__(self, n_obs, output_dim, hidden_size, init_w=3e-3): super(Critic, self).__init__() - self.linear1 = nn.Linear(n_obs + action_dim, hidden_size) + self.linear1 = nn.Linear(n_obs + output_dim, hidden_size) self.linear2 = nn.Linear(hidden_size, hidden_size) self.linear3 = nn.Linear(hidden_size, 1) # 随机初始化为较小的值 @@ -51,11 +51,11 @@ class Critic(nn.Module): return x class Actor(nn.Module): - def __init__(self, n_obs, action_dim, hidden_size, init_w=3e-3): + def __init__(self, n_obs, output_dim, hidden_size, init_w=3e-3): super(Actor, self).__init__() self.linear1 = nn.Linear(n_obs, hidden_size) self.linear2 = nn.Linear(hidden_size, hidden_size) - self.linear3 = nn.Linear(hidden_size, action_dim) + self.linear3 = nn.Linear(hidden_size, output_dim) self.linear3.weight.data.uniform_(-init_w, init_w) self.linear3.bias.data.uniform_(-init_w, init_w) @@ -67,18 +67,18 @@ class Actor(nn.Module): return x class ActorCritic(nn.Module): - def __init__(self, state_dim, action_dim, hidden_dim=256): + def __init__(self, input_dim, output_dim, hidden_dim=256): super(ActorCritic, self).__init__() self.critic = nn.Sequential( - nn.Linear(state_dim, hidden_dim), + nn.Linear(input_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, 1) ) self.actor = nn.Sequential( - nn.Linear(state_dim, hidden_dim), + nn.Linear(input_dim, hidden_dim), nn.ReLU(), - nn.Linear(hidden_dim, action_dim), + nn.Linear(hidden_dim, output_dim), nn.Softmax(dim=1), ) diff --git a/codes/common/plot.py b/codes/common/plot.py index 409004a..b8684d0 100644 --- a/codes/common/plot.py +++ b/codes/common/plot.py @@ -5,13 +5,13 @@ Author: John Email: johnjim0816@gmail.com Date: 2020-10-07 20:57:11 LastEditor: John -LastEditTime: 2021-03-13 11:31:49 +LastEditTime: 2021-03-31 14:05:52 Discription: Environment: ''' import matplotlib.pyplot as plt import seaborn as sns -def plot_rewards(rewards,ma_rewards,tag="train",algo = "On-Policy First-Visit MC Control",path='./'): +def plot_rewards(rewards,ma_rewards,tag="train",algo = "DQN",path='./'): sns.set() plt.title("average learning curve of {}".format(algo)) plt.xlabel('epsiodes') @@ -20,4 +20,13 @@ def plot_rewards(rewards,ma_rewards,tag="train",algo = "On-Policy First-Visit MC plt.legend() plt.savefig(path+"rewards_curve_{}".format(tag)) plt.show() + +def plot_losses(losses,algo = "DQN",path='./'): + sns.set() + plt.title("loss curve of {}".format(algo)) + plt.xlabel('epsiodes') + plt.plot(losses,label='rewards') + plt.legend() + plt.savefig(path+"losses_curve") + plt.show()