diff --git a/codes/A2C/agent.py b/codes/A2C/agent.py index e095bc5..997401b 100644 --- a/codes/A2C/agent.py +++ b/codes/A2C/agent.py @@ -10,12 +10,40 @@ Discription: Environment: ''' import torch.optim as optim -from A2C.model import ActorCritic +import torch.nn as nn +import torch.nn.functional as F +from torch.distributions import Categorical + +class ActorCritic(nn.Module): + ''' A2C网络模型,包含一个Actor和Critic + ''' + def __init__(self, input_dim, output_dim, hidden_dim): + super(ActorCritic, self).__init__() + self.critic = nn.Sequential( + nn.Linear(input_dim, hidden_dim), + nn.ReLU(), + nn.Linear(hidden_dim, 1) + ) + + self.actor = nn.Sequential( + nn.Linear(input_dim, hidden_dim), + nn.ReLU(), + nn.Linear(hidden_dim, output_dim), + nn.Softmax(dim=1), + ) + + def forward(self, x): + value = self.critic(x) + probs = self.actor(x) + dist = Categorical(probs) + return dist, value class A2C: - def __init__(self,n_states,n_actions,cfg) -> None: + ''' A2C算法 + ''' + def __init__(self,state_dim,action_dim,cfg) -> None: self.gamma = cfg.gamma self.device = cfg.device - self.model = ActorCritic(n_states, n_actions, cfg.hidden_size).to(self.device) + self.model = ActorCritic(state_dim, action_dim, cfg.hidden_size).to(self.device) self.optimizer = optim.Adam(self.model.parameters()) def compute_returns(self,next_value, rewards, masks): diff --git a/codes/A2C/model.py b/codes/A2C/model.py deleted file mode 100644 index 473bcb2..0000000 --- a/codes/A2C/model.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python -# coding=utf-8 -''' -Author: JiangJi -Email: johnjim0816@gmail.com -Date: 2021-05-03 21:38:54 -LastEditor: JiangJi -LastEditTime: 2021-05-03 21:40:06 -Discription: -Environment: -''' -import torch.nn as nn -import torch.nn.functional as F -from torch.distributions import Categorical -class ActorCritic(nn.Module): - def __init__(self, n_states, n_actions, hidden_dim): - super(ActorCritic, self).__init__() - - self.critic = nn.Sequential( - nn.Linear(n_states, hidden_dim), - nn.ReLU(), - nn.Linear(hidden_dim, 1) - ) - - self.actor = nn.Sequential( - nn.Linear(n_states, hidden_dim), - nn.ReLU(), - nn.Linear(hidden_dim, n_actions), - nn.Softmax(dim=1), - ) - - def forward(self, x): - value = self.critic(x) - probs = self.actor(x) - dist = Categorical(probs) - return dist, value \ No newline at end of file diff --git a/codes/A2C/outputs/CartPole-v0/20211221-165620/results/train_ma_rewards.npy b/codes/A2C/outputs/CartPole-v0/20211221-165620/results/train_ma_rewards.npy new file mode 100644 index 0000000..6537afd Binary files /dev/null and b/codes/A2C/outputs/CartPole-v0/20211221-165620/results/train_ma_rewards.npy differ diff --git a/codes/A2C/outputs/CartPole-v0/20211221-165620/results/train_rewards.npy b/codes/A2C/outputs/CartPole-v0/20211221-165620/results/train_rewards.npy new file mode 100644 index 0000000..56f779b Binary files /dev/null and b/codes/A2C/outputs/CartPole-v0/20211221-165620/results/train_rewards.npy differ diff --git a/codes/A2C/task0_train.ipynb b/codes/A2C/task0.ipynb similarity index 100% rename from codes/A2C/task0_train.ipynb rename to codes/A2C/task0.ipynb diff --git a/codes/A2C/task0_train.py b/codes/A2C/task0.py similarity index 86% rename from codes/A2C/task0_train.py rename to codes/A2C/task0.py index 5927048..fd54d87 100644 --- a/codes/A2C/task0_train.py +++ b/codes/A2C/task0.py @@ -1,7 +1,8 @@ -import sys,os -curr_path = os.path.dirname(os.path.abspath(__file__)) # 当前文件所在绝对路径 -parent_path = os.path.dirname(curr_path) # 父路径 -sys.path.append(parent_path) # 添加路径到系统路径sys.path +import sys +import os +curr_path = os.path.dirname(os.path.abspath(__file__)) # 当前文件所在绝对路径 +parent_path = os.path.dirname(curr_path) # 父路径 +sys.path.append(parent_path) # 添加路径到系统路径 import gym import numpy as np @@ -9,15 +10,18 @@ import torch import torch.optim as optim import datetime from common.multiprocessing_env import SubprocVecEnv -from A2C.model import ActorCritic +from A2C.agent import ActorCritic from common.utils import save_results, make_dir -from common.plot import plot_rewards +from common.utils import plot_rewards + +curr_time = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") # 获取当前时间 +algo_name = 'A2C' # 算法名称 +env_name = 'CartPole-v0' # 环境名称 -curr_time = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") # obtain current time class A2CConfig: def __init__(self) -> None: - self.algo='A2C' # 算法名称 - self.env_name= 'CartPole-v0' # 环境名称 + self.algo_name = algo_name# 算法名称 + self.env_name = env_name # 环境名称 self.n_envs = 8 # 异步的环境数目 self.gamma = 0.99 # 强化学习中的折扣因子 self.hidden_dim = 256 @@ -27,10 +31,9 @@ class A2CConfig: self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") class PlotConfig: def __init__(self) -> None: - self.algo = "DQN" # 算法名称 - self.env_name = 'CartPole-v0' # 环境名称 + self.algo_name = algo_name # 算法名称 + self.env_name = env_name # 环境名称 self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 检测GPU - self.result_path = curr_path+"/outputs/" + self.env_name + \ '/'+curr_time+'/results/' # 保存结果的路径 self.model_path = curr_path+"/outputs/" + self.env_name + \ @@ -67,6 +70,8 @@ def compute_returns(next_value, rewards, masks, gamma=0.99): def train(cfg,envs): + print('开始训练!') + print(f'环境:{cfg.env_name}, 算法:{cfg.algo}, 设备:{cfg.device}') env = gym.make(cfg.env_name) # a single env env.seed(10) state_dim = envs.observation_space.shape[0] @@ -119,6 +124,7 @@ def train(cfg,envs): optimizer.zero_grad() loss.backward() optimizer.step() + print('完成训练!') return test_rewards, test_ma_rewards if __name__ == "__main__": cfg = A2CConfig() diff --git a/codes/DDPG/agent.py b/codes/DDPG/agent.py index 528872e..6ec2eef 100644 --- a/codes/DDPG/agent.py +++ b/codes/DDPG/agent.py @@ -9,15 +9,68 @@ LastEditTime: 2021-09-16 00:55:30 @Discription: @Environment: python 3.7.7 ''' +import random import numpy as np import torch import torch.nn as nn import torch.optim as optim - -from common.model import Actor, Critic -from common.memory import ReplayBuffer - - +import torch.nn.functional as F +class ReplayBuffer: + def __init__(self, capacity): + self.capacity = capacity # 经验回放的容量 + self.buffer = [] # 缓冲区 + self.position = 0 + + def push(self, state, action, reward, next_state, done): + ''' 缓冲区是一个队列,容量超出时去掉开始存入的转移(transition) + ''' + 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 = random.sample(self.buffer, batch_size) # 随机采出小批量转移 + state, action, reward, next_state, done = zip(*batch) # 解压成状态,动作等 + return state, action, reward, next_state, done + + def __len__(self): + ''' 返回当前存储的量 + ''' + return len(self.buffer) +class Actor(nn.Module): + def __init__(self, state_dim, action_dim, hidden_dim, init_w=3e-3): + super(Actor, self).__init__() + self.linear1 = nn.Linear(state_dim, hidden_dim) + self.linear2 = nn.Linear(hidden_dim, hidden_dim) + self.linear3 = nn.Linear(hidden_dim, action_dim) + + self.linear3.weight.data.uniform_(-init_w, init_w) + self.linear3.bias.data.uniform_(-init_w, init_w) + + def forward(self, x): + x = F.relu(self.linear1(x)) + x = F.relu(self.linear2(x)) + x = torch.tanh(self.linear3(x)) + return x +class Critic(nn.Module): + def __init__(self, state_dim, action_dim, hidden_dim, init_w=3e-3): + super(Critic, self).__init__() + + self.linear1 = nn.Linear(state_dim + action_dim, hidden_dim) + self.linear2 = nn.Linear(hidden_dim, hidden_dim) + self.linear3 = nn.Linear(hidden_dim, 1) + # 随机初始化为较小的值 + self.linear3.weight.data.uniform_(-init_w, init_w) + self.linear3.bias.data.uniform_(-init_w, init_w) + + def forward(self, state, action): + # 按维数1拼接 + x = torch.cat([state, action], 1) + x = F.relu(self.linear1(x)) + x = F.relu(self.linear2(x)) + x = self.linear3(x) + return x class DDPG: def __init__(self, state_dim, action_dim, cfg): self.device = cfg.device diff --git a/codes/DDPG/env.py b/codes/DDPG/env.py index 99da3c5..92fe482 100644 --- a/codes/DDPG/env.py +++ b/codes/DDPG/env.py @@ -16,12 +16,10 @@ class NormalizedActions(gym.ActionWrapper): ''' 将action范围重定在[0.1]之间 ''' def action(self, action): - low_bound = self.action_space.low upper_bound = self.action_space.high action = low_bound + (action + 1.0) * 0.5 * (upper_bound - low_bound) action = np.clip(action, low_bound, upper_bound) - return action def reverse_action(self, action): diff --git a/codes/DDPG/task0.py b/codes/DDPG/task0.py new file mode 100644 index 0000000..81fa9a6 --- /dev/null +++ b/codes/DDPG/task0.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# coding=utf-8 +''' +@Author: John +@Email: johnjim0816@gmail.com +@Date: 2020-06-11 20:58:21 +@LastEditor: John +LastEditTime: 2021-09-16 01:31:33 +@Discription: +@Environment: python 3.7.7 +''' +import sys,os +curr_path = os.path.dirname(os.path.abspath(__file__)) # 当前文件所在绝对路径 +parent_path = os.path.dirname(curr_path) # 父路径 +sys.path.append(parent_path) # 添加路径到系统路径sys.path + +import datetime +import gym +import torch + +from DDPG.env import NormalizedActions +from DDPG.agent import DDPG +from DDPG.train import train,test +from common.utils import save_results,make_dir +from common.utils import plot_rewards + +curr_time = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") # 获取当前时间 +algo_name = 'DDPG' # 算法名称 +env_name = 'Pendulum-v1' # 环境名称,gym新版本(约0.21.0之后)中Pendulum-v0改为Pendulum-v1 + +class DDPGConfig: + def __init__(self): + self.algo_name = algo_name # 算法名称 + self.env_name = env_name # 环境名称 + self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 检测GPU + self.train_eps = 300 # 训练的回合数 + self.test_eps = 50 # 测试的回合数 + self.gamma = 0.99 # 折扣因子 + self.critic_lr = 1e-3 # 评论家网络的学习率 + self.actor_lr = 1e-4 # 演员网络的学习率 + self.memory_capacity = 8000 # 经验回放的容量 + self.batch_size = 128 # mini-batch SGD中的批量大小 + self.target_update = 2 # 目标网络的更新频率 + self.hidden_dim = 256 # 网络隐藏层维度 + self.soft_tau = 1e-2 # 软更新参数 + +class PlotConfig: + def __init__(self) -> None: + self.algo_name = algo_name # 算法名称 + self.env_name = env_name # 环境名称 + self.result_path = curr_path+"/outputs/" + self.env_name + \ + '/'+curr_time+'/results/' # 保存结果的路径 + self.model_path = curr_path+"/outputs/" + self.env_name + \ + '/'+curr_time+'/models/' # 保存模型的路径 + self.save = True # 是否保存图片 + self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 检测GPU + +def env_agent_config(cfg,seed=1): + env = NormalizedActions(gym.make(cfg.env_name)) # 装饰action噪声 + env.seed(seed) # 随机种子 + state_dim = env.observation_space.shape[0] + action_dim = env.action_space.shape[0] + agent = DDPG(state_dim,action_dim,cfg) + return env,agent + +cfg = DDPGConfig() +plot_cfg = PlotConfig() +# 训练 +env,agent = env_agent_config(cfg,seed=1) +rewards, ma_rewards = train(cfg, env, agent) +make_dir(plot_cfg.result_path, plot_cfg.model_path) +agent.save(path=plot_cfg.model_path) +save_results(rewards, ma_rewards, tag='train', path=plot_cfg.result_path) +plot_rewards(rewards, ma_rewards, plot_cfg, tag="train") # 画出结果 +# 测试 +env,agent = env_agent_config(cfg,seed=10) +agent.load(path=plot_cfg.model_path) +rewards,ma_rewards = test(plot_cfg,env,agent) +save_results(rewards,ma_rewards,tag = 'test',path = cfg.result_path) +plot_rewards(rewards, ma_rewards, plot_cfg, tag="test") # 画出结果 + diff --git a/codes/DDPG/task0_train.py b/codes/DDPG/task0_train.py deleted file mode 100644 index ea76661..0000000 --- a/codes/DDPG/task0_train.py +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env python -# coding=utf-8 -''' -@Author: John -@Email: johnjim0816@gmail.com -@Date: 2020-06-11 20:58:21 -@LastEditor: John -LastEditTime: 2021-09-16 01:31:33 -@Discription: -@Environment: python 3.7.7 -''' -import sys,os -curr_path = os.path.dirname(os.path.abspath(__file__)) # 当前文件所在绝对路径 -parent_path = os.path.dirname(curr_path) # 父路径 -sys.path.append(parent_path) # 添加路径到系统路径sys.path - -import datetime -import gym -import torch - -from DDPG.env import NormalizedActions, OUNoise -from DDPG.agent import DDPG -from common.utils import save_results,make_dir -from common.plot import plot_rewards - -curr_time = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") # 获取当前时间 - -class DDPGConfig: - def __init__(self): - self.algo = 'DDPG' # 算法名称 - self.env_name = 'Pendulum-v0' # 环境名称 - self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 检测GPU - self.train_eps = 300 # 训练的回合数 - self.eval_eps = 50 # 测试的回合数 - self.gamma = 0.99 # 折扣因子 - self.critic_lr = 1e-3 # 评论家网络的学习率 - self.actor_lr = 1e-4 # 演员网络的学习率 - self.memory_capacity = 8000 # 经验回放的容量 - self.batch_size = 128 # mini-batch SGD中的批量大小 - self.target_update = 2 # 目标网络的更新频率 - self.hidden_dim = 256 # 网络隐藏层维度 - self.soft_tau = 1e-2 # 软更新参数 - -class PlotConfig: - def __init__(self) -> None: - self.algo = "DQN" # 算法名称 - self.env_name = 'CartPole-v0' # 环境名称 - self.result_path = curr_path+"/outputs/" + self.env_name + \ - '/'+curr_time+'/results/' # 保存结果的路径 - self.model_path = curr_path+"/outputs/" + self.env_name + \ - '/'+curr_time+'/models/' # 保存模型的路径 - self.save = True # 是否保存图片 - self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 检测GPU - -def env_agent_config(cfg,seed=1): - env = NormalizedActions(gym.make(cfg.env_name)) # 装饰action噪声 - env.seed(seed) # 随机种子 - n_states = env.observation_space.shape[0] - n_actions = env.action_space.shape[0] - agent = DDPG(n_states,n_actions,cfg) - return env,agent - -def train(cfg, env, agent): - print('开始训练!') - print(f'环境:{cfg.env_name},算法:{cfg.algo},设备:{cfg.device}') - ou_noise = OUNoise(env.action_space) # 动作噪声 - rewards = [] # 记录所有回合的奖励 - ma_rewards = [] # 记录所有回合的滑动平均奖励 - for i_ep in range(cfg.train_eps): - state = env.reset() - ou_noise.reset() - done = False - ep_reward = 0 - i_step = 0 - while not done: - i_step += 1 - action = agent.choose_action(state) - action = ou_noise.get_action(action, i_step) - 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 (i_ep+1)%10 == 0: - print('回合:{}/{},奖励:{:.2f}'.format(i_ep+1, cfg.train_eps, ep_reward)) - rewards.append(ep_reward) - if ma_rewards: - ma_rewards.append(0.9*ma_rewards[-1]+0.1*ep_reward) - else: - ma_rewards.append(ep_reward) - print('完成训练!') - return rewards, ma_rewards - -def eval(cfg, env, agent): - print('开始测试!') - print(f'环境:{cfg.env_name}, 算法:{cfg.algo}, 设备:{cfg.device}') - rewards = [] # 记录所有回合的奖励 - ma_rewards = [] # 记录所有回合的滑动平均奖励 - for i_ep in range(cfg.eval_eps): - state = env.reset() - done = False - ep_reward = 0 - i_step = 0 - while not done: - i_step += 1 - action = agent.choose_action(state) - next_state, reward, done, _ = env.step(action) - ep_reward += reward - state = next_state - print('回合:{}/{}, 奖励:{}'.format(i_ep+1, cfg.train_eps, ep_reward)) - rewards.append(ep_reward) - if ma_rewards: - ma_rewards.append(0.9*ma_rewards[-1]+0.1*ep_reward) - else: - ma_rewards.append(ep_reward) - print('完成测试!') - return rewards, ma_rewards - - -if __name__ == "__main__": - cfg = DDPGConfig() - plot_cfg = PlotConfig() - # 训练 - env,agent = env_agent_config(cfg,seed=1) - rewards, ma_rewards = train(cfg, env, agent) - make_dir(plot_cfg.result_path, plot_cfg.model_path) - agent.save(path=plot_cfg.model_path) - save_results(rewards, ma_rewards, tag='train', path=plot_cfg.result_path) - plot_rewards(rewards, ma_rewards, plot_cfg, tag="train") - # 测试 - env,agent = env_agent_config(cfg,seed=10) - agent.load(path=plot_cfg.model_path) - rewards,ma_rewards = eval(plot_cfg,env,agent) - save_results(rewards,ma_rewards,tag = 'eval',path = cfg.result_path) - plot_rewards(rewards,ma_rewards,plot_cfg,tag = "eval") - diff --git a/codes/DDPG/train.py b/codes/DDPG/train.py new file mode 100644 index 0000000..4cdfa9d --- /dev/null +++ b/codes/DDPG/train.py @@ -0,0 +1,64 @@ +import sys +import os +curr_path = os.path.dirname(os.path.abspath(__file__)) # 当前文件所在绝对路径 +parent_path = os.path.dirname(curr_path) # 父路径 +sys.path.append(parent_path) # 添加路径到系统路径 + +from DDPG.env import OUNoise + +def train(cfg, env, agent): + print('开始训练!') + print(f'环境:{cfg.env_name},算法:{cfg.algo},设备:{cfg.device}') + ou_noise = OUNoise(env.action_space) # 动作噪声 + rewards = [] # 记录所有回合的奖励 + ma_rewards = [] # 记录所有回合的滑动平均奖励 + for i_ep in range(cfg.train_eps): + state = env.reset() + ou_noise.reset() + done = False + ep_reward = 0 + i_step = 0 + while not done: + i_step += 1 + action = agent.choose_action(state) + action = ou_noise.get_action(action, i_step) + 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 (i_ep+1)%10 == 0: + print('回合:{}/{},奖励:{:.2f}'.format(i_ep+1, cfg.train_eps, ep_reward)) + rewards.append(ep_reward) + if ma_rewards: + ma_rewards.append(0.9*ma_rewards[-1]+0.1*ep_reward) + else: + ma_rewards.append(ep_reward) + print('完成训练!') + return rewards, ma_rewards + +def test(cfg, env, agent): + print('开始测试!') + print(f'环境:{cfg.env_name}, 算法:{cfg.algo}, 设备:{cfg.device}') + rewards = [] # 记录所有回合的奖励 + ma_rewards = [] # 记录所有回合的滑动平均奖励 + for i_ep in range(cfg.test_eps): + state = env.reset() + done = False + ep_reward = 0 + i_step = 0 + while not done: + i_step += 1 + action = agent.choose_action(state) + next_state, reward, done, _ = env.step(action) + ep_reward += reward + state = next_state + print('回合:{}/{}, 奖励:{}'.format(i_ep+1, cfg.train_eps, ep_reward)) + rewards.append(ep_reward) + if ma_rewards: + ma_rewards.append(0.9*ma_rewards[-1]+0.1*ep_reward) + else: + ma_rewards.append(ep_reward) + print(f"回合:{i_ep+1}/{cfg.test_eps},奖励:{ep_reward:.1f}") + print('完成测试!') + return rewards, ma_rewards \ No newline at end of file diff --git a/codes/DQN/agent.py b/codes/DQN/dqn.py similarity index 63% rename from codes/DQN/agent.py rename to codes/DQN/dqn.py index 27845d2..4a4dfc4 100644 --- a/codes/DQN/agent.py +++ b/codes/DQN/dqn.py @@ -5,7 +5,7 @@ @Email: johnjim0816@gmail.com @Date: 2020-06-12 00:50:49 @LastEditor: John -LastEditTime: 2021-09-15 13:35:36 +LastEditTime: 2021-12-22 14:01:37 @Discription: @Environment: python 3.7.7 ''' @@ -14,16 +14,57 @@ LastEditTime: 2021-09-15 13:35:36 import torch import torch.nn as nn +import torch.nn.functional as F import torch.optim as optim import random import math import numpy as np -from common.memory import ReplayBuffer -from common.model import MLP -class DQN: - def __init__(self, n_states, n_actions, cfg): - self.n_actions = n_actions # 总的动作个数 +class MLP(nn.Module): + def __init__(self, state_dim,action_dim,hidden_dim=128): + """ 初始化q网络,为全连接网络 + state_dim: 输入的特征数即环境的状态维度 + action_dim: 输出的动作维度 + """ + 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) + +class ReplayBuffer: + def __init__(self, capacity): + self.capacity = capacity # 经验回放的容量 + self.buffer = [] # 缓冲区 + self.position = 0 + + def push(self, state, action, reward, next_state, done): + ''' 缓冲区是一个队列,容量超出时去掉开始存入的转移(transition) + ''' + 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 = random.sample(self.buffer, batch_size) # 随机采出小批量转移 + state, action, reward, next_state, done = zip(*batch) # 解压成状态,动作等 + return state, action, reward, next_state, done + + def __len__(self): + ''' 返回当前存储的量 + ''' + return len(self.buffer) + +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 # 奖励的折扣因子 # e-greedy策略相关参数 @@ -32,8 +73,8 @@ class DQN: (cfg.epsilon_start - cfg.epsilon_end) * \ math.exp(-1. * frame_idx / cfg.epsilon_decay) self.batch_size = cfg.batch_size - self.policy_net = MLP(n_states, n_actions,hidden_dim=cfg.hidden_dim).to(self.device) - self.target_net = MLP(n_states, n_actions,hidden_dim=cfg.hidden_dim).to(self.device) + 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) for target_param, param in zip(self.target_net.parameters(),self.policy_net.parameters()): # 复制参数到目标网路targe_net target_param.data.copy_(param.data) self.optimizer = optim.Adam(self.policy_net.parameters(), lr=cfg.lr) # 优化器 @@ -49,7 +90,7 @@ class DQN: q_values = self.policy_net(state) action = q_values.max(1)[1].item() # 选择Q值最大的动作 else: - action = random.randrange(self.n_actions) + action = random.randrange(self.action_dim) return action def update(self): if len(self.memory) < self.batch_size: # 当memory中不满足一个批量时,不更新策略 diff --git a/codes/DQN/dqn_cnn.py b/codes/DQN/dqn_cnn.py new file mode 100644 index 0000000..c14118f --- /dev/null +++ b/codes/DQN/dqn_cnn.py @@ -0,0 +1,133 @@ +import torch +import torch.nn as nn +import torch.optim as optim +import torch.autograd as autograd +import random +import math +class CNN(nn.Module): + def __init__(self, input_dim, output_dim): + super(CNN, self).__init__() + + self.input_dim = input_dim + self.output_dim = output_dim + + self.features = nn.Sequential( + nn.Conv2d(input_dim[0], 32, kernel_size=8, stride=4), + nn.ReLU(), + nn.Conv2d(32, 64, kernel_size=4, stride=2), + nn.ReLU(), + nn.Conv2d(64, 64, kernel_size=3, stride=1), + nn.ReLU() + ) + + self.fc = nn.Sequential( + nn.Linear(self.feature_size(), 512), + nn.ReLU(), + nn.Linear(512, self.output_dim) + ) + + def forward(self, x): + x = self.features(x) + x = x.view(x.size(0), -1) + x = self.fc(x) + return x + + def feature_size(self): + return self.features(autograd.Variable(torch.zeros(1, *self.input_dim))).view(1, -1).size(1) + + + def act(self, state, epsilon): + if random.random() > epsilon: + state = Variable(torch.FloatTensor(np.float32(state)).unsqueeze(0), volatile=True) + q_value = self.forward(state) + action = q_value.max(1)[1].data[0] + else: + action = random.randrange(env.action_space.n) + return action + +class ReplayBuffer: + def __init__(self, capacity): + self.capacity = capacity # 经验回放的容量 + self.buffer = [] # 缓冲区 + self.position = 0 + + def push(self, state, action, reward, next_state, done): + ''' 缓冲区是一个队列,容量超出时去掉开始存入的转移(transition) + ''' + 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 = random.sample(self.buffer, batch_size) # 随机采出小批量转移 + state, action, reward, next_state, done = zip(*batch) # 解压成状态,动作等 + return state, action, reward, next_state, done + + def __len__(self): + ''' 返回当前存储的量 + ''' + return len(self.buffer) + +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 # 奖励的折扣因子 + # e-greedy策略相关参数 + 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 = CNN(state_dim, action_dim).to(self.device) + self.target_net = CNN(state_dim, action_dim).to(self.device) + for target_param, param in zip(self.target_net.parameters(),self.policy_net.parameters()): # 复制参数到目标网路targe_net + target_param.data.copy_(param.data) + self.optimizer = optim.Adam(self.policy_net.parameters(), lr=cfg.lr) # 优化器 + self.memory = ReplayBuffer(cfg.memory_capacity) # 经验回放 + + def choose_action(self, state): + ''' 选择动作 + ''' + 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) + q_values = self.policy_net(state) + action = q_values.max(1)[1].item() # 选择Q值最大的动作 + else: + action = random.randrange(self.action_dim) + return action + def update(self): + if len(self.memory) < self.batch_size: # 当memory中不满足一个批量时,不更新策略 + return + # 从经验回放中(replay memory)中随机采样一个批量的转移(transition) + 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) + q_values = self.policy_net(state_batch).gather(dim=1, index=action_batch) # 计算当前状态(s_t,a)对应的Q(s_t, a) + next_q_values = self.target_net(next_state_batch).max(1)[0].detach() # 计算下一时刻的状态(s_t_,a)对应的Q值 + # 计算期望的Q值,对于终止状态,此时done_batch[0]=1, 对应的expected_q_value等于reward + expected_q_values = reward_batch + self.gamma * next_q_values * (1-done_batch) + loss = nn.MSELoss()(q_values, expected_q_values.unsqueeze(1)) # 计算均方根损失 + # 优化更新模型 + self.optimizer.zero_grad() + loss.backward() + for param in self.policy_net.parameters(): # clip防止梯度爆炸 + param.grad.data.clamp_(-1, 1) + self.optimizer.step() + + 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')) + for target_param, param in zip(self.target_net.parameters(), self.policy_net.parameters()): + param.data.copy_(target_param.data) \ No newline at end of file diff --git a/codes/DQN/outputs/CartPole-v0/20211111-165800/models/dqn_checkpoint.pth b/codes/DQN/outputs/CartPole-v0/20211111-165800/models/dqn_checkpoint.pth deleted file mode 100644 index a0b6ef9..0000000 Binary files a/codes/DQN/outputs/CartPole-v0/20211111-165800/models/dqn_checkpoint.pth and /dev/null differ diff --git a/codes/DQN/outputs/CartPole-v0/20211111-165800/results/eval_rewards_curve.png b/codes/DQN/outputs/CartPole-v0/20211111-165800/results/eval_rewards_curve.png deleted file mode 100644 index a260f79..0000000 Binary files a/codes/DQN/outputs/CartPole-v0/20211111-165800/results/eval_rewards_curve.png and /dev/null differ diff --git a/codes/DQN/outputs/CartPole-v0/20211111-165800/results/train_ma_rewards.npy b/codes/DQN/outputs/CartPole-v0/20211111-165800/results/train_ma_rewards.npy deleted file mode 100644 index 1e0ab6c..0000000 Binary files a/codes/DQN/outputs/CartPole-v0/20211111-165800/results/train_ma_rewards.npy and /dev/null differ diff --git a/codes/DQN/outputs/CartPole-v0/20211111-165800/results/train_rewards_curve.png b/codes/DQN/outputs/CartPole-v0/20211111-165800/results/train_rewards_curve.png deleted file mode 100644 index 4c14b8d..0000000 Binary files a/codes/DQN/outputs/CartPole-v0/20211111-165800/results/train_rewards_curve.png and /dev/null differ diff --git a/codes/DQN/outputs/CartPole-v0/20211229-144313/models/dqn_checkpoint.pth b/codes/DQN/outputs/CartPole-v0/20211229-144313/models/dqn_checkpoint.pth new file mode 100644 index 0000000..7fcf736 Binary files /dev/null and b/codes/DQN/outputs/CartPole-v0/20211229-144313/models/dqn_checkpoint.pth differ diff --git a/codes/DQN/outputs/CartPole-v0/20211111-165800/results/eval_ma_rewards.npy b/codes/DQN/outputs/CartPole-v0/20211229-144313/results/test_ma_rewards.npy similarity index 100% rename from codes/DQN/outputs/CartPole-v0/20211111-165800/results/eval_ma_rewards.npy rename to codes/DQN/outputs/CartPole-v0/20211229-144313/results/test_ma_rewards.npy diff --git a/codes/DQN/outputs/CartPole-v0/20211111-165800/results/eval_rewards.npy b/codes/DQN/outputs/CartPole-v0/20211229-144313/results/test_rewards.npy similarity index 100% rename from codes/DQN/outputs/CartPole-v0/20211111-165800/results/eval_rewards.npy rename to codes/DQN/outputs/CartPole-v0/20211229-144313/results/test_rewards.npy diff --git a/codes/DQN/outputs/CartPole-v0/20211229-144313/results/test_rewards_curve.png b/codes/DQN/outputs/CartPole-v0/20211229-144313/results/test_rewards_curve.png new file mode 100644 index 0000000..bc60080 Binary files /dev/null and b/codes/DQN/outputs/CartPole-v0/20211229-144313/results/test_rewards_curve.png differ diff --git a/codes/DQN/outputs/CartPole-v0/20211229-144313/results/train_ma_rewards.npy b/codes/DQN/outputs/CartPole-v0/20211229-144313/results/train_ma_rewards.npy new file mode 100644 index 0000000..d81acd2 Binary files /dev/null and b/codes/DQN/outputs/CartPole-v0/20211229-144313/results/train_ma_rewards.npy differ diff --git a/codes/DQN/outputs/CartPole-v0/20211111-165800/results/train_rewards.npy b/codes/DQN/outputs/CartPole-v0/20211229-144313/results/train_rewards.npy similarity index 55% rename from codes/DQN/outputs/CartPole-v0/20211111-165800/results/train_rewards.npy rename to codes/DQN/outputs/CartPole-v0/20211229-144313/results/train_rewards.npy index 88c137f..900914d 100644 Binary files a/codes/DQN/outputs/CartPole-v0/20211111-165800/results/train_rewards.npy and b/codes/DQN/outputs/CartPole-v0/20211229-144313/results/train_rewards.npy differ diff --git a/codes/DQN/outputs/CartPole-v0/20211229-144313/results/train_rewards_curve.png b/codes/DQN/outputs/CartPole-v0/20211229-144313/results/train_rewards_curve.png new file mode 100644 index 0000000..9df7664 Binary files /dev/null and b/codes/DQN/outputs/CartPole-v0/20211229-144313/results/train_rewards_curve.png differ diff --git a/codes/DQN/task0.py b/codes/DQN/task0.py new file mode 100644 index 0000000..c7cd5da --- /dev/null +++ b/codes/DQN/task0.py @@ -0,0 +1,148 @@ +import sys +import os +curr_path = os.path.dirname(os.path.abspath(__file__)) # 当前文件所在绝对路径 +parent_path = os.path.dirname(curr_path) # 父路径 +sys.path.append(parent_path) # 添加路径到系统路径 + +import gym +import torch +import datetime +import numpy as np +from common.utils import save_results, make_dir +from common.utils import plot_rewards +from DQN.dqn import DQN + +curr_time = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") # 获取当前时间 + + +class Config: + '''超参数 + ''' + + def __init__(self): + ################################## 环境超参数 ################################### + self.algo_name = 'DQN' # 算法名称 + self.env_name = 'CartPole-v0' # 环境名称 + self.device = torch.device( + "cuda" if torch.cuda.is_available() else "cpu") # 检测GPUgjgjlkhfsf风刀霜的撒发十 + self.seed = 10 # 随机种子,置0则不设置随机种子 + self.train_eps = 200 # 训练的回合数 + self.test_eps = 30 # 测试的回合数 + ################################################################################ + + ################################## 算法超参数 ################################### + self.gamma = 0.95 # 强化学习中的折扣因子 + self.epsilon_start = 0.90 # e-greedy策略中初始epsilon + self.epsilon_end = 0.01 # e-greedy策略中的终止epsilon + self.epsilon_decay = 500 # e-greedy策略中epsilon的衰减率 + self.lr = 0.0001 # 学习率 + self.memory_capacity = 100000 # 经验回放的容量 + self.batch_size = 64 # mini-batch SGD中的批量大小 + self.target_update = 4 # 目标网络的更新频率 + self.hidden_dim = 256 # 网络隐藏层 + ################################################################################ + + ################################# 保存结果相关参数 ############################## + self.result_path = curr_path + "/outputs/" + self.env_name + \ + '/' + curr_time + '/results/' # 保存结果的路径 + self.model_path = curr_path + "/outputs/" + self.env_name + \ + '/' + curr_time + '/models/' # 保存模型的路径 + self.save = True # 是否保存图片 + ################################################################################ + + +def env_agent_config(cfg): + ''' 创建环境和智能体 + ''' + env = gym.make(cfg.env_name) # 创建环境 + state_dim = env.observation_space.shape[0] # 状态维度 + action_dim = env.action_space.n # 动作维度 + agent = DQN(state_dim, action_dim, cfg) # 创建智能体 + if cfg.seed !=0: # 设置随机种子 + torch.manual_seed(cfg.seed) + env.seed(cfg.seed) + np.random.seed(cfg.seed) + return env, agent + + +def train(cfg, env, agent): + ''' 训练 + ''' + print('开始训练!') + print(f'环境:{cfg.env_name}, 算法:{cfg.algo_name}, 设备:{cfg.device}') + rewards = [] # 记录所有回合的奖励 + ma_rewards = [] # 记录所有回合的滑动平均奖励 + for i_ep in range(cfg.train_eps): + ep_reward = 0 # 记录一回合内的奖励 + state = env.reset() # 重置环境,返回初始状态 + while True: + action = agent.choose_action(state) # 选择动作 + next_state, reward, done, _ = env.step(action) # 更新环境,返回transition + agent.memory.push(state, action, reward, + next_state, done) # 保存transition + state = next_state # 更新下一个状态 + agent.update() # 更新智能体 + ep_reward += reward # 累加奖励 + if done: + break + if (i_ep + 1) % cfg.target_update == 0: # 智能体目标网络更新 + agent.target_net.load_state_dict(agent.policy_net.state_dict()) + rewards.append(ep_reward) + if ma_rewards: + ma_rewards.append(0.9 * ma_rewards[-1] + 0.1 * ep_reward) + else: + ma_rewards.append(ep_reward) + if (i_ep + 1) % 10 == 0: + print('回合:{}/{}, 奖励:{}'.format(i_ep + 1, cfg.train_eps, ep_reward)) + print('完成训练!') + env.close() + return rewards, ma_rewards + + +def test(cfg, env, agent): + print('开始测试!') + print(f'环境:{cfg.env_name}, 算法:{cfg.algo_name}, 设备:{cfg.device}') + ############# 由于测试不需要使用epsilon-greedy策略,所以相应的值设置为0 ############### + cfg.epsilon_start = 0.0 # e-greedy策略中初始epsilon + cfg.epsilon_end = 0.0 # e-greedy策略中的终止epsilon + ################################################################################ + rewards = [] # 记录所有回合的奖励 + ma_rewards = [] # 记录所有回合的滑动平均奖励 + for i_ep in range(cfg.test_eps): + ep_reward = 0 # 记录一回合内的奖励 + state = env.reset() # 重置环境,返回初始状态 + while True: + action = agent.choose_action(state) # 选择动作 + next_state, reward, done, _ = env.step(action) # 更新环境,返回transition + state = next_state # 更新下一个状态 + ep_reward += reward # 累加奖励 + if done: + break + rewards.append(ep_reward) + if ma_rewards: + ma_rewards.append(ma_rewards[-1] * 0.9 + ep_reward * 0.1) + else: + ma_rewards.append(ep_reward) + print(f"回合:{i_ep+1}/{cfg.test_eps},奖励:{ep_reward:.1f}") + print('完成测试!') + env.close() + return rewards, ma_rewards + + +if __name__ == "__main__": + cfg = Config() + # 训练 + env, agent = env_agent_config(cfg) + rewards, ma_rewards = train(cfg, env, agent) + make_dir(cfg.result_path, cfg.model_path) # 创建保存结果和模型路径的文件夹 + agent.save(path=cfg.model_path) # 保存模型 + save_results(rewards, ma_rewards, tag='train', + path=cfg.result_path) # 保存结果 + plot_rewards(rewards, ma_rewards, cfg, tag="train") # 画出结果 + # 测试 + env, agent = env_agent_config(cfg) + agent.load(path=cfg.model_path) # 导入模型 + rewards, ma_rewards = test(cfg, env, agent) + save_results(rewards, ma_rewards, tag='test', + path=cfg.result_path) # 保存结果 + plot_rewards(rewards, ma_rewards, cfg, tag="test") # 画出结果 diff --git a/codes/DQN/task0_train.ipynb b/codes/DQN/task0_train.ipynb deleted file mode 100644 index 464e216..0000000 --- a/codes/DQN/task0_train.ipynb +++ /dev/null @@ -1,423 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "import sys\n", - "from pathlib import Path\n", - "curr_path = str(Path().absolute()) # 当前路径\n", - "parent_path = str(Path().absolute().parent) # 父路径\n", - "sys.path.append(parent_path) # 添加路径到系统路径\n", - "\n", - "import math,random\n", - "import gym\n", - "import torch\n", - "import torch.nn as nn\n", - "import torch.optim as optim\n", - "import torch.nn.functional as F\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "import seaborn as sns\n", - "from IPython.display import clear_output # 清空单元格输出区域" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 网络模型" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "class MLP(nn.Module):\n", - " def __init__(self, n_states,n_actions,hidden_dim=128):\n", - " \"\"\" 初始化q网络,为全连接网络\n", - " n_states: 输入的特征数即环境的状态数\n", - " n_actions: 输出的动作维度\n", - " \"\"\"\n", - " super(MLP, self).__init__()\n", - " self.fc1 = nn.Linear(n_states, hidden_dim) # 输入层\n", - " self.fc2 = nn.Linear(hidden_dim,hidden_dim) # 隐藏层\n", - " self.fc3 = nn.Linear(hidden_dim, n_actions) # 输出层\n", - " \n", - " def forward(self, x):\n", - " # 各层对应的激活函数\n", - " x = F.relu(self.fc1(x)) \n", - " x = F.relu(self.fc2(x))\n", - " return self.fc3(x)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 经验回放" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ - "class ReplayBuffer:\n", - " def __init__(self, capacity):\n", - " self.capacity = capacity # 经验回放的容量\n", - " self.buffer = [] # 缓冲区\n", - " self.position = 0 \n", - " \n", - " def push(self, state, action, reward, next_state, done):\n", - " ''' 缓冲区是一个队列,容量超出时去掉开始存入的转移(transition)\n", - " '''\n", - " if len(self.buffer) < self.capacity:\n", - " self.buffer.append(None)\n", - " self.buffer[self.position] = (state, action, reward, next_state, done)\n", - " self.position = (self.position + 1) % self.capacity \n", - " \n", - " def sample(self, batch_size):\n", - " batch = random.sample(self.buffer, batch_size) # 随机采出小批量转移\n", - " state, action, reward, next_state, done = zip(*batch) # 解压成状态,动作等\n", - " return state, action, reward, next_state, done\n", - " \n", - " def __len__(self):\n", - " ''' 返回当前存储的量\n", - " '''\n", - " return len(self.buffer)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## DQN" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [], - "source": [ - "class DQN:\n", - " def __init__(self, n_states, n_actions, cfg):\n", - "\n", - " self.n_actions = n_actions # 总的动作个数\n", - " self.device = cfg.device # 设备,cpu或gpu等\n", - " self.gamma = cfg.gamma # 奖励的折扣因子\n", - " # e-greedy策略相关参数\n", - " self.frame_idx = 0 # 用于epsilon的衰减计数\n", - " self.epsilon = lambda frame_idx: cfg.epsilon_end + \\\n", - " (cfg.epsilon_start - cfg.epsilon_end) * \\\n", - " math.exp(-1. * frame_idx / cfg.epsilon_decay)\n", - " self.batch_size = cfg.batch_size\n", - " self.policy_net = MLP(n_states, n_actions,hidden_dim=cfg.hidden_dim).to(self.device)\n", - " self.target_net = MLP(n_states, n_actions,hidden_dim=cfg.hidden_dim).to(self.device)\n", - " for target_param, param in zip(self.target_net.parameters(),self.policy_net.parameters()): # 复制参数到目标网路targe_net\n", - " target_param.data.copy_(param.data)\n", - " self.optimizer = optim.Adam(self.policy_net.parameters(), lr=cfg.lr) # 优化器\n", - " self.memory = ReplayBuffer(cfg.memory_capacity) # 经验回放\n", - "\n", - " def choose_action(self, state):\n", - " ''' 选择动作\n", - " '''\n", - " self.frame_idx += 1\n", - " if random.random() > self.epsilon(self.frame_idx):\n", - " with torch.no_grad():\n", - " state = torch.tensor([state], device=self.device, dtype=torch.float32)\n", - " q_values = self.policy_net(state)\n", - " action = q_values.max(1)[1].item() # 选择Q值最大的动作\n", - " else:\n", - " action = random.randrange(self.n_actions)\n", - " return action\n", - " def update(self):\n", - " if len(self.memory) < self.batch_size: # 当memory中不满足一个批量时,不更新策略\n", - " return\n", - " # 从经验回放中(replay memory)中随机采样一个批量的转移(transition)\n", - " state_batch, action_batch, reward_batch, next_state_batch, done_batch = self.memory.sample(\n", - " self.batch_size)\n", - " # 转为张量\n", - " state_batch = torch.tensor(state_batch, device=self.device, dtype=torch.float)\n", - " action_batch = torch.tensor(action_batch, device=self.device).unsqueeze(1) \n", - " reward_batch = torch.tensor(reward_batch, device=self.device, dtype=torch.float) \n", - " next_state_batch = torch.tensor(next_state_batch, device=self.device, dtype=torch.float)\n", - " done_batch = torch.tensor(np.float32(done_batch), device=self.device)\n", - " q_values = self.policy_net(state_batch).gather(dim=1, index=action_batch) # 计算当前状态(s_t,a)对应的Q(s_t, a)\n", - " next_q_values = self.target_net(next_state_batch).max(1)[0].detach() # 计算下一时刻的状态(s_t_,a)对应的Q值\n", - " # 计算期望的Q值,对于终止状态,此时done_batch[0]=1, 对应的expected_q_value等于reward\n", - " expected_q_values = reward_batch + self.gamma * next_q_values * (1-done_batch)\n", - " loss = nn.MSELoss()(q_values, expected_q_values.unsqueeze(1)) # 计算均方根损失\n", - " # 优化更新模型\n", - " self.optimizer.zero_grad() \n", - " loss.backward()\n", - " for param in self.policy_net.parameters(): # clip防止梯度爆炸\n", - " param.grad.data.clamp_(-1, 1)\n", - " self.optimizer.step()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### DQN参数" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [], - "source": [ - "class DQNConfig:\n", - " def __init__(self):\n", - " self.algo = \"DQN\" # 算法名称\n", - " self.env = 'CartPole-v0' # 环境名称\n", - " self.train_eps = 200 # 训练的回合数\n", - " self.eval_eps = 20 # 测试的回合数\n", - " self.gamma = 0.95 # 强化学习中的折扣因子\n", - " self.epsilon_start = 0.90 # e-greedy策略中初始epsilon\n", - " self.epsilon_end = 0.01 # e-greedy策略中的终止epsilon\n", - " self.epsilon_decay = 500 # e-greedy策略中epsilon的衰减率\n", - " self.lr = 0.0001 # 学习率\n", - " self.memory_capacity = 100000 # 经验回放的容量\n", - " self.batch_size = 64 # mini-batch SGD中的批量大小\n", - " self.target_update = 4 # 目标网络的更新频率\n", - " self.device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\") # 检测GPU\n", - " self.hidden_dim = 256 # 网络隐藏层" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 创建环境" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "def env_agent_config(cfg,seed=1):\n", - " ''' 创建环境和智能体\n", - " '''\n", - " env = gym.make(cfg.env) # 创建环境\n", - " env.seed(seed) # 设置随机种子\n", - " n_states = env.observation_space.shape[0] # 状态数\n", - " n_actions = env.action_space.n # 动作数\n", - " agent = DQN(n_states,n_actions,cfg) # 创建智能体\n", - " return env,agent" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 训练" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "开始训练!\n", - "环境:CartPole-v0, 算法:DQN, 设备:cuda\n", - "回合:10/200, 奖励:12.0\n", - "回合:20/200, 奖励:16.0\n", - "回合:30/200, 奖励:15.0\n", - "回合:40/200, 奖励:14.0\n", - "回合:50/200, 奖励:13.0\n", - "回合:60/200, 奖励:27.0\n", - "回合:70/200, 奖励:36.0\n", - "回合:80/200, 奖励:33.0\n", - "回合:90/200, 奖励:200.0\n", - "回合:100/200, 奖励:200.0\n", - "回合:110/200, 奖励:200.0\n", - "回合:120/200, 奖励:200.0\n", - "回合:130/200, 奖励:200.0\n", - "回合:140/200, 奖励:200.0\n", - "回合:150/200, 奖励:200.0\n", - "回合:160/200, 奖励:200.0\n", - "回合:170/200, 奖励:200.0\n", - "回合:180/200, 奖励:200.0\n", - "回合:190/200, 奖励:200.0\n", - "回合:200/200, 奖励:200.0\n", - "完成训练!\n" - ] - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXsAAAEcCAYAAAAmzxTpAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/MnkTPAAAACXBIWXMAAAsTAAALEwEAmpwYAABYfklEQVR4nO3deXwU9f348dfMXrlPkhAIgiDEACKBACqHAlZRUbSWSql41m+tFk+8KgVFsUWtWi0WrVZr5SdqRRAEEW/xQJBLBBHCTe773mNmfn9sdsmxuTfJJnk/Hw8eJDvXezaT937yns98PophGAZCCCG6NbWzAxBCCNH+JNkLIUQPIMleCCF6AEn2QgjRA0iyF0KIHkCSvRBC9ACS7GuYMmUKX3/9dYcfd+vWrVx44YUdflxx0vHjx0lOTsblcvl93xs3buTcc88lNTWVPXv2+H3/bVFVVcXNN9/M6NGjue222zo7nHZ3//338/TTT3d2GJ1Ckn0ASEtLY8OGDZ0dhmgnS5Ys4c9//jPbt29n6NCh9ZYnJyczcuRIUlNTGTduHNdeey3r1q2rt96nn37Kr371K0aOHMm4ceOYN28e2dnZ3uUrV64kOTmZf/3rX7W2mzRpEps3b/YZ2wcffEBeXh6bN2/m2WefbeOZupWVlbF48WLOO+88UlNTOf/881m8eDEFBQWt2t/KlSv5zW9+U+u1+++/n+HDh5OamsrYsWO5/vrrSU9P90f4reJwOHjggQcYNWoU48eP55VXXum0WBoiyb4DaJrW2SG0WXc4h86SkZHB4MGDG11n9erVbN++nfXr13PFFVewaNEi/vGPf3iXf/DBB9x9991ce+21fPvtt6xduxaLxcLs2bMpKSnxrhcVFcVLL71EWVlZs2MbMGAAZrO5xefl668gh8PBtddey4EDB3jppZf4/vvvefPNN4mKiuKHH37wyzE8brzxRrZv387nn39OTEwMDzzwQIv37y/PPfccR44c4dNPP+W1117jpZde4osvvui0eHyRZN8AXdd58cUXOf/88xk3bhy33347RUVF3uW33XYb48ePZ/To0fz2t79l//793mX3338/Cxcu5KabbmLkyJFs3ryZKVOm8PLLL3PppZcyevRo7rjjDux2OwCbN29m0qRJ3u0bWxfgX//6FxMmTGDChAm8/fbbJCcnc+TIEZ/nUVRUxAMPPMCECRMYM2YMt9xyC+C7tVRzP3XP4eWXX2b8+PG1kv7GjRu59NJLm/V+1fXWW2/xi1/8grFjx3LzzTfXaqEmJyfzxhtvcMEFF5CWlsbDDz9MQw96a5rGsmXLOP/880lNTeWXv/wlmZmZPssyc+bM4e233/Zut2TJEsaNG8fUqVP5/PPPa+33nXfe4aKLLiI1NZWpU6eyYsWKBs9F13Wef/55Jk+ezNlnn829995LaWkpDoeD1NRUNE1jxowZnH/++Q3uwyMmJobLL7+chx56iBdeeIHCwkIMw2DJkiX84Q9/4NJLLyUoKIi4uDgWL15McHAwr732mnf7gQMHkpqayquvvtrksZ599lmef/551q9fT2pqKm+//XaD5wInS11vv/025513Htdee229fa5evZrMzEz+8Y9/cNppp6GqKrGxsdx6662ce+65AN7rJDU1lYsvvpiNGzd6t1+5ciWzZs3iscceY9y4cdx5550sXLiQHTt2kJqaSlpaWr1jBgcHc+mll3p/B9PT05kzZw5paWlccsklfPzxxw2+B59++ikzZswgLS2NWbNm8dNPP/lc78UXX6xX5nr00Ud59NFHAXj33Xe55ZZbiIyMZNCgQcycOZN33323sbe/w0myb8B///tfPvroI15//XW+/PJLIiMjWbRokXf5pEmT2LBhA9988w1Dhw5l3rx5tbZfu3YtN998M9u2bWP06NEArF+/npdeeomPP/6Yffv2sXLlygaP39C6X3zxBa+++iqvvPIKGzdubPDPc497772XyspK3n//fb7++muuu+66Zr8HNc/h2muvJTg4mG+//da7fM2aNd5k39T7VdM333zD3/72N5555hk2bdpE3759ueuuu2qt89lnn/G///2P9957j/Xr1/Pll1/63Ncrr7zC+++/z4svvsi2bdt47LHHCAoKavLc3nrrLT799FNWrVrFO++8wwcffFBreWxsLC+88ALbtm3jL3/5C3/5y1/48ccffe5r5cqVvPvuu7z22mt89NFHVFRUsGjRIqxWK9u3bwfcSfCjjz5qMi6PqVOnomkau3bt4uDBg2RkZDBt2rRa66iqygUXXMCmTZtqvX777bfzn//8p9EPW3A3WH7/+99z0UUXsX37dmbOnNngudS0ZcsW1q1bx8svv1xvn19//TUTJ04kNDS0weP269eP5cuX8/333/PHP/6Re+65h5ycHO/yXbt20a9fP7766iueeOIJHn74YUaOHMn27dvZunVrvf2Vl5ezZs0aUlJScDqd3HzzzYwfP56vv/6a+fPnM2/ePA4ePFhvuz179vCnP/2JRYsWsXnzZq666ipuueUWHA5HvXUvueQSPv/8c+9fTJqm8cEHHzB9+nSKi4vJzc3l9NNP965/+umnc+DAgQbfg84gyb4BK1as4M4776R3795YrVb++Mc/smHDBm9L8Ve/+hVhYWFYrVbmzp3LTz/95G0BgfuXdfTo0aiqis1mA9wty4SEBKKiopg8eTJ79+5t8PgNrbt+/Xp++ctfMnjwYIKDg5k7d26D+8jJyeGLL77g4YcfJjIyEovFwtixY5v9HtQ9h0suuYS1a9cC7rrsF198wSWXXNKs96umNWvWcOWVVzJs2DCsVit33XUXO3bs4Pjx4951brrpJiIiIujTpw/jxo1rsMX19ttvc/vttzNw4EAUReH0008nOjq6yXNbv3491157LYmJiURFRfH73/++1vLzzjuPU045BUVRGDt2LOPHj/eZaDznc91119GvXz9CQ0O56667WLduXZtu9losFqKjoykuLqawsBCA+Pj4euvFxcV5l3ukpKRwzjnn1KvdN0dzzmXu3LmEhIT4/FAtKioiLi6u0WNcdNFFJCQkoKoqF198Mf3792fXrl3e5fHx8cyZMwez2dzoB/e///1v0tLSuOCCCygvL+evf/0rO3fupKKigv/7v//DarVy9tlnM3nyZN5///1627/55ptcddVVnHnmmZhMJq644gosFgs7duyot27fvn0ZOnSo9wP722+/JSgoiJEjR1JRUQFAeHi4d/3w8HDKy8sbfR86WssLdT1ERkYGt956K6p68vNQVVXy8/Pp1asXTz/9NB988AEFBQXedQoLC70/8MTExHr7rPlLEBwcXKs109x1c3JyGD58uHeZr+N4ZGVlERkZSWRkZFOn61PdfV966aXMmjWLhx9+mI0bNzJ06FD69u0LNP5+JSQk1NpPTk4Ow4YN834fGhpKVFQU2dnZJCUlAfXPv6FfnKysLE455ZQWn1tOTk6t8+vTp0+t5Z9//jlLly7l8OHD6LpOVVUVQ4YMaXBfnvcB3InB5XL5PPfmcjqdFBQUEBkZ6f3wysnJoV+/frXWy83N9fnhdttttzFz5kyuv/76Fh23sXPx6N27d4PbR0VFkZub2+gxVq1axSuvvMKJEycAqKioqPWB1dj+a7rhhhu48847a722e/duevfuXes67NOnT60yoUdGRgarVq3i9ddf977mdDrJycnhvffeY+HChQCMHj2al156ienTp7N27Vouv/xy1q5dy/Tp0wEICQkB3A0gT8OurKys0b9uOoMk+wb07t2bxx57zFuCqWnVqlV8/PHHvPLKKyQlJVFaWsqYMWMarCv7U3x8fK0LNzMzs8F1e/fuTXFxMSUlJURERNRaFhwcTFVVlff7pn5BAU477TT69OnDF198Ueti9xyroffL1zl4ftHB/cteVFTUqsTYu3dvjh49Wi8Re34Bq6qqCAsLA2qfY1xcXK33rubXDoeD2267jSVLljB16lQsFgu33HJLgz/fuueTkZGB2WwmNja2xefj8fHHH2MymRgxYgRRUVH07t2bDz74gJtuusm7jq7rfPjhh0yZMqXe9oMGDeKCCy5g2bJlLTpuY+eSlZUFgKIoDW5/zjnn8Mwzz1BRUeH9GdR04sQJ5s+fz6uvvkpqaiomk4kZM2bUWqfu/hs7nq/4s7Ky0HXdm/AzMzMZMGBAvXUTExO5+eab+cMf/uBzX5dddlmt7y+66CKWLFlCVlYWGzdu5M033wQgMjKSuLg4fvrpJ8aPHw/ATz/9xGmnndbsuDuClHEa8Jvf/IZnnnnGe+EXFBR4/4QrLy/HarUSHR1NZWUlTz31VIfFNW3aNFauXEl6ejqVlZU8//zzDa4bHx/PpEmTePjhhykuLsbpdLJlyxbAXVPcv38/e/fuxW6389xzzzXr+NOnT+c///kPW7ZsqVVDbuz98rWPlStXsnfvXhwOB0899RQjRozwtupbYubMmfz973/n8OHDGIbBTz/9RGFhITExMSQkJLB69Wo0TeN///sfx44d82530UUX8d///pesrCyKi4t58cUXvcscDgcOh4OYmBjMZjOff/45X331VZPvybFjxygvL+fpp5/moosualUPl6KiIt577z0WLVrETTfdRHR0NIqicN999/HPf/6TNWvWYLfbyc3N5cEHH6SwsJCrr77a575uvfVW3nnnnVrlxaa09VxmzJhB7969mTt3Lunp6ei6TmFhIcuWLePzzz+nsrISRVGIiYkB3DfCa3Zu8CU2Npbs7GyftfS6RowYQVBQEC+99BJOp5PNmzfzySefcPHFF9dbd+bMmaxYsYKdO3diGAYVFRV89tlnDfZkiomJYezYsTzwwAMkJSUxaNAg77LLL7+cf/7znxQXF5Oens7bb7/NFVdc0WS8HUla9g245pprMAyDG264gZycHGJjY7n44os5//zzufzyy9m0aRMTJ04kKiqK22+/nTfeeKND4jr33HOZM2cO11xzDYqicMstt7Bq1SqsVqvP9R9//HH+8pe/cNFFF+F0Ohk3bhxjxozh1FNP5dZbb+W6664jKCiIu+66y9tSacz06dN56qmnmDRpkvcXFhp/v+o655xzuP3225k7dy4lJSWkpqa2+kGX66+/HofDwQ033EBhYSEDBw5k6dKlADzyyCM8/PDDPP300/zqV78iNTXVu92vf/1rDh8+zIwZMwgNDeXGG2/03nwOCwtj/vz53HHHHTgcDiZPnuyz9exx5ZVXkp2dzdVXX43dbmfChAn8+c9/btF5zJgxA0VRsFgsJCcn88ADD3hvfgNcfPHFWK1W/vnPfzJ//nxvWem///2vz1o+uG+Ezpgxo0XXZlvPxWq18uqrr/Lss89yww03UFJSQmxsLFOnTmXEiBFER0dzww03MGvWLBRF4fLLL2fUqFGN7vOss87itNNOY8KECSiK0minBKvVyrJly3j44Yd54YUXSEhI4PHHH6+VmD3OOOMMHnnkERYtWsSRI0cICgpi1KhRPnv8eEyfPp377ruPe+65p9brt912GwsXLmTy5MkEBQVx00031ephFwgUmbyka0tPT2f69On88MMPrWpJiq5p06ZN3H333bz66qukpKR0djiiC5AyThe0ceNGHA4HxcXFPPHEE0yePFkSfQ8zYcIE/vKXv/jsOSKEL9Ky74JuvPFGduzYgclkYsyYMSxcuLDBP+WFEAIk2QshRI8gZRwhhOgBJNkLIUQPIMleCCF6gIDuwlFYWI6ut/yWQmxsGPn5zRvitSNJXC0XqLFJXC0TqHFB4MbWmrhUVSE62vcwDQGd7HXdaFWy92wbiCSulgvU2CSulgnUuCBwY/NnXFLGEUKIHkCSvRBC9AABXcapyzAMCgtzcTiqgIb/vMnJUdF1veMCa6buHZeC1RpEdHRci0YpFEJ0jCaTfWFhIffeey9Hjx7FarXSv39/Fi1aRExMDDt27GDBggXY7Xb69u3LE0884R3WtbFlrVVWVoyiKCQkJKEoDf9RYjaruFyBl1S7c1yGoVNUlEdZWTHh4VH+CUwI4TdNlnEUReF3v/sdGzZsYM2aNfTr148nn3wSXde55557WLBgARs2bCAtLY0nn3wSoNFlbVFZWUZ4eFSjiV50DkVRCQ+PprIy8Ho1CCGakeyjoqIYN26c9/uRI0eSkZHB7t27sdls3uFAZ82a5Z3Hs7FlbaHrGiZTl6o89Sgmkxld15pesZMYhoHur3+6H/clcXWb2DRdb/O/9ir1tihz6rrOG2+8wZQpU8jMzKw1lVtMTAy6rlNUVNTosqioqDYFLPXgwBVIP5t/rfmRuKhgLp84EICcwgoW/nsLdmfgfhh1BAWdYMVJiGInSHFiVVxYFRc2qv+v8ZoVFxZFw4SBSdExUf2vztcqOubq11QMFM8/Be/Xhzn5tVpjGVB7G896ysmvGz8fo873TZ1//f2VNLp97fXVDrjEc4hl0P/9ze/7bVGyf+SRRwgJCeHqq69m48aNfg+mrtjYsFrf5+SomM3NK+E0d72O1l5xLVq0kJSUFGbOnNWq7f0Vl6qqxMWFN71iC7Rmf0dzyrC7DO+2B7PLsDs1LjpnANHhDU9i3VWpmgOrowirowSrsxirw/OvBIurHLOrErOrApNW1WQC9dAVM7pqxlBMGIoJXTVVf61iKObq/y3oSs3XVVBUb9pGofprFUNxp3NNqV4GGJ6vlRqp3lumVaqX19V0Sq/JqLd6x27fUiHx/bzXrT9/l5qd7JcsWcKRI0dYtmwZqqqSmJhIRkaGd7ln4u2oqKhGl7VEfn5ZrYcKdF1v1o3EzrgR6nK5mhxT3l9x+TqWUf3naGv278/3S9d1cnObPw1eU+Liwlu1P7tDo7i0yrvt8Sx3+23ymYn0igzutLjaynA50PIOo+cfQy/K9P4zygvqrasEhaOERqOER6HYklBsoShBYe7/baFgDUYx21AsNvD+b0UxV3+v+q9h0lnvV3MEYmy5uaWtiktVlXqNZI9mJfunnnqK3bt38+KLL3qnvxs+fDhVVVVs3bqVtLQ0VqxY4Z2TtLFl3cmECWlcf/1NfPPNV4wbdzazZ8/hueeeJj19Pw6Hg9TUNObOvZMTJ47xpz/dy4oV/8PlcnHJJVO59tobmT37Gj7+eCNffvkZDz20mDfeeJ2PP/4QTXNhtdqYN+9+Bg9O9nmsyy+/kkcfXUh+fh69eyd6J1cGWL16JW+99f+wWKwYhs6iRX+lf/8BnfIedYT0E8X877N07p41ErPJ/T5oukGF3eVdp7TCPX9peIjv6RsDlV5eiJaxFy07HS0nHT3/GBjVpShLEGpUIqY+KahRvVHDexHdty8lziCUkCgUc9c6V9G+mkz2+/fv54UXXmDAgAHMmuUuESQlJbF06VIef/xxFi5cWKt7Jbj/lG9omT999UMmm3Zl1ntdUcBo41PGE0YkMv6MxCbXs9lsvPTSawD89a+PMHLkKO6//8/ous7DD8/n/fff47LLrqCiopy8vFyOHz/BqacOYuvWLcyefQ3ff/8daWljAJg27RJ+8xv35NFbtmzmiSf+wosvvurzWA8+eA9nnpnKDTf8HydOHOe662YzbtzZADz//N9ZvvwdevXqhcPhCMi+/f50KLOEfceKKKt0EhVmA8Cl6VTYT14EpRVObBYTNoups8JsFsMw0HMP4Tq0FdexH9ALqidJtwRhijsV65kXYUoYhBrb391qr1PmCI4LpyzAWqkiMDSZ7AcPHsy+fft8Lhs1ahRr1qxp8bLu5KKLpnu/3rTpC/bu/ZEVK5YDUFVVRXx8AgCjRqWxZct3nDhxghkzfsny5a/hdDrZuvU7rr76OgD27dvLf//7CiUlxaiqyrFjRxs81rZt33PHHe5Jj/v2TfJ+YLiPNYbFixcyfvxEzj57An37JrXLuQcKT6WvZslP04xaN2NLKxyEh1g6OrRm00tycO77Emf6ZoySHFBMmHoPxjr215iThqHG9PNrWUX0PF26H+P4M3y3vjuyZh8cHFLjO4PHHnvSZ3IdPXoMW7e6k/2CBY+wY8c2PvpoA4YBffr0xel08uc/38c//vEvkpNPJy8vl8svv6iRYzXssceeYO/eH/n++63cdtvNzJv3AGefPb4tpxnQPEneVSPZu3Qdh1PHpemYTSqlFc6AS/aGYaBl7MW5eyOuIztAAVOfoVhGTsd86mh3XV0IP5Gmgh+NHz+J11//D5rmblEWFRWRkXECcCf7zZu/obS0lPj4BNLSxvLyyy94W+QOhx1N07x/Caxc+Xajxxo9Oo33338PgIyME2zdugVw37zNyDjB0KHDmTPnOsaOPYv9+33/ZdZd6NU1u7ote4Aqh/tn4U72gVPDdmXspWLVI1S+/zha9gGsqdMJnf0UIZfcg+X0SZLohd916ZZ9oLn99rt5/vlnue6636AoChaLldtuu5s+ffoSH59ASEgoI0aMBNzJPzs7i1Gj3A+ehYaGceONv+emm64hIiKSyZOnNnGseTz66EI++mgDiYl9SE0dDbh7wyxe/BBlZaUoikpCQgI33/zHdj3vzuZJ8lr1/4ZheL+usLsIC7ZQWukgKa7zE6hWcAz7t2+iHd+NEhqDbeJ1WAafIzdTRbuTZN8GmzZtrfV9SEgo8+Y90OD6b731rre8FBMTy5dfbqm1/Le/vZbf/vZa7/dz5lzf4LHi4uL5+9//6fM4zz//UvNOoJvwtOw1zf3eajVa+JVVLgzD6PSWvaG5cGxfg2P7WrAGYTtrFpahUyTJiw4jyV50eZ6W/cmkfzLZV9hdVDk0nC6902r2Wt4Rqj77F3rBccynnY3tnNmoQf598EyIpkiyF12epyHvSfKuGl1NK+0uSiudQOf0sXce+Iaqz/+NYgsl+MLbMfdP7fAYhABJ9qIbMIzaNfuaLftKu6vGA1Ud17I3dB37d2/j3LUeU2IyQeffihoc0WHHF6IuSfaiy/OWcTxdMLWTLfsKu4vSio5t2Ruak6qPnsd1ZDuWoVOxnfMbFFV+1UTnkitQdHland44rjo3aDuyZW9oTio3LkU7ugPbOVdjHX5+ux9TiOaQZC+6PL1eGad2y95S4X6cpL2Tfa1EP+EarEOntOvxhGgJSfaiyzOqc7tWfWO2bs1eVRQsZrVdx8UxdJ2qj5e5E/34OZLoRcCRJ2hFgxYvfoh33nmzs8NoUt0naGv1s7e7KKkeF6c9J1exf/cWrsPfYzv7N1iHNf5AnBCdQZJ9AHO5XE2v1AWP5W91yzg1b9BWVt+gDQ9uv5uzzn1f4tz1AZZhU7GecWG7HUeItujSZRznz1/h3PdFvdcVRfF2x2stS/IkLEMaHzxswoQ0brrpD3z55ecUFxdz330PsnXrd2ze/DUul4tHHlnCgAGnkp+fx0MPPUhFRTl2u4NzzhnPLbfc3uA+WzJG/uuvv9XmMfK//fYrxo7tumPk123Re5K9SVW8D1UlRDdvELmW0vKPUrXpNUx9UrCdPbtdjiGEP3TpZB8IwsLCeeml1/jkk4944IG7eeihx7j55j+yfPl/eO21f7NgwSOEhYWzZMnTRESEUVXl4K67/si3337NWWed43OfLRsjP4+srIw2jZH/yiuv43LpXXaM/IbKOGEhFgpK7JSUOxg3NMHvxzWcdio/WopiCyVo6h9Q1MAeK1/0bF062VuGjPfZ+u7IIY6nTr0AgOTk0wGF8eMnVn+fwueffwq4Byd7/vm/s3v3LgzDID8/n/37f24w2bdkjPzvv/+OzMyMHj1Gfv2Wvfv/8GArx3PLABjYx/8PNNk3v4lRnE3w9PvkgSkR8Lp0sg8EnmkaVVXFaj3ZtU9VVe9Qx2++uZzS0hJefvk1TCYLS5YsxuGwN7jPloyR//33W8jI6Nlj5HuHS/AmffcHfc2ulgN6+zcZVxzciXPPJ1jOuBBznxS/7luI9tCsG7RLlixhypQpJCcn8/PPPwNw/PhxZsyY4f03ZcoUxo4d691mypQpTJs2zbv8yy+/bJ8z6AJKS0uJje2FzWYjNzeHTZs+b/a2MkZ+0/Q6/es9XS89yT4hOpiwYP/1sTdcDvLWv4AS2RvbmCv9tl8h2lOzWvZTp07lmmuu4be//a33taSkJFavXu39fvHixd6E5PHss88yZMgQP4Xadc2cOYs///k+Zs+eSa9e8YwePabpjao1PUZ+SI8fI79uzd5zg9bTA+dUP5dwHNvX4Cpyl29kiGLRVShGC7qtTJkyhWXLltVL4A6Hg0mTJvHyyy8zbNiwRtdtifz8slqzD2VlHaF37/5NbteRNfuW6AlxNfdn1FxxceHkNjGB9nPv7GL7/jxmnjeIi87qz1c/ZPLy+3u5fMKprNp0iNnnD+b8tH5+iUcvyaH8rT8RNvQclHNu8Ms+/ak571dnCNS4IHBja01cqqoQGxvmc5lfavaffPIJCQkJ3kTvMW/ePAzDYPTo0dx1111ERLSshVU36JwcFbO5eY8GNHe9jtbd41JVlbg4/47V3tT+LBb3ZRwUbCUuLpzgkHwABvWPQfnqEGed2ddvMWV/+QKKyUTM5KsxRwTmmPT+fv/9JVDjgsCNzZ9x+SXZv/POO1x5Ze3a5fLly0lMTMThcLB48WIWLVrEk08+2aL91m3Z67rerBZoT2hB+5M/49J13a+tpOa0bqrs7lEtS0uryM0tpai4EoCkmGAev/kcwiyqX2LSsg9QsfcbrKMvxxwR221agx0hUOOCwI3N3y37NjfnsrOz2bJlC5deemmt1xMTEwF3b5XZs2ezbdu2th4KoM0PS4n201k/m7qjXXq6XppNCrGRQX47jn3LOyjBEVhHXNT0ykIEmDYn+3fffZdzzz2X6Oho72sVFRWUlro/kQzDYN26daSktL17mqqa0LSu+1h/d6dpLtROeLCo7nj2nq6XZtV/JTNX5j60jL1YR16CYrH5bb9CdJRmlXEeffRRPvzwQ/Ly8rj++uuJiori/fffB9zJ/sEHH6y1fn5+PnPnzkXTNHRdZ9CgQSxcuLDNwQYHh1FaWkRUVCyKEpi1757KMHRKSwsJDvb9J2T7Hrt2kve07E0m/w185tj6LkpIFJaUyX7bpxAdqVnJfv78+cyfP9/nsg0bNtR7rV+/fqxatapNgfkSFhZJYWEu2dnHgYZLBqqqBtTj/B7dOy4FqzWIsLBIv8TUEnWfoNVqjI3jD1pOOlrmT9jO+o10tRRdVpd6glZRFGJi4ptcrzvdcOkIgRpXc3nu4ddM+iZV8duQxo6d68EaguX0SX7ZnxCdQWohosvzNcSxv0o4ekkOrsPfYx06GcUa7Jd9CtEZJNmLLq/uHLSaZvjt5qxj90egqFiGyVyyomuTZC+6PKNGkgd3F0x/tOwNlx3nz5swDxiNGhrd9AZCBDBJ9qLL846NY5ws45hNbb+0XenfgaMCi8wnK7oBSfaiy/MOcVxj1Et/9MRx7PkUNaoPpsTkNu9LiM4myV50efW6Xuo6pja27LWC4+i5B7GknNuuE5UL0VEk2Ysur960hJqBuY01e9f+r0ExYT7t7DbHJ0QgkGQvujxfE463pYxj6DrO/V9j6neGTDcoug1J9qLLq9vPXtONNt2g1TL2YFQU+ZzfWIiuSpK96PL83bJ3HvgGrMGYTznTL/EJEQgk2Ysur+5wCa42tOwNzYXr8HbM/UfJODiiW5FkL7q8ukMbt6XrpXZij7tv/cDmzxMsRFcgyV50eUbdmn0bHqpyHtwClmBMScOaXlmILkSSvejy6nW9bOVwCYbuwnVkG+YBqSgmi19jFKKzSbIXXZ5nKP623qDVsvaDvRzzgNH+DE+IgCDJXnR5dUe9dGmtu0HrOrIDVDNmKeGIbqhZk5csWbKEDRs2cOLECdasWcOQIUMAmDJlClarFZvNPSfnvHnzmDhxIgA7duxgwYIF2O12+vbtyxNPPEFsbGw7nYboybw1e63GcAmtaNm7ju7E1Od0FIv/JikXIlA0q/kzdepUli9fTt++feste/bZZ1m9ejWrV6/2Jnpd17nnnntYsGABGzZsIC0tjSeffNK/kQtRzTvhuNH6lr1elIVRnIW5/0h/hydEQGjWb0RaWhqJiYnN3unu3bux2WykpaUBMGvWLD744IPWRShEIwzD8M5GrLXhBq3r6E4AeZBKdFttnoN23rx5GIbB6NGjueuuu4iIiCAzM5M+ffp414mJiUHXdYqKioiKimr2vmNjw1odV1xceKu3bU8SV8s1FptnWGNwJ/64uHA03SA8LKhF55SZswdLryQSBg70S1ydSeJquUCNzZ9xtSnZL1++nMTERBwOB4sXL2bRokV+Ldfk55d5/0RviUCdQFviarmmYnO6TiZ7l6aTm1uKy6XjsDubfU6Gy0HlkT1YUs5r9jaB+p5JXC0XqLG1Ji5VVRpsJLepN46ntGO1Wpk9ezbbtm3zvp6RkeFdr6CgAFVVW9SqF6I5ajYGNM1AN9z/WnKDVss+AJpTeuGIbq3Vyb6iooLSUvenjmEYrFu3jpSUFACGDx9OVVUVW7duBWDFihVMmzbND+EKUZvnpqzZpKDphrdHTktu0GonfgTFhKm3zEgluq9mlXEeffRRPvzwQ/Ly8rj++uuJiopi2bJlzJ07F03T0HWdQYMGsXDhQgBUVeXxxx9n4cKFtbpeCuFvJ5O9SpVDw1Vdw2/JDVrX8R8xJQxCsQa3S4xCBIJmJfv58+czf/78eq+vWrWqwW1GjRrFmjVrWh2YEM3hKeNYzO5k76xO9ma1eS17o6oMPe8I1tGXt1eIQgQEeYJWdGmekr2nbON0Vif7ZrbsXVk/AwamPqe3R3hCBAxJ9qJL87bsq5O93akBNHvCcS1zH5jMmOKb3+VSiK5Ikr3o0jxDJVjM7kvZ4apO9s3sjaNl/YwpfpCMcim6PUn2okvzPDVr9iR7Z/Nv0BqOSvS8I5h6D2m/AIUIEJLsRZem123ZV5dxmnODVstJB0PHlChdLkX3J8ledGn1a/aeG7TNSPaZ+0BRMcUPar8AhQgQkuxFl1a3N463Zt+MMo6WtR819hTpXy96BEn2oksz9IbKOI0ne0PX0XIPSate9BiS7EWXVnO4BKh5g7bxS1svPAEuO6YESfaiZ5BkL7q0ejdom1nG0XLS3etJ/3rRQ0iyF12a5i3jmIAaN2ib6I2j56SDLRQlIqF9AxQiQEiyF12aUT2cvac3jsPZ3Jb9QffDVErL56oVoiuSZC+6NG/N3lxds6+ezKSxJ2gNRyV6YYbcnBU9iiR70aXV7WdfVuEAIMTW8ICuWt5hwMAUf2p7hydEwJBkL7q0ujdoSyqcAIQGNzzWjZ53GAC114B2jU2IQCLJXnRpNScvASgpd2Czmhp9glbLO4ISGoMaHNEhMQoRCJo1ecmSJUvYsGEDJ06cYM2aNQwZMoTCwkLuvfdejh49itVqpX///ixatIiYmBgAkpOTGTJkCGp1r4jHH3+c5GQZg0T4l17noarSCgdhQY1f1nreUUy9+rd7bEIEkma17KdOncry5cvp27ev9zVFUfjd737Hhg0bWLNmDf369ePJJ5+std2KFStYvXo1q1evlkQv2oVepzdOeZWL0KCGSziG045elCklHNHjNCvZp6WlkZiYWOu1qKgoxo0b5/1+5MiRZGRk+Dc6IZpQt2YPTdTr848ChrTsRY/TrDJOU3Rd54033mDKlCm1Xp8zZw6apjFp0iTmzp2L1Wr1x+GE8PKUcWrW6EMbKeNoeUcAUCXZix7GL8n+kUceISQkhKuvvtr72meffUZiYiJlZWXcc889LF26lDvvvLNF+42NDWt1THFx4a3etj1JXC3XWGxhJ0oAiI0N9b4WGx3S4DY5ZRm4QqOI79+vzQ9UBep7JnG1XKDG5s+42pzslyxZwpEjR1i2bJn3ZizgLfuEhYUxc+ZMXnnllRbvOz+/zNtya4m4uHByc0tbvF17k7harqnYioorAKgot3tfM0GD21ScSEeJTiIvr6xd4+osElfLBWpsrYlLVZUGG8lt6nr51FNPsXv3bpYuXVqrRFNcXExVVRUALpeLDRs2kJKS0pZDCeFT3eESAEKDfbdhDF1DLzqBGpPUEaEJEVCa1bJ/9NFH+fDDD8nLy+P6668nKiqKZ555hhdeeIEBAwYwa9YsAJKSkli6dCkHDx5kwYIFKIqCy+UiNTWV22+/vV1PRPRMmu7jBm0DvXH0kmzQXJhi+nVIbEIEkmYl+/nz5zN//vx6r+/bt8/n+qmpqaxZs6ZtkQnRDN7eODVa9mEN9MbRC44DSMte9EjyBK3o0k4OhNZ0bxy94DgoKmpUos/lQnRnkuxFl2bo9Vv2DfWz1wuOo0YmoJilC7DoeSTZiy7NO+F4M2r2WsFxKeGIHkuSvejSPF1zTaqCp9e8rzKO4azCKMmRZC96LL88VCVEZ/HU7FVFwWRSUBUFq8VUf71C91AekuxFTyXJXnRpNVv2qqo03O2yyJ3sTVF9fS4XoruTMo7o0rwtexVMqtpwT5zCDFBNKBFxHRmeEAFDkr3o0jwte0VRMKlKw33sizLdPXHU+iUeIXoCSfaiS/P0xlGbKONoRZmokdK/XvRckuxFl+Zp2auKQq/IIBJ7hdZbx9Bc7p440X06OjwhAobcoBVdmm4YqNVDFT9w9ShOdsCssU5JNhi6PDkrejRJ9qJL0w0Dz8jaJtX3H6p6USYAapS07EXPJWUc0aXpuoGqNj4JibePfVTvjghJiIAkyV50abqOt4zT4DpFmSihMSiWoA6KSojAI8ledGk1a/YNrlOUKfV60eNJshddmrtm33CyN3QdvVBmpxJCkr3o0gzdoLGSvVGSA5oTkyR70cM1meyXLFnClClTSE5O5ueff/a+fujQIa666iouvPBCrrrqKg4fPtysZUL4k24YKI1ke63gGCADoAnRZLKfOnUqy5cvp2/f2gNILVy4kNmzZ7NhwwZmz57NggULmrVMCH/S9MZr9nrhCUCRB6pEj9dksk9LSyMxsfbNrfz8fPbs2cP06dMBmD59Onv27KGgoKDRZUL4m667R7xscHnBcZSIeBSzrQOjEiLwtOqhqszMTBISEjCZ3INKmUwm4uPjyczMxDCMBpfFxMT4L3IhAKOJ3jhawXGp1wtBgD9BGxsb1upt4+LC/RiJ/0hcLddYbBarGYtF9bmO7rRTWpJD2BkTiWmH8wvU90ziarlAjc2fcbUq2ScmJpKdnY2maZhMJjRNIycnh8TERAzDaHBZS+Xnl3kHumqJuLhwcnNLW7xde5O4Wq6p2CorHei64XMdLe8wGDpVQXF+P79Afc8krpYL1NhaE5eqKg02klvV9TI2NpaUlBTWrl0LwNq1a0lJSSEmJqbRZUL4m27QYD97veA4ID1xhIBmtOwfffRRPvzwQ/Ly8rj++uuJiori/fff56GHHuL+++/n+eefJyIigiVLlni3aWyZEP6kN9IbRys4DiYzakRCB0clROBpMtnPnz+f+fPn13t90KBBvP322z63aWyZEP7U2HAJesFx1Ki+MjuVEMgTtMIPtu/P5V9rfuyUYzc26qVecBw1RiYYFwIk2Qs/+CE9n29/zMYwWn4zva1qjmdfk1FVhlFRJN0uhagmyV60WXG5AwNwaXqHH7uhmr1WeAKQm7NCeEiyF21WWuEEwO7shGRv+B7PXveMiRMtyV4IkGQv/KCkwgGAw6l1+LEbGuJYLzgO1hCU0OgOj0mIQCTJXrRZSbk72ds7Idk3NMSxXnACU0wSShMTmwjRU0iyF23icGpUOdxJvjOSvab7HuJYL8lGjZQ5Z4XwkGQv2sRTrwdwdErN3sBUp/VuuBwYlSUo4bEdHo8QgUqSvWgTT70eOqdlr+v1h0swytzDaathvTo8HiEClSR70Saeej10zg1aX0Mc62X5AChh0rIXwkOSvWiTTm/Z+5iWUC/LA0CVMo4QXpLsRZvUbtk3XbP/8XABR7L8N5ys7qM3jlGWD4oi3S6FqEGSvWiT0gonnipKc1r2/2/jz6z9+rDfjq/5GBtHL81HCYlGUQN6bh4hOpQke9EmJeUOosPd87s2J9nbnRoOl/967fiq2RtleahSrxeiFkn2ok1KKtzJ3qQqzSrjOJw6Tpf/avu+Ji/Ry/Kl26UQdUiyF21SUu4kIsSK1WJqVsve4dRw+nHAtLoDoRm6jlFWKN0uhahDkr1ok5IKB+EhVmwWtcmul7ph4HDpOBsp4+w+mN+ieYfrjo1jVBSCoUm3SyHqaNMdrOPHj3Prrbd6vy8tLaWsrIzvvvuOKVOmYLVasdnc9dx58+YxceLEtkUrAopuGJRWOIgIbV7L3pPkG0r2GXnlPPXWTm69Yjijk+ObF0Od3ji654EqKeMIUUubkn1SUhKrV6/2fr948WI07eQv/LPPPsuQIUPacggRwCrtLgwDwoIt2CymJmv2npZ/Q8m+0u4CIK+4qtkx1B3i2CjJAUCRMo4QtfitjONwOFizZg1XXnmlv3YpApwnuVvNKrZmtOw96zeU7D2vF5bam3V8u0PD4dSwWU/OMatl7wdLsAyCJkQdfuuI/Mknn5CQkMCwYcO8r82bNw/DMBg9ejR33XUXERERLdpnbGxYq+OJiwtv9bbtqTvFpVXPBxgTHUJYiJVKh6vR/VRV53iXbvhc71h+JQAVDq3W8ob2uXl3JppucPaIvt51juUeIPiUFOITIlt8Pi3VnX6WHSFQ44LAjc2fcfkt2b/zzju1WvXLly8nMTERh8PB4sWLWbRoEU8++WSL9pmfX9aim3UecXHh5Ob67ylNf+lucWXnlQNQVelAwaC8wtnofrJz3MscTs3nenn5ZQBk5Zd7lzcW2xfbjhNsMxEfYSU3txS9sgRn3nGUgWe3+/vc3X6W7S1Q44LAja01camq0mAj2S9lnOzsbLZs2cKll17qfS0xMREAq9XK7Nmz2bZtmz8OJQKIZ85Zs0nFajE12RvHXqNm72tyck+XzKJmlHF0w2Bneh7DTo3FbHJfxlrWz+54EpObfxJC9BB+Sfbvvvsu5557LtHR7rFIKioqKC11fyIZhsG6detISUnxx6FEAPHU2C1mFZtFxd7Ew1KOGstdmo9kX72/ojK7zw+Dmo5ml1Jc5uDMQSd73WiZ+8BkRe01oLmnIESP4ZcyzrvvvsuDDz7o/T4/P5+5c+eiaRq6rjNo0CAWLlzoj0OJAOJJzs1t2dfsreN06VjMtdsanpa9SzMorXQ/rNWQn44UAXDGwNrJ3pQwCMUkY+IIUZdffis2bNhQ6/t+/fqxatUqf+xaBDBPGcfi6Y3jcJdnGpr3teaHga+naGv20ikqtTea7CvsLlRFITzEAoBemoeefxTr2F+16lyE6O7kCVrRat4yTnXLXjcMtEZuqNccAM3X+DiuGssLmqjbO5waVovq/WBxHdrijmXg2OafgBA9iCR70Wqe1rnZrGKrLsk01te+5jJffe3rtuwb43DpWC0n+9c7D25Bje2PGtG8J2+F6Gkk2YtWq3mD1lr9YFNjT9E6mkr2mo6igELTD1Y5nBrW6g8YvSwfPecg5oFjWnoKQvQYcidLtJqnZW8xuWv20HjLvu4N2nr7q26tB1lNFJY1new9x3Qd+8Edx6mjW3YCQvQg0rIXreaq2bI3e1r2jSR7V9Mte4tJJTrM1swyjvvydU9DqKJEJrT4HIToKSTZi1ar1bK3Nl2zr9Wyb6A3jsWsEh1uIyO/vNFJTtxlHPcHjF5ehBIShaLI5SxEQ+S3Q7Sap2VvNivexNt4sm+8Ze9yuVv2547sQ0GJnTc/OdDgvuxOzXuD1qgoRAmJas0pCNFjSLIXrebUdFRFwaSerNk3doPW7tLx9MBvsIxjVhkxqBcXju3HJ9tO8MOBPJ/7cjhrlHHKi1BDo9p0LkJ0d5LsRas5XTpmszt9exJvUy37kCCzd1vf+3PvZ8aEUwHYe7jA577sNcs40rIXokmS7EWruVwGlupByE627BtP9qHB7ideG6vZAwRZzUSEWsnKL/e9L5eOzaJiuBxgL0cJjW7TuQjR3UmyF63m1DRvcvZMIGJvrJ+9Syc0qDrZN9IbxyM+Kpis/Arf+6qu2RsVxQCo0rIXolGS7EWrOV2Gd3jh5t6gDQ32lHF8D5dQc3C0uKggMn207A3D8Nbs9YpCAGnZC9EESfai1Tw3VAHMJgVVUZoo47SsZR8XFUx+cSV2h8bL7+/hSJZ72GxNN9ANA6vZhFFeBCA1eyGaIMletJqnqySAoijYrCbsjkbGxnFp2CwqFrPaZM0e3MneMGDrvhy++iGL7ftzgZP3BdxlHHfLXso4QjROkr1otZotewCbRaWqiZa91WzCYlIb7o1Ts2YfHQzApl2ZAOQVVwEn7wtYLSp6eRGYzGALbfP5CNGdSbIXrVa3JW6zmpvsjWOzmrCY1VrDGTe0v/god7Lfd6wIOJnsPcMu2MwmjIoilJDoBsfQF0K4SbIXrebSarfEbRaVqgbKOC5NR9MNrGZ3Gcfh6wnaOn8pRIRavb18APKLK4GTD25ZLSpGRZGUcIRohjaPejllyhSsVis2mw2AefPmMXHiRHbs2MGCBQuw2+307duXJ554gtjY2Cb2JroSp0vHEnoyOQc1MjWhp2xjtbhb9g2VcWome0VR6B0TwpGsUnrHhJBTWImm67Vq9np5IabYU/x5WkJ0S35p2T/77LOsXr2a1atXM3HiRHRd55577mHBggVs2LCBtLQ0nnzySX8cSjThaHYp3+7J6pBj1a2xW62mBlv2NRN0zZq906Wz5qtD2J0amm7U2h9A71h3Lf6c4b3RDYPCErt3X0FaOUZZHkpYjN/PTYjupl3KOLt378Zms5GWlgbArFmz+OCDD9rjUKKOj78/zv/buL9DjlW37BJkMTXYz97uadmba/fG2XeskHe/PMTug/kA9SYhP/uMRM4alsDAPhGAu27v2VfkwQ9BN7Cefp5fz0uI7sgvk5fMmzcPwzAYPXo0d911F5mZmfTp08e7PCYmBl3XKSoqIioqqtn7jY0Na3VMcXHhrd62PbV3XIrqTqQtPU5r4tJ0g/Awm3fbiPAgDmeX+dxXhcs9N21cbBghwVZ0wyAuLhzlmPsJWK16iLToyOBa20+NC2fqmFPIzHM/XOUwICjYSrxaTPDRr4kYdQG9Bg9ucez+0FOvsdYK1LggcGPzZ1xtTvbLly8nMTERh8PB4sWLWbRoEb/4xS/8ERv5+WXojUxg3ZC4uHByc0v9EoM/dURcJWV2HA6NnJySZvdQaW1cDqeGy6md3FbXqaxykptbyo4DefTtFUpcdY+arBz3OlWVdgxDp6LSRW5uKRnZJQAcr/7fXr19zdiy9u3FvvVdZocWUbanCr3vSK4I2YJhsqINvbhTftY9+RprjUCNCwI3ttbEpapKg43kNpdxEhMTAbBarcyePZtt27aRmJhIRkaGd52CggJUVW1Rq160jsOlYQAureUfki1Vr+tljTLOstW7+WDzUQB2H8zncJY7mdftZ19W4QSgqNQB4K3ZG4aB8/A2ctY8R8XKheiZP3GG9ThnHH+LU/a9zlBrBpx5GWpwRLufpxDdQZta9hUVFWiaRnh4OIZhsG7dOlJSUhg+fDhVVVVs3bqVtLQ0VqxYwbRp0/wVs2iEJ9k6XFq9+rc/GYZR7watzWrCpRlU2l04nDq51V0lX1yzh/JKd1K3WkxYLSZvzb6s+vXicney98Ts2Lkex3dvoQaFYRkyHuuYX/GP/+3jQudGBhft4YQrmv7Dprbb+QnR3bQp2efn5zN37lw0TUPXdQYNGsTChQtRVZXHH3+chQsX1up6Kdqfpw+6exya9juOphsYUK9lD1BUPVl4fnEVFVVOb0IHd994i0nFVf1gVKkn2VdvYzGrONM34/juLcyDxtHv13eTVz3yZWxkCG+emMSsPv15c18Ij1mt7XeCQnQzbUr2/fr1Y9WqVT6XjRo1ijVr1rRl96IVPN0SG5u/1R88ZRhLnZY9QEHpyWSfXehu3Z85KJZ9x4qICrPV6mdfVuFu0Xs+IIIdBVRteQVTwmCCzvsdinryoarYyCC2/ORkf/g4CpTjqKo8NStEc/mlN44IHCfLOA2PK+8PLs9k4z5a9oUldm8MBzPctforJg2kb1woJrV210tPq7+0womKTvyP/w8UlaCpN6OYLLWO2SsyCE03yC6swGaRh7+FaAn5jelmapZx2pO3ZV+nnz1AYWmV97W9R9yjUsZFBWNSVe82nu09ZRwDGG45hrX4CEHjr0YNq/+0dWykuy6VkV/RrvcjhOiO5Demm/EMEtbuZZzqlrnZdLKUYq1TxgHYd7SQ8BALwbaTf0RaTCouzUDXDW9vHIBxtnT0oEjMg8b5PGavSHc3zpzCCqwWk891hBC+SRmnG9F03dvlst3LON6W/cmke7JlfzLZl1e5GNTnZPdIx48fMSQnndPM4ZSW24mklHNDfmKnox9DLSdw9r+gVp2+ptgI9/hLhnFyZiwhRPNIsu9GapZu2r2Mo/m4QVud7Auqa/aeyUziqselN5x27N++SV/NydwIcK7fx90RWYSpds4L2uPeycBzGjymxWwiMtRKcblDavZCtJD8xnQjNUecdLRzGcdVPfyB2XyyjOPpjVNYWoXVopJQneTjqssvruM/gObkwOA5vFF+NlQUUaoHsy7sStKd8ex0nIIpqnejx+1VXbeXMo4QLSPJvhux1yjd+BpC2J889wR8tezLq1yEBlmIjXAnZs+MU67D28AWSkVsMt/aB7N9+N0sKZmOHn86z5ZO499l5zV549Vzk9YqN2iFaBH5jelGHDWGF25sxih/8N6gNdfvZw8QYjN7b6jGRQVj6C5cR3diPmUkFou7S2VhmRMDld4xwd7tmp3spWUvRItIzb4bsbtqlnHau2XvLuPUbNlbzSoK7m6UIUFmeseGoCiQEB2Mc89nYC/HfOoogjR3os6oHskyPibEuw+LqfFk7/kAsUrNXogWkWTfjdS+QdveLfvqMk6dmaWs1TdlQ2xmJpyRyKnxQVg3v4I9/VtMicmYk85gsKZiNavsTM/DpCr0qi73mFSlyadiPaUhadkL0TLSPOpGak4c0t41e5ePlj2c7H4ZalNRi44Rv/0lXOnfYk37JcGX3IdithJsMzMqOQ7DgLBgC6HB7rKOuRl1eM8NWpt0vRSiRaRl343U7o3TQV0v6yRom8VElFrO9LzVVKwsdg99cN5NWIaMr7Xe+OGJfPtjNmEhFkKqH7hqqoQD7pq9qigEB8mlK0RLyG9MN9KhZRxX7Ru0hqOC8ncWMsdkwRJWjk2vIui8mzD1Od3n0Acp/aOJDrcREWJFVRWCbeZmDYFgs5i45zcj6RvX+lnMhOiJJNl3I56+9UFWU/uXceo8VOX44UOM0lxiCCXIVMm+U6/hrDqt+ZpUVeG2K0dgqq7RhwaZUZs5s1byKdFtjF6InkeSfTfiqdmHBVs6oDfOyZa9UVWGY9cGzANGs6LgHI4cyeRXcclN7qN/75Pza4YGWbwfIEII/5MbtC10KLOEBS9/R0WVs+mV2+Bwlvs4lXZXs7fxlHFCgy3tXsZxabq794yi4NjzCTirsKZdgcVqpcQIIaSFNfWwYLN0pxSiHbWpZV9YWMi9997L0aNHsVqt9O/fn0WLFhETE0NycjJDhgxBrR7W9vHHHyc5uenWXlvtXPE8EaFmBlzyOxTV/8njwIlijueWcTS7jNP7t1854edj7uPkFFbWagE3xuHUMJtUgiymDmnZe2rsWtbPqDFJmGKSsFnc49eHtjDZXzFpULuP1ClET9ambKgoCr/73e/YsGEDa9asoV+/fjz55JPe5StWrGD16tWsXr26QxI9wNFKG70yv6bqi39jGP5PeKXVMytlFVb4fd81eabpK2vBXxB2p4bNomKxqO2SOD/ZdpzlH/4M4J1/1jAMtNxDmOJOBU4+RRsSZGlwP74M7BMhtXgh2lGbkn1UVBTjxp0ce3zkyJFkZGS0Oai2OBo1lq/VNFw/b8K+6TUMw/Dr/kuqJ8bOLmjfZF9U5j5OzfHem+Jw6u4Jvc3t07Lfvj+PTbszMQyDgpIqosJsGKV5YC9HjRsAnOxnH2KT20FCBBK/1Tl0XeeNN95gypQp3tfmzJnDjBkz+Nvf/obD4fDXoRoVFWpjbfkIrCOn49z7GfYvX8XQm1/3bkpJuTv5ZhdU+m2fvnjmZK05WXdTHC6tOtmr7VKzLy6zY3doFJc7yCqspHdMMFruIQBMcQOBk8MYtLRmL4RoX377jXzkkUcICQnh6quvBuCzzz4jMTGRsrIy7rnnHpYuXcqdd97Zon3Gxra8L3Xf3uGUbztOr1/MoSLEStHXKzFV5JLwq/swBTe9P103eGbFNs4fewojTourt7yyOonmFlcRF9e8WnpNzd2mrMr9AWWoavOPo6iEBluICA9C04taFF9z1i2u/qArd+rkFVUyKbUvtvL9VJnMJCSfjmKyMG38QKIigzklyX8lmda8zx1B4mqZQI0LAjc2f8bll2S/ZMkSjhw5wrJly7w3ZBMTEwEICwtj5syZvPLKKy3eb35+GbresjKM54TSjxQQP/wygoLiqPrsZY69voiQS+5BsQQ1uv2hzBI+/f445RUOEiPrr5tf5G7RZ+WXk5Vd7J1XtTni4sLJzS1t1roFxe7j5OSVNXub0nI7qgKaS6PKrjV7u+bE5dJ07/2Kr7afQNMNIoLMlB3ehxpzCnkFVUAVQSqcNyKx2cf2R2ydQeJqmUCNCwI3ttbEpapKg43kNpdxnnrqKXbv3s3SpUuxWq0AFBcXU1XlnnTa5XKxYcMGUlJS2nqoZokKq46hugxiOe1sgs7/A3ruISrWPYleXtjo9jsP5AGw71iRz3p/aYWT0CAzmm6QV1xVb7k/OJwa5dUt+5bcoHU4NWxm9yBj/p68pLjsZBluZ7r7PUqIDkLLO4Kpul4vhAhcbUr2+/fv54UXXiAnJ4dZs2YxY8YMbr31Vg4ePMjMmTO57LLLuOyyyzCbzdx+++3+irlRUWHueUqLaiQny4DRBE39A3r+MSreWYDr2K4Gt995IB8Fd3LLKaxdl7c7NOxOjcFJUUD73aQtLj8Ze0tq9nbPDVqLyTuht7947iEAZOa7zztBLQJnFab4QX47jhCifbSpjDN48GD27dvnc9maNWvasutWi6xu2ddMTgCWgWMwxSRR+dFSKtc/hfXMi7GO+SWKevItKCy1cyS7lAlnJLLph0x+OlpIQo2x1kuqyxiD+0Wy40AeWQWVjGhFnks/UcyPhwu4bPypPpd7WtEmVaG8JTdonRq26hu04O4eWXNCkbbwfHj2igwir7iK0CAztoJ07IApcYhfjiGEaD/d7pHFsGALZpNSq+zgoUYlEnL5Aiynn4dj5zoq1vwV14k9GLq75OEpT1yQ2os+oS72H82rtb0n2feJDSU0yOydfKOlPvjuKKu+PERuke8ePZ4Pqt6xIS1r2bs0rBbV+7CTP0s5xeXumJJPiXLHFhOClrUPJTQGJayX344jhGgf3a5/nKIoREcE1WvZe5ebrQRNug5Tn9Op+vI/VL7/OEpwJJaUczH2l3NT5EEi31/OfTYNZ6aJyk/PwjpsKmrcqZSWO4lWy+h9ZD03xmSSk+4i5/OBRMXFY4obiNqrP0oTg3kZhsHPx4oA2JWez9TRSfXW8cTet1coPxwsaPa5O5w6VrPJO7FHzVEw26qozI6iwOCkKL76IYuE6GC0zH2Y+g5r8pyFEJ2v2yV7gJjwIO8N2oZYTjsL84BUXEd34dz3JY5t75EGVJmDsAw/nwPFVo7/vI9zDm3Ftf8r1Nj+hASdxt0RXxF8yMnAoAgSLJUE/fQz9upKlhqThPnUNMynjGww8WfkV1Ba/aDUzgN5PpN9cbkDk6rQOyaE7/bmoOl6s3r9OJwasUY+Cbl7CFKC/NqyLypzEBlqJTHWXdYaEFaJkVmCKbFjnowWQrRN90z2kUEczSppcj3FbMMycAyWgWMoLcjnT//azLSJp3PJ2QOJySvnqR3RhI+fxZigQzj3fUniiY/JNcKJvXw+tl5JZB0r4pG3thFlquLGkS4Si3/A8f1qHN+vQgmJwjxwDOYBozDF9EMJcneH2ne0EBWd8QOt/Hj0BJWlAwkKDUVRT9bWi0rtxISq9HUdxYRGeaWLiFBro+fi0nQ03WBoznqiKo7ycJQZtmehpV2AGpnY5tZ3cZmDyDAb/eLDGJwUyfAQd4lL6vVCdA3dMtlHh9vYtb/xln1dh4ug3AhiYJ8oABJjQ4gIsbAno4rxl07FOmwqK9dv5fOfivl7L3drfEi/KB664Sz+sfIHnv/RxRN/+BPYS9GO7cJ1eBvOPZ/i3L0RACUkCmdcX3pnVvFY9HGCi+z8KgJcb7xNsWolaOi5WJInocYkUVpWzjXWDzll/wkWRIXg2FaMfua5qBH1H/LycDh1EtQioiqOUpJ0DvvSs0g79AUVBz8DWyimXgMwJZyGZch41Ih473YlFQ62fXuYM0+NbvSvh6IyOzHhNoKsZu6dFkvlB/+F0BjUyMQWvc9CiM7RLZN9TEQQ5VWuWiMzNuREXjlZ+eWcyC1HAQYkRgDu2v+QflH8fOxkv/wcRxDBIbVvmMZHhzD9nAEsW/0je48WMmxADOqQCViGTMCwl6PlpKMXHEcrOIFekYetKp+s0NMYMnoMa786SGVFBUlqPqP3fIJz90aUkCiuqnAQQiVFAy8ge98ukveupXzvWkwJgzH1G44a2RtTwmCMkCi+3JnJ2cN643BpnGU7gK6oVAyexuu7DtD3F1dzqn4EPe8wWt5hHNvfw7HtPUwJp2HqOxQjKJxNW49QXFSGbcRgTh86GCUiDiU4st5fAsVlds4P30/522+hF2WghMYQfOEdUq8Xoovotske3AkqItTKt3uyOWd4b8w+5jhd+Xk62/fnERNhI7FXKME1BvBKPiWarftyySuqpFdUMKUVTp/llNTBvQixmfnqh0xURcHp0hgxqBeKLRS17xl8U9CLImsKWw7lcryojD9OOYOgIXH8auh5HM0u5aFXthAyvh+pQcexZ+xj/09Z6P3HMGD4RJ7f2ps7L05iiP4zrv3f4tj6rve4umrmTE0nb+8pmGL7Mc52gPLYYZjDogCoNEdgPW0y3+/Lpc+wEBJsdpw/f4nr8DYc21YDMBEgBDiwnYoD7v0aqgUttBe26AT3XxNhvZjKDkYW7oWE07CmXoZl6BTUkEi//LyEEO2vWyb7/tWt84++P06VQ+OLnRlYTCpnD+9daz29Rs+YghI7E86IqbXc081w1aZDJMWFkZlfzsA+9ROcxWxibEo8X+zM5NsfswEYmxLP1Rck8+m247z7pXuwsPjoYO6+aiTDTj15nKT4MEKDzPyY5WLcxZP5qmow/y37mYVjxnjHhC8ywrCNnI5t5HRcjkp+2vUTg8xZbNt5gPyKCkaYcgmu+I5iw0rCmOkU1OhnfzCjhKXv/oDZpDLzvEH8YswMbKNmkF9QyuKXv+TMIfGMTzuVl5Z/waxxUfQJquC77/YQ6yhlqJqLmrEXXHbOC4K8XqMZcOmt7TJPgBCifXXLZD/klGimjkriwy3HvK/tTM9j3LAEvtmdRZVD45SEMGwWE+VVLqaf05/PtmcwfGDtZN+nVygJ0cF8vTvL+9op8b7HnTgvtS+b9+Zw3sg+2Kwm1nx1mL1HCimrdDJuaAI3XpJC74QI8vLKam2nespFR4sA+Gp3FklxoZySEEaVw92bpmZf+0935vHGx4WMOb0f32cFYbGorC3UiQkPIjYqiHv6JVNW3X/f4dR476tDhAaZGdQ3kjc+3s+gvpEM7BPBui0ZlBrBXDp5OINP7cUbH/XjuW9KCA+JAM5CcxjEVgRx329T+eirvXy1NZ0bpp0niV6ILqpbJnuAX08ZxOHsEqxmE9HhNnbsz+PrH7L497q9gHtS7gvG9ANg0pl9uHziwHoTXquKwqM3jas1eXeQ1fdbdkpCOP+4Y6K3hj3ytF78+/29hIdYuebCZMwmtcH6dnK/KLbvz2P3oXwOZpTw68mnoSgKQVYTJlUhv6SK7/flMnRANOs2H8FqUdnyUw4AV00+jdc27COnqJIp1d04PU/Qbvkph13p+fxy0kDOT0vi3n9+w3tfHeLqC4bw5a4MJo5IJCYiCFVVmDdrJG9/ls5XP2Ry269G4HTqPPfOLua/9B1FpXYmjDidITK5iBBdVrdN9haziQeuHg3A9p/z+Hp3Fv/vo59Jigvlmmmn89h/v+f9b47QKzKIXpHBDe7HpKqYrM1rzdZM5qckhLPw+jHohtFkH3nPDE1/f3sXVovK2cMSvPsLC7bwybYTfLLtBGHBFsoqndx11Zms++YIJpPKhBGJ/O+zdCrsLs48LRZwTxwSbDOxKz2fiBALU0cnEWQ1c+HYfrzz+UEWvboVVVG4+Oz+3hiCbWauuTCZ3/5isDfe+347in+/v5ek+DBm/0K6WArRlXXbZA94W+pDB0RjNilUOTQuG38qp/WNZHRyHN/vy/XW5duDoiiYmtFbpV98GLERQYQFW7jxkhQiqwdzA+gbF4rNYmLq6CTWfXuElP7RDBsQw7ABMWi6gdmkMjo5jqPZZSREux94slpMPHnLeKocGiE2s3d8nCmjkvjo++NEhdq48ZIUnx9yNT+YhvSL4rH/OwvdMHze3BZCdB3dOtl7BNvMnDEwlvySKkYlu/uqXzb+VHbsz+OMgbGdHJ17DOrH/m8cJpNar5R0x8wzUVUFVVGYPKovhnHyLwizyf3/nAuT6w3HHGwz1+pZ5Hntr78/G4u5/nEai01FulcK0dX1iGQP8PvLhmEYJ1v7/eLDeHruBG+Pl85mMfsenbJmi7qh1nVLWt02i39GwRRCdC2Bkek6gNVHkgsLtnRCJEII0fGkECuEED2AJHshhOgB2jXZHzp0iKuuuooLL7yQq666isOHD7fn4YQQQjSgXZP9woULmT17Nhs2bGD27NksWLCgPQ8nhBCiAe2W7PPz89mzZw/Tp08HYPr06ezZs4eCgubPvCSEEMI/2i3ZZ2ZmkpCQgMnk7gVjMpmIj48nMzOzvQ4phBCiAQHd9TI21vegY80RFxfux0j8R+JquUCNTeJqmUCNCwI3Nn/G1W4t+8TERLKzs9E098iNmqaRk5NDYqLMbCSEEB2t3ZJ9bGwsKSkprF27FoC1a9eSkpJCTExME1sKIYTwN8WoO6iKH6Wnp3P//fdTUlJCREQES5YsYeDAge11OCGEEA1o12QvhBAiMMgTtEII0QNIshdCiB5Akr0QQvQAkuyFEKIHkGQvhBA9gCR7IYToAQJ6uISWOnToEPfffz9FRUVERUWxZMkSBgwY0OFxFBYWcu+993L06FGsViv9+/dn0aJFxMTEkJyczJAhQ1CrJ/Z+/PHHSU5O7rDYpkyZgtVqxWZzT2o+b948Jk6cyI4dO1iwYAF2u52+ffvyxBNPEBvbcfPzHj9+nFtvvdX7fWlpKWVlZXz33XcNxtxelixZwoYNGzhx4gRr1qxhyJAhQOPXV0dce77iauxaAzrkemvo/Wrs59YR15uvuBq7zpqK2V8a+5k19r60+T0zupE5c+YYq1atMgzDMFatWmXMmTOnU+IoLCw0vv32W+/3f/3rX40HHnjAMAzDGDJkiFFWVtYpcRmGYUyePNnYt29frdc0TTPOP/98Y8uWLYZhGMbSpUuN+++/vzPC83r00UeNhx9+2DAM3zG3py1bthgZGRn1jtvY9dUR156vuBq71gyjY663ht6vhn5uHXW9NRRXTTWvs8Zi9qeGfmaNvS/+eM+6TRknkIZUjoqKYty4cd7vR44cSUZGRofH0Vy7d+/GZrORlpYGwKxZs/jggw86LR6Hw8GaNWu48sorO+X4aWlp9cZwauz66qhrz1dcgXCt+YqrMR11vTUVV2ddZw39zBp7X/zxnnWbMk5jQyp35ng8uq7zxhtvMGXKFO9rc+bMQdM0Jk2axNy5c7FarR0a07x58zAMg9GjR3PXXXeRmZlJnz59vMtjYmLQdd1bkuhon3zyCQkJCQwbNqzBmCMiIjo0psauL8MwAuLa83WtQedeb75+boFyvfm6zhqKub3U/Jk19r744z3rNi37QPXII48QEhLC1VdfDcBnn33GypUrWb58OQcOHGDp0qUdGs/y5ct57733eOeddzAMg0WLFnXo8ZvjnXfeqdXa6goxB4K61xp07vUW6D+3utcZdHzMvn5m7aXbJPtAHFJ5yZIlHDlyhGeeecZ7g8wTT1hYGDNnzmTbtm0dGpPn+FarldmzZ7Nt2zYSExNr/elfUFCAqqqd0qrPzs5my5YtXHrppd7XfMXc0Rq7vgLh2vN1rXnihs653hr6uQXC9ebrOmss5vZQ92fW2Pvij/es2yT7QBtS+amnnmL37t0sXbrU+2dzcXExVVVVALhcLjZs2EBKSkqHxVRRUUFpaSkAhmGwbt06UlJSGD58OFVVVWzduhWAFStWMG3atA6Lq6Z3332Xc889l+jo6EZj7miNXV+dfe35utagc6+3xn5ugXC91b3OmorZ33z9zBp7X/zxnnWrUS8DZUjl/fv3M336dAYMGEBQUBAASUlJ/O53v2PBggUoioLL5SI1NZU//elPhIaGdkhcx44dY+7cuWiahq7rDBo0iPnz5xMfH8+2bdtYuHBhrW5dvXr16pC4arrwwgt58MEHmTRpUpMxt5dHH32UDz/8kLy8PKKjo4mKiuL9999v9PrqiGvPV1zPPPOMz2tt6dKlbN++vUOuN19xLVu2rNGfW0dcbw39HKH+dQYdd601lB+WLl3a6PvS1vesWyV7IYQQvnWbMo4QQoiGSbIXQogeQJK9EEL0AJLshRCiB5BkL4QQPYAkeyEasWzZMh588MFWbXv//ffz9NNP+zkiIVqn24yNI0R7uPnmmzs7BCH8Qlr2QgjRA0iyF91KdnY2c+fO5ayzzmLKlCm89tprADz33HPcdttt3HHHHaSmpnLFFVfw008/ebd78cUXmThxIqmpqVx44YV888033u3mzZvnXe/jjz/mkksuIS0tjTlz5pCenu5dtmfPHq644gpSU1O54447sNvttWL79NNPmTFjBmlpacyaNatZxxfCb1o3/L4QgUfTNOOKK64wnnvuOcNutxtHjx41pkyZYnzxxRfGs88+awwdOtRYv3694XA4jJdeesmYPHmy4XA4jPT0dGPSpElGVlaWYRiGcezYMePIkSOGYRjGs88+a9x9992GYRjGwYMHjTPPPNPYtGmT4XA4jBdffNE4//zzDbvdbtjtduO8884zXnnlFcPhcBjr1683hg4dajz11FOGYRjGjz/+aJx11lnGjh07DJfLZaxcudKYPHmyYbfbGz2+EP4iLXvRbfzwww8UFBTwxz/+EavVSr9+/fj1r3/NunXrABg2bBjTpk3DYrFw/fXX43A42LlzJyaTCYfDQXp6Ok6nk6SkJE455ZR6+1+3bh3nnnsu48ePx2KxcOONN1JVVcX27dvZuXMnTqeTa6+9FovFwrRp0zjjjDO827755ptcddVVnHnmmZhMJq644gosFgs7duxo9vGFaAu5QSu6jRMnTpCTk+OdzQfcww2npaXRp08fevfu7X1dVVUSEhK86//pT3/iueee48CBA0yYMIH777+fhISEWvvPycmpNYGEZ1ja7OxsTCYTCQkJKIriXV5z3YyMDFatWsXrr7/ufc3pdJKTk8PYsWObdXwh2kJa9qLbSExMJCkpia1bt3r/bd++nX/9618AZGVledfVdZ3s7GzviIaXXnopb7zxBp9++imKovDkk0/W2398fHytMcUNw/DOYBUXF0d2djZGjXEFa66bmJjIzTffXCu2nTt3eqcybM7xhWgLSfai2xgxYgShoaG8+OKLVFVVoWkaP//8M7t27QLgxx9/5MMPP8TlcvGf//wHq9XKmWeeycGDB/nmm29wOBxYrVZsNlutCUA8LrroIj7//HO++eYbnE4n//73v7FaraSmpjJy5EjMZjOvvfYaTqeTDz/8kB9++MG77cyZM1mxYgU7d+7EMAwqKir47LPPKCsra/bxhWgLKeOIbsNkMrFs2TKWLFnC1KlTcTgcnHrqqdxxxx0ATJ06lXXr1nHffffRv39/nnvuOSwWCw6Hg7/97W+kp6djsVhITU31OR3dwIEDeeKJJ3jkkUfIzs4mJSWFZcuWeSefeO655/jzn//MM888w7nnnssvfvEL77ZnnHEGjzzyCIsWLeLIkSMEBQUxatQo0tLSmn18IdpCxrMXPcJzzz3HkSNHpDwieiz5W1EIIXoASfZCCNEDSBlHCCF6AGnZCyFEDyDJXgghegBJ9kII0QNIshdCiB5Akr0QQvQAkuyFEKIH+P/8aCkb/MwAKAAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "def train(cfg, env, agent):\n", - " ''' 训练\n", - " '''\n", - " print('开始训练!')\n", - " print(f'环境:{cfg.env}, 算法:{cfg.algo}, 设备:{cfg.device}')\n", - " rewards = [] # 记录所有回合的奖励\n", - " ma_rewards = [] # 记录所有回合的滑动平均奖励\n", - " for i_ep in range(cfg.train_eps):\n", - " ep_reward = 0 # 记录一回合内的奖励\n", - " state = env.reset() # 重置环境,返回初始状态\n", - " while True:\n", - " action = agent.choose_action(state) # 选择动作\n", - " next_state, reward, done, _ = env.step(action) # 更新环境,返回transition\n", - " agent.memory.push(state, action, reward, next_state, done) # 保存transition\n", - " state = next_state # 更新下一个状态\n", - " agent.update() # 更新智能体\n", - " ep_reward += reward # 累加奖励\n", - " if done:\n", - " break\n", - " if (i_ep+1) % cfg.target_update == 0: # 智能体目标网络更新\n", - " agent.target_net.load_state_dict(agent.policy_net.state_dict())\n", - " if (i_ep+1)%10 == 0: \n", - " print('回合:{}/{}, 奖励:{}'.format(i_ep+1, cfg.train_eps, ep_reward))\n", - " rewards.append(ep_reward)\n", - " if ma_rewards:\n", - " ma_rewards.append(0.9*ma_rewards[-1]+0.1*ep_reward)\n", - " else:\n", - " ma_rewards.append(ep_reward)\n", - " print('完成训练!')\n", - " return rewards, ma_rewards\n", - "\n", - "def plot_rewards(rewards,ma_rewards,plot_cfg):\n", - " # clear_output(True) # 清空单元格输出区域,因为多次打印,每次需要清楚前面打印的图片\n", - " sns.set() \n", - " plt.figure() # 创建一个图形实例,方便同时多画几个图\n", - " plt.title(\"learning curve on {} of {} for {}\".format(plot_cfg.device, plot_cfg.algo, plot_cfg.env))\n", - " plt.xlabel('epsiodes')\n", - " plt.plot(rewards,label='rewards')\n", - " plt.plot(ma_rewards,label='ma rewards')\n", - " plt.legend()\n", - " plt.show()\n", - "\n", - "class PlotConfig:\n", - " def __init__(self) -> None:\n", - " self.algo = \"DQN\" # 算法名称\n", - " self.env = 'CartPole-v0' # 环境名称\n", - " self.device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\") # 检测GPU\n", - "\n", - "cfg = DQNConfig()\n", - "plot_cfg = PlotConfig()\n", - "env,agent = env_agent_config(cfg,seed=1)\n", - "rewards, ma_rewards = train(cfg, env, agent)\n", - "plot_rewards(rewards, ma_rewards, plot_cfg) # 画出结果" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "开始测试!\n", - "环境:CartPole-v0, 算法:DQN, 设备:cuda\n", - "回合:3/20, 奖励:200.0\n", - "回合:6/20, 奖励:200.0\n", - "回合:9/20, 奖励:200.0\n", - "回合:12/20, 奖励:200.0\n", - "回合:15/20, 奖励:200.0\n", - "回合:18/20, 奖励:200.0\n", - "完成测试!\n" - ] - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "def eval(cfg,env,agent):\n", - " print('开始测试!')\n", - " print(f'环境:{cfg.env}, 算法:{cfg.algo}, 设备:{cfg.device}')\n", - " # 由于测试不需要使用epsilon-greedy策略,所以相应的值设置为0\n", - " cfg.epsilon_start = 0.0 # e-greedy策略中初始epsilon\n", - " cfg.epsilon_end = 0.0 # e-greedy策略中的终止epsilon\n", - " rewards = [] # 记录所有回合的奖励\n", - " ma_rewards = [] # 记录所有回合的滑动平均奖励\n", - " for i_ep in range(cfg.eval_eps):\n", - " ep_reward = 0 # 记录一回合内的奖励\n", - " state = env.reset() # 重置环境,返回初始状态\n", - " while True:\n", - " action = agent.choose_action(state) # 选择动作\n", - " next_state, reward, done, _ = env.step(action) # 更新环境,返回transition\n", - " state = next_state # 更新下一个状态\n", - " ep_reward += reward # 累加奖励\n", - " if done:\n", - " break\n", - " rewards.append(ep_reward)\n", - " if ma_rewards:\n", - " ma_rewards.append(ma_rewards[-1]*0.9+ep_reward*0.1)\n", - " else:\n", - " ma_rewards.append(ep_reward)\n", - " if (i_ep+1)%3 == 0: \n", - " print(f\"回合:{i_ep+1}/{cfg.eval_eps}, 奖励:{ep_reward:.1f}\")\n", - " print('完成测试!')\n", - " return rewards,ma_rewards\n", - "\n", - "rewards,ma_rewards = eval(cfg,env,agent)\n", - "plot_rewards(rewards,ma_rewards, plot_cfg) # 画出结果\n" - ] - } - ], - "metadata": { - "interpreter": { - "hash": "fe38df673a99c62a9fea33a7aceda74c9b65b12ee9d076c5851d98b692a4989a" - }, - "kernelspec": { - "display_name": "Python 3.7.10 64-bit ('py37': conda)", - "language": "python", - "name": "python3" - }, - "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.9" - }, - "metadata": { - "interpreter": { - "hash": "366e1054dee9d4501b0eb8f87335afd3c67fc62db6ee611bbc7f8f5a1fefe232" - } - }, - "orig_nbformat": 2 - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/codes/DQN/task0_train.py b/codes/DQN/task0_train.py deleted file mode 100644 index 5fd0ccd..0000000 --- a/codes/DQN/task0_train.py +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/env python -# coding=utf-8 -''' -@Author: John -@Email: johnjim0816@gmail.com -@Date: 2020-06-12 00:48:57 -@LastEditor: John -LastEditTime: 2021-09-15 15:34:13 -@Discription: -@Environment: python 3.7.7 -''' -import sys,os -curr_path = os.path.dirname(os.path.abspath(__file__)) # 当前文件所在绝对路径 -parent_path = os.path.dirname(curr_path) # 父路径 -sys.path.append(parent_path) # 添加路径到系统路径 - -import gym -import torch -import datetime - -from common.utils import save_results, make_dir -from common.plot import plot_rewards -from DQN.agent import DQN - -curr_time = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") # 获取当前时间 -class DQNConfig: - def __init__(self): - self.algo = "DQN" # 算法名称 - self.env_name = 'CartPole-v0' # 环境名称 - self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 检测GPU - self.train_eps = 200 # 训练的回合数 - self.eval_eps = 30 # 测试的回合数 - # 超参数 - self.gamma = 0.95 # 强化学习中的折扣因子 - self.epsilon_start = 0.90 # e-greedy策略中初始epsilon - self.epsilon_end = 0.01 # e-greedy策略中的终止epsilon - self.epsilon_decay = 500 # e-greedy策略中epsilon的衰减率 - self.lr = 0.0001 # 学习率 - self.memory_capacity = 100000 # 经验回放的容量 - self.batch_size = 64 # mini-batch SGD中的批量大小 - self.target_update = 4 # 目标网络的更新频率 - self.hidden_dim = 256 # 网络隐藏层 -class PlotConfig: - def __init__(self) -> None: - self.algo = "DQN" # 算法名称 - self.env_name = 'CartPole-v0' # 环境名称 - self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 检测GPU - self.result_path = curr_path+"/outputs/" + self.env_name + \ - '/'+curr_time+'/results/' # 保存结果的路径 - self.model_path = curr_path+"/outputs/" + self.env_name + \ - '/'+curr_time+'/models/' # 保存模型的路径 - self.save = True # 是否保存图片 - -def env_agent_config(cfg,seed=1): - ''' 创建环境和智能体 - ''' - env = gym.make(cfg.env_name) # 创建环境 - env.seed(seed) # 设置随机种子 - n_states = env.observation_space.shape[0] # 状态数 - n_actions = env.action_space.n # 动作数 - agent = DQN(n_states,n_actions,cfg) # 创建智能体 - return env,agent - -def train(cfg, env, agent): - ''' 训练 - ''' - print('开始训练!') - print(f'环境:{cfg.env_name}, 算法:{cfg.algo}, 设备:{cfg.device}') - rewards = [] # 记录所有回合的奖励 - ma_rewards = [] # 记录所有回合的滑动平均奖励 - for i_ep in range(cfg.train_eps): - ep_reward = 0 # 记录一回合内的奖励 - state = env.reset() # 重置环境,返回初始状态 - while True: - action = agent.choose_action(state) # 选择动作 - next_state, reward, done, _ = env.step(action) # 更新环境,返回transition - agent.memory.push(state, action, reward, next_state, done) # 保存transition - state = next_state # 更新下一个状态 - agent.update() # 更新智能体 - ep_reward += reward # 累加奖励 - if done: - break - if (i_ep+1) % cfg.target_update == 0: # 智能体目标网络更新 - agent.target_net.load_state_dict(agent.policy_net.state_dict()) - if (i_ep+1)%10 == 0: - print('回合:{}/{}, 奖励:{}'.format(i_ep+1, cfg.train_eps, ep_reward)) - rewards.append(ep_reward) - if ma_rewards: - ma_rewards.append(0.9*ma_rewards[-1]+0.1*ep_reward) - else: - ma_rewards.append(ep_reward) - print('完成训练!') - return rewards, ma_rewards - -def eval(cfg,env,agent): - print('开始测试!') - print(f'环境:{cfg.env_name}, 算法:{cfg.algo}, 设备:{cfg.device}') - # 由于测试不需要使用epsilon-greedy策略,所以相应的值设置为0 - cfg.epsilon_start = 0.0 # e-greedy策略中初始epsilon - cfg.epsilon_end = 0.0 # e-greedy策略中的终止epsilon - rewards = [] # 记录所有回合的奖励 - ma_rewards = [] # 记录所有回合的滑动平均奖励 - for i_ep in range(cfg.eval_eps): - ep_reward = 0 # 记录一回合内的奖励 - state = env.reset() # 重置环境,返回初始状态 - while True: - action = agent.choose_action(state) # 选择动作 - next_state, reward, done, _ = env.step(action) # 更新环境,返回transition - state = next_state # 更新下一个状态 - ep_reward += reward # 累加奖励 - if done: - break - rewards.append(ep_reward) - if ma_rewards: - ma_rewards.append(ma_rewards[-1]*0.9+ep_reward*0.1) - else: - ma_rewards.append(ep_reward) - print(f"回合:{i_ep+1}/{cfg.eval_eps}, 奖励:{ep_reward:.1f}") - print('完成测试!') - return rewards,ma_rewards - -if __name__ == "__main__": - cfg = DQNConfig() - plot_cfg = PlotConfig() - # 训练 - env,agent = env_agent_config(cfg,seed=1) - rewards, ma_rewards = train(cfg, env, agent) - make_dir(plot_cfg.result_path, plot_cfg.model_path) # 创建保存结果和模型路径的文件夹 - agent.save(path=plot_cfg.model_path) # 保存模型 - save_results(rewards, ma_rewards, tag='train', path=plot_cfg.result_path) # 保存结果 - plot_rewards(rewards, ma_rewards, plot_cfg, tag="train") # 画出结果 - # 测试 - env,agent = env_agent_config(cfg,seed=10) - agent.load(path=plot_cfg.model_path) # 导入模型 - rewards,ma_rewards = eval(cfg,env,agent) - save_results(rewards,ma_rewards,tag='eval',path=plot_cfg.result_path) # 保存结果 - plot_rewards(rewards,ma_rewards, plot_cfg, tag="eval") # 画出结果 diff --git a/codes/DQN/task1.py b/codes/DQN/task1.py new file mode 100644 index 0000000..078aa4c --- /dev/null +++ b/codes/DQN/task1.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python +# coding=utf-8 +''' +Author: JiangJi +Email: johnjim0816@gmail.com +Date: 2021-12-22 11:14:17 +LastEditor: JiangJi +LastEditTime: 2021-12-22 11:40:44 +Discription: 使用 Nature DQN 训练 CartPole-v1 +''' +import sys +import os +curr_path = os.path.dirname(os.path.abspath(__file__)) # 当前文件所在绝对路径 +parent_path = os.path.dirname(curr_path) # 父路径 +sys.path.append(parent_path) # 添加路径到系统路径 + +import gym +import torch +import datetime +from common.utils import save_results, make_dir +from common.utils import plot_rewards, plot_rewards_cn +from DQN.dqn import DQN + +curr_time = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") # 获取当前时间 +algo_name = "DQN" # 算法名称 +env_name = 'CartPole-v1' # 环境名称 +class DQNConfig: + ''' 算法相关参数设置 + ''' + + def __init__(self): + self.algo_name = algo_name # 算法名称 + self.env_name = env_name # 环境名称 + self.device = torch.device( + "cuda" if torch.cuda.is_available() else "cpu") # 检测GPU + self.train_eps = 200 # 训练的回合数 + self.test_eps = 30 # 测试的回合数 + # 超参数 + self.gamma = 0.95 # 强化学习中的折扣因子 + self.epsilon_start = 0.90 # e-greedy策略中初始epsilon + self.epsilon_end = 0.01 # e-greedy策略中的终止epsilon + self.epsilon_decay = 500 # e-greedy策略中epsilon的衰减率 + self.lr = 0.0001 # 学习率 + self.memory_capacity = 100000 # 经验回放的容量 + self.batch_size = 64 # mini-batch SGD中的批量大小 + self.target_update = 4 # 目标网络的更新频率 + self.hidden_dim = 256 # 网络隐藏层 +class PlotConfig: + ''' 绘图相关参数设置 + ''' + + def __init__(self) -> None: + self.algo_name = algo_name # 算法名称 + self.env_name = env_name # 环境名称 + self.device = torch.device( + "cuda" if torch.cuda.is_available() else "cpu") # 检测GPU + self.result_path = curr_path + "/outputs/" + self.env_name + \ + '/' + curr_time + '/results/' # 保存结果的路径 + self.model_path = curr_path + "/outputs/" + self.env_name + \ + '/' + curr_time + '/models/' # 保存模型的路径 + self.save = True # 是否保存图片 + + +def env_agent_config(cfg, seed=1): + ''' 创建环境和智能体 + ''' + env = gym.make(cfg.env_name) # 创建环境 + env.seed(seed) # 设置随机种子 + state_dim = env.observation_space.shape[0] # 状态维度 + action_dim = env.action_space.n # 动作维度 + agent = DQN(state_dim, action_dim, cfg) # 创建智能体 + return env, agent + +def train(cfg, env, agent): + ''' 训练 + ''' + print('开始训练!') + print(f'环境:{cfg.env_name}, 算法:{cfg.algo_name}, 设备:{cfg.device}') + rewards = [] # 记录所有回合的奖励 + ma_rewards = [] # 记录所有回合的滑动平均奖励 + for i_ep in range(cfg.train_eps): + ep_reward = 0 # 记录一回合内的奖励 + state = env.reset() # 重置环境,返回初始状态 + while True: + action = agent.choose_action(state) # 选择动作 + next_state, reward, done, _ = env.step(action) # 更新环境,返回transition + agent.memory.push(state, action, reward, next_state, done) # 保存transition + state = next_state # 更新下一个状态 + agent.update() # 更新智能体 + ep_reward += reward # 累加奖励 + if done: + break + if (i_ep+1) % cfg.target_update == 0: # 智能体目标网络更新 + agent.target_net.load_state_dict(agent.policy_net.state_dict()) + rewards.append(ep_reward) + if ma_rewards: + ma_rewards.append(0.9*ma_rewards[-1]+0.1*ep_reward) + else: + ma_rewards.append(ep_reward) + if (i_ep+1)%10 == 0: + print('回合:{}/{}, 奖励:{}'.format(i_ep+1, cfg.train_eps, ep_reward)) + print('完成训练!') + return rewards, ma_rewards + +def test(cfg,env,agent): + print('开始测试!') + print(f'环境:{cfg.env_name}, 算法:{cfg.algo_name}, 设备:{cfg.device}') + # 由于测试不需要使用epsilon-greedy策略,所以相应的值设置为0 + cfg.epsilon_start = 0.0 # e-greedy策略中初始epsilon + cfg.epsilon_end = 0.0 # e-greedy策略中的终止epsilon + rewards = [] # 记录所有回合的奖励 + ma_rewards = [] # 记录所有回合的滑动平均奖励 + for i_ep in range(cfg.test_eps): + ep_reward = 0 # 记录一回合内的奖励 + state = env.reset() # 重置环境,返回初始状态 + while True: + action = agent.choose_action(state) # 选择动作 + next_state, reward, done, _ = env.step(action) # 更新环境,返回transition + state = next_state # 更新下一个状态 + ep_reward += reward # 累加奖励 + if done: + break + rewards.append(ep_reward) + if ma_rewards: + ma_rewards.append(ma_rewards[-1]*0.9+ep_reward*0.1) + else: + ma_rewards.append(ep_reward) + print(f"回合:{i_ep+1}/{cfg.test_eps},奖励:{ep_reward:.1f}") + print('完成测试!') + return rewards,ma_rewards +if __name__ == "__main__": + cfg = DQNConfig() + plot_cfg = PlotConfig() + # 训练 + env, agent = env_agent_config(cfg, seed=1) + rewards, ma_rewards = train(cfg, env, agent) + make_dir(plot_cfg.result_path, plot_cfg.model_path) # 创建保存结果和模型路径的文件夹 + agent.save(path=plot_cfg.model_path) # 保存模型 + save_results(rewards, ma_rewards, tag='train', + path=plot_cfg.result_path) # 保存结果 + plot_rewards_cn(rewards, ma_rewards, plot_cfg, tag="train") # 画出结果 + # 测试 + env, agent = env_agent_config(cfg, seed=10) + agent.load(path=plot_cfg.model_path) # 导入模型 + rewards, ma_rewards = test(cfg, env, agent) + save_results(rewards, ma_rewards, tag='test', + path=plot_cfg.result_path) # 保存结果 + plot_rewards_cn(rewards, ma_rewards, plot_cfg, tag="test") # 画出结果 diff --git a/codes/DQN/task2.py b/codes/DQN/task2.py new file mode 100644 index 0000000..16571b2 --- /dev/null +++ b/codes/DQN/task2.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python +# coding=utf-8 +''' +Author: JiangJi +Email: johnjim0816@gmail.com +Date: 2021-12-22 11:14:17 +LastEditor: JiangJi +LastEditTime: 2021-12-22 15:27:48 +Discription: 使用 DQN-cnn 训练 PongNoFrameskip-v4 +''' +import sys +import os +curr_path = os.path.dirname(os.path.abspath(__file__)) # 当前文件所在绝对路径 +parent_path = os.path.dirname(curr_path) # 父路径 +sys.path.append(parent_path) # 添加路径到系统路径 + +import gym +import torch +import datetime +from common.utils import save_results, make_dir +from common.utils import plot_rewards, plot_rewards_cn +from common.atari_wrappers import make_atari, wrap_deepmind +from DQN.dqn import DQN + +curr_time = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") # 获取当前时间 +algo_name = 'DQN-cnn' # 算法名称 +env_name = 'PongNoFrameskip-v4' # 环境名称 +device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 检测GPU +class DQNConfig: + ''' 算法相关参数设置 + ''' + + def __init__(self): + self.algo_name = algo_name # 算法名称 + self.env_name = env_name # 环境名称 + self.device = device # 检测GPU + self.train_eps = 500 # 训练的回合数 + self.test_eps = 30 # 测试的回合数 + # 超参数 + self.gamma = 0.95 # 强化学习中的折扣因子 + self.epsilon_start = 0.90 # e-greedy策略中初始epsilon + self.epsilon_end = 0.01 # e-greedy策略中的终止epsilon + self.epsilon_decay = 500 # e-greedy策略中epsilon的衰减率 + self.lr = 0.0001 # 学习率 + self.memory_capacity = 100000 # 经验回放的容量 + self.batch_size = 64 # mini-batch SGD中的批量大小 + self.target_update = 4 # 目标网络的更新频率 + self.hidden_dim = 256 # 网络隐藏层 +class PlotConfig: + ''' 绘图相关参数设置 + ''' + + def __init__(self) -> None: + self.algo_name = algo_name # 算法名称 + self.env_name = env_name # 环境名称 + self.device = device # 检测GPU + self.result_path = curr_path + "/outputs/" + self.env_name + \ + '/' + curr_time + '/results/' # 保存结果的路径 + self.model_path = curr_path + "/outputs/" + self.env_name + \ + '/' + curr_time + '/models/' # 保存模型的路径 + self.save = True # 是否保存图片 + + +def env_agent_config(cfg, seed=1): + ''' 创建环境和智能体 + ''' + env = make_atari(cfg.env_name) # 创建环境 + # env = wrap_deepmind(env) + # env = wrap_pytorch(env) + env.seed(seed) # 设置随机种子 + state_dim = env.observation_space.shape[0] # 状态维度 + action_dim = env.action_space.n # 动作维度 + agent = DQN(state_dim, action_dim, cfg) # 创建智能体 + return env, agent + +def train(cfg, env, agent): + ''' 训练 + ''' + print('开始训练!') + print(f'环境:{cfg.env_name}, 算法:{cfg.algo_name}, 设备:{cfg.device}') + rewards = [] # 记录所有回合的奖励 + ma_rewards = [] # 记录所有回合的滑动平均奖励 + for i_ep in range(cfg.train_eps): + ep_reward = 0 # 记录一回合内的奖励 + state = env.reset() # 重置环境,返回初始状态 + while True: + action = agent.choose_action(state) # 选择动作 + next_state, reward, done, _ = env.step(action) # 更新环境,返回transition + agent.memory.push(state, action, reward, next_state, done) # 保存transition + state = next_state # 更新下一个状态 + agent.update() # 更新智能体 + ep_reward += reward # 累加奖励 + if done: + break + if (i_ep+1) % cfg.target_update == 0: # 智能体目标网络更新 + agent.target_net.load_state_dict(agent.policy_net.state_dict()) + rewards.append(ep_reward) + if ma_rewards: + ma_rewards.append(0.9*ma_rewards[-1]+0.1*ep_reward) + else: + ma_rewards.append(ep_reward) + if (i_ep+1)%10 == 0: + print('回合:{}/{}, 奖励:{}'.format(i_ep+1, cfg.train_eps, ep_reward)) + print('完成训练!') + return rewards, ma_rewards + +def test(cfg,env,agent): + print('开始测试!') + print(f'环境:{cfg.env_name}, 算法:{cfg.algo_name}, 设备:{cfg.device}') + # 由于测试不需要使用epsilon-greedy策略,所以相应的值设置为0 + cfg.epsilon_start = 0.0 # e-greedy策略中初始epsilon + cfg.epsilon_end = 0.0 # e-greedy策略中的终止epsilon + rewards = [] # 记录所有回合的奖励 + ma_rewards = [] # 记录所有回合的滑动平均奖励 + for i_ep in range(cfg.test_eps): + ep_reward = 0 # 记录一回合内的奖励 + state = env.reset() # 重置环境,返回初始状态 + while True: + action = agent.choose_action(state) # 选择动作 + next_state, reward, done, _ = env.step(action) # 更新环境,返回transition + state = next_state # 更新下一个状态 + ep_reward += reward # 累加奖励 + if done: + break + rewards.append(ep_reward) + if ma_rewards: + ma_rewards.append(ma_rewards[-1]*0.9+ep_reward*0.1) + else: + ma_rewards.append(ep_reward) + print(f"回合:{i_ep+1}/{cfg.test_eps},奖励:{ep_reward:.1f}") + print('完成测试!') + return rewards,ma_rewards +if __name__ == "__main__": + cfg = DQNConfig() + plot_cfg = PlotConfig() + # 训练 + env, agent = env_agent_config(cfg, seed=1) + rewards, ma_rewards = train(cfg, env, agent) + make_dir(plot_cfg.result_path, plot_cfg.model_path) # 创建保存结果和模型路径的文件夹 + agent.save(path=plot_cfg.model_path) # 保存模型 + save_results(rewards, ma_rewards, tag='train', + path=plot_cfg.result_path) # 保存结果 + plot_rewards_cn(rewards, ma_rewards, plot_cfg, tag="train") # 画出结果 + # 测试 + env, agent = env_agent_config(cfg, seed=10) + agent.load(path=plot_cfg.model_path) # 导入模型 + rewards, ma_rewards = test(cfg, env, agent) + save_results(rewards, ma_rewards, tag='test', + path=plot_cfg.result_path) # 保存结果 + plot_rewards_cn(rewards, ma_rewards, plot_cfg, tag="test") # 画出结果 diff --git a/codes/Docs/使用DDPG解决倒立摆问题.md b/codes/Docs/使用DDPG解决倒立摆问题.md index da815dc..fd625f5 100644 --- a/codes/Docs/使用DDPG解决倒立摆问题.md +++ b/codes/Docs/使用DDPG解决倒立摆问题.md @@ -6,7 +6,7 @@ image-20210915161550713 -该环境的状态数有三个,设摆针竖直方向上的顺时针旋转角为$\theta$,$\theta$设在$[-\pi,\pi]$之间,则相应的状态为$[cos\theta,sin\theta,\dot{\theta}]$,即表示角度和角速度,我们的动作则是一个-2到2之间的力矩,它是一个连续量,因而该环境不能用离散动作的算法比如 DQN 来解决。关于奖励是根据相关的物理原理而计算出的等式,如下: +该环境的状态维度有三个,设摆针竖直方向上的顺时针旋转角为$\theta$,$\theta$设在$[-\pi,\pi]$之间,则相应的状态为$[cos\theta,sin\theta,\dot{\theta}]$,即表示角度和角速度,我们的动作则是一个-2到2之间的力矩,它是一个连续量,因而该环境不能用离散动作的算法比如 DQN 来解决。关于奖励是根据相关的物理原理而计算出的等式,如下: $$ -\left(\theta^{2}+0.1 * \hat{\theta}^{2}+0.001 * \text { action }^{2}\right) $$ diff --git a/codes/Docs/使用DQN解决推车杆问题.md b/codes/Docs/使用DQN解决推车杆问题.md index 5889165..393c52d 100644 --- a/codes/Docs/使用DQN解决推车杆问题.md +++ b/codes/Docs/使用DQN解决推车杆问题.md @@ -14,21 +14,21 @@ CartPole-v0是一个经典的入门环境,如下图,它通过向左(动作=0 import gym env = gym.make('CartPole-v0') # 建立环境 env.seed(1) # 随机种子 -n_states = env.observation_space.shape[0] # 状态数 -n_actions = env.action_space.n # 动作数 +state_dim = env.observation_space.shape[0] # 状态维度 +action_dim = env.action_space.n # 动作维度 state = env.reset() # 初始化环境 -print(f"状态数:{n_states},动作数:{n_actions}") +print(f"状态维度:{state_dim},动作维度:{action_dim}") print(f"初始状态:{state}") ``` 可以得到结果: ```bash -状态数:4,动作数:2 +状态维度:4,动作维度:2 初始状态:[ 0.03073904 0.00145001 -0.03088818 -0.03131252] ``` -该环境状态数是四个,分别为车的位置、车的速度、杆的角度以及杆顶部的速度,动作数为两个,并且是离散的向左或者向右。理论上达到最优化算法的情况下,推车杆是一直能保持平衡的,也就是每回合的步数是无限,但是这不方便训练,所以环境内部设置了每回合的最大步数为200,也就是说理想情况下,只需要我们每回合的奖励达到200就算训练完成。 +该环境状态维度是四个,分别为车的位置、车的速度、杆的角度以及杆顶部的速度,动作维度为两个,并且是离散的向左或者向右。理论上达到最优化算法的情况下,推车杆是一直能保持平衡的,也就是每回合的步数是无限,但是这不方便训练,所以环境内部设置了每回合的最大步数为200,也就是说理想情况下,只需要我们每回合的奖励达到200就算训练完成。 ## DQN基本接口 @@ -125,7 +125,7 @@ class ReplayBuffer: class MLP(nn.Module): def __init__(self, input_dim,output_dim,hidden_dim=128): """ 初始化q网络,为全连接网络 - input_dim: 输入的特征数即环境的状态数 + input_dim: 输入的特征数即环境的状态维度 output_dim: 输出的动作维度 """ super(MLP, self).__init__() diff --git a/codes/Docs/使用Q-learning解决悬崖寻路问题.md b/codes/Docs/使用Q-learning解决悬崖寻路问题.md index 244d85b..44e5b6c 100644 --- a/codes/Docs/使用Q-learning解决悬崖寻路问题.md +++ b/codes/Docs/使用Q-learning解决悬崖寻路问题.md @@ -27,21 +27,21 @@ env = gym.make('CliffWalking-v0') # 定义环境 env = CliffWalkingWapper(env) # 装饰环境 ``` -这里我们在程序中使用了一个装饰器重新定义环境,但不影响对环境的理解,感兴趣的同学具体看相关代码。可以由于gym环境封装得比较好,所以我们想要使用这个环境只需要使用gym.make命令输入函数名即可,然后我们可以查看环境的状态和动作数目: +这里我们在程序中使用了一个装饰器重新定义环境,但不影响对环境的理解,感兴趣的同学具体看相关代码。可以由于gym环境封装得比较好,所以我们想要使用这个环境只需要使用gym.make命令输入函数名即可,然后我们可以查看环境的状态和动作维度目: ```python -n_states = env.observation_space.n # 状态数 -n_actions = env.action_space.n # 动作数 -print(f"状态数:{n_states},动作数:{n_actions}") +state_dim = env.observation_space.n # 状态维度 +action_dim = env.action_space.n # 动作维度 +print(f"状态维度:{state_dim},动作维度:{action_dim}") ``` 打印出来的结果如下: ```bash -状态数:48,动作数:4 +状态维度:48,动作维度:4 ``` -我们的状态数是48个,这里我们设置的是智能体当前所在网格的编号,而动作数是4,这表示有0,1,2,3对应着上下左右四个动作。另外我们也可以初始化环境并打印当前所在的状态: +我们的状态维度是48个,这里我们设置的是智能体当前所在网格的编号,而动作维度是4,这表示有0,1,2,3对应着上下左右四个动作。另外我们也可以初始化环境并打印当前所在的状态: ```python state = env.reset() @@ -72,9 +72,9 @@ print(state) env = gym.make('CliffWalking-v0') # 定义环境 env = CliffWalkingWapper(env) # 装饰环境 env.seed(1) # 设置随机种子 -n_states = env.observation_space.n # 状态数 -n_actions = env.action_space.n # 动作数 -agent = QLearning(n_states,n_actions,cfg) # cfg存储算法相关参数 +state_dim = env.observation_space.n # 状态维度 +action_dim = env.action_space.n # 动作维度 +agent = QLearning(state_dim,action_dim,cfg) # cfg存储算法相关参数 for i_ep in range(cfg.train_eps): # cfg.train_eps表示最大训练的回合数 ep_reward = 0 # 记录每个回合的奖励 state = env.reset() # 重置环境 diff --git a/codes/DoubleDQN/agent.py b/codes/DoubleDQN/double_dqn.py similarity index 76% rename from codes/DoubleDQN/agent.py rename to codes/DoubleDQN/double_dqn.py index 1ade5f8..e712edb 100644 --- a/codes/DoubleDQN/agent.py +++ b/codes/DoubleDQN/double_dqn.py @@ -5,7 +5,7 @@ @Email: johnjim0816@gmail.com @Date: 2020-06-12 00:50:49 @LastEditor: John -LastEditTime: 2021-05-04 22:28:06 +LastEditTime: 2021-11-19 18:07:09 @Discription: @Environment: python 3.7.7 ''' @@ -16,14 +16,54 @@ LastEditTime: 2021-05-04 22:28:06 import torch import torch.nn as nn import torch.optim as optim +import torch.nn.functional as F import random import math import numpy as np -from common.memory import ReplayBuffer -from common.model import MLP + +class ReplayBuffer: + def __init__(self, capacity): + self.capacity = capacity # 经验回放的容量 + self.buffer = [] # 缓冲区 + self.position = 0 + + def push(self, state, action, reward, next_state, done): + ''' 缓冲区是一个队列,容量超出时去掉开始存入的转移(transition) + ''' + 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 = random.sample(self.buffer, batch_size) # 随机采出小批量转移 + state, action, reward, next_state, done = zip(*batch) # 解压成状态,动作等 + return state, action, reward, next_state, done + + def __len__(self): + ''' 返回当前存储的量 + ''' + return len(self.buffer) + +class MLP(nn.Module): + def __init__(self, state_dim,action_dim,hidden_dim=128): + """ 初始化q网络,为全连接网络 + state_dim: 输入的特征数即环境的状态维度 + action_dim: 输出的动作维度 + """ + 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) + class DoubleDQN: def __init__(self, state_dim, action_dim, cfg): - self.action_dim = action_dim # 总的动作个数 self.device = cfg.device # 设备,cpu或gpu等 self.gamma = cfg.gamma @@ -43,8 +83,15 @@ class DoubleDQN: self.optimizer = optim.Adam(self.policy_net.parameters(), lr=cfg.lr) self.loss = 0 self.memory = ReplayBuffer(cfg.memory_capacity) - def predict(self,state): - with torch.no_grad(): + + def choose_action(self, state): + '''选择动作 + ''' + self.actions_count += 1 + self.epsilon = self.epsilon_end + (self.epsilon_start - self.epsilon_end) * \ + math.exp(-1. * self.actions_count / self.epsilon_decay) + if random.random() > self.epsilon: + with torch.no_grad(): # 先转为张量便于丢给神经网络,state元素数据原本为float64 # 注意state=torch.tensor(state).unsqueeze(0)跟state=torch.tensor([state])等价 state = torch.tensor( @@ -55,15 +102,6 @@ class DoubleDQN: # 如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 - def choose_action(self, state): - '''选择动作 - ''' - self.actions_count += 1 - self.epsilon = self.epsilon_end + (self.epsilon_start - self.epsilon_end) * \ - math.exp(-1. * self.actions_count / self.epsilon_decay) - if random.random() > self.epsilon: - action = self.predict(state) else: action = random.randrange(self.action_dim) return action diff --git a/codes/DoubleDQN/outputs/CartPole-v0/20210504-150900/models/checkpoint.pth b/codes/DoubleDQN/outputs/CartPole-v0/20210504-150900/models/checkpoint.pth deleted file mode 100644 index 8c4b561..0000000 Binary files a/codes/DoubleDQN/outputs/CartPole-v0/20210504-150900/models/checkpoint.pth and /dev/null differ diff --git a/codes/DoubleDQN/outputs/CartPole-v0/20210504-150900/results/eval_ma_rewards.npy b/codes/DoubleDQN/outputs/CartPole-v0/20210504-150900/results/eval_ma_rewards.npy deleted file mode 100644 index 0f77696..0000000 Binary files a/codes/DoubleDQN/outputs/CartPole-v0/20210504-150900/results/eval_ma_rewards.npy and /dev/null differ diff --git a/codes/DoubleDQN/outputs/CartPole-v0/20210504-150900/results/eval_rewards.npy b/codes/DoubleDQN/outputs/CartPole-v0/20210504-150900/results/eval_rewards.npy deleted file mode 100644 index 57f8759..0000000 Binary files a/codes/DoubleDQN/outputs/CartPole-v0/20210504-150900/results/eval_rewards.npy and /dev/null differ diff --git a/codes/DoubleDQN/outputs/CartPole-v0/20210504-150900/results/eval_rewards_curve.png b/codes/DoubleDQN/outputs/CartPole-v0/20210504-150900/results/eval_rewards_curve.png deleted file mode 100644 index 038e031..0000000 Binary files a/codes/DoubleDQN/outputs/CartPole-v0/20210504-150900/results/eval_rewards_curve.png and /dev/null differ diff --git a/codes/DoubleDQN/outputs/CartPole-v0/20210504-150900/results/train_ma_rewards.npy b/codes/DoubleDQN/outputs/CartPole-v0/20210504-150900/results/train_ma_rewards.npy deleted file mode 100644 index 63d10e7..0000000 Binary files a/codes/DoubleDQN/outputs/CartPole-v0/20210504-150900/results/train_ma_rewards.npy and /dev/null differ diff --git a/codes/DoubleDQN/outputs/CartPole-v0/20210504-150900/results/train_rewards.npy b/codes/DoubleDQN/outputs/CartPole-v0/20210504-150900/results/train_rewards.npy deleted file mode 100644 index d486ad9..0000000 Binary files a/codes/DoubleDQN/outputs/CartPole-v0/20210504-150900/results/train_rewards.npy and /dev/null differ diff --git a/codes/DoubleDQN/outputs/CartPole-v0/20210504-150900/results/train_rewards_curve.png b/codes/DoubleDQN/outputs/CartPole-v0/20210504-150900/results/train_rewards_curve.png deleted file mode 100644 index f91bc4d..0000000 Binary files a/codes/DoubleDQN/outputs/CartPole-v0/20210504-150900/results/train_rewards_curve.png and /dev/null differ diff --git a/codes/DoubleDQN/outputs/CartPole-v0/20211229-145006/models/checkpoint.pth b/codes/DoubleDQN/outputs/CartPole-v0/20211229-145006/models/checkpoint.pth new file mode 100644 index 0000000..2ec6bfd Binary files /dev/null and b/codes/DoubleDQN/outputs/CartPole-v0/20211229-145006/models/checkpoint.pth differ diff --git a/codes/DoubleDQN/outputs/CartPole-v0/20211229-145006/results/test_ma_rewards.npy b/codes/DoubleDQN/outputs/CartPole-v0/20211229-145006/results/test_ma_rewards.npy new file mode 100644 index 0000000..81e0bba Binary files /dev/null and b/codes/DoubleDQN/outputs/CartPole-v0/20211229-145006/results/test_ma_rewards.npy differ diff --git a/codes/DoubleDQN/outputs/CartPole-v0/20211229-145006/results/test_rewards.npy b/codes/DoubleDQN/outputs/CartPole-v0/20211229-145006/results/test_rewards.npy new file mode 100644 index 0000000..e7b6307 Binary files /dev/null and b/codes/DoubleDQN/outputs/CartPole-v0/20211229-145006/results/test_rewards.npy differ diff --git a/codes/DoubleDQN/outputs/CartPole-v0/20211229-145006/results/test_rewards_curve.png b/codes/DoubleDQN/outputs/CartPole-v0/20211229-145006/results/test_rewards_curve.png new file mode 100644 index 0000000..4fbd77c Binary files /dev/null and b/codes/DoubleDQN/outputs/CartPole-v0/20211229-145006/results/test_rewards_curve.png differ diff --git a/codes/DoubleDQN/outputs/CartPole-v0/20211229-145006/results/train_ma_rewards.npy b/codes/DoubleDQN/outputs/CartPole-v0/20211229-145006/results/train_ma_rewards.npy new file mode 100644 index 0000000..a73bbde Binary files /dev/null and b/codes/DoubleDQN/outputs/CartPole-v0/20211229-145006/results/train_ma_rewards.npy differ diff --git a/codes/DoubleDQN/outputs/CartPole-v0/20211229-145006/results/train_rewards.npy b/codes/DoubleDQN/outputs/CartPole-v0/20211229-145006/results/train_rewards.npy new file mode 100644 index 0000000..3e707c5 Binary files /dev/null and b/codes/DoubleDQN/outputs/CartPole-v0/20211229-145006/results/train_rewards.npy differ diff --git a/codes/DoubleDQN/outputs/CartPole-v0/20211229-145006/results/train_rewards_curve.png b/codes/DoubleDQN/outputs/CartPole-v0/20211229-145006/results/train_rewards_curve.png new file mode 100644 index 0000000..cb9dbeb Binary files /dev/null and b/codes/DoubleDQN/outputs/CartPole-v0/20211229-145006/results/train_rewards_curve.png differ diff --git a/codes/DoubleDQN/task0.py b/codes/DoubleDQN/task0.py new file mode 100644 index 0000000..7657a88 --- /dev/null +++ b/codes/DoubleDQN/task0.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python +# coding=utf-8 +''' +Author: JiangJi +Email: johnjim0816@gmail.com +Date: 2021-11-07 18:10:37 +LastEditor: JiangJi +LastEditTime: 2021-12-29 15:02:30 +Discription: +''' + +import sys,os +curr_path = os.path.dirname(os.path.abspath(__file__)) # 当前文件所在绝对路径 +parent_path = os.path.dirname(curr_path) # 父路径 +sys.path.append(parent_path) # 添加路径到系统路径 + +import gym +import torch +import datetime + +from common.utils import save_results, make_dir +from common.utils import plot_rewards +from DoubleDQN.double_dqn import DoubleDQN + +curr_time = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") # 获取当前时间 + +class Config: + def __init__(self): + ################################## 环境超参数 ################################### + self.algo_name = 'DoubleDQN' # 算法名称 + self.env_name = 'CartPole-v0' # 环境名称 + self.device = torch.device( + "cuda" if torch.cuda.is_available() else "cpu") # 检测GPU + self.train_eps = 200 # 训练的回合数 + self.test_eps = 30 # 测试的回合数 + ################################################################################ + + ################################## 算法超参数 ################################### + self.gamma = 0.95 # 强化学习中的折扣因子 + self.epsilon_start = 0.95 # e-greedy策略中初始epsilon + self.epsilon_end = 0.01 # e-greedy策略中的终止epsilon + self.epsilon_decay = 500 # e-greedy策略中epsilon的衰减率 + self.lr = 0.0001 # 学习率 + self.memory_capacity = 100000 # 经验回放的容量 + self.batch_size = 64 # mini-batch SGD中的批量大小 + self.target_update = 2 # 目标网络的更新频率 + self.hidden_dim = 256 # 网络隐藏层 + ################################################################################ + + ################################# 保存结果相关参数 ############################## + self.result_path = curr_path + "/outputs/" + self.env_name + \ + '/' + curr_time + '/results/' # 保存结果的路径 + self.model_path = curr_path + "/outputs/" + self.env_name + \ + '/' + curr_time + '/models/' # 保存模型的路径 + self.save = True # 是否保存图片 + ################################################################################ + + +def env_agent_config(cfg,seed=1): + env = gym.make(cfg.env_name) + env.seed(seed) + state_dim = env.observation_space.shape[0] + action_dim = env.action_space.n + agent = DoubleDQN(state_dim,action_dim,cfg) + return env,agent + +def train(cfg,env,agent): + print('开始训练!') + print(f'环境:{cfg.env_name}, 算法:{cfg.algo_name}, 设备:{cfg.device}') + rewards = [] # 记录所有回合的奖励 + ma_rewards = [] # 记录所有回合的滑动平均奖励 + for i_ep in range(cfg.train_eps): + ep_reward = 0 # 记录一回合内的奖励 + state = env.reset() # 重置环境,返回初始状态 + while True: + 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 = next_state + agent.update() + if done: + break + if i_ep % cfg.target_update == 0: + agent.target_net.load_state_dict(agent.policy_net.state_dict()) + if (i_ep+1)%10 == 0: + print(f'回合:{i_ep+1}/{cfg.train_eps},奖励:{ep_reward}') + rewards.append(ep_reward) + if ma_rewards: + ma_rewards.append( + 0.9*ma_rewards[-1]+0.1*ep_reward) + else: + ma_rewards.append(ep_reward) + print('完成训练!') + env.close() + return rewards,ma_rewards + +def test(cfg,env,agent): + print('开始测试!') + print(f'环境:{cfg.env_name}, 算法:{cfg.algo_name}, 设备:{cfg.device}') + ############# 由于测试不需要使用epsilon-greedy策略,所以相应的值设置为0 ############### + cfg.epsilon_start = 0.0 # e-greedy策略中初始epsilon + cfg.epsilon_end = 0.0 # e-greedy策略中的终止epsilon + ################################################################################ + rewards = [] # 记录所有回合的奖励 + ma_rewards = [] # 记录所有回合的滑动平均奖励 + + for i_ep in range(cfg.test_eps): + state = env.reset() + ep_reward = 0 + while True: + action = agent.choose_action(state) + next_state, reward, done, _ = env.step(action) + state = next_state + ep_reward += reward + if done: + break + rewards.append(ep_reward) + if ma_rewards: + ma_rewards.append(ma_rewards[-1]*0.9+ep_reward*0.1) + else: + ma_rewards.append(ep_reward) + print(f"回合:{i_ep+1}/{cfg.test_eps},奖励:{ep_reward:.1f}") + print('完成测试!') + env.close() + return rewards,ma_rewards + +if __name__ == "__main__": + cfg = Config() + # 训练 + env, agent = env_agent_config(cfg) + rewards, ma_rewards = train(cfg, env, agent) + make_dir(cfg.result_path, cfg.model_path) # 创建保存结果和模型路径的文件夹 + agent.save(path=cfg.model_path) # 保存模型 + save_results(rewards, ma_rewards, tag='train', + path=cfg.result_path) # 保存结果 + plot_rewards(rewards, ma_rewards, cfg, tag="train") # 画出结果 + # 测试 + env, agent = env_agent_config(cfg) + agent.load(path=cfg.model_path) # 导入模型 + rewards, ma_rewards = test(cfg, env, agent) + save_results(rewards, ma_rewards, tag='test', + path=cfg.result_path) # 保存结果 + plot_rewards(rewards, ma_rewards, cfg, tag="test") # 画出结果 diff --git a/codes/DoubleDQN/task0_train.ipynb b/codes/DoubleDQN/task0_train.ipynb deleted file mode 100644 index ee2e5d4..0000000 --- a/codes/DoubleDQN/task0_train.ipynb +++ /dev/null @@ -1,194 +0,0 @@ -{ - "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" - }, - "orig_nbformat": 2, - "kernelspec": { - "name": "python3710jvsc74a57bd0366e1054dee9d4501b0eb8f87335afd3c67fc62db6ee611bbc7f8f5a1fefe232", - "display_name": "Python 3.7.10 64-bit ('py37': conda)" - }, - "metadata": { - "interpreter": { - "hash": "366e1054dee9d4501b0eb8f87335afd3c67fc62db6ee611bbc7f8f5a1fefe232" - } - } - }, - "nbformat": 4, - "nbformat_minor": 2, - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import sys\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" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import gym\n", - "import torch\n", - "import datetime\n", - "from DoubleDQN.agent import DoubleDQN\n", - "from common.plot import plot_rewards\n", - "from common.utils import save_results, make_dir\n", - "\n", - "curr_time = datetime.datetime.now().strftime(\n", - " \"%Y%m%d-%H%M%S\") # obtain current time" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class DoubleDQNConfig:\n", - " def __init__(self):\n", - " self.algo = \"DoubleDQN\" # name of algo\n", - " self.env = 'CartPole-v0' # env name\n", - " self.result_path = curr_path+\"/outputs/\" + self.env + \\\n", - " '/'+curr_time+'/results/' # path to save results\n", - " self.model_path = curr_path+\"/outputs/\" + self.env + \\\n", - " '/'+curr_time+'/models/' # path to save models\n", - " self.train_eps = 200 # max tranng episodes\n", - " self.eval_eps = 50 # max evaling episodes\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.001 # learning rate\n", - " self.memory_capacity = 100000 # capacity of Replay Memory\n", - " self.batch_size = 64\n", - " self.target_update = 2 # update frequency of target net\n", - " self.device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\") # check gpu\n", - " self.hidden_dim = 256 # hidden size of net" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def env_agent_config(cfg,seed=1):\n", - " env = gym.make(cfg.env) \n", - " env.seed(seed)\n", - " state_dim = env.observation_space.shape[0]\n", - " action_dim = env.action_space.n\n", - " agent = DoubleDQN(state_dim,action_dim,cfg)\n", - " return env,agent" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def train(cfg,env,agent):\n", - " print('Start to train !')\n", - " rewards,ma_rewards = [],[]\n", - " for i_ep in range(cfg.train_eps):\n", - " state = env.reset() \n", - " ep_reward = 0\n", - " while True:\n", - " action = agent.choose_action(state) \n", - " next_state, reward, done, _ = env.step(action)\n", - " ep_reward += reward\n", - " agent.memory.push(state, action, reward, next_state, done) \n", - " state = next_state \n", - " agent.update() \n", - " if done:\n", - " break\n", - " if i_ep % cfg.target_update == 0:\n", - " agent.target_net.load_state_dict(agent.policy_net.state_dict())\n", - " if (i_ep+1)%10 == 0:\n", - " print(f'Episode:{i_ep+1}/{cfg.train_eps}, Reward:{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": null, - "metadata": {}, - "outputs": [], - "source": [ - "def eval(cfg,env,agent):\n", - " print('Start to eval !')\n", - " print(f'Env:{cfg.env}, Algorithm:{cfg.algo}, Device:{cfg.device}')\n", - " rewards = [] \n", - " ma_rewards = []\n", - " for i_ep in range(cfg.eval_eps):\n", - " state = env.reset() \n", - " ep_reward = 0 \n", - " while True:\n", - " action = agent.predict(state) \n", - " next_state, reward, done, _ = env.step(action) \n", - " state = next_state \n", - " ep_reward += reward\n", - " if done:\n", - " break\n", - " rewards.append(ep_reward)\n", - " if ma_rewards:\n", - " ma_rewards.append(ma_rewards[-1]*0.9+ep_reward*0.1)\n", - " else:\n", - " ma_rewards.append(ep_reward)\n", - " print(f\"Episode:{i_ep+1}/{cfg.eval_eps}, reward:{ep_reward:.1f}\")\n", - " print('Complete evaling!')\n", - " return rewards,ma_rewards " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "if __name__ == \"__main__\":\n", - " cfg = DoubleDQNConfig()\n", - " # train\n", - " env,agent = env_agent_config(cfg,seed=1)\n", - " rewards, ma_rewards = train(cfg, env, agent)\n", - " make_dir(cfg.result_path, cfg.model_path)\n", - " agent.save(path=cfg.model_path)\n", - " save_results(rewards, ma_rewards, tag='train', path=cfg.result_path)\n", - " plot_rewards(rewards, ma_rewards, tag=\"train\",\n", - " algo=cfg.algo, path=cfg.result_path)\n", - "\n", - " # eval\n", - " env,agent = env_agent_config(cfg,seed=10)\n", - " agent.load(path=cfg.model_path)\n", - " rewards,ma_rewards = eval(cfg,env,agent)\n", - " save_results(rewards,ma_rewards,tag='eval',path=cfg.result_path)\n", - " plot_rewards(rewards,ma_rewards,tag=\"eval\",env=cfg.env,algo = cfg.algo,path=cfg.result_path)" - ] - } - ] -} \ No newline at end of file diff --git a/codes/DoubleDQN/task0_train.py b/codes/DoubleDQN/task0_train.py deleted file mode 100644 index 0148ea2..0000000 --- a/codes/DoubleDQN/task0_train.py +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env python -# coding=utf-8 -''' -@Author: John -@Email: johnjim0816@gmail.com -@Date: 2020-06-12 00:48:57 -@LastEditor: John -LastEditTime: 2021-09-10 15:26:05 -@Discription: -@Environment: python 3.7.7 -''' -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 -from DoubleDQN.agent import DoubleDQN -from common.plot import plot_rewards -from common.utils import save_results, make_dir - -curr_time = datetime.datetime.now().strftime( - "%Y%m%d-%H%M%S") # obtain current time - -class DoubleDQNConfig: - def __init__(self): - self.algo = "DoubleDQN" # name of algo - self.env = 'CartPole-v0' # env name - self.result_path = curr_path+"/outputs/" + self.env + \ - '/'+curr_time+'/results/' # path to save results - self.model_path = curr_path+"/outputs/" + self.env + \ - '/'+curr_time+'/models/' # path to save models - self.train_eps = 200 # max tranng episodes - self.eval_eps = 50 # max evaling episodes - self.gamma = 0.95 - self.epsilon_start = 1 # start epsilon of e-greedy policy - self.epsilon_end = 0.01 - self.epsilon_decay = 500 - self.lr = 0.001 # learning rate - self.memory_capacity = 100000 # capacity of Replay Memory - self.batch_size = 64 - self.target_update = 2 # update frequency of target net - self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # check gpu - self.hidden_dim = 256 # hidden size of net - -def env_agent_config(cfg,seed=1): - env = gym.make(cfg.env) - env.seed(seed) - state_dim = env.observation_space.shape[0] - action_dim = env.action_space.n - agent = DoubleDQN(state_dim,action_dim,cfg) - return env,agent - -def train(cfg,env,agent): - print('Start to train !') - rewards,ma_rewards = [],[] - for i_ep in range(cfg.train_eps): - state = env.reset() - ep_reward = 0 - while True: - 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 = next_state - agent.update() - if done: - break - if i_ep % cfg.target_update == 0: - agent.target_net.load_state_dict(agent.policy_net.state_dict()) - print(f'Episode:{i_ep+1}/{cfg.train_eps}, Reward:{ep_reward},Epsilon:{agent.epsilon:.2f}') - rewards.append(ep_reward) - if ma_rewards: - ma_rewards.append( - 0.9*ma_rewards[-1]+0.1*ep_reward) - else: - ma_rewards.append(ep_reward) - print('Complete training!') - return rewards,ma_rewards - -def eval(cfg,env,agent): - print('Start to eval !') - print(f'Env:{cfg.env}, Algorithm:{cfg.algo}, Device:{cfg.device}') - rewards = [] - ma_rewards = [] - for i_ep in range(cfg.eval_eps): - state = env.reset() - ep_reward = 0 - while True: - action = agent.predict(state) - next_state, reward, done, _ = env.step(action) - state = next_state - ep_reward += reward - if done: - break - rewards.append(ep_reward) - if ma_rewards: - ma_rewards.append(ma_rewards[-1]*0.9+ep_reward*0.1) - else: - ma_rewards.append(ep_reward) - print(f"Episode:{i_ep+1}/{cfg.eval_eps}, reward:{ep_reward:.1f}") - print('Complete evaling!') - return rewards,ma_rewards - -if __name__ == "__main__": - cfg = DoubleDQNConfig() - # 训练 - env,agent = env_agent_config(cfg,seed=1) - rewards, ma_rewards = train(cfg, env, agent) - make_dir(cfg.result_path, cfg.model_path) - agent.save(path=cfg.model_path) - save_results(rewards, ma_rewards, tag='train', path=cfg.result_path) - plot_rewards(rewards, ma_rewards, tag="train", - algo=cfg.algo, path=cfg.result_path) - - # 测试 - env,agent = env_agent_config(cfg,seed=10) - agent.load(path=cfg.model_path) - rewards,ma_rewards = eval(cfg,env,agent) - save_results(rewards,ma_rewards,tag='eval',path=cfg.result_path) - plot_rewards(rewards,ma_rewards,tag="eval",env=cfg.env,algo = cfg.algo,path=cfg.result_path) diff --git a/codes/DuelingDQN/task0_train.ipynb b/codes/DuelingDQN/task0_train.ipynb index c2cd1c3..7e38218 100644 --- a/codes/DuelingDQN/task0_train.ipynb +++ b/codes/DuelingDQN/task0_train.ipynb @@ -136,12 +136,12 @@ "outputs": [], "source": [ "class DuelingNet(nn.Module):\n", - " def __init__(self, n_states, n_actions,hidden_size=128):\n", + " def __init__(self, state_dim, action_dim,hidden_size=128):\n", " super(DuelingNet, self).__init__()\n", " \n", " # 隐藏层\n", " self.hidden = nn.Sequential(\n", - " nn.Linear(n_states, hidden_size),\n", + " nn.Linear(state_dim, hidden_size),\n", " nn.ReLU()\n", " )\n", " \n", @@ -149,7 +149,7 @@ " self.advantage = nn.Sequential(\n", " nn.Linear(hidden_size, hidden_size),\n", " nn.ReLU(),\n", - " nn.Linear(hidden_size, n_actions)\n", + " nn.Linear(hidden_size, action_dim)\n", " )\n", " \n", " # 价值函数\n", @@ -192,7 +192,7 @@ ], "source": [ "class DuelingDQN:\n", - " def __init__(self,n_states,n_actions,cfg) -> None:\n", + " def __init__(self,state_dim,action_dim,cfg) -> None:\n", " self.batch_size = cfg.batch_size\n", " self.device = cfg.device\n", " self.loss_history = [] # 记录loss的变化\n", @@ -200,8 +200,8 @@ " self.epsilon = lambda frame_idx: cfg.epsilon_end + \\\n", " (cfg.epsilon_start - cfg.epsilon_end) * \\\n", " math.exp(-1. * frame_idx / cfg.epsilon_decay)\n", - " self.policy_net = DuelingNet(n_states, n_actions,hidden_dim=cfg.hidden_dim).to(self.device)\n", - " self.target_net = DuelingNet(n_states, n_actions,hidden_dim=cfg.hidden_dim).to(self.device)\n", + " self.policy_net = DuelingNet(state_dim, action_dim,hidden_dim=cfg.hidden_dim).to(self.device)\n", + " self.target_net = DuelingNet(state_dim, action_dim,hidden_dim=cfg.hidden_dim).to(self.device)\n", " for target_param, param in zip(self.target_net.parameters(),self.policy_net.parameters()): # 复制参数到目标网络targe_net\n", " target_param.data.copy_(param.data)\n", " self.optimizer = optim.Adam(self.policy_net.parameters(), lr=cfg.lr) # 优化器\n", diff --git a/codes/HierarchicalDQN/agent.py b/codes/HierarchicalDQN/agent.py index 3760643..ce0cd1f 100644 --- a/codes/HierarchicalDQN/agent.py +++ b/codes/HierarchicalDQN/agent.py @@ -11,12 +11,51 @@ Environment: ''' import torch import torch.nn as nn +import torch.optim as optim +import torch.nn.functional as F import numpy as np import random,math -import torch.optim as optim -from common.model import MLP -from common.memory import ReplayBuffer +class ReplayBuffer: + def __init__(self, capacity): + self.capacity = capacity # 经验回放的容量 + self.buffer = [] # 缓冲区 + self.position = 0 + + def push(self, state, action, reward, next_state, done): + ''' 缓冲区是一个队列,容量超出时去掉开始存入的转移(transition) + ''' + 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 = random.sample(self.buffer, batch_size) # 随机采出小批量转移 + state, action, reward, next_state, done = zip(*batch) # 解压成状态,动作等 + return state, action, reward, next_state, done + + def __len__(self): + ''' 返回当前存储的量 + ''' + return len(self.buffer) +class MLP(nn.Module): + def __init__(self, input_dim,output_dim,hidden_dim=128): + """ 初始化q网络,为全连接网络 + input_dim: 输入的特征数即环境的状态维度 + output_dim: 输出的动作维度 + """ + super(MLP, self).__init__() + self.fc1 = nn.Linear(input_dim, hidden_dim) # 输入层 + self.fc2 = nn.Linear(hidden_dim,hidden_dim) # 隐藏层 + self.fc3 = nn.Linear(hidden_dim, output_dim) # 输出层 + + def forward(self, x): + # 各层对应的激活函数 + x = F.relu(self.fc1(x)) + x = F.relu(self.fc2(x)) + return self.fc3(x) + class HierarchicalDQN: def __init__(self,state_dim,action_dim,cfg): self.state_dim = state_dim @@ -24,7 +63,7 @@ class HierarchicalDQN: self.gamma = cfg.gamma self.device = cfg.device self.batch_size = cfg.batch_size - self.frame_idx = 0 + 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.policy_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) diff --git a/codes/HierarchicalDQN/outputs/CartPole-v0/20211221-200119/models/meta_checkpoint.pth b/codes/HierarchicalDQN/outputs/CartPole-v0/20211221-200119/models/meta_checkpoint.pth new file mode 100644 index 0000000..02f3f7c Binary files /dev/null and b/codes/HierarchicalDQN/outputs/CartPole-v0/20211221-200119/models/meta_checkpoint.pth differ diff --git a/codes/HierarchicalDQN/outputs/CartPole-v0/20211221-200119/models/policy_checkpoint.pth b/codes/HierarchicalDQN/outputs/CartPole-v0/20211221-200119/models/policy_checkpoint.pth new file mode 100644 index 0000000..9d906ea Binary files /dev/null and b/codes/HierarchicalDQN/outputs/CartPole-v0/20211221-200119/models/policy_checkpoint.pth differ diff --git a/codes/HierarchicalDQN/outputs/CartPole-v0/20211221-200119/results/test_ma_rewards.npy b/codes/HierarchicalDQN/outputs/CartPole-v0/20211221-200119/results/test_ma_rewards.npy new file mode 100644 index 0000000..14dd955 Binary files /dev/null and b/codes/HierarchicalDQN/outputs/CartPole-v0/20211221-200119/results/test_ma_rewards.npy differ diff --git a/codes/HierarchicalDQN/outputs/CartPole-v0/20211221-200119/results/test_rewards.npy b/codes/HierarchicalDQN/outputs/CartPole-v0/20211221-200119/results/test_rewards.npy new file mode 100644 index 0000000..e815222 Binary files /dev/null and b/codes/HierarchicalDQN/outputs/CartPole-v0/20211221-200119/results/test_rewards.npy differ diff --git a/codes/HierarchicalDQN/outputs/CartPole-v0/20211221-200119/results/test_rewards_curve.png b/codes/HierarchicalDQN/outputs/CartPole-v0/20211221-200119/results/test_rewards_curve.png new file mode 100644 index 0000000..645b21a Binary files /dev/null and b/codes/HierarchicalDQN/outputs/CartPole-v0/20211221-200119/results/test_rewards_curve.png differ diff --git a/codes/HierarchicalDQN/outputs/CartPole-v0/20211221-200119/results/train_ma_rewards.npy b/codes/HierarchicalDQN/outputs/CartPole-v0/20211221-200119/results/train_ma_rewards.npy new file mode 100644 index 0000000..bf58391 Binary files /dev/null and b/codes/HierarchicalDQN/outputs/CartPole-v0/20211221-200119/results/train_ma_rewards.npy differ diff --git a/codes/HierarchicalDQN/outputs/CartPole-v0/20211221-200119/results/train_rewards.npy b/codes/HierarchicalDQN/outputs/CartPole-v0/20211221-200119/results/train_rewards.npy new file mode 100644 index 0000000..f4d20ff Binary files /dev/null and b/codes/HierarchicalDQN/outputs/CartPole-v0/20211221-200119/results/train_rewards.npy differ diff --git a/codes/HierarchicalDQN/outputs/CartPole-v0/20211221-200119/results/train_rewards_curve.png b/codes/HierarchicalDQN/outputs/CartPole-v0/20211221-200119/results/train_rewards_curve.png new file mode 100644 index 0000000..20ccbc5 Binary files /dev/null and b/codes/HierarchicalDQN/outputs/CartPole-v0/20211221-200119/results/train_rewards_curve.png differ diff --git a/codes/HierarchicalDQN/results/20210331-134559/ma_rewards_train.npy b/codes/HierarchicalDQN/results/20210331-134559/ma_rewards_train.npy deleted file mode 100644 index daab87d..0000000 Binary files a/codes/HierarchicalDQN/results/20210331-134559/ma_rewards_train.npy and /dev/null differ diff --git a/codes/HierarchicalDQN/results/20210331-134559/rewards_curve_train.png b/codes/HierarchicalDQN/results/20210331-134559/rewards_curve_train.png deleted file mode 100644 index 77555ad..0000000 Binary files a/codes/HierarchicalDQN/results/20210331-134559/rewards_curve_train.png and /dev/null differ diff --git a/codes/HierarchicalDQN/results/20210331-134559/rewards_train.npy b/codes/HierarchicalDQN/results/20210331-134559/rewards_train.npy deleted file mode 100644 index 5a1ad82..0000000 Binary files a/codes/HierarchicalDQN/results/20210331-134559/rewards_train.npy and /dev/null differ diff --git a/codes/HierarchicalDQN/results/20210331-145852/losses_curve.png b/codes/HierarchicalDQN/results/20210331-145852/losses_curve.png deleted file mode 100644 index 4f962ea..0000000 Binary files a/codes/HierarchicalDQN/results/20210331-145852/losses_curve.png and /dev/null differ diff --git a/codes/HierarchicalDQN/results/20210331-145852/ma_rewards_train.npy b/codes/HierarchicalDQN/results/20210331-145852/ma_rewards_train.npy deleted file mode 100644 index 523bdb4..0000000 Binary files a/codes/HierarchicalDQN/results/20210331-145852/ma_rewards_train.npy and /dev/null differ diff --git a/codes/HierarchicalDQN/results/20210331-145852/rewards_curve_train.png b/codes/HierarchicalDQN/results/20210331-145852/rewards_curve_train.png deleted file mode 100644 index 97443e5..0000000 Binary files a/codes/HierarchicalDQN/results/20210331-145852/rewards_curve_train.png and /dev/null differ diff --git a/codes/HierarchicalDQN/results/20210331-145852/rewards_train.npy b/codes/HierarchicalDQN/results/20210331-145852/rewards_train.npy deleted file mode 100644 index 99cf87a..0000000 Binary files a/codes/HierarchicalDQN/results/20210331-145852/rewards_train.npy and /dev/null differ diff --git a/codes/HierarchicalDQN/saved_model/20210331-134559/meta_checkpoint.pth b/codes/HierarchicalDQN/saved_model/20210331-134559/meta_checkpoint.pth deleted file mode 100644 index 873b3ef..0000000 Binary files a/codes/HierarchicalDQN/saved_model/20210331-134559/meta_checkpoint.pth and /dev/null differ diff --git a/codes/HierarchicalDQN/saved_model/20210331-134559/policy_checkpoint.pth b/codes/HierarchicalDQN/saved_model/20210331-134559/policy_checkpoint.pth deleted file mode 100644 index be8ea8a..0000000 Binary files a/codes/HierarchicalDQN/saved_model/20210331-134559/policy_checkpoint.pth and /dev/null differ diff --git a/codes/HierarchicalDQN/saved_model/20210331-145852/meta_checkpoint.pth b/codes/HierarchicalDQN/saved_model/20210331-145852/meta_checkpoint.pth deleted file mode 100644 index e3f7c38..0000000 Binary files a/codes/HierarchicalDQN/saved_model/20210331-145852/meta_checkpoint.pth and /dev/null differ diff --git a/codes/HierarchicalDQN/saved_model/20210331-145852/policy_checkpoint.pth b/codes/HierarchicalDQN/saved_model/20210331-145852/policy_checkpoint.pth deleted file mode 100644 index 6be6ea3..0000000 Binary files a/codes/HierarchicalDQN/saved_model/20210331-145852/policy_checkpoint.pth and /dev/null differ diff --git a/codes/HierarchicalDQN/task0.py b/codes/HierarchicalDQN/task0.py new file mode 100644 index 0000000..3eceefd --- /dev/null +++ b/codes/HierarchicalDQN/task0.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python +# coding=utf-8 +''' +Author: John +Email: johnjim0816@gmail.com +Date: 2021-03-29 10:37:32 +LastEditor: John +LastEditTime: 2021-05-04 22:35:56 +Discription: +Environment: +''' +import sys +import os +curr_path = os.path.dirname(os.path.abspath(__file__)) # 当前文件所在绝对路径 +parent_path = os.path.dirname(curr_path) # 父路径 +sys.path.append(parent_path) # 添加路径到系统路径 + +import datetime +import numpy as np +import torch +import gym + +from common.utils import save_results,make_dir +from common.utils import plot_rewards +from HierarchicalDQN.agent import HierarchicalDQN +from HierarchicalDQN.train import train,test + +curr_time = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") # 获取当前时间 +algo_name = "Hierarchical DQN" # 算法名称 +env_name = 'CartPole-v0' # 环境名称 +class HierarchicalDQNConfig: + def __init__(self): + self.algo_name = algo_name # 算法名称 + self.env_name = env_name # 环境名称 + self.device = torch.device( + "cuda" if torch.cuda.is_available() else "cpu") # 检测GPU + self.train_eps = 300 # 训练的episode数目 + self.test_eps = 50 # 测试的episode数目 + self.gamma = 0.99 + self.epsilon_start = 1 # start epsilon of e-greedy policy + self.epsilon_end = 0.01 + self.epsilon_decay = 200 + self.lr = 0.0001 # learning rate + self.memory_capacity = 10000 # Replay Memory capacity + self.batch_size = 32 + self.target_update = 2 # 目标网络的更新频率 + self.hidden_dim = 256 # 网络隐藏层 +class PlotConfig: + ''' 绘图相关参数设置 + ''' + + def __init__(self) -> None: + self.algo_name = algo_name # 算法名称 + self.env_name = env_name # 环境名称 + self.device = torch.device( + "cuda" if torch.cuda.is_available() else "cpu") # 检测GPU + self.result_path = curr_path + "/outputs/" + self.env_name + \ + '/' + curr_time + '/results/' # 保存结果的路径 + self.model_path = curr_path + "/outputs/" + self.env_name + \ + '/' + curr_time + '/models/' # 保存模型的路径 + self.save = True # 是否保存图片 + +def env_agent_config(cfg,seed=1): + env = gym.make(cfg.env_name) + env.seed(seed) + state_dim = env.observation_space.shape[0] + action_dim = env.action_space.n + agent = HierarchicalDQN(state_dim,action_dim,cfg) + return env,agent + +if __name__ == "__main__": + cfg = HierarchicalDQNConfig() + plot_cfg = PlotConfig() + # 训练 + env, agent = env_agent_config(cfg, seed=1) + rewards, ma_rewards = train(cfg, env, agent) + make_dir(plot_cfg.result_path, plot_cfg.model_path) # 创建保存结果和模型路径的文件夹 + agent.save(path=plot_cfg.model_path) # 保存模型 + save_results(rewards, ma_rewards, tag='train', + path=plot_cfg.result_path) # 保存结果 + plot_rewards(rewards, ma_rewards, plot_cfg, tag="train") # 画出结果 + # 测试 + env, agent = env_agent_config(cfg, seed=10) + agent.load(path=plot_cfg.model_path) # 导入模型 + rewards, ma_rewards = test(cfg, env, agent) + save_results(rewards, ma_rewards, tag='test', path=plot_cfg.result_path) # 保存结果 + plot_rewards(rewards, ma_rewards, plot_cfg, tag="test") # 画出结果 + diff --git a/codes/HierarchicalDQN/task0_train.ipynb b/codes/HierarchicalDQN/task0_train.ipynb deleted file mode 100644 index c63e950..0000000 --- a/codes/HierarchicalDQN/task0_train.ipynb +++ /dev/null @@ -1,477 +0,0 @@ -{ - "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/task0_train.py b/codes/HierarchicalDQN/task0_train.py deleted file mode 100644 index 2676094..0000000 --- a/codes/HierarchicalDQN/task0_train.py +++ /dev/null @@ -1,146 +0,0 @@ -#!/usr/bin/env python -# coding=utf-8 -''' -Author: John -Email: johnjim0816@gmail.com -Date: 2021-03-29 10:37:32 -LastEditor: John -LastEditTime: 2021-05-04 22:35:56 -Discription: -Environment: -''' - - -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 datetime -import numpy as np -import torch -import gym - -from common.utils import save_results,make_dir -from common.plot import plot_rewards -from HierarchicalDQN.agent import HierarchicalDQN - -curr_time = datetime.datetime.now().strftime( - "%Y%m%d-%H%M%S") # obtain current time - -class HierarchicalDQNConfig: - def __init__(self): - self.algo = "H-DQN" # name of algo - self.env = 'CartPole-v0' - self.result_path = curr_path+"/outputs/" + self.env + \ - '/'+curr_time+'/results/' # path to save results - self.model_path = curr_path+"/outputs/" + self.env + \ - '/'+curr_time+'/models/' # path to save models - self.train_eps = 300 # 训练的episode数目 - self.eval_eps = 50 # 测试的episode数目 - self.gamma = 0.99 - self.epsilon_start = 1 # start epsilon of e-greedy policy - self.epsilon_end = 0.01 - self.epsilon_decay = 200 - self.lr = 0.0001 # learning rate - self.memory_capacity = 10000 # Replay Memory capacity - self.batch_size = 32 - self.target_update = 2 # target net的更新频率 - self.device = torch.device( - "cuda" if torch.cuda.is_available() else "cpu") # 检测gpu - self.hidden_dim = 256 # dimension of hidden layer - -def env_agent_config(cfg,seed=1): - env = gym.make(cfg.env) - env.seed(seed) - state_dim = env.observation_space.shape[0] - action_dim = env.action_space.n - agent = HierarchicalDQN(state_dim,action_dim,cfg) - return env,agent - -def train(cfg, env, agent): - print('Start to train !') - print(f'Env:{cfg.env}, Algorithm:{cfg.algo}, Device:{cfg.device}') - rewards = [] - ma_rewards = [] # moveing average reward - for i_ep in range(cfg.train_eps): - 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 - 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_ep+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*ep_reward) - else: - ma_rewards.append(ep_reward) - print('Complete training!') - return rewards, ma_rewards - -def eval(cfg, env, agent): - print('Start to eval !') - print(f'Env:{cfg.env}, Algorithm:{cfg.algo}, Device:{cfg.device}') - rewards = [] - ma_rewards = [] # moveing average reward - for i_ep in range(cfg.train_eps): - state = env.reset() - done = False - ep_reward = 0 - while not done: - goal = agent.set_goal(state) - onehot_goal = agent.to_onehot(goal) - 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 - state = next_state - agent.update() - print(f'Episode:{i_ep+1}/{cfg.train_eps}, Reward:{ep_reward}, Loss:{agent.loss_numpy:.2f}, Meta_Loss:{agent.meta_loss_numpy:.2f}') - rewards.append(ep_reward) - if ma_rewards: - ma_rewards.append( - 0.9*ma_rewards[-1]+0.1*ep_reward) - else: - ma_rewards.append(ep_reward) - print('Complete training!') - return rewards, ma_rewards - -if __name__ == "__main__": - cfg = HierarchicalDQNConfig() - - # train - env,agent = env_agent_config(cfg,seed=1) - rewards, ma_rewards = train(cfg, env, agent) - make_dir(cfg.result_path, cfg.model_path) - agent.save(path=cfg.model_path) - save_results(rewards, ma_rewards, tag='train', path=cfg.result_path) - plot_rewards(rewards, ma_rewards, tag="train", - algo=cfg.algo, path=cfg.result_path) - # eval - env,agent = env_agent_config(cfg,seed=10) - agent.load(path=cfg.model_path) - rewards,ma_rewards = eval(cfg,env,agent) - save_results(rewards,ma_rewards,tag='eval',path=cfg.result_path) - plot_rewards(rewards,ma_rewards,tag="eval",env=cfg.env,algo = cfg.algo,path=cfg.result_path) - diff --git a/codes/HierarchicalDQN/train.py b/codes/HierarchicalDQN/train.py new file mode 100644 index 0000000..3dc8aa3 --- /dev/null +++ b/codes/HierarchicalDQN/train.py @@ -0,0 +1,77 @@ +import sys +import os +curr_path = os.path.dirname(os.path.abspath(__file__)) # 当前文件所在绝对路径 +parent_path = os.path.dirname(curr_path) # 父路径 +sys.path.append(parent_path) # 添加路径到系统路径 + +import numpy as np + +def train(cfg, env, agent): + print('开始训练!') + print(f'环境:{cfg.env_name}, 算法:{cfg.algo_name}, 设备:{cfg.device}') + rewards = [] # 记录所有回合的奖励 + ma_rewards = [] # 记录所有回合的滑动平均奖励 + for i_ep in range(cfg.train_eps): + 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 + 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() + if (i_ep+1)%10 == 0: + print(f'回合:{i_ep+1}/{cfg.train_eps},奖励:{ep_reward},Loss:{agent.loss_numpy:.2f}, Meta_Loss:{agent.meta_loss_numpy:.2f}') + agent.meta_memory.push(meta_state, goal, extrinsic_reward, state, done) + rewards.append(ep_reward) + if ma_rewards: + ma_rewards.append( + 0.9*ma_rewards[-1]+0.1*ep_reward) + else: + ma_rewards.append(ep_reward) + print('完成训练!') + return rewards, ma_rewards + +def test(cfg, env, agent): + print('开始测试!') + print(f'环境:{cfg.env_name}, 算法:{cfg.algo_name}, 设备:{cfg.device}') + rewards = [] # 记录所有回合的奖励 + ma_rewards = [] # 记录所有回合的滑动平均奖励 + for i_ep in range(cfg.train_eps): + state = env.reset() + done = False + ep_reward = 0 + while not done: + goal = agent.set_goal(state) + onehot_goal = agent.to_onehot(goal) + 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 + state = next_state + agent.update() + if (i_ep+1)%10 == 0: + print(f'回合:{i_ep+1}/{cfg.train_eps},奖励:{ep_reward},Loss:{agent.loss_numpy:.2f}, Meta_Loss:{agent.meta_loss_numpy:.2f}') + rewards.append(ep_reward) + if ma_rewards: + ma_rewards.append( + 0.9*ma_rewards[-1]+0.1*ep_reward) + else: + ma_rewards.append(ep_reward) + print('完成训练!') + return rewards, ma_rewards \ No newline at end of file diff --git a/codes/Logs.md b/codes/Logs.md new file mode 100644 index 0000000..7dc6497 --- /dev/null +++ b/codes/Logs.md @@ -0,0 +1,7 @@ +## 记录笔者更新的日志 + +**2021.12.28-1**:将```task.py```中的两个Config类合并为一个,并加以注释便于阅读,从DQN算法开始更新 + +**2021.12.22-3**:将```agent.py```更改为对应的算法名称,便于区分如```dqn```与```dqn_cnn```的情况 +**2021.12.22-2**:简化了代码结构,将原来的```train.py```和```task.py```等合并到```task.py```中 +**2021.12.22-1**:简化了代码结构,将原来的```model.py```和```memory.py```等合并到```agent.py```中,```plot.py```的内容合并到```common.utils.py```中 \ No newline at end of file diff --git a/codes/NoisyDQN/noisy_dqn.py b/codes/NoisyDQN/noisy_dqn.py new file mode 100644 index 0000000..45cc5d2 --- /dev/null +++ b/codes/NoisyDQN/noisy_dqn.py @@ -0,0 +1,52 @@ +import torch +import torch.nn as nn + +class NoisyLinear(nn.Module): + def __init__(self, input_dim, output_dim, std_init=0.4): + super(NoisyLinear, self).__init__() + + self.input_dim = input_dim + self.output_dim = output_dim + self.std_init = std_init + + self.weight_mu = nn.Parameter(torch.FloatTensor(output_dim, input_dim)) + self.weight_sigma = nn.Parameter(torch.FloatTensor(output_dim, input_dim)) + self.register_buffer('weight_epsilon', torch.FloatTensor(output_dim, input_dim)) + + self.bias_mu = nn.Parameter(torch.FloatTensor(output_dim)) + self.bias_sigma = nn.Parameter(torch.FloatTensor(output_dim)) + self.register_buffer('bias_epsilon', torch.FloatTensor(output_dim)) + + self.reset_parameters() + self.reset_noise() + + def forward(self, x): + if self.training: + weight = self.weight_mu + self.weight_sigma.mul( (self.weight_epsilon)) + bias = self.bias_mu + self.bias_sigma.mul(Variable(self.bias_epsilon)) + else: + weight = self.weight_mu + bias = self.bias_mu + + return F.linear(x, weight, bias) + + def reset_parameters(self): + mu_range = 1 / math.sqrt(self.weight_mu.size(1)) + + self.weight_mu.data.uniform_(-mu_range, mu_range) + self.weight_sigma.data.fill_(self.std_init / math.sqrt(self.weight_sigma.size(1))) + + self.bias_mu.data.uniform_(-mu_range, mu_range) + self.bias_sigma.data.fill_(self.std_init / math.sqrt(self.bias_sigma.size(0))) + + def reset_noise(self): + epsilon_in = self._scale_noise(self.input_dim) + epsilon_out = self._scale_noise(self.output_dim) + + self.weight_epsilon.copy_(epsilon_out.ger(epsilon_in)) + self.bias_epsilon.copy_(self._scale_noise(self.output_dim)) + + def _scale_noise(self, size): + x = torch.randn(size) + x = x.sign().mul(x.abs().sqrt()) + return x \ No newline at end of file diff --git a/codes/PPO/task0.py b/codes/PPO/task0.py index cd55eda..8e0d92a 100644 --- a/codes/PPO/task0.py +++ b/codes/PPO/task0.py @@ -20,7 +20,7 @@ class PPOConfig: self.continuous = False # 环境是否为连续动作 self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 检测GPU self.train_eps = 200 # 训练的回合数 - self.eval_eps = 20 # 测试的回合数 + self.test_eps = 20 # 测试的回合数 self.batch_size = 5 self.gamma=0.99 self.n_epochs = 4 diff --git a/codes/PPO/task1.py b/codes/PPO/task1.py index 178efba..38d9152 100644 --- a/codes/PPO/task1.py +++ b/codes/PPO/task1.py @@ -20,7 +20,7 @@ class PPOConfig: self.continuous = True # 环境是否为连续动作 self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 检测GPU self.train_eps = 200 # 训练的回合数 - self.eval_eps = 20 # 测试的回合数 + self.test_eps = 20 # 测试的回合数 self.batch_size = 5 self.gamma=0.99 self.n_epochs = 4 diff --git a/codes/PPO/train.ipynb b/codes/PPO/train.ipynb index 9c74585..b2dc91a 100644 --- a/codes/PPO/train.ipynb +++ b/codes/PPO/train.ipynb @@ -68,7 +68,7 @@ " self.result_path = curr_path+\"/results/\" +self.env+'/'+curr_time+'/results/' # path to save results\n", " self.model_path = curr_path+\"/results/\" +self.env+'/'+curr_time+'/models/' # path to save models\n", " self.train_eps = 200 # max training episodes\n", - " self.eval_eps = 50\n", + " self.test_eps = 50\n", " self.batch_size = 5\n", " self.gamma=0.99\n", " self.n_epochs = 4\n", @@ -144,7 +144,7 @@ " print(f'Env:{cfg.env}, Algorithm:{cfg.algo}, Device:{cfg.device}')\n", " rewards= []\n", " ma_rewards = [] # moving average rewards\n", - " for i_ep in range(cfg.eval_eps):\n", + " for i_ep in range(cfg.test_eps):\n", " state = env.reset()\n", " done = False\n", " ep_reward = 0\n", diff --git a/codes/PPO/train.py b/codes/PPO/train.py index aff54bf..e642df0 100644 --- a/codes/PPO/train.py +++ b/codes/PPO/train.py @@ -32,7 +32,7 @@ def eval(cfg,env,agent): print(f'环境:{cfg.env_name}, 算法:{cfg.algo}, 设备:{cfg.device}') rewards = [] # 记录所有回合的奖励 ma_rewards = [] # 记录所有回合的滑动平均奖励 - for i_ep in range(cfg.eval_eps): + for i_ep in range(cfg.test_eps): state = env.reset() done = False ep_reward = 0 @@ -47,7 +47,7 @@ def eval(cfg,env,agent): 0.9*ma_rewards[-1]+0.1*ep_reward) else: ma_rewards.append(ep_reward) - print('回合:{}/{}, 奖励:{}'.format(i_ep+1, cfg.eval_eps, ep_reward)) + print('回合:{}/{}, 奖励:{}'.format(i_ep+1, cfg.test_eps, ep_reward)) print('完成训练!') return rewards,ma_rewards @@ -74,7 +74,7 @@ if __name__ == '__main__': self.continuous = False # 环境是否为连续动作 self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 检测GPU self.train_eps = 200 # 训练的回合数 - self.eval_eps = 20 # 测试的回合数 + self.test_eps = 20 # 测试的回合数 self.batch_size = 5 self.gamma=0.99 self.n_epochs = 4 diff --git a/codes/PolicyGradient/model.py b/codes/PolicyGradient/model.py index 7f5b1a8..6d9bc64 100644 --- a/codes/PolicyGradient/model.py +++ b/codes/PolicyGradient/model.py @@ -5,21 +5,22 @@ Author: John Email: johnjim0816@gmail.com Date: 2021-03-23 16:35:58 LastEditor: John -LastEditTime: 2021-03-23 16:36:20 +LastEditTime: 2021-12-21 23:21:26 Discription: Environment: ''' import torch.nn as nn import torch.nn.functional as F class MLP(nn.Module): + ''' 多层感知机 输入:state维度 输出:概率 ''' - def __init__(self,state_dim,hidden_dim = 36): + def __init__(self,input_dim,hidden_dim = 36): super(MLP, self).__init__() - # 24和36为hidden layer的层数,可根据state_dim, action_dim的情况来改变 - self.fc1 = nn.Linear(state_dim, hidden_dim) + # 24和36为hidden layer的层数,可根据input_dim, action_dim的情况来改变 + self.fc1 = nn.Linear(input_dim, hidden_dim) self.fc2 = nn.Linear(hidden_dim,hidden_dim) self.fc3 = nn.Linear(hidden_dim, 1) # Prob of Left diff --git a/codes/PolicyGradient/task0_train.py b/codes/PolicyGradient/task0_train.py index a7fb0d2..b6866f0 100644 --- a/codes/PolicyGradient/task0_train.py +++ b/codes/PolicyGradient/task0_train.py @@ -34,7 +34,7 @@ class PGConfig: self.model_path = curr_path+"/outputs/" + self.env + \ '/'+curr_time+'/models/' # 保存模型的路径 self.train_eps = 300 # 训练的回合数 - self.eval_eps = 30 # 测试的回合数 + self.test_eps = 30 # 测试的回合数 self.batch_size = 8 self.lr = 0.01 # 学习率 self.gamma = 0.99 @@ -94,7 +94,7 @@ def eval(cfg,env,agent): print(f'Env:{cfg.env}, Algorithm:{cfg.algo}, Device:{cfg.device}') rewards = [] ma_rewards = [] - for i_ep in range(cfg.eval_eps): + for i_ep in range(cfg.test_eps): state = env.reset() ep_reward = 0 for _ in count(): diff --git a/codes/QLearning/agent.py b/codes/QLearning/agent.py index 4587c86..b72de22 100644 --- a/codes/QLearning/agent.py +++ b/codes/QLearning/agent.py @@ -5,7 +5,7 @@ Author: John Email: johnjim0816@gmail.com Date: 2020-09-11 23:03:00 LastEditor: John -LastEditTime: 2021-09-19 23:05:45 +LastEditTime: 2021-12-22 10:54:57 Discription: use defaultdict to define Q table Environment: ''' @@ -17,15 +17,15 @@ from collections import defaultdict class QLearning(object): def __init__(self,state_dim, action_dim,cfg): - self.action_dim = action_dim # dimension of acgtion - self.lr = cfg.lr # learning rate + self.action_dim = action_dim + self.lr = cfg.lr # 学习率 self.gamma = cfg.gamma self.epsilon = 0 self.sample_count = 0 self.epsilon_start = cfg.epsilon_start self.epsilon_end = cfg.epsilon_end self.epsilon_decay = cfg.epsilon_decay - self.Q_table = defaultdict(lambda: np.zeros(action_dim)) # A nested dictionary that maps state -> (action -> action-value) + self.Q_table = defaultdict(lambda: np.zeros(action_dim)) # 用嵌套字典存放状态->动作->状态-动作值(Q值)的映射,即Q表 def choose_action(self, state): self.sample_count += 1 self.epsilon = self.epsilon_end + (self.epsilon_start - self.epsilon_end) * \ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20210919-211343/models/Qleaning_model.pkl b/codes/QLearning/outputs/CliffWalking-v0/20210919-211343/models/Qleaning_model.pkl deleted file mode 100644 index 45dce51..0000000 Binary files a/codes/QLearning/outputs/CliffWalking-v0/20210919-211343/models/Qleaning_model.pkl and /dev/null differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20210919-211343/results/train_ma_rewards.npy b/codes/QLearning/outputs/CliffWalking-v0/20210919-211343/results/train_ma_rewards.npy deleted file mode 100644 index 3a8bde0..0000000 Binary files a/codes/QLearning/outputs/CliffWalking-v0/20210919-211343/results/train_ma_rewards.npy and /dev/null differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20210919-211343/results/train_rewards.npy b/codes/QLearning/outputs/CliffWalking-v0/20210919-211343/results/train_rewards.npy deleted file mode 100644 index 36de6fc..0000000 Binary files a/codes/QLearning/outputs/CliffWalking-v0/20210919-211343/results/train_rewards.npy and /dev/null differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20210919-211343/results/train_rewards_curve_cn.png b/codes/QLearning/outputs/CliffWalking-v0/20210919-211343/results/train_rewards_curve_cn.png deleted file mode 100644 index 3226b8a..0000000 Binary files a/codes/QLearning/outputs/CliffWalking-v0/20210919-211343/results/train_rewards_curve_cn.png and /dev/null differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20210919-211442/models/Qleaning_model.pkl b/codes/QLearning/outputs/CliffWalking-v0/20210919-211442/models/Qleaning_model.pkl deleted file mode 100644 index 5c46ec6..0000000 Binary files a/codes/QLearning/outputs/CliffWalking-v0/20210919-211442/models/Qleaning_model.pkl and /dev/null differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20210919-211442/results/train_ma_rewards.npy b/codes/QLearning/outputs/CliffWalking-v0/20210919-211442/results/train_ma_rewards.npy deleted file mode 100644 index 1d6b889..0000000 Binary files a/codes/QLearning/outputs/CliffWalking-v0/20210919-211442/results/train_ma_rewards.npy and /dev/null differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20210919-211442/results/train_rewards.npy b/codes/QLearning/outputs/CliffWalking-v0/20210919-211442/results/train_rewards.npy deleted file mode 100644 index 6e6ccf0..0000000 Binary files a/codes/QLearning/outputs/CliffWalking-v0/20210919-211442/results/train_rewards.npy and /dev/null differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20210919-211442/results/train_rewards_curve_cn.png b/codes/QLearning/outputs/CliffWalking-v0/20210919-211442/results/train_rewards_curve_cn.png deleted file mode 100644 index e1cd04e..0000000 Binary files a/codes/QLearning/outputs/CliffWalking-v0/20210919-211442/results/train_rewards_curve_cn.png and /dev/null differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20210919-211456/models/Qleaning_model.pkl b/codes/QLearning/outputs/CliffWalking-v0/20210919-211456/models/Qleaning_model.pkl deleted file mode 100644 index 6986805..0000000 Binary files a/codes/QLearning/outputs/CliffWalking-v0/20210919-211456/models/Qleaning_model.pkl and /dev/null differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20210919-211456/results/train_ma_rewards.npy b/codes/QLearning/outputs/CliffWalking-v0/20210919-211456/results/train_ma_rewards.npy deleted file mode 100644 index e6793df..0000000 Binary files a/codes/QLearning/outputs/CliffWalking-v0/20210919-211456/results/train_ma_rewards.npy and /dev/null differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20210919-211456/results/train_rewards.npy b/codes/QLearning/outputs/CliffWalking-v0/20210919-211456/results/train_rewards.npy deleted file mode 100644 index e6793df..0000000 Binary files a/codes/QLearning/outputs/CliffWalking-v0/20210919-211456/results/train_rewards.npy and /dev/null differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20210919-211456/results/train_rewards_curve_cn.png b/codes/QLearning/outputs/CliffWalking-v0/20210919-211456/results/train_rewards_curve_cn.png deleted file mode 100644 index 9c98cc9..0000000 Binary files a/codes/QLearning/outputs/CliffWalking-v0/20210919-211456/results/train_rewards_curve_cn.png and /dev/null differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20210920-003309/models/Qleaning_model.pkl b/codes/QLearning/outputs/CliffWalking-v0/20210920-003309/models/Qleaning_model.pkl deleted file mode 100644 index 4d6ba95..0000000 Binary files a/codes/QLearning/outputs/CliffWalking-v0/20210920-003309/models/Qleaning_model.pkl and /dev/null differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20210920-003309/results/eval_rewards_curve_cn.png b/codes/QLearning/outputs/CliffWalking-v0/20210920-003309/results/eval_rewards_curve_cn.png deleted file mode 100644 index 91ca06c..0000000 Binary files a/codes/QLearning/outputs/CliffWalking-v0/20210920-003309/results/eval_rewards_curve_cn.png and /dev/null differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20210920-003309/results/train_ma_rewards.npy b/codes/QLearning/outputs/CliffWalking-v0/20210920-003309/results/train_ma_rewards.npy deleted file mode 100644 index 7184c7b..0000000 Binary files a/codes/QLearning/outputs/CliffWalking-v0/20210920-003309/results/train_ma_rewards.npy and /dev/null differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20210920-003309/results/train_rewards.npy b/codes/QLearning/outputs/CliffWalking-v0/20210920-003309/results/train_rewards.npy deleted file mode 100644 index f037a25..0000000 Binary files a/codes/QLearning/outputs/CliffWalking-v0/20210920-003309/results/train_rewards.npy and /dev/null differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20210920-003309/results/train_rewards_curve_cn.png b/codes/QLearning/outputs/CliffWalking-v0/20210920-003309/results/train_rewards_curve_cn.png deleted file mode 100644 index 9c0943a..0000000 Binary files a/codes/QLearning/outputs/CliffWalking-v0/20210920-003309/results/train_rewards_curve_cn.png and /dev/null differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20211222-110223/models/Qleaning_model.pkl b/codes/QLearning/outputs/CliffWalking-v0/20211222-110223/models/Qleaning_model.pkl new file mode 100644 index 0000000..dc89386 Binary files /dev/null and b/codes/QLearning/outputs/CliffWalking-v0/20211222-110223/models/Qleaning_model.pkl differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20210920-003309/results/eval_ma_rewards.npy b/codes/QLearning/outputs/CliffWalking-v0/20211222-110223/results/test_ma_rewards.npy similarity index 100% rename from codes/QLearning/outputs/CliffWalking-v0/20210920-003309/results/eval_ma_rewards.npy rename to codes/QLearning/outputs/CliffWalking-v0/20211222-110223/results/test_ma_rewards.npy diff --git a/codes/QLearning/outputs/CliffWalking-v0/20210920-003309/results/eval_rewards.npy b/codes/QLearning/outputs/CliffWalking-v0/20211222-110223/results/test_rewards.npy similarity index 100% rename from codes/QLearning/outputs/CliffWalking-v0/20210920-003309/results/eval_rewards.npy rename to codes/QLearning/outputs/CliffWalking-v0/20211222-110223/results/test_rewards.npy diff --git a/codes/QLearning/outputs/CliffWalking-v0/20211222-110223/results/test_rewards_curve.png b/codes/QLearning/outputs/CliffWalking-v0/20211222-110223/results/test_rewards_curve.png new file mode 100644 index 0000000..d745634 Binary files /dev/null and b/codes/QLearning/outputs/CliffWalking-v0/20211222-110223/results/test_rewards_curve.png differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20211222-110223/results/train_ma_rewards.npy b/codes/QLearning/outputs/CliffWalking-v0/20211222-110223/results/train_ma_rewards.npy new file mode 100644 index 0000000..23e7c95 Binary files /dev/null and b/codes/QLearning/outputs/CliffWalking-v0/20211222-110223/results/train_ma_rewards.npy differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20211222-110223/results/train_rewards.npy b/codes/QLearning/outputs/CliffWalking-v0/20211222-110223/results/train_rewards.npy new file mode 100644 index 0000000..0ceb153 Binary files /dev/null and b/codes/QLearning/outputs/CliffWalking-v0/20211222-110223/results/train_rewards.npy differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20211222-110223/results/train_rewards_curve.png b/codes/QLearning/outputs/CliffWalking-v0/20211222-110223/results/train_rewards_curve.png new file mode 100644 index 0000000..a15bd2a Binary files /dev/null and b/codes/QLearning/outputs/CliffWalking-v0/20211222-110223/results/train_rewards_curve.png differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20211222-111747/models/Qleaning_model.pkl b/codes/QLearning/outputs/CliffWalking-v0/20211222-111747/models/Qleaning_model.pkl new file mode 100644 index 0000000..c362dbd Binary files /dev/null and b/codes/QLearning/outputs/CliffWalking-v0/20211222-111747/models/Qleaning_model.pkl differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20211222-111747/results/test_ma_rewards.npy b/codes/QLearning/outputs/CliffWalking-v0/20211222-111747/results/test_ma_rewards.npy new file mode 100644 index 0000000..9bee5e4 Binary files /dev/null and b/codes/QLearning/outputs/CliffWalking-v0/20211222-111747/results/test_ma_rewards.npy differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20211222-111747/results/test_rewards.npy b/codes/QLearning/outputs/CliffWalking-v0/20211222-111747/results/test_rewards.npy new file mode 100644 index 0000000..8aeb5dd Binary files /dev/null and b/codes/QLearning/outputs/CliffWalking-v0/20211222-111747/results/test_rewards.npy differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20211222-111747/results/test_rewards_curve.png b/codes/QLearning/outputs/CliffWalking-v0/20211222-111747/results/test_rewards_curve.png new file mode 100644 index 0000000..5f3ffb5 Binary files /dev/null and b/codes/QLearning/outputs/CliffWalking-v0/20211222-111747/results/test_rewards_curve.png differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20211222-111747/results/train_ma_rewards.npy b/codes/QLearning/outputs/CliffWalking-v0/20211222-111747/results/train_ma_rewards.npy new file mode 100644 index 0000000..261a3d5 Binary files /dev/null and b/codes/QLearning/outputs/CliffWalking-v0/20211222-111747/results/train_ma_rewards.npy differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20211222-111747/results/train_rewards.npy b/codes/QLearning/outputs/CliffWalking-v0/20211222-111747/results/train_rewards.npy new file mode 100644 index 0000000..b1a0f23 Binary files /dev/null and b/codes/QLearning/outputs/CliffWalking-v0/20211222-111747/results/train_rewards.npy differ diff --git a/codes/QLearning/outputs/CliffWalking-v0/20211222-111747/results/train_rewards_curve.png b/codes/QLearning/outputs/CliffWalking-v0/20211222-111747/results/train_rewards_curve.png new file mode 100644 index 0000000..9a9d6ad Binary files /dev/null and b/codes/QLearning/outputs/CliffWalking-v0/20211222-111747/results/train_rewards_curve.png differ diff --git a/codes/QLearning/task0.ipynb b/codes/QLearning/task0.ipynb new file mode 100644 index 0000000..dc447ce --- /dev/null +++ b/codes/QLearning/task0.ipynb @@ -0,0 +1,386 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "from pathlib import Path\n", + "curr_path = str(Path().absolute())\n", + "parent_path = str(Path().absolute().parent)\n", + "sys.path.append(parent_path) # 添加路径到系统路径\n", + "\n", + "import gym\n", + "import torch\n", + "import math\n", + "import datetime\n", + "import numpy as np\n", + "from collections import defaultdict\n", + "from envs.gridworld_env import CliffWalkingWapper\n", + "from QLearning.agent import QLearning\n", + "from common.utils import plot_rewards\n", + "from common.utils import save_results,make_dir\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## QLearning算法" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [], + "source": [ + "class QLearning(object):\n", + " def __init__(self,state_dim,\n", + " action_dim,cfg):\n", + " self.action_dim = action_dim \n", + " self.lr = cfg.lr # 学习率\n", + " self.gamma = cfg.gamma \n", + " self.epsilon = 0 \n", + " self.sample_count = 0 \n", + " self.epsilon_start = cfg.epsilon_start\n", + " self.epsilon_end = cfg.epsilon_end\n", + " self.epsilon_decay = cfg.epsilon_decay\n", + " self.Q_table = defaultdict(lambda: np.zeros(action_dim)) # 用嵌套字典存放状态->动作->状态-动作值(Q值)的映射,即Q表\n", + " def choose_action(self, state):\n", + " self.sample_count += 1\n", + " self.epsilon = self.epsilon_end + (self.epsilon_start - self.epsilon_end) * \\\n", + " math.exp(-1. * self.sample_count / self.epsilon_decay) # epsilon是会递减的,这里选择指数递减\n", + " # e-greedy 策略\n", + " if np.random.uniform(0, 1) > self.epsilon:\n", + " action = np.argmax(self.Q_table[str(state)]) # 选择Q(s,a)最大对应的动作\n", + " else:\n", + " action = np.random.choice(self.action_dim) # 随机选择动作\n", + " return action\n", + " def predict(self,state):\n", + " action = np.argmax(self.Q_table[str(state)])\n", + " return action\n", + " def update(self, state, action, reward, next_state, done):\n", + " Q_predict = self.Q_table[str(state)][action] \n", + " if done: # 终止状态\n", + " Q_target = reward \n", + " else:\n", + " Q_target = reward + self.gamma * np.max(self.Q_table[str(next_state)]) \n", + " self.Q_table[str(state)][action] += self.lr * (Q_target - Q_predict)\n", + " def save(self,path):\n", + " import dill\n", + " torch.save(\n", + " obj=self.Q_table,\n", + " f=path+\"Qleaning_model.pkl\",\n", + " pickle_module=dill\n", + " )\n", + " print(\"保存模型成功!\")\n", + " def load(self, path):\n", + " import dill\n", + " self.Q_table =torch.load(f=path+'Qleaning_model.pkl',pickle_module=dill)\n", + " print(\"加载模型成功!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 训练" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [], + "source": [ + "def train(cfg,env,agent):\n", + " print('开始训练!')\n", + " print(f'环境:{cfg.env_name}, 算法:{cfg.algo_name}, 设备:{cfg.device}')\n", + " rewards = [] # 记录奖励\n", + " ma_rewards = [] # 记录滑动平均奖励\n", + " for i_ep in range(cfg.train_eps):\n", + " ep_reward = 0 # 记录每个episode的reward\n", + " state = env.reset() # 重置环境, 重新开一局(即开始新的一个episode)\n", + " while True:\n", + " action = agent.choose_action(state) # 根据算法选择一个动作\n", + " next_state, reward, done, _ = env.step(action) # 与环境进行一次动作交互\n", + " agent.update(state, action, reward, next_state, done) # Q-learning算法更新\n", + " state = next_state # 存储上一个观察值\n", + " ep_reward += reward\n", + " if done:\n", + " break\n", + " rewards.append(ep_reward)\n", + " if ma_rewards:\n", + " ma_rewards.append(ma_rewards[-1]*0.9+ep_reward*0.1)\n", + " else:\n", + " ma_rewards.append(ep_reward)\n", + " if (i_ep+1)%20 == 0: \n", + " print('回合:{}/{}, 奖励:{}'.format(i_ep+1, cfg.train_eps, ep_reward))\n", + " print('完成训练!')\n", + " return rewards,ma_rewards" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 测试" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [], + "source": [ + "def test(cfg,env,agent):\n", + " # env = gym.make(\"FrozenLake-v0\", is_slippery=False) # 0 left, 1 down, 2 right, 3 up\n", + " # env = FrozenLakeWapper(env)\n", + " print('开始测试!')\n", + " print(f'环境:{cfg.env_name}, 算法:{cfg.algo_name}, 设备:{cfg.device}')\n", + " # 由于测试不需要使用epsilon-greedy策略,所以相应的值设置为0\n", + " cfg.epsilon_start = 0.0 # e-greedy策略中初始epsilon\n", + " cfg.epsilon_end = 0.0 # e-greedy策略中的终止epsilon\n", + " rewards = [] # 记录所有回合的奖励\n", + " ma_rewards = [] # 记录所有回合的滑动平均奖励\n", + " rewards = [] # 记录所有episode的reward\n", + " ma_rewards = [] # 滑动平均的reward\n", + " for i_ep in range(cfg.test_eps):\n", + " ep_reward = 0 # 记录每个episode的reward\n", + " state = env.reset() # 重置环境, 重新开一局(即开始新的一个episode)\n", + " while True:\n", + " action = agent.predict(state) # 根据算法选择一个动作\n", + " next_state, reward, done, _ = env.step(action) # 与环境进行一个交互\n", + " state = next_state # 存储上一个观察值\n", + " ep_reward += reward\n", + " if done:\n", + " break\n", + " rewards.append(ep_reward)\n", + " if ma_rewards:\n", + " ma_rewards.append(ma_rewards[-1]*0.9+ep_reward*0.1)\n", + " else:\n", + " ma_rewards.append(ep_reward)\n", + " print(f\"回合:{i_ep+1}/{cfg.test_eps},奖励:{ep_reward:.1f}\")\n", + " print('完成测试!')\n", + " return rewards,ma_rewards" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 设置参数" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [], + "source": [ + "curr_time = datetime.datetime.now().strftime(\"%Y%m%d-%H%M%S\") # 获取当前时间\n", + "algo_name = 'Q-learning' # 算法名称\n", + "env_name = 'CliffWalking-v0' # 环境名称\n", + "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\") # 检测GPU\n", + "class QlearningConfig:\n", + " '''训练相关参数'''\n", + " def __init__(self):\n", + " self.algo_name = algo_name # 算法名称\n", + " self.env_name = env_name # 环境名称\n", + " self.device = device # 检测GPU\n", + " self.train_eps = 400 # 训练的回合数\n", + " self.test_eps = 20 # 测试的回合数\n", + " self.gamma = 0.9 # reward的衰减率\n", + " self.epsilon_start = 0.95 # e-greedy策略中初始epsilon\n", + " self.epsilon_end = 0.01 # e-greedy策略中的终止epsilon\n", + " self.epsilon_decay = 300 # e-greedy策略中epsilon的衰减率\n", + " self.lr = 0.1 # 学习率 \n", + "class PlotConfig:\n", + " ''' 绘图相关参数设置\n", + " '''\n", + "\n", + " def __init__(self) -> None:\n", + " self.algo_name = algo_name # 算法名称\n", + " self.env_name = env_name # 环境名称\n", + " self.device = device # 检测GPU\n", + " self.result_path = curr_path + \"/outputs/\" + self.env_name + \\\n", + " '/' + curr_time + '/results/' # 保存结果的路径\n", + " self.model_path = curr_path + \"/outputs/\" + self.env_name + \\\n", + " '/' + curr_time + '/models/' # 保存模型的路径\n", + " self.save = True # 是否保存图片" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 创建环境和智能体" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [], + "source": [ + "def env_agent_config(cfg,seed=1):\n", + " '''创建环境和智能体\n", + " Args:\n", + " cfg ([type]): [description]\n", + " seed (int, optional): 随机种子. Defaults to 1.\n", + " Returns:\n", + " env [type]: 环境\n", + " agent : 智能体\n", + " ''' \n", + " env = gym.make(cfg.env_name) \n", + " env = CliffWalkingWapper(env)\n", + " env.seed(seed) # 设置随机种子\n", + " state_dim = env.observation_space.n # 状态维度\n", + " action_dim = env.action_space.n # 动作维度\n", + " agent = QLearning(state_dim,action_dim,cfg)\n", + " return env,agent" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 执行训练并输出结果" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "开始训练!\n", + "环境:CliffWalking-v0, 算法:Q-learning, 设备:cuda\n", + "回合:20/400, 奖励:-82\n", + "回合:40/400, 奖励:-51\n", + "回合:60/400, 奖励:-50\n", + "回合:80/400, 奖励:-53\n", + "回合:100/400, 奖励:-21\n", + "回合:120/400, 奖励:-35\n", + "回合:140/400, 奖励:-44\n", + "回合:160/400, 奖励:-28\n", + "回合:180/400, 奖励:-28\n", + "回合:200/400, 奖励:-17\n", + "回合:220/400, 奖励:-18\n", + "回合:240/400, 奖励:-22\n", + "回合:260/400, 奖励:-19\n", + "回合:280/400, 奖励:-15\n", + "回合:300/400, 奖励:-14\n", + "回合:320/400, 奖励:-13\n", + "回合:340/400, 奖励:-13\n", + "回合:360/400, 奖励:-13\n", + "回合:380/400, 奖励:-13\n", + "回合:400/400, 奖励:-13\n", + "完成训练!\n", + "保存模型成功!\n", + "结果保存完毕!\n" + ] + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "加载模型成功!\n", + "开始测试!\n", + "环境:CliffWalking-v0, 算法:Q-learning, 设备:cuda\n", + "回合:1/20,奖励:-13.0\n", + "回合:2/20,奖励:-13.0\n", + "回合:3/20,奖励:-13.0\n", + "回合:4/20,奖励:-13.0\n", + "回合:5/20,奖励:-13.0\n", + "回合:6/20,奖励:-13.0\n", + "回合:7/20,奖励:-13.0\n", + "回合:8/20,奖励:-13.0\n", + "回合:9/20,奖励:-13.0\n", + "回合:10/20,奖励:-13.0\n", + "回合:11/20,奖励:-13.0\n", + "回合:12/20,奖励:-13.0\n", + "回合:13/20,奖励:-13.0\n", + "回合:14/20,奖励:-13.0\n", + "回合:15/20,奖励:-13.0\n", + "回合:16/20,奖励:-13.0\n", + "回合:17/20,奖励:-13.0\n", + "回合:18/20,奖励:-13.0\n", + "回合:19/20,奖励:-13.0\n", + "回合:20/20,奖励:-13.0\n", + "完成测试!\n", + "结果保存完毕!\n" + ] + }, + { + "data": { + "image/png": "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sD5pWJ80z2t9crkMDRpYFD/dlJ2npqYaHqelpSElJQWOjo5F2mVnZ2PMmDF4++23MWTIEFOWREREZST5JHVcXByWLl2K+/fvIyEhAdHR0Vi/fj0cHR0RFRWF1NRUmJmZQaFQYN68eWjRogUAYPny5bC1tcXw4cMRHR2N69evY8eOHdixYwcAYOTIkRg8eLDU8oiIqIJKPQfxd8JDTNWH9UnD+qRhfRVTrYeYiIjo74sBQUREohgQREQkigFBRESiGBBERCSKAUFERKIYEEREJIoBQUREohgQREQkigFBRESiGBBERCSKAUFERKIYEEREJIoBQUREohgQREQkigFBRESiGBBERCSKAUFERKIYEEREJIoBQUREohgQREQkigFBRESiJAdETEwM1Go1nJ2dsWXLFqNpq1evhlqtRkBAAPz9/bF///4Sl1VQUABfX18MGjRIallERCSRmdQFODk5ITIyEtHR0UWmBQUFYdKkSQCAjIwM+Pj4wMPDA1ZWVqLLioyMRPv27XH58mWpZRERkUSS9yBatmwJR0dHyOVFF2VpaWl4nJ+fD5lMBr1eL7qc06dP4/r16/D395daEhERVQLJexCl2bZtGzZt2oT09HQsWrQI1tbWRdrk5+dj0aJFWL16Na5fv17hvho0sJBQKWBjY1l6o2rE+qRhfdKwPmme9/rElBoQgYGB0Gg0otOSkpKgUChKnH/48OEYPnw4rly5gpCQEHTp0qVISCxduhRvvfUW7OzsJAVEVlYu9HqhQvPa2Fji1q2cCvdtaqxPGtYnDeuT5nmtTy6XlfjFutSA2L17d6UU0qpVK9ja2uLkyZPo37+/0bQzZ84gMTERq1atQkFBAe7duwe1Wo3Y2NhK6ZuIiMrPpIeYUlNT4ejoCABIS0tDSkqK4fnTng6CEydOICIiArt27TJlaUREVArJAREXF4elS5fi/v37SEhIQHR0NNavXw9HR0dERUUhNTUVZmZmUCgUmDdvHlq0aAEAWL58OWxtbTF8+HDJK0FERJVPJghCxQ7aP4d4DqL6sD5pWJ80rK9iSjsHwf9JTUREohgQREQkigFBRESiGBBERCSKAUFERKIYEEREJIoBQUREohgQREQkigFBRESiGBBERCSKAUFERKIYEEREJIoBQUREohgQREQkigFBRESiGBBERCSKAUFERKIYEEREJIoBQURE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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "cfg = QlearningConfig()\n", + "plot_cfg = PlotConfig()\n", + "# 训练\n", + "env, agent = env_agent_config(cfg, seed=1)\n", + "rewards, ma_rewards = train(cfg, env, agent)\n", + "make_dir(plot_cfg.result_path, plot_cfg.model_path) # 创建保存结果和模型路径的文件夹\n", + "agent.save(path=plot_cfg.model_path) # 保存模型\n", + "save_results(rewards, ma_rewards, tag='train',\n", + " path=plot_cfg.result_path) # 保存结果\n", + "plot_rewards(rewards, ma_rewards, plot_cfg, tag=\"train\") # 画出结果\n", + "# 测试\n", + "env, agent = env_agent_config(cfg, seed=10)\n", + "agent.load(path=plot_cfg.model_path) # 导入模型\n", + "rewards, ma_rewards = test(cfg, env, agent)\n", + "save_results(rewards, ma_rewards, tag='test', path=plot_cfg.result_path) # 保存结果\n", + "plot_rewards(rewards, ma_rewards, plot_cfg, tag=\"test\") # 画出结果" + ] + } + ], + "metadata": { + "interpreter": { + "hash": "fbea1422c2cf61ed9c0cfc03f38f71cc9083cc288606edc4170b5309b352ce27" + }, + "kernelspec": { + "display_name": "Python 3.7.11 64-bit ('py37': conda)", + "name": "python3" + }, + "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" + }, + "orig_nbformat": 2 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/codes/QLearning/task0.py b/codes/QLearning/task0.py new file mode 100644 index 0000000..59a1668 --- /dev/null +++ b/codes/QLearning/task0.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +# coding=utf-8 +''' +Author: John +Email: johnjim0816@gmail.com +Date: 2020-09-11 23:03:00 +LastEditor: John +LastEditTime: 2021-12-22 11:13:23 +Discription: +Environment: +''' +import sys +import os +curr_path = os.path.dirname(os.path.abspath(__file__)) # 当前文件所在绝对路径 +parent_path = os.path.dirname(curr_path) # 父路径 +sys.path.append(parent_path) # 添加路径到系统路径 + +import gym +import torch +import datetime + +from envs.gridworld_env import CliffWalkingWapper +from QLearning.agent import QLearning +from QLearning.train import train,test +from common.utils import plot_rewards,plot_rewards_cn +from common.utils import save_results,make_dir + +curr_time = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") # 获取当前时间 +algo_name = 'Q-learning' # 算法名称 +env_name = 'CliffWalking-v0' # 环境名称 +device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 检测GPU +class QlearningConfig: + '''训练相关参数''' + def __init__(self): + self.algo_name = algo_name # 算法名称 + self.env_name = env_name # 环境名称 + self.device = device # 检测GPU + self.train_eps = 400 # 训练的回合数 + self.test_eps = 30 # 测试的回合数 + self.gamma = 0.9 # reward的衰减率 + self.epsilon_start = 0.95 # e-greedy策略中初始epsilon + self.epsilon_end = 0.01 # e-greedy策略中的终止epsilon + self.epsilon_decay = 300 # e-greedy策略中epsilon的衰减率 + self.lr = 0.1 # 学习率 +class PlotConfig: + ''' 绘图相关参数设置 + ''' + + def __init__(self) -> None: + self.algo_name = algo_name # 算法名称 + self.env_name = env_name # 环境名称 + self.device = device # 检测GPU + self.result_path = curr_path + "/outputs/" + self.env_name + \ + '/' + curr_time + '/results/' # 保存结果的路径 + self.model_path = curr_path + "/outputs/" + self.env_name + \ + '/' + curr_time + '/models/' # 保存模型的路径 + self.save = True # 是否保存图片 + +def env_agent_config(cfg,seed=1): + '''创建环境和智能体 + Args: + cfg ([type]): [description] + seed (int, optional): 随机种子. Defaults to 1. + Returns: + env [type]: 环境 + agent : 智能体 + ''' + env = gym.make(cfg.env_name) + env = CliffWalkingWapper(env) + env.seed(seed) # 设置随机种子 + state_dim = env.observation_space.n # 状态维度 + action_dim = env.action_space.n # 动作维度 + agent = QLearning(state_dim,action_dim,cfg) + return env,agent + +cfg = QlearningConfig() +plot_cfg = PlotConfig() +# 训练 +env, agent = env_agent_config(cfg, seed=1) +rewards, ma_rewards = train(cfg, env, agent) +make_dir(plot_cfg.result_path, plot_cfg.model_path) # 创建保存结果和模型路径的文件夹 +agent.save(path=plot_cfg.model_path) # 保存模型 +save_results(rewards, ma_rewards, tag='train', + path=plot_cfg.result_path) # 保存结果 +plot_rewards(rewards, ma_rewards, plot_cfg, tag="train") # 画出结果 +# 测试 +env, agent = env_agent_config(cfg, seed=10) +agent.load(path=plot_cfg.model_path) # 导入模型 +rewards, ma_rewards = test(cfg, env, agent) +save_results(rewards, ma_rewards, tag='test', path=plot_cfg.result_path) # 保存结果 +plot_rewards(rewards, ma_rewards, plot_cfg, tag="test") # 画出结果 + + diff --git a/codes/QLearning/task0_train.ipynb b/codes/QLearning/task0_train.ipynb deleted file mode 100644 index 5715766..0000000 --- a/codes/QLearning/task0_train.ipynb +++ /dev/null @@ -1,216 +0,0 @@ -{ - "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.11" - }, - "orig_nbformat": 2, - "kernelspec": { - "name": "python3", - "display_name": "Python 3.7.11 64-bit ('py37': conda)" - }, - "interpreter": { - "hash": "fbea1422c2cf61ed9c0cfc03f38f71cc9083cc288606edc4170b5309b352ce27" - } - }, - "nbformat": 4, - "nbformat_minor": 2, - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "source": [ - "import sys\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 datetime\n", - "\n", - "from envs.gridworld_env import CliffWalkingWapper\n", - "from QLearning.agent import QLearning\n", - "from common.plot import plot_rewards\n", - "from common.utils import save_results,make_dir\n", - "curr_time = datetime.datetime.now().strftime(\"%Y%m%d-%H%M%S\") # obtain current time" - ], - "outputs": [], - "metadata": {} - }, - { - "cell_type": "code", - "execution_count": 2, - "source": [ - "class QlearningConfig:\n", - " '''训练相关参数'''\n", - " def __init__(self):\n", - " self.algo = 'Qlearning'\n", - " self.env = 'CliffWalking-v0' # 0 up, 1 right, 2 down, 3 left\n", - " self.result_path = curr_path+\"/outputs/\" +self.env+'/'+curr_time+'/results/' # path to save results\n", - " self.model_path = curr_path+\"/outputs/\" +self.env+'/'+curr_time+'/models/' # path to save models\n", - " self.train_eps = 200 # 训练的episode数目\n", - " self.eval_eps = 30\n", - " self.gamma = 0.9 # reward的衰减率\n", - " self.epsilon_start = 0.95 # e-greedy策略中初始epsilon\n", - " self.epsilon_end = 0.01 # e-greedy策略中的终止epsilon\n", - " self.epsilon_decay = 200 # e-greedy策略中epsilon的衰减率\n", - " self.lr = 0.1 # learning rate" - ], - "outputs": [], - "metadata": {} - }, - { - "cell_type": "code", - "execution_count": 3, - "source": [ - "def env_agent_config(cfg,seed=1):\n", - " env = gym.make(cfg.env) \n", - " env = CliffWalkingWapper(env)\n", - " env.seed(seed)\n", - " state_dim = env.observation_space.n\n", - " action_dim = env.action_space.n\n", - " agent = QLearning(state_dim,action_dim,cfg)\n", - " return env,agent" - ], - "outputs": [], - "metadata": {} - }, - { - "cell_type": "code", - "execution_count": 4, - "source": [ - "def train(cfg,env,agent):\n", - " rewards = [] \n", - " ma_rewards = [] # moving average reward\n", - " for i_ep in range(cfg.train_eps):\n", - " ep_reward = 0 # 记录每个episode的reward\n", - " state = env.reset() # 重置环境, 重新开一局(即开始新的一个episode)\n", - " while True:\n", - " action = agent.choose_action(state) # 根据算法选择一个动作\n", - " next_state, reward, done, _ = env.step(action) # 与环境进行一次动作交互\n", - " agent.update(state, action, reward, next_state, done) # Q-learning算法更新\n", - " state = next_state # 存储上一个观察值\n", - " ep_reward += reward\n", - " if done:\n", - " break\n", - " rewards.append(ep_reward)\n", - " if ma_rewards:\n", - " ma_rewards.append(ma_rewards[-1]*0.9+ep_reward*0.1)\n", - " else:\n", - " ma_rewards.append(ep_reward)\n", - " if (i_ep+1)%10==0:\n", - " print(\"Episode:{}/{}: reward:{:.1f}\".format(i_ep+1, cfg.train_eps,ep_reward))\n", - " return rewards,ma_rewards" - ], - "outputs": [], - "metadata": {} - }, - { - "cell_type": "code", - "execution_count": 5, - "source": [ - "def eval(cfg,env,agent):\n", - " # env = gym.make(\"FrozenLake-v0\", is_slippery=False) # 0 left, 1 down, 2 right, 3 up\n", - " # env = FrozenLakeWapper(env)\n", - " rewards = [] # 记录所有episode的reward\n", - " ma_rewards = [] # 滑动平均的reward\n", - " for i_ep in range(cfg.eval_eps):\n", - " ep_reward = 0 # 记录每个episode的reward\n", - " state = env.reset() # 重置环境, 重新开一局(即开始新的一个episode)\n", - " while True:\n", - " action = agent.predict(state) # 根据算法选择一个动作\n", - " next_state, reward, done, _ = env.step(action) # 与环境进行一个交互\n", - " state = next_state # 存储上一个观察值\n", - " ep_reward += reward\n", - " if done:\n", - " break\n", - " rewards.append(ep_reward)\n", - " if ma_rewards:\n", - " ma_rewards.append(ma_rewards[-1]*0.9+ep_reward*0.1)\n", - " else:\n", - " ma_rewards.append(ep_reward)\n", - " if (i_ep+1)%10==0:\n", - " print(f\"Episode:{i_ep+1}/{cfg.eval_eps}, reward:{ep_reward:.1f}\")\n", - " return rewards,ma_rewards" - ], - "outputs": [], - "metadata": {} - }, - { - "cell_type": "code", - "execution_count": 6, - "source": [ - "cfg = QlearningConfig()\n", - "env,agent = env_agent_config(cfg,seed=1)\n", - "rewards,ma_rewards = train(cfg,env,agent)\n", - "make_dir(cfg.result_path,cfg.model_path)\n", - "agent.save(path=cfg.model_path)\n", - "save_results(rewards,ma_rewards,tag='train',path=cfg.result_path)\n", - "plot_rewards(rewards,ma_rewards,tag=\"train\",env=cfg.env,algo = cfg.algo,path=cfg.result_path)\n", - "\n", - "env,agent = env_agent_config(cfg,seed=10)\n", - "agent.load(path=cfg.model_path)\n", - "rewards,ma_rewards = eval(cfg,env,agent)\n", - "save_results(rewards,ma_rewards,tag='eval',path=cfg.result_path)\n", - "plot_rewards(rewards,ma_rewards,tag=\"eval\",env=cfg.env,algo = cfg.algo,path=cfg.result_path)" - ], - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "Episode:10/200: reward:-287.0\n", - "Episode:20/200: reward:-142.0\n", - "Episode:30/200: reward:-67.0\n", - "Episode:40/200: reward:-61.0\n", - "Episode:50/200: reward:-74.0\n", - "Episode:60/200: reward:-41.0\n", - "Episode:70/200: reward:-55.0\n", - "Episode:80/200: reward:-66.0\n", - "Episode:90/200: reward:-31.0\n", - "Episode:100/200: reward:-31.0\n", - "Episode:110/200: reward:-58.0\n", - "Episode:120/200: reward:-25.0\n", - "Episode:130/200: reward:-18.0\n", - "Episode:140/200: reward:-27.0\n", - "Episode:150/200: reward:-28.0\n", - "Episode:160/200: reward:-25.0\n", - "Episode:170/200: reward:-35.0\n", - "Episode:180/200: reward:-13.0\n", - "Episode:190/200: reward:-22.0\n", - "Episode:200/200: reward:-26.0\n", - "保存模型成功!\n", - "结果保存完毕!\n" - ] - }, - { - "output_type": "display_data", - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYUAAAEXCAYAAABCjVgAAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/MnkTPAAAACXBIWXMAAAsTAAALEwEAmpwYAABRX0lEQVR4nO3dZ2AU5drw8f/sbrakF1KoofdOkE6kqAQIHQURQeztIOeVQ8ByFEUFUbGCciyPWACRIkoTFBAivYsghBIIIZvek23zfliyEJIAAUKie/2+wE5mZq+5d3euucvco6iqqiKEEEIAmsoOQAghRNUhSUEIIYSLJAUhhBAukhSEEEK4SFIQQgjhIklBCCGEiySFf6D333+f6dOn35L3evjhhzl+/Pgtea/KNnfuXG6//XamTp1a4m/5+fm89dZb9O/fn+joaKKjo3nnnXfIz893rdOkSRPS0tIqPM4NGzbw6quv3rT9Xem4yyMuLo6nn36a6OhoBg0axH333ceuXbsAOHv2LO3atQPg22+/5ZNPPgFgy5Yt9OrVi+HDh7N371769u3L0KFDad26NevXr3fte8uWLTRp0oSFCxe6lh04cIBu3bpxpVH3vXv35uDBg2zfvp2BAweW+PvBgwf517/+dUPHfa2WLFlCVFQUd955J//973+xWq235H1LUMU/znvvvae+/PLLlR3GP07v3r3VnTt3llhutVrVUaNGqa+88oqan5+vqqqq5uXlqa+88oo6atQo1Wq1qqqqqo0bN1ZTU1Nvacw3Q1nHXR5xcXFqt27d1M2bN7uWxcbGqh06dFD/+usv9cyZM2rbtm1LbBcTE6N++OGHqqqq6vvvv69OmzZNVVVVfe2119RXX33Vtd706dPVRx99VH344Yddyz7++GM1JibminH16tVLPXDggLpt2zZ1wIABN3SMN+Lo0aNqz5491dTUVNVut6uTJk1SP/nkk0qJRVc5qajqczgcvPbaa+zfv5/c3FxUVeXVV1+lcePGREZGsnbtWoKDgwG4++67efLJJ+nSpQuzZ89m586d2O12mjdvzvPPP4+3tze9e/emdevWHD16lH//+9/odDo+/vhjLBYLaWlpDBkyhGeeeQaATz75hCVLluDl5UVERAQbNmzgl19+wWKxlLn/siQlJTF9+nQSExOxWq0MGDCAxx57DIB58+axfv16CgsL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", - "text/plain": [ - "
" - ] - }, - "metadata": {} - }, - { - "output_type": "stream", - "name": "stdout", - "text": [ - "加载模型成功!\n" - ] - } - ], - "metadata": {} - } - ] -} \ No newline at end of file diff --git a/codes/QLearning/task0_train.py b/codes/QLearning/task0_train.py deleted file mode 100644 index 6e616ab..0000000 --- a/codes/QLearning/task0_train.py +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env python -# coding=utf-8 -''' -Author: John -Email: johnjim0816@gmail.com -Date: 2020-09-11 23:03:00 -LastEditor: John -LastEditTime: 2021-09-23 12:22:58 -Discription: -Environment: -''' -import sys,os -curr_path = os.path.dirname(os.path.abspath(__file__)) # 当前路径 -parent_path=os.path.dirname(curr_path) # 父路径,这里就是我们的项目路径 -sys.path.append(parent_path) # 由于需要引用项目路径下的其他模块比如envs,所以需要添加路径到sys.path - -import gym -import torch -import datetime - -from envs.gridworld_env import CliffWalkingWapper -from QLearning.agent import QLearning -from common.plot import plot_rewards,plot_rewards_cn -from common.utils import save_results,make_dir - -curr_time = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") # 获取当前时间 -class QlearningConfig: - '''训练相关参数''' - def __init__(self): - self.algo = 'Q-learning' # 算法名称 - self.env = 'CliffWalking-v0' # 环境名称 - self.result_path = curr_path+"/outputs/" +self.env+'/'+curr_time+'/results/' # 保存结果的路径 - self.model_path = curr_path+"/outputs/" +self.env+'/'+curr_time+'/models/' # 保存模型的路径 - self.train_eps = 400 # 训练的回合数 - self.eval_eps = 30 # 测试的回合数 - self.gamma = 0.9 # reward的衰减率 - self.epsilon_start = 0.95 # e-greedy策略中初始epsilon - self.epsilon_end = 0.01 # e-greedy策略中的终止epsilon - self.epsilon_decay = 300 # e-greedy策略中epsilon的衰减率 - self.lr = 0.1 # 学习率 - self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 检测GPU - - -def env_agent_config(cfg,seed=1): - env = gym.make(cfg.env) - env = CliffWalkingWapper(env) - env.seed(seed) # 设置随机种子 - n_states = env.observation_space.n # 状态维度 - n_actions = env.action_space.n # 动作维度 - agent = QLearning(n_states,n_actions,cfg) - return env,agent - -def train(cfg,env,agent): - print('开始训练!') - print(f'环境:{cfg.env}, 算法:{cfg.algo}, 设备:{cfg.device}') - rewards = [] # 记录奖励 - ma_rewards = [] # 记录滑动平均奖励 - for i_ep in range(cfg.train_eps): - ep_reward = 0 # 记录每个回合的奖励 - state = env.reset() # 重置环境,即开始新的回合 - while True: - action = agent.choose_action(state) # 根据算法选择一个动作 - next_state, reward, done, _ = env.step(action) # 与环境进行一次动作交互 - print(reward) - agent.update(state, action, reward, next_state, done) # Q学习算法更新 - state = next_state # 更新状态 - ep_reward += reward - if done: - break - rewards.append(ep_reward) - if ma_rewards: - ma_rewards.append(ma_rewards[-1]*0.9+ep_reward*0.1) - else: - ma_rewards.append(ep_reward) - print("回合数:{}/{},奖励{:.1f}".format(i_ep+1, cfg.train_eps,ep_reward)) - print('完成训练!') - return rewards,ma_rewards - -def eval(cfg,env,agent): - print('开始测试!') - print(f'环境:{cfg.env}, 算法:{cfg.algo}, 设备:{cfg.device}') - for item in agent.Q_table.items(): - print(item) - rewards = [] # 记录所有回合的奖励 - ma_rewards = [] # 滑动平均的奖励 - for i_ep in range(cfg.eval_eps): - ep_reward = 0 # 记录每个episode的reward - state = env.reset() # 重置环境, 重新开一局(即开始新的一个回合) - while True: - action = agent.predict(state) # 根据算法选择一个动作 - next_state, reward, done, _ = env.step(action) # 与环境进行一个交互 - state = next_state # 更新状态 - ep_reward += reward - if done: - break - rewards.append(ep_reward) - if ma_rewards: - ma_rewards.append(ma_rewards[-1]*0.9+ep_reward*0.1) - else: - ma_rewards.append(ep_reward) - print(f"回合数:{i_ep+1}/{cfg.eval_eps}, 奖励:{ep_reward:.1f}") - print('完成测试!') - return rewards,ma_rewards - -if __name__ == "__main__": - cfg = QlearningConfig() - - # 训练 - env,agent = env_agent_config(cfg,seed=0) - rewards,ma_rewards = train(cfg,env,agent) - make_dir(cfg.result_path,cfg.model_path) # 创建文件夹 - agent.save(path=cfg.model_path) # 保存模型 - for item in agent.Q_table.items(): - print(item) - save_results(rewards,ma_rewards,tag='train',path=cfg.result_path) # 保存结果 - plot_rewards_cn(rewards,ma_rewards,tag="train",env=cfg.env,algo = cfg.algo,path=cfg.result_path) - - # # 测试 - env,agent = env_agent_config(cfg,seed=10) - agent.load(path=cfg.model_path) # 加载模型 - rewards,ma_rewards = eval(cfg,env,agent) - - save_results(rewards,ma_rewards,tag='eval',path=cfg.result_path) - plot_rewards_cn(rewards,ma_rewards,tag="eval",env=cfg.env,algo = cfg.algo,path=cfg.result_path) - - diff --git a/codes/QLearning/train.py b/codes/QLearning/train.py new file mode 100644 index 0000000..2c4aa09 --- /dev/null +++ b/codes/QLearning/train.py @@ -0,0 +1,50 @@ +def train(cfg,env,agent): + print('开始训练!') + print(f'环境:{cfg.env_name}, 算法:{cfg.algo_name}, 设备:{cfg.device}') + rewards = [] # 记录奖励 + ma_rewards = [] # 记录滑动平均奖励 + for i_ep in range(cfg.train_eps): + ep_reward = 0 # 记录每个回合的奖励 + state = env.reset() # 重置环境,即开始新的回合 + while True: + action = agent.choose_action(state) # 根据算法选择一个动作 + next_state, reward, done, _ = env.step(action) # 与环境进行一次动作交互 + agent.update(state, action, reward, next_state, done) # Q学习算法更新 + state = next_state # 更新状态 + ep_reward += reward + if done: + break + rewards.append(ep_reward) + if ma_rewards: + ma_rewards.append(ma_rewards[-1]*0.9+ep_reward*0.1) + else: + ma_rewards.append(ep_reward) + print("回合数:{}/{},奖励{:.1f}".format(i_ep+1, cfg.train_eps,ep_reward)) + print('完成训练!') + return rewards,ma_rewards + +def test(cfg,env,agent): + print('开始测试!') + print(f'环境:{cfg.env_name}, 算法:{cfg.algo_name}, 设备:{cfg.device}') + for item in agent.Q_table.items(): + print(item) + rewards = [] # 记录所有回合的奖励 + ma_rewards = [] # 滑动平均的奖励 + for i_ep in range(cfg.test_eps): + ep_reward = 0 # 记录每个episode的reward + state = env.reset() # 重置环境, 重新开一局(即开始新的一个回合) + while True: + action = agent.predict(state) # 根据算法选择一个动作 + next_state, reward, done, _ = env.step(action) # 与环境进行一个交互 + state = next_state # 更新状态 + ep_reward += reward + if done: + break + rewards.append(ep_reward) + if ma_rewards: + ma_rewards.append(ma_rewards[-1]*0.9+ep_reward*0.1) + else: + ma_rewards.append(ep_reward) + print(f"回合数:{i_ep+1}/{cfg.test_eps}, 奖励:{ep_reward:.1f}") + print('完成测试!') + return rewards,ma_rewards \ No newline at end of file diff --git a/codes/README.md b/codes/README.md index fdee344..3896fbb 100644 --- a/codes/README.md +++ b/codes/README.md @@ -13,9 +13,10 @@ 其中```model.py```,```memory.py```,```plot.py``` 由于不同算法都会用到,所以放入```common```文件夹中。 +**注意:新版本中将```model```,```memory```相关内容全部放到了```agent.py```里面,```plot```放到了```common.utils```中。** ## 运行环境 -python 3.7、pytorch 1.6.0-1.8.1、gym 0.17.0-0.19.0 +python 3.7、pytorch 1.6.0-1.8.1、gym 0.21.0 ## 使用说明 @@ -35,7 +36,7 @@ python 3.7、pytorch 1.6.0-1.8.1、gym 0.17.0-0.19.0 | [Hierarchical DQN](HierarchicalDQN) | [H-DQN Paper](https://arxiv.org/abs/1604.06057) | [CartPole-v0](./envs/gym_info.md) | | | [PolicyGradient](./PolicyGradient) | [Lil'log](https://lilianweng.github.io/lil-log/2018/04/08/policy-gradient-algorithms.html) | [CartPole-v0](./envs/gym_info.md) | | | [A2C](./A2C) | [A3C Paper](https://arxiv.org/abs/1602.01783) | [CartPole-v0](./envs/gym_info.md) | | -| [SAC](./SAC) | [SAC Paper](https://arxiv.org/abs/1801.01290) | [Pendulum-v0](./envs/gym_info.md) | | +| [SAC](./SoftActorCritic) | [SAC Paper](https://arxiv.org/abs/1801.01290) | [Pendulum-v0](./envs/gym_info.md) | | | [PPO](./PPO) | [PPO paper](https://arxiv.org/abs/1707.06347) | [CartPole-v0](./envs/gym_info.md) | | | [DDPG](./DDPG) | [DDPG Paper](https://arxiv.org/abs/1509.02971) | [Pendulum-v0](./envs/gym_info.md) | | | [TD3](./TD3) | [TD3 Paper](https://arxiv.org/abs/1802.09477) | [HalfCheetah-v2]((./envs/mujoco_info.md)) | | @@ -45,4 +46,6 @@ python 3.7、pytorch 1.6.0-1.8.1、gym 0.17.0-0.19.0 [RL-Adventure-2](https://github.com/higgsfield/RL-Adventure-2) -[RL-Adventure](https://github.com/higgsfield/RL-Adventure) \ No newline at end of file +[RL-Adventure](https://github.com/higgsfield/RL-Adventure) + +[Google 开源项目风格指南——中文版](https://zh-google-styleguide.readthedocs.io/en/latest/google-python-styleguide/python_style_rules/#comments) \ No newline at end of file diff --git a/codes/SAC/outputs/Pendulum-v0/20210506-014740/models/sac_policy b/codes/SAC/outputs/Pendulum-v0/20210506-014740/models/sac_policy deleted file mode 100644 index 12479e2..0000000 Binary files a/codes/SAC/outputs/Pendulum-v0/20210506-014740/models/sac_policy and /dev/null differ diff --git a/codes/SAC/outputs/Pendulum-v0/20210506-014740/models/sac_policy_optimizer b/codes/SAC/outputs/Pendulum-v0/20210506-014740/models/sac_policy_optimizer deleted file mode 100644 index 6dea232..0000000 Binary files a/codes/SAC/outputs/Pendulum-v0/20210506-014740/models/sac_policy_optimizer and /dev/null differ diff --git a/codes/SAC/outputs/Pendulum-v0/20210506-014740/models/sac_soft_q b/codes/SAC/outputs/Pendulum-v0/20210506-014740/models/sac_soft_q deleted file mode 100644 index d2d5352..0000000 Binary files a/codes/SAC/outputs/Pendulum-v0/20210506-014740/models/sac_soft_q and /dev/null differ diff --git a/codes/SAC/outputs/Pendulum-v0/20210506-014740/models/sac_soft_q_optimizer b/codes/SAC/outputs/Pendulum-v0/20210506-014740/models/sac_soft_q_optimizer deleted file mode 100644 index d4c3e48..0000000 Binary files a/codes/SAC/outputs/Pendulum-v0/20210506-014740/models/sac_soft_q_optimizer and /dev/null differ diff --git a/codes/SAC/outputs/Pendulum-v0/20210506-014740/models/sac_value b/codes/SAC/outputs/Pendulum-v0/20210506-014740/models/sac_value deleted file mode 100644 index a180f73..0000000 Binary files a/codes/SAC/outputs/Pendulum-v0/20210506-014740/models/sac_value and /dev/null differ diff --git a/codes/SAC/outputs/Pendulum-v0/20210506-014740/models/sac_value_optimizer b/codes/SAC/outputs/Pendulum-v0/20210506-014740/models/sac_value_optimizer deleted file mode 100644 index f2ab113..0000000 Binary files a/codes/SAC/outputs/Pendulum-v0/20210506-014740/models/sac_value_optimizer and /dev/null differ diff --git a/codes/SAC/outputs/Pendulum-v0/20210506-014740/results/eval_ma_rewards.npy b/codes/SAC/outputs/Pendulum-v0/20210506-014740/results/eval_ma_rewards.npy deleted file mode 100644 index 4971d4f..0000000 Binary files a/codes/SAC/outputs/Pendulum-v0/20210506-014740/results/eval_ma_rewards.npy and /dev/null differ diff --git a/codes/SAC/outputs/Pendulum-v0/20210506-014740/results/eval_rewards.npy b/codes/SAC/outputs/Pendulum-v0/20210506-014740/results/eval_rewards.npy deleted file mode 100644 index 46bd706..0000000 Binary files a/codes/SAC/outputs/Pendulum-v0/20210506-014740/results/eval_rewards.npy and /dev/null differ diff --git a/codes/SAC/outputs/Pendulum-v0/20210506-014740/results/eval_rewards_curve.png b/codes/SAC/outputs/Pendulum-v0/20210506-014740/results/eval_rewards_curve.png deleted file mode 100644 index 3d4dd84..0000000 Binary files a/codes/SAC/outputs/Pendulum-v0/20210506-014740/results/eval_rewards_curve.png and /dev/null differ diff --git a/codes/SAC/outputs/Pendulum-v0/20210506-014740/results/train_ma_rewards.npy b/codes/SAC/outputs/Pendulum-v0/20210506-014740/results/train_ma_rewards.npy deleted file mode 100644 index bffae05..0000000 Binary files a/codes/SAC/outputs/Pendulum-v0/20210506-014740/results/train_ma_rewards.npy and /dev/null differ diff --git a/codes/SAC/outputs/Pendulum-v0/20210506-014740/results/train_rewards.npy b/codes/SAC/outputs/Pendulum-v0/20210506-014740/results/train_rewards.npy deleted file mode 100644 index 37837a6..0000000 Binary files a/codes/SAC/outputs/Pendulum-v0/20210506-014740/results/train_rewards.npy and /dev/null differ diff --git a/codes/SAC/outputs/Pendulum-v0/20210506-014740/results/train_rewards_curve.png b/codes/SAC/outputs/Pendulum-v0/20210506-014740/results/train_rewards_curve.png deleted file mode 100644 index 399b952..0000000 Binary files a/codes/SAC/outputs/Pendulum-v0/20210506-014740/results/train_rewards_curve.png and /dev/null differ diff --git a/codes/Sarsa/task0_train.py b/codes/Sarsa/task0_train.py index d21db17..e477afa 100644 --- a/codes/Sarsa/task0_train.py +++ b/codes/Sarsa/task0_train.py @@ -31,7 +31,7 @@ class SarsaConfig: self.result_path = curr_path+"/outputs/" +self.env+'/'+curr_time+'/results/' # path to save results self.model_path = curr_path+"/outputs/" +self.env+'/'+curr_time+'/models/' # path to save models self.train_eps = 200 - self.eval_eps = 50 + self.test_eps = 50 self.epsilon = 0.15 # epsilon: The probability to select a random action . self.gamma = 0.9 # gamma: Gamma discount factor. self.lr = 0.2 # learning rate: step size parameter @@ -74,7 +74,7 @@ def train(cfg,env,agent): def eval(cfg,env,agent): rewards = [] ma_rewards = [] - for i_episode in range(cfg.eval_eps): + for i_episode in range(cfg.test_eps): # Print out which episode we're on, useful for debugging. # Generate an episode. # An episode is an array of (state, action, reward) tuples @@ -94,7 +94,7 @@ def eval(cfg,env,agent): ma_rewards.append(ep_reward) rewards.append(ep_reward) if (i_episode+1)%10==0: - print("Episode:{}/{}: Reward:{}".format(i_episode+1, cfg.eval_eps,ep_reward)) + print("Episode:{}/{}: Reward:{}".format(i_episode+1, cfg.test_eps,ep_reward)) print('Complete evaling!') return rewards,ma_rewards diff --git a/codes/SAC/env.py b/codes/SoftActorCritic/env_wrapper.py similarity index 95% rename from codes/SAC/env.py rename to codes/SoftActorCritic/env_wrapper.py index 14e37a7..dfe1c4d 100644 --- a/codes/SAC/env.py +++ b/codes/SoftActorCritic/env_wrapper.py @@ -5,12 +5,13 @@ Author: JiangJi Email: johnjim0816@gmail.com Date: 2021-04-29 12:52:11 LastEditor: JiangJi -LastEditTime: 2021-04-29 12:52:31 +LastEditTime: 2021-12-22 15:36:36 Discription: Environment: ''' import gym import numpy as np + class NormalizedActions(gym.ActionWrapper): def action(self, action): low = self.action_space.low diff --git a/codes/SAC/model.py b/codes/SoftActorCritic/model.py similarity index 81% rename from codes/SAC/model.py rename to codes/SoftActorCritic/model.py index 146db0d..85bbfcd 100644 --- a/codes/SAC/model.py +++ b/codes/SoftActorCritic/model.py @@ -5,7 +5,7 @@ Author: JiangJi Email: johnjim0816@gmail.com Date: 2021-04-29 12:53:58 LastEditor: JiangJi -LastEditTime: 2021-04-29 12:57:29 +LastEditTime: 2021-11-19 18:04:19 Discription: Environment: ''' @@ -35,12 +35,12 @@ class ValueNet(nn.Module): class SoftQNet(nn.Module): - def __init__(self, num_inputs, num_actions, hidden_size, init_w=3e-3): + def __init__(self, state_dim, action_dim, hidden_dim, init_w=3e-3): super(SoftQNet, self).__init__() - self.linear1 = nn.Linear(num_inputs + num_actions, hidden_size) - self.linear2 = nn.Linear(hidden_size, hidden_size) - self.linear3 = nn.Linear(hidden_size, 1) + self.linear1 = nn.Linear(state_dim + action_dim, hidden_dim) + self.linear2 = nn.Linear(hidden_dim, hidden_dim) + self.linear3 = nn.Linear(hidden_dim, 1) self.linear3.weight.data.uniform_(-init_w, init_w) self.linear3.bias.data.uniform_(-init_w, init_w) @@ -54,20 +54,20 @@ class SoftQNet(nn.Module): class PolicyNet(nn.Module): - def __init__(self, num_inputs, num_actions, hidden_size, init_w=3e-3, log_std_min=-20, log_std_max=2): + def __init__(self, state_dim, action_dim, hidden_dim, init_w=3e-3, log_std_min=-20, log_std_max=2): super(PolicyNet, self).__init__() self.log_std_min = log_std_min self.log_std_max = log_std_max - self.linear1 = nn.Linear(num_inputs, hidden_size) - self.linear2 = nn.Linear(hidden_size, hidden_size) + self.linear1 = nn.Linear(state_dim, hidden_dim) + self.linear2 = nn.Linear(hidden_dim, hidden_dim) - self.mean_linear = nn.Linear(hidden_size, num_actions) + self.mean_linear = nn.Linear(hidden_dim, action_dim) self.mean_linear.weight.data.uniform_(-init_w, init_w) self.mean_linear.bias.data.uniform_(-init_w, init_w) - self.log_std_linear = nn.Linear(hidden_size, num_actions) + self.log_std_linear = nn.Linear(hidden_dim, action_dim) self.log_std_linear.weight.data.uniform_(-init_w, init_w) self.log_std_linear.bias.data.uniform_(-init_w, init_w) diff --git a/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/models/sac_policy b/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/models/sac_policy new file mode 100644 index 0000000..9ae4e7b Binary files /dev/null and b/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/models/sac_policy differ diff --git a/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/models/sac_policy_optimizer b/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/models/sac_policy_optimizer new file mode 100644 index 0000000..49c0d2a Binary files /dev/null and b/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/models/sac_policy_optimizer differ diff --git a/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/models/sac_soft_q b/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/models/sac_soft_q new file mode 100644 index 0000000..3ff692f Binary files /dev/null and b/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/models/sac_soft_q differ diff --git a/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/models/sac_soft_q_optimizer b/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/models/sac_soft_q_optimizer new file mode 100644 index 0000000..73be931 Binary files /dev/null and b/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/models/sac_soft_q_optimizer differ diff --git a/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/models/sac_value b/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/models/sac_value new file mode 100644 index 0000000..853ac6f Binary files /dev/null and b/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/models/sac_value differ diff --git a/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/models/sac_value_optimizer b/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/models/sac_value_optimizer new file mode 100644 index 0000000..79410e4 Binary files /dev/null and b/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/models/sac_value_optimizer differ diff --git a/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/results/test_ma_rewards.npy b/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/results/test_ma_rewards.npy new file mode 100644 index 0000000..eca3369 Binary files /dev/null and b/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/results/test_ma_rewards.npy differ diff --git a/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/results/test_rewards.npy b/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/results/test_rewards.npy new file mode 100644 index 0000000..09edb0e Binary files /dev/null and b/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/results/test_rewards.npy differ diff --git a/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/results/test_rewards_curve.png b/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/results/test_rewards_curve.png new file mode 100644 index 0000000..5cc6e1d Binary files /dev/null and b/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/results/test_rewards_curve.png differ diff --git a/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/results/train_ma_rewards.npy b/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/results/train_ma_rewards.npy new file mode 100644 index 0000000..3e1feac Binary files /dev/null and b/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/results/train_ma_rewards.npy differ diff --git a/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/results/train_rewards.npy b/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/results/train_rewards.npy new file mode 100644 index 0000000..1c77a83 Binary files /dev/null and b/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/results/train_rewards.npy differ diff --git a/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/results/train_rewards_curve.png b/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/results/train_rewards_curve.png new file mode 100644 index 0000000..3e4c8aa Binary files /dev/null and b/codes/SoftActorCritic/outputs/Pendulum-v1/20211222-162722/results/train_rewards_curve.png differ diff --git a/codes/SAC/agent.py b/codes/SoftActorCritic/sac.py similarity index 51% rename from codes/SAC/agent.py rename to codes/SoftActorCritic/sac.py index 1568eb3..d565db5 100644 --- a/codes/SAC/agent.py +++ b/codes/SoftActorCritic/sac.py @@ -5,7 +5,7 @@ Author: JiangJi Email: johnjim0816@gmail.com Date: 2021-04-29 12:53:54 LastEditor: JiangJi -LastEditTime: 2021-04-29 13:56:39 +LastEditTime: 2021-12-22 15:41:19 Discription: Environment: ''' @@ -13,10 +13,126 @@ import copy import torch import torch.nn as nn import torch.optim as optim +import torch.nn.functional as F +from torch.distributions import Normal import numpy as np -from common.memory import ReplayBuffer -from SAC.model import ValueNet,PolicyNet,SoftQNet +import random +device=torch.device("cuda" if torch.cuda.is_available() else "cpu") +class ReplayBuffer: + def __init__(self, capacity): + self.capacity = capacity # 经验回放的容量 + self.buffer = [] # 缓冲区 + self.position = 0 + + def push(self, state, action, reward, next_state, done): + ''' 缓冲区是一个队列,容量超出时去掉开始存入的转移(transition) + ''' + 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 = random.sample(self.buffer, batch_size) # 随机采出小批量转移 + state, action, reward, next_state, done = zip(*batch) # 解压成状态,动作等 + return state, action, reward, next_state, done + + def __len__(self): + ''' 返回当前存储的量 + ''' + return len(self.buffer) +class ValueNet(nn.Module): + def __init__(self, state_dim, hidden_dim, init_w=3e-3): + super(ValueNet, self).__init__() + + self.linear1 = nn.Linear(state_dim, hidden_dim) + self.linear2 = nn.Linear(hidden_dim, hidden_dim) + self.linear3 = nn.Linear(hidden_dim, 1) + + self.linear3.weight.data.uniform_(-init_w, init_w) + self.linear3.bias.data.uniform_(-init_w, init_w) + + def forward(self, state): + x = F.relu(self.linear1(state)) + x = F.relu(self.linear2(x)) + x = self.linear3(x) + return x + + +class SoftQNet(nn.Module): + def __init__(self, state_dim, action_dim, hidden_dim, init_w=3e-3): + super(SoftQNet, self).__init__() + + self.linear1 = nn.Linear(state_dim + action_dim, hidden_dim) + self.linear2 = nn.Linear(hidden_dim, hidden_dim) + self.linear3 = nn.Linear(hidden_dim, 1) + + self.linear3.weight.data.uniform_(-init_w, init_w) + self.linear3.bias.data.uniform_(-init_w, init_w) + + def forward(self, state, action): + x = torch.cat([state, action], 1) + x = F.relu(self.linear1(x)) + x = F.relu(self.linear2(x)) + x = self.linear3(x) + return x + + +class PolicyNet(nn.Module): + def __init__(self, state_dim, action_dim, hidden_dim, init_w=3e-3, log_std_min=-20, log_std_max=2): + super(PolicyNet, self).__init__() + + self.log_std_min = log_std_min + self.log_std_max = log_std_max + + self.linear1 = nn.Linear(state_dim, hidden_dim) + self.linear2 = nn.Linear(hidden_dim, hidden_dim) + + self.mean_linear = nn.Linear(hidden_dim, action_dim) + self.mean_linear.weight.data.uniform_(-init_w, init_w) + self.mean_linear.bias.data.uniform_(-init_w, init_w) + + self.log_std_linear = nn.Linear(hidden_dim, action_dim) + self.log_std_linear.weight.data.uniform_(-init_w, init_w) + self.log_std_linear.bias.data.uniform_(-init_w, init_w) + + def forward(self, state): + x = F.relu(self.linear1(state)) + x = F.relu(self.linear2(x)) + + mean = self.mean_linear(x) + log_std = self.log_std_linear(x) + log_std = torch.clamp(log_std, self.log_std_min, self.log_std_max) + + return mean, log_std + + def evaluate(self, state, epsilon=1e-6): + mean, log_std = self.forward(state) + std = log_std.exp() + + normal = Normal(mean, std) + z = normal.sample() + action = torch.tanh(z) + + log_prob = normal.log_prob(z) - torch.log(1 - action.pow(2) + epsilon) + log_prob = log_prob.sum(-1, keepdim=True) + + return action, log_prob, z, mean, log_std + + + def get_action(self, state): + state = torch.FloatTensor(state).unsqueeze(0).to(device) + mean, log_std = self.forward(state) + std = log_std.exp() + + normal = Normal(mean, std) + z = normal.sample() + action = torch.tanh(z) + + action = action.detach().cpu().numpy() + return action[0] + class SAC: def __init__(self,state_dim,action_dim,cfg) -> None: self.batch_size = cfg.batch_size @@ -81,7 +197,6 @@ class SAC: policy_loss.backward() self.policy_optimizer.step() - for target_param, param in zip(self.target_value_net.parameters(), self.value_net.parameters()): target_param.data.copy_( target_param.data * (1.0 - soft_tau) + param.data * soft_tau @@ -89,15 +204,12 @@ class SAC: def save(self, path): torch.save(self.value_net.state_dict(), path + "sac_value") torch.save(self.value_optimizer.state_dict(), path + "sac_value_optimizer") - torch.save(self.soft_q_net.state_dict(), path + "sac_soft_q") torch.save(self.soft_q_optimizer.state_dict(), path + "sac_soft_q_optimizer") torch.save(self.policy_net.state_dict(), path + "sac_policy") torch.save(self.policy_optimizer.state_dict(), path + "sac_policy_optimizer") - - def load(self, path): self.value_net.load_state_dict(torch.load(path + "sac_value")) self.value_optimizer.load_state_dict(torch.load(path + "sac_value_optimizer")) diff --git a/codes/SAC/task0_train.py b/codes/SoftActorCritic/task0.py similarity index 51% rename from codes/SAC/task0_train.py rename to codes/SoftActorCritic/task0.py index 625f1d7..e910749 100644 --- a/codes/SAC/task0_train.py +++ b/codes/SoftActorCritic/task0.py @@ -5,7 +5,7 @@ Author: JiangJi Email: johnjim0816@gmail.com Date: 2021-04-29 12:59:22 LastEditor: JiangJi -LastEditTime: 2021-05-06 16:58:01 +LastEditTime: 2021-12-22 16:27:13 Discription: Environment: ''' @@ -18,23 +18,24 @@ import gym import torch import datetime -from SAC.env import NormalizedActions -from SAC.agent import SAC +from SoftActorCritic.env_wrapper import NormalizedActions +from SoftActorCritic.sac import SAC from common.utils import save_results, make_dir -from common.plot import plot_rewards +from common.utils import plot_rewards curr_time = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") # 获取当前时间 +algo_name = 'SAC' # 算法名称 +env_name = 'Pendulum-v1' # 环境名称 +device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 检测GPU class SACConfig: def __init__(self) -> None: - self.algo = 'SAC' - self.env_name = 'Pendulum-v1' - self.result_path = curr_path+"/outputs/" +self.env_name+'/'+curr_time+'/results/' # path to save results - self.model_path = curr_path+"/outputs/" +self.env_name+'/'+curr_time+'/models/' # path to save models + self.algo_name = algo_name + self.env_name = env_name # 环境名称 + self.device= device self.train_eps = 300 - self.train_steps = 500 - self.eval_eps = 50 - self.eval_steps = 500 + self.test_eps = 20 + self.max_steps = 500 # 每回合的最大步数 self.gamma = 0.99 self.mean_lambda=1e-3 self.std_lambda=1e-3 @@ -46,15 +47,18 @@ class SACConfig: self.capacity = 1000000 self.hidden_dim = 256 self.batch_size = 128 - self.device=torch.device("cuda" if torch.cuda.is_available() else "cpu") -class PlotConfig(SACConfig): - def __init__(self) -> None: - super().__init__() - self.result_path = curr_path+"/outputs/" + self.env_name + \ - '/'+curr_time+'/results/' # 保存结果的路径 - self.model_path = curr_path+"/outputs/" + self.env_name + \ - '/'+curr_time+'/models/' # 保存模型的路径 - self.save = True # 是否保存图片 + + +class PlotConfig: + def __init__(self) -> None: + self.algo_name = algo_name # 算法名称 + self.env_name = env_name # 环境名称 + self.device= device + self.result_path = curr_path + "/outputs/" + self.env_name + \ + '/' + curr_time + '/results/' # 保存结果的路径 + self.model_path = curr_path + "/outputs/" + self.env_name + \ + '/' + curr_time + '/models/' # 保存模型的路径 + self.save = True # 是否保存图片 def env_agent_config(cfg,seed=1): env = NormalizedActions(gym.make(cfg.env_name)) @@ -66,13 +70,13 @@ def env_agent_config(cfg,seed=1): def train(cfg,env,agent): print('开始训练!') - print(f'环境:{cfg.env_name}, 算法:{cfg.algo}, 设备:{cfg.device}') + print(f'环境:{cfg.env_name}, 算法:{cfg.algo_name}, 设备:{cfg.device}') rewards = [] # 记录所有回合的奖励 ma_rewards = [] # 记录所有回合的滑动平均奖励 for i_ep in range(cfg.train_eps): ep_reward = 0 # 记录一回合内的奖励 state = env.reset() # 重置环境,返回初始状态 - for i_step in range(cfg.train_steps): + for i_step in range(cfg.max_steps): action = agent.policy_net.get_action(state) next_state, reward, done, _ = env.step(action) agent.memory.push(state, action, reward, next_state, done) @@ -81,57 +85,57 @@ def train(cfg,env,agent): ep_reward += reward if done: break - if (i_ep+1)%10==0: - print(f"Episode:{i_ep+1}/{cfg.train_eps}, Reward:{ep_reward:.3f}") rewards.append(ep_reward) if ma_rewards: ma_rewards.append(0.9*ma_rewards[-1]+0.1*ep_reward) else: ma_rewards.append(ep_reward) - print('Complete training!') + if (i_ep+1)%10 == 0: + print(f'回合:{i_ep+1}/{cfg.train_eps}, 奖励:{ep_reward:.3f}') + print('完成训练!') return rewards, ma_rewards -def eval(cfg,env,agent): - print('Start to eval !') - print(f'Env: {cfg.env_name}, Algorithm: {cfg.algo}, Device: {cfg.device}') - rewards = [] - ma_rewards = [] # moveing average reward - for i_ep in range(cfg.eval_eps): +def test(cfg,env,agent): + print('开始测试!') + print(f'环境:{cfg.env_name}, 算法:{cfg.algo_name}, 设备:{cfg.device}') + rewards = [] # 记录所有回合的奖励 + ma_rewards = [] # 记录所有回合的滑动平均奖励 + for i_ep in range(cfg.test_eps): state = env.reset() ep_reward = 0 - for i_step in range(cfg.eval_steps): + for i_step in range(cfg.max_steps): action = agent.policy_net.get_action(state) next_state, reward, done, _ = env.step(action) state = next_state ep_reward += reward if done: break - if (i_ep+1)%10==0: - print(f"Episode:{i_ep+1}/{cfg.train_eps}, Reward:{ep_reward:.3f}") rewards.append(ep_reward) if ma_rewards: ma_rewards.append(0.9*ma_rewards[-1]+0.1*ep_reward) else: ma_rewards.append(ep_reward) - print('Complete evaling!') + print(f"回合:{i_ep+1}/{cfg.test_eps},奖励:{ep_reward:.1f}") + print('完成测试!') return rewards, ma_rewards if __name__ == "__main__": cfg=SACConfig() plot_cfg = PlotConfig() - # train - env,agent = env_agent_config(cfg,seed=1) + # 训练 + env, agent = env_agent_config(cfg, seed=1) rewards, ma_rewards = train(cfg, env, agent) - make_dir(plot_cfg.result_path, plot_cfg.model_path) - agent.save(path=plot_cfg.model_path) - save_results(rewards, ma_rewards, tag='train', path=plot_cfg.result_path) - plot_rewards(rewards, ma_rewards, plot_cfg, tag="train") - # eval - env,agent = env_agent_config(cfg,seed=10) - agent.load(path=plot_cfg.model_path) - rewards,ma_rewards = eval(cfg,env,agent) - save_results(rewards,ma_rewards,tag='eval',path=plot_cfg.result_path) - plot_rewards(rewards,ma_rewards,plot_cfg,tag="eval") + make_dir(plot_cfg.result_path, plot_cfg.model_path) # 创建保存结果和模型路径的文件夹 + agent.save(path=plot_cfg.model_path) # 保存模型 + save_results(rewards, ma_rewards, tag='train', + path=plot_cfg.result_path) # 保存结果 + plot_rewards(rewards, ma_rewards, plot_cfg, tag="train") # 画出结果 + # 测试 + env, agent = env_agent_config(cfg, seed=10) + agent.load(path=plot_cfg.model_path) # 导入模型 + rewards, ma_rewards = test(cfg, env, agent) + save_results(rewards, ma_rewards, tag='test', path=plot_cfg.result_path) # 保存结果 + plot_rewards(rewards, ma_rewards, plot_cfg, tag="test") # 画出结果 diff --git a/codes/SAC/task0_train.ipynb b/codes/SoftActorCritic/task0_train.ipynb similarity index 99% rename from codes/SAC/task0_train.ipynb rename to codes/SoftActorCritic/task0_train.ipynb index 8148a4b..14be84e 100644 --- a/codes/SAC/task0_train.ipynb +++ b/codes/SoftActorCritic/task0_train.ipynb @@ -45,7 +45,7 @@ " self.model_path = curr_path+\"/outputs/\" +self.env+'/'+curr_time+'/models/' # path to save models\n", " self.train_eps = 300\n", " self.train_steps = 500\n", - " self.eval_eps = 50\n", + " self.test_eps = 50\n", " self.eval_steps = 500\n", " self.gamma = 0.99\n", " self.mean_lambda=1e-3\n", @@ -121,7 +121,7 @@ " print(f'Env: {cfg.env}, Algorithm: {cfg.algo}, Device: {cfg.device}')\n", " rewards = []\n", " ma_rewards = [] # moveing average reward\n", - " for i_ep in range(cfg.eval_eps):\n", + " for i_ep in range(cfg.test_eps):\n", " state = env.reset()\n", " ep_reward = 0\n", " for i_step in range(cfg.eval_steps):\n", diff --git a/codes/TD3/README.md b/codes/TD3/README.md new file mode 100644 index 0000000..8001e9c --- /dev/null +++ b/codes/TD3/README.md @@ -0,0 +1 @@ +这是对[Implementation of Twin Delayed Deep Deterministic Policy Gradients (TD3)](https://arxiv.org/abs/1802.09477)的复现 \ No newline at end of file diff --git a/codes/TD3/agent.py b/codes/TD3/agent.py index 3d43700..91939a6 100644 --- a/codes/TD3/agent.py +++ b/codes/TD3/agent.py @@ -1,3 +1,13 @@ +#!/usr/bin/env python +# coding=utf-8 +''' +Author: JiangJi +Email: johnjim0816@gmail.com +Date: 2021-12-22 10:40:05 +LastEditor: JiangJi +LastEditTime: 2021-12-22 10:43:55 +Discription: +''' import copy import numpy as np import torch @@ -5,40 +15,41 @@ import torch.nn as nn import torch.nn.functional as F from TD3.memory import ReplayBuffer - - -# Implementation of Twin Delayed Deep Deterministic Policy Gradients (TD3) -# Paper: https://arxiv.org/abs/1802.09477 - - class Actor(nn.Module): - def __init__(self, state_dim, action_dim, max_action): + + def __init__(self, input_dim, output_dim, max_action): + '''[summary] + + Args: + input_dim (int): 输入维度,这里等于state_dim + output_dim (int): 输出维度,这里等于action_dim + max_action (int): action的最大值 + ''' super(Actor, self).__init__() - self.l1 = nn.Linear(state_dim, 256) + self.l1 = nn.Linear(input_dim, 256) self.l2 = nn.Linear(256, 256) - self.l3 = nn.Linear(256, action_dim) - + self.l3 = nn.Linear(256, output_dim) self.max_action = max_action - - + def forward(self, state): + a = F.relu(self.l1(state)) a = F.relu(self.l2(a)) return self.max_action * torch.tanh(self.l3(a)) class Critic(nn.Module): - def __init__(self, state_dim, action_dim): + def __init__(self, input_dim, output_dim): super(Critic, self).__init__() # Q1 architecture - self.l1 = nn.Linear(state_dim + action_dim, 256) + self.l1 = nn.Linear(input_dim + output_dim, 256) self.l2 = nn.Linear(256, 256) self.l3 = nn.Linear(256, 1) # Q2 architecture - self.l4 = nn.Linear(state_dim + action_dim, 256) + self.l4 = nn.Linear(input_dim + output_dim, 256) self.l5 = nn.Linear(256, 256) self.l6 = nn.Linear(256, 1) @@ -68,8 +79,8 @@ class Critic(nn.Module): class TD3(object): def __init__( self, - state_dim, - action_dim, + input_dim, + output_dim, max_action, cfg, ): @@ -83,14 +94,14 @@ class TD3(object): self.device = cfg.device self.total_it = 0 - self.actor = Actor(state_dim, action_dim, max_action).to(self.device) + self.actor = Actor(input_dim, output_dim, max_action).to(self.device) self.actor_target = copy.deepcopy(self.actor) self.actor_optimizer = torch.optim.Adam(self.actor.parameters(), lr=3e-4) - self.critic = Critic(state_dim, action_dim).to(self.device) + self.critic = Critic(input_dim, output_dim).to(self.device) self.critic_target = copy.deepcopy(self.critic) self.critic_optimizer = torch.optim.Adam(self.critic.parameters(), lr=3e-4) - self.memory = ReplayBuffer(state_dim, action_dim) + self.memory = ReplayBuffer(input_dim, output_dim) def choose_action(self, state): state = torch.FloatTensor(state.reshape(1, -1)).to(self.device) diff --git a/codes/common/atari_wrappers.py b/codes/common/atari_wrappers.py new file mode 100644 index 0000000..48dab94 --- /dev/null +++ b/codes/common/atari_wrappers.py @@ -0,0 +1,284 @@ +import numpy as np +import os +os.environ.setdefault('PATH', '') +from collections import deque +import gym +from gym import spaces +import cv2 +cv2.ocl.setUseOpenCL(False) +from .wrappers import TimeLimit + + +class NoopResetEnv(gym.Wrapper): + def __init__(self, env, noop_max=30): + """Sample initial states by taking random number of no-ops on reset. + No-op is assumed to be action 0. + """ + gym.Wrapper.__init__(self, env) + self.noop_max = noop_max + self.override_num_noops = None + self.noop_action = 0 + assert env.unwrapped.get_action_meanings()[0] == 'NOOP' + + def reset(self, **kwargs): + """ Do no-op action for a number of steps in [1, noop_max].""" + self.env.reset(**kwargs) + if self.override_num_noops is not None: + noops = self.override_num_noops + else: + noops = self.unwrapped.np_random.randint(1, self.noop_max + 1) #pylint: disable=E1101 + assert noops > 0 + obs = None + for _ in range(noops): + obs, _, done, _ = self.env.step(self.noop_action) + if done: + obs = self.env.reset(**kwargs) + return obs + + def step(self, ac): + return self.env.step(ac) + +class FireResetEnv(gym.Wrapper): + def __init__(self, env): + """Take action on reset for environments that are fixed until firing.""" + gym.Wrapper.__init__(self, env) + assert env.unwrapped.get_action_meanings()[1] == 'FIRE' + assert len(env.unwrapped.get_action_meanings()) >= 3 + + def reset(self, **kwargs): + self.env.reset(**kwargs) + obs, _, done, _ = self.env.step(1) + if done: + self.env.reset(**kwargs) + obs, _, done, _ = self.env.step(2) + if done: + self.env.reset(**kwargs) + return obs + + def step(self, ac): + return self.env.step(ac) + +class EpisodicLifeEnv(gym.Wrapper): + def __init__(self, env): + """Make end-of-life == end-of-episode, but only reset on true game over. + Done by DeepMind for the DQN and co. since it helps value estimation. + """ + gym.Wrapper.__init__(self, env) + self.lives = 0 + self.was_real_done = True + + def step(self, action): + obs, reward, done, info = self.env.step(action) + self.was_real_done = done + # check current lives, make loss of life terminal, + # then update lives to handle bonus lives + lives = self.env.unwrapped.ale.lives() + if lives < self.lives and lives > 0: + # for Qbert sometimes we stay in lives == 0 condition for a few frames + # so it's important to keep lives > 0, so that we only reset once + # the environment advertises done. + done = True + self.lives = lives + return obs, reward, done, info + + def reset(self, **kwargs): + """Reset only when lives are exhausted. + This way all states are still reachable even though lives are episodic, + and the learner need not know about any of this behind-the-scenes. + """ + if self.was_real_done: + obs = self.env.reset(**kwargs) + else: + # no-op step to advance from terminal/lost life state + obs, _, _, _ = self.env.step(0) + self.lives = self.env.unwrapped.ale.lives() + return obs + +class MaxAndSkipEnv(gym.Wrapper): + def __init__(self, env, skip=4): + """Return only every `skip`-th frame""" + gym.Wrapper.__init__(self, env) + # most recent raw observations (for max pooling across time steps) + self._obs_buffer = np.zeros((2,)+env.observation_space.shape, dtype=np.uint8) + self._skip = skip + + def step(self, action): + """Repeat action, sum reward, and max over last observations.""" + total_reward = 0.0 + done = None + for i in range(self._skip): + obs, reward, done, info = self.env.step(action) + if i == self._skip - 2: self._obs_buffer[0] = obs + if i == self._skip - 1: self._obs_buffer[1] = obs + total_reward += reward + if done: + break + # Note that the observation on the done=True frame + # doesn't matter + max_frame = self._obs_buffer.max(axis=0) + + return max_frame, total_reward, done, info + + def reset(self, **kwargs): + return self.env.reset(**kwargs) + +class ClipRewardEnv(gym.RewardWrapper): + def __init__(self, env): + gym.RewardWrapper.__init__(self, env) + + def reward(self, reward): + """Bin reward to {+1, 0, -1} by its sign.""" + return np.sign(reward) + + +class WarpFrame(gym.ObservationWrapper): + def __init__(self, env, width=84, height=84, grayscale=True, dict_space_key=None): + """ + Warp frames to 84x84 as done in the Nature paper and later work. + If the environment uses dictionary observations, `dict_space_key` can be specified which indicates which + observation should be warped. + """ + super().__init__(env) + self._width = width + self._height = height + self._grayscale = grayscale + self._key = dict_space_key + if self._grayscale: + num_colors = 1 + else: + num_colors = 3 + + new_space = gym.spaces.Box( + low=0, + high=255, + shape=(self._height, self._width, num_colors), + dtype=np.uint8, + ) + if self._key is None: + original_space = self.observation_space + self.observation_space = new_space + else: + original_space = self.observation_space.spaces[self._key] + self.observation_space.spaces[self._key] = new_space + assert original_space.dtype == np.uint8 and len(original_space.shape) == 3 + + def observation(self, obs): + if self._key is None: + frame = obs + else: + frame = obs[self._key] + + if self._grayscale: + frame = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) + frame = cv2.resize( + frame, (self._width, self._height), interpolation=cv2.INTER_AREA + ) + if self._grayscale: + frame = np.expand_dims(frame, -1) + + if self._key is None: + obs = frame + else: + obs = obs.copy() + obs[self._key] = frame + return obs + + +class FrameStack(gym.Wrapper): + def __init__(self, env, k): + """Stack k last frames. + Returns lazy array, which is much more memory efficient. + See Also + -------- + baselines.common.atari_wrappers.LazyFrames + """ + gym.Wrapper.__init__(self, env) + self.k = k + self.frames = deque([], maxlen=k) + shp = env.observation_space.shape + self.observation_space = spaces.Box(low=0, high=255, shape=(shp[:-1] + (shp[-1] * k,)), dtype=env.observation_space.dtype) + + def reset(self): + ob = self.env.reset() + for _ in range(self.k): + self.frames.append(ob) + return self._get_ob() + + def step(self, action): + ob, reward, done, info = self.env.step(action) + self.frames.append(ob) + return self._get_ob(), reward, done, info + + def _get_ob(self): + assert len(self.frames) == self.k + return LazyFrames(list(self.frames)) + +class ScaledFloatFrame(gym.ObservationWrapper): + def __init__(self, env): + gym.ObservationWrapper.__init__(self, env) + self.observation_space = gym.spaces.Box(low=0, high=1, shape=env.observation_space.shape, dtype=np.float32) + + def observation(self, observation): + # careful! This undoes the memory optimization, use + # with smaller replay buffers only. + return np.array(observation).astype(np.float32) / 255.0 + +class LazyFrames(object): + def __init__(self, frames): + """This object ensures that common frames between the observations are only stored once. + It exists purely to optimize memory usage which can be huge for DQN's 1M frames replay + buffers. + This object should only be converted to numpy array before being passed to the model. + You'd not believe how complex the previous solution was.""" + self._frames = frames + self._out = None + + def _force(self): + if self._out is None: + self._out = np.concatenate(self._frames, axis=-1) + self._frames = None + return self._out + + def __array__(self, dtype=None): + out = self._force() + if dtype is not None: + out = out.astype(dtype) + return out + + def __len__(self): + return len(self._force()) + + def __getitem__(self, i): + return self._force()[i] + + def count(self): + frames = self._force() + return frames.shape[frames.ndim - 1] + + def frame(self, i): + return self._force()[..., i] + +def make_atari(env_id, max_episode_steps=None): + env = gym.make(env_id) + assert 'NoFrameskip' in env.spec.id + env = NoopResetEnv(env, noop_max=30) + env = MaxAndSkipEnv(env, skip=4) + if max_episode_steps is not None: + env = TimeLimit(env, max_episode_steps=max_episode_steps) + return env + +def wrap_deepmind(env, episode_life=True, clip_rewards=True, frame_stack=False, scale=False): + """Configure environment for DeepMind-style Atari. + """ + if episode_life: + env = EpisodicLifeEnv(env) + if 'FIRE' in env.unwrapped.get_action_meanings(): + env = FireResetEnv(env) + env = WarpFrame(env) + if scale: + env = ScaledFloatFrame(env) + if clip_rewards: + env = ClipRewardEnv(env) + if frame_stack: + env = FrameStack(env, 4) + return env \ No newline at end of file diff --git a/codes/common/model.py b/codes/common/model.py index be03368..4ab0b8b 100644 --- a/codes/common/model.py +++ b/codes/common/model.py @@ -15,15 +15,15 @@ import torch.nn.functional as F from torch.distributions import Categorical class MLP(nn.Module): - def __init__(self, n_states,n_actions,hidden_dim=128): + def __init__(self, input_dim,output_dim,hidden_dim=128): """ 初始化q网络,为全连接网络 - n_states: 输入的特征数即环境的状态数 - n_actions: 输出的动作维度 + input_dim: 输入的特征数即环境的状态维度 + output_dim: 输出的动作维度 """ super(MLP, self).__init__() - self.fc1 = nn.Linear(n_states, hidden_dim) # 输入层 + self.fc1 = nn.Linear(input_dim, hidden_dim) # 输入层 self.fc2 = nn.Linear(hidden_dim,hidden_dim) # 隐藏层 - self.fc3 = nn.Linear(hidden_dim, n_actions) # 输出层 + 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, n_actions, hidden_size, init_w=3e-3): + def __init__(self, n_obs, action_dim, hidden_size, init_w=3e-3): super(Critic, self).__init__() - self.linear1 = nn.Linear(n_obs + n_actions, hidden_size) + self.linear1 = nn.Linear(n_obs + action_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, n_actions, hidden_size, init_w=3e-3): + def __init__(self, n_obs, action_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, n_actions) + self.linear3 = nn.Linear(hidden_size, action_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, n_states, n_actions, hidden_dim=256): + def __init__(self, state_dim, action_dim, hidden_dim=256): super(ActorCritic, self).__init__() self.critic = nn.Sequential( - nn.Linear(n_states, hidden_dim), + nn.Linear(state_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, 1) ) self.actor = nn.Sequential( - nn.Linear(n_states, hidden_dim), + nn.Linear(state_dim, hidden_dim), nn.ReLU(), - nn.Linear(hidden_dim, n_actions), + nn.Linear(hidden_dim, action_dim), nn.Softmax(dim=1), ) diff --git a/codes/common/multiprocessing_env.py b/codes/common/multiprocessing_env.py index 04b4e3c..28c8aba 100644 --- a/codes/common/multiprocessing_env.py +++ b/codes/common/multiprocessing_env.py @@ -1,5 +1,5 @@ -#This code is from openai baseline -#https://github.com/openai/baselines/tree/master/baselines/common/vec_env +# 该代码来自 openai baseline,用于多线程环境 +# https://github.com/openai/baselines/tree/master/baselines/common/vec_env import numpy as np from multiprocessing import Process, Pipe diff --git a/codes/common/plot.py b/codes/common/plot.py deleted file mode 100644 index bc9c1dd..0000000 --- a/codes/common/plot.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python -# coding=utf-8 -''' -Author: John -Email: johnjim0816@gmail.com -Date: 2020-10-07 20:57:11 -LastEditor: John -LastEditTime: 2021-09-23 12:23:01 -Discription: -Environment: -''' -import matplotlib.pyplot as plt -import seaborn as sns -from matplotlib.font_manager import FontProperties # 导入字体模块 - -def plot_rewards(rewards,ma_rewards,plot_cfg,tag='train'): - sns.set() - plt.figure() # 创建一个图形实例,方便同时多画几个图 - plt.title("learning curve on {} of {} for {}".format(plot_cfg.device, plot_cfg.algo, plot_cfg.env_name)) - plt.xlabel('epsiodes') - plt.plot(rewards,label='rewards') - plt.plot(ma_rewards,label='ma rewards') - plt.legend() - if plot_cfg.save: - plt.savefig(plot_cfg.result_path+"{}_rewards_curve".format(tag)) - plt.show() - -def plot_losses(losses,algo = "DQN",save=True,path='./'): - sns.set() - plt.figure() - plt.title("loss curve of {}".format(algo)) - plt.xlabel('epsiodes') - plt.plot(losses,label='rewards') - plt.legend() - if save: - plt.savefig(path+"losses_curve") - plt.show() - diff --git a/codes/common/utils.py b/codes/common/utils.py index a3ca7be..6027804 100644 --- a/codes/common/utils.py +++ b/codes/common/utils.py @@ -5,29 +5,90 @@ Author: John Email: johnjim0816@gmail.com Date: 2021-03-12 16:02:24 LastEditor: John -LastEditTime: 2021-09-11 21:48:49 +LastEditTime: 2021-11-30 18:39:19 Discription: Environment: ''' import os import numpy as np from pathlib import Path +import matplotlib.pyplot as plt +import seaborn as sns -def save_results(rewards,ma_rewards,tag='train',path='./results'): - '''save rewards and ma_rewards +from matplotlib.font_manager import FontProperties # 导入字体模块 + +def chinese_font(): + ''' 设置中文字体,注意需要根据自己电脑情况更改字体路径,否则还是默认的字体 + ''' + try: + font = FontProperties( + fname='/System/Library/Fonts/STHeiti Light.ttc', size=15) # fname系统字体路径,此处是mac的 + except: + font = None + return font + +def plot_rewards_cn(rewards, ma_rewards, plot_cfg, tag='train'): + ''' 中文画图 + ''' + sns.set() + plt.figure() + plt.title(u"{}环境下{}算法的学习曲线".format(plot_cfg.env_name, + plot_cfg.algo_name), fontproperties=chinese_font()) + plt.xlabel(u'回合数', fontproperties=chinese_font()) + plt.plot(rewards) + plt.plot(ma_rewards) + plt.legend((u'奖励', u'滑动平均奖励',), loc="best", prop=chinese_font()) + if plot_cfg.save: + plt.savefig(plot_cfg.result_path+f"{tag}_rewards_curve_cn") + # plt.show() + + +def plot_rewards(rewards, ma_rewards, plot_cfg, tag='train'): + sns.set() + plt.figure() # 创建一个图形实例,方便同时多画几个图 + plt.title("learning curve on {} of {} for {}".format( + plot_cfg.device, plot_cfg.algo_name, plot_cfg.env_name)) + plt.xlabel('epsiodes') + plt.plot(rewards, label='rewards') + plt.plot(ma_rewards, label='ma rewards') + plt.legend() + if plot_cfg.save: + plt.savefig(plot_cfg.result_path+"{}_rewards_curve".format(tag)) + plt.show() + + +def plot_losses(losses, algo="DQN", save=True, path='./'): + sns.set() + plt.figure() + plt.title("loss curve of {}".format(algo)) + plt.xlabel('epsiodes') + plt.plot(losses, label='rewards') + plt.legend() + if save: + plt.savefig(path+"losses_curve") + plt.show() + + +def save_results(rewards, ma_rewards, tag='train', path='./results'): + ''' 保存奖励 ''' np.save(path+'{}_rewards.npy'.format(tag), rewards) np.save(path+'{}_ma_rewards.npy'.format(tag), ma_rewards) print('结果保存完毕!') + def make_dir(*paths): + ''' 创建文件夹 + ''' for path in paths: Path(path).mkdir(parents=True, exist_ok=True) + + def del_empty_dir(*paths): - '''del_empty_dir delete empty folders unders "paths" + ''' 删除目录下所有空文件夹 ''' for path in paths: dirs = os.listdir(path) for dir in dirs: if not os.listdir(os.path.join(path, dir)): - os.removedirs(os.path.join(path, dir)) \ No newline at end of file + os.removedirs(os.path.join(path, dir)) diff --git a/codes/common/wrappers.py b/codes/common/wrappers.py new file mode 100644 index 0000000..4793b36 --- /dev/null +++ b/codes/common/wrappers.py @@ -0,0 +1,29 @@ +import gym + +class TimeLimit(gym.Wrapper): + def __init__(self, env, max_episode_steps=None): + super(TimeLimit, self).__init__(env) + self._max_episode_steps = max_episode_steps + self._elapsed_steps = 0 + + def step(self, ac): + observation, reward, done, info = self.env.step(ac) + self._elapsed_steps += 1 + if self._elapsed_steps >= self._max_episode_steps: + done = True + info['TimeLimit.truncated'] = True + return observation, reward, done, info + + def reset(self, **kwargs): + self._elapsed_steps = 0 + return self.env.reset(**kwargs) + +class ClipActionsWrapper(gym.Wrapper): + def step(self, action): + import numpy as np + action = np.nan_to_num(action) + action = np.clip(action, self.action_space.low, self.action_space.high) + return self.env.step(action) + + def reset(self, **kwargs): + return self.env.reset(**kwargs) \ No newline at end of file diff --git a/codes/envs/assets/gym_info_20211130180023.png b/codes/envs/assets/gym_info_20211130180023.png new file mode 100644 index 0000000..723b67f Binary files /dev/null and b/codes/envs/assets/gym_info_20211130180023.png differ diff --git a/codes/envs/gym_info.md b/codes/envs/gym_info.md index dd4268a..49da18f 100644 --- a/codes/envs/gym_info.md +++ b/codes/envs/gym_info.md @@ -1,4 +1,5 @@ -## 环境说明 +# OpenAi Gym 环境说明 +## 基础控制 ### [CartPole v0](https://github.com/openai/gym/wiki/CartPole-v0) @@ -6,6 +7,17 @@ 通过向左或向右推车能够实现平衡,所以动作空间由两个动作组成。每进行一个step就会给一个reward,如果无法保持平衡那么done等于true,本次episode失败。理想状态下,每个episode至少能进行200个step,也就是说每个episode的reward总和至少为200,step数目至少为200 +### CartPole-v1 + +```CartPole v1```环境其实跟```CartPole v0```是一模一样的,区别在于每回合最大步数(max_episode_steps)以及奖励阈值(reward_threshold),如下是相关源码: + +![](assets/gym_info_20211130180023.png) + +这里先解释一下奖励阈值(reward_threshold),即Gym设置的一个合格标准,比如对于```CartPole v0```如果算法能够将奖励收敛到195以上,说明该算法合格。但实际上```CartPole v0```的每回合最大步数(max_episode_steps)是200,每步的奖励最大是1,也就是每回合最大奖励是200,比Gym设置的奖励阈值高。笔者猜测这是Gym可能是给算法学习者们设置的一个参考线,而实际中在写算法时并不会用到这个算法阈值,所以可以忽略。 + +再看每回合最大步数,可以看到```CartPole v1```的步数更长,相应的奖励要求更高,可以理解为```v1```是```v0```的难度升级版。 + + ### [Pendulum-v0](https://github.com/openai/gym/wiki/Pendulum-v0) 注:gym 0.18.0之后版本中Pendulum-v0已经改为Pendulum-v1 @@ -31,4 +43,8 @@ image-20201007211858925 -由于从起点到终点最少需要13步,每步得到-1的reward,因此最佳训练算法下,每个episode下reward总和应该为-13。 \ No newline at end of file +由于从起点到终点最少需要13步,每步得到-1的reward,因此最佳训练算法下,每个episode下reward总和应该为-13。 + +## 参考 + +[Gym环境相关源码](https://github.com/openai/gym/tree/master/gym/envs) \ No newline at end of file diff --git a/docs/chapter4/chapter4_questions&keywords.md b/docs/chapter4/chapter4_questions&keywords.md index eb04dd4..e1d5786 100644 --- a/docs/chapter4/chapter4_questions&keywords.md +++ b/docs/chapter4/chapter4_questions&keywords.md @@ -7,7 +7,7 @@ - **Trajectory:** 一个试验中我们将environment 输出的 $s$ 跟 actor 输出的行为 $a$,把这个 $s$ 跟 $a$ 全部串起来形成的集合,我们称为Trajectory,即 $\text { Trajectory } \tau=\left\{s_{1}, a_{1}, s_{2}, a_{2}, \cdots, s_{t}, a_{t}\right\}$。 - **Reward function:** 根据在某一个 state 采取的某一个 action 决定说现在这个行为可以得到多少的分数,它是一个 function。也就是给一个 $s_1$,$a_1$,它告诉你得到 $r_1$。给它 $s_2$ ,$a_2$,它告诉你得到 $r_2$。 把所有的 $r$ 都加起来,我们就得到了 $R(\tau)$ ,代表某一个 trajectory $\tau$ 的 reward。 - **Expected reward:** $\bar{R}_{\theta}=\sum_{\tau} R(\tau) p_{\theta}(\tau)=E_{\tau \sim p_{\theta}(\tau)}[R(\tau)]$。 -- **Reinforce:** 基于策略梯度的强化学习的经典算法,其采用回合更新的模式。 +- **REINFORCE:** 基于策略梯度的强化学习的经典算法,其采用回合更新的模式。 ## 2 Questions