第六章的代码:DQN.ipynb及其涉及的其他代码的更新以及注解(gym版本 >= 0.26)
摘要
本系列知识点讲解基于蘑菇书EasyRL中的内容进行详细的疑难点分析!具体内容请阅读蘑菇书EasyRL!
我跑出来的和给的代码的结果不太一样,没看出来哪里出了问题
# -*- coding: utf-8 -*-
""" 定义模型 """
"""
前面说了DQN的模型不再是Q表,而是一个深度神经网络,这里我只用了一个三层的全连接网络(FCN),这种网络也叫多层感知机(MLP),
至于怎么用Torch写网络这里就不多说明了,以下仅供参考。
"""
import torch.nn as nn
import torch.nn.functional as F
class MLP(nn.Module):
def __init__(self, n_states,n_actions,hidden_dim=128):
""" 初始化q网络,为全连接网络
"""
super(MLP, self).__init__()
self.fc1 = nn.Linear(n_states, hidden_dim) # 输入层
self.fc2 = nn.Linear(hidden_dim,hidden_dim) # 隐藏层
self.fc3 = nn.Linear(hidden_dim, n_actions) # 输出层
def forward(self, x):
# 各层对应的激活函数
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
return self.fc3(x)
""" 定义经验回放 """
"""
经验回放首先是具有一定容量的,只有存储一定的transition网络才会更新,否则就退回到了之前的逐步更新了。
另外写经验回放的时候一般需要包涵两个功能或方法,一个是push,即将一个transition样本按顺序放到经验回放中,如果满了就把最开始放进去的样本挤掉,
因此如果大家学过数据结构的话推荐用队列来写,虽然这里不是。
另外一个是sample,很简单就是随机采样出一个或者若干个(具体多少就是batch_size了)样本供DQN网络更新。
功能讲清楚了,大家可以按照自己的想法用代码来实现,参考如下。
"""
'''导入 Python 中的双端队列(deque)数据结构。
如果创建 deque(maxlen=5),当插入第 6 个元素时,第 1 个元素会自动被移除。
'''
from collections import deque
import random
class ReplayBuffer(object):
"""定义一个名为 ReplayBuffer 的类,用于存储经验数据(通常为 transition)。"""
def __init__(self, capacity: int) -> None:
"""
初始化 ReplayBuffer 实例时需要传入容量 capacity,代表回放池最多存储的经验数量。
"""
self.capacity = capacity
'''
作用:
- 创建一个双端队列,并设置最大长度为 self.capacity。
效果:
- 当队列中的元素数量超过容量时,新加入的元素会自动导致最旧的元素被删除。
举例:
- 如果队列当前存储 [e1, e2, e3, e4, e5],再 push(e6) 后,队列将变为 [e2, e3, e4, e5, e6]。
'''
self.buffer = deque(maxlen=self.capacity)
def push(self,transitions):
''' 存储transition到经验回放中
作用:
- 将单个经验 transition 添加到回放池中。
参数 transitions:
- 通常是一个 tuple,格式为 (state, action, reward, next_state, done) 或其他形式,取决于具体算法。
内部操作:
- 使用 deque 的 append() 方法将该 transition 加入到队列末尾。
数值例子:
- 假设 transitions = ([0, 0, 0, 0], 1, 0.5, [0, 1, 0, 0], False)
表示从状态 [0,0,0,0] 采取动作 1 得到奖励 0.5,转移到 [0,1,0,0],回合未结束。
调用 push() 后,该 tuple 被加入到 self.buffer。
'''
self.buffer.append(transitions)
def sample(self, batch_size: int, sequential: bool = False):
'''
作用:
- 从回放池中采样一批经验数据。
参数:
- batch_size:希望采样的样本数量。
- sequential:一个布尔值,决定采样方式是否为顺序采样(连续的一段)还是随机采样。
'''
if batch_size > len(self.buffer): # 如果批量大小大于经验回放的容量,则取经验回放的容量
batch_size = len(self.buffer)
if sequential: # 顺序采样
"""
作用(顺序采样):
- 随机选取一个起始索引 rand,保证从 rand 到 rand + batch_size 的区间在 buffer 内。
- 用列表推导式取出连续的一段数据,形成 batch。
- 使用 zip(*batch) 将批次数据“解压”:例如将所有状态放一起、所有动作放一起等,便于批量训练。
举例:
- 假设 self.buffer 中有 5 个经验:[e1, e2, e3, e4, e5],且 batch_size=3。
- 如果随机起始索引 rand = 1,则 batch = [e2, e3, e4]。
- 调用 zip(*batch) 后,返回的结构类似于:
(states=(s2,s3,s4), actions=(a2,a3,a4), rewards=(r2,r3,r4), ...)
"""
rand = random.randint(0, len(self.buffer) - batch_size)
batch = [self.buffer[i] for i in range(rand, rand + batch_size)]
return zip(*batch)
else: # 随机采样
"""
作用(随机采样):
- 使用 random.sample 从 self.buffer 中随机选择 batch_size 个不重复的样本。
- 同样通过 zip(*batch) 返回各组成部分的分组。
举例:
- 如果 buffer = [e1, e2, e3, e4, e5] 且 batch_size=3,可能随机采样到 [e1, e4, e5],返回
(states=(s1,s4,s5), actions=(a1,a4,a5), rewards=(r1,r4,r5), ...)
"""
batch = random.sample(self.buffer, batch_size)
return zip(*batch)
def clear(self):
''' 清空经验回放
作用:
- 清空回放池中的所有数据,调用 deque 的 clear() 方法。
数值例子:
- 如果 buffer 当前存储 [e1, e2, e3],调用 clear() 后,buffer 变为空 []。
'''
self.buffer.clear()
def __len__(self):
''' 返回当前存储的量
作用:
- 定义当调用 len(replay_buffer) 时返回缓冲区中当前存储的样本数。
举例:
- 如果 buffer 中有 3 个经验,则 len(replay_buffer) 返回 3。
'''
return len(self.buffer)
import torch
import torch.optim as optim
import math
import numpy as np
class DQN:
"""
实现了一个标准的 DQN 智能体,包含以下主要部分:
- 初始化阶段,构造演员(policy_net)和目标网络(target_net)、
经验回放、以及与 ε‑greedy 策略相关的参数和优化器。
- 根据当前状态选择动作(sample_action 和 predict_action),
其中 sample_action 包含探索(ε‑greedy)机制。
- 更新阶段,从经验回放中采样一个批次数据,通过计算 Q 值损失(均方误差)来更新网络参数,
同时使用目标网络来计算下一状态的 Q 值。
"""
def __init__(self,model,memory,cfg):
"""
model : TYPE
一个神经网络模型(例如一个全连接网络),作为 Q 网络。
memory : TYPE
经验回放池对象,用于存储经验数据。
cfg : TYPE
配置字典,包含各个超参数,例如 n_actions、device、gamma、epsilon_start、epsilon_end、
epsilon_decay、batch_size、lr 等。
"""
'''将动作数存入 self.n_actions。'''
self.n_actions = cfg['n_actions']
'''根据 cfg['device'](例如 "cuda" 或 "cpu")创建 torch.device 对象。'''
self.device = torch.device(cfg['device'])
'''将折扣因子 γ 存入 self.gamma,用于计算未来奖励的折扣。'''
self.gamma = cfg['gamma'] # 奖励的折扣因子
# e-greedy策略相关参数
'''初始化 sample_count 用于记录采样步数,从而对 ε 进行指数衰减。'''
self.sample_count = 0 # 用于epsilon的衰减计数
'''设置当前 ε 值初始为 cfg['epsilon_start']。'''
self.epsilon = cfg['epsilon_start']
self.sample_count = 0
'''分别保存 ε 的起始值、结束值和衰减参数。'''
self.epsilon_start = cfg['epsilon_start']
self.epsilon_end = cfg['epsilon_end']
self.epsilon_decay = cfg['epsilon_decay']
'''保存批量采样大小。'''
self.batch_size = cfg['batch_size']
'''
将传入的模型作为策略网络(policy_net)并移动到指定设备。
复制同样的模型作为目标网络(target_net)。
'''
self.policy_net = model.to(self.device)
self.target_net = model.to(self.device)
# 复制参数到目标网络
'''
作用:
- 遍历策略网络与目标网络中对应的参数,将策略网络的参数复制给目标网络,确保两者初始一致。
举例:
- 如果某层策略网络权重矩阵为 [[0.1, 0.2], [0.3, 0.4]],则目标网络中对应参数也会被设置为相同矩阵。
'''
for target_param, param in zip(self.target_net.parameters(),self.policy_net.parameters()):
target_param.data.copy_(param.data)
'''初始化优化器
作用:
- 使用 Adam 优化器对策略网络参数进行更新,学习率由 cfg['lr'] 指定。
数值例子:
- 如果 cfg['lr']=0.001,则优化器会以学习率 0.001 更新策略网络参数。
'''
self.optimizer = optim.Adam(self.policy_net.parameters(), lr=cfg['lr']) # 优化器
'''
作用:
- 将传入的 memory 对象保存到 self.memory,用于后续存储和采样经验数据。
'''
self.memory = memory # 经验回放
def sample_action(self, state):
''' 采样动作
作用:
- 每调用一次 sample_action(),采样计数 self.sample_count 增加 1,用于后续 ε 衰减计算。
'''
self.sample_count += 1
'''epsilon指数衰减'''
self.epsilon = self.epsilon_end + (self.epsilon_start - self.epsilon_end) * \
math.exp(-1. * self.sample_count / self.epsilon_decay)
'''
根据 ε‑greedy 策略选择动作:
- 以概率 1−𝜖(当 random.random() > ε 时)采用贪婪策略,即从策略网络获得的 Q 值中选择最大的动作;
- 否则(以概率 ε)随机选择一个动作。
'''
if random.random() > self.epsilon:
'''使用 torch.no_grad() 禁用梯度计算。'''
with torch.no_grad():
if isinstance(state, tuple):
state = state[0]
else:
state = state
'''
将输入 state(例如一个 NumPy 数组)转换为 tensor,
并添加 batch 维度(unsqueeze(dim=0)),使形状变为 (1, n_states)。'''
state = torch.tensor(state, device=self.device, dtype=torch.float32).unsqueeze(dim=0)
'''
将 state 输入 policy_net,获得 q_values(形状 (1, n_actions)),
例如假设 q_values = [[0.2, 0.5, 0.1, 0.3]]。
'''
q_values = self.policy_net(state)
'''
q_values.max(1) 返回最大值及对应索引,此处最大值为 0.5,对应动作 1,action = 1。
'''
action = q_values.max(1)[1].item() # choose action corresponding to the maximum q value
else:
'''否则,随机选择一个动作: random.randrange(self.n_actions)。'''
action = random.randrange(self.n_actions)
return action
@torch.no_grad() # 不计算梯度,该装饰器效果等同于with torch.no_grad():
def predict_action(self, state):
''' 预测动作
在测试或评估阶段,使用预测动作方法,不进行 ε‑greedy 探索,直接根据策略网络选择最优动作。
'''
if isinstance(state, tuple):
state = state[0]
else:
state = state
state = torch.tensor(state, device=self.device, dtype=torch.float32).unsqueeze(dim=0)
q_values = self.policy_net(state)
action = q_values.max(1)[1].item() # choose action corresponding to the maximum q value
return action
def update(self):
'''如果经验回放中的样本数小于 batch_size,则不进行更新,直接返回。'''
if len(self.memory) < self.batch_size: # 当经验回放中不满足一个批量时,不更新策略
return
# 从经验回放中随机采样一个批量的转移(transition)
'''
作用:
- 从经验回放中随机采样一个批量的转移,
每个转移通常格式为 (state, action, reward, next_state, done)。
数值例子:
- 假设 memory 中存有 100 个经验,采样 batch_size=32 后返回 32 个样本,
之后使用 zip(*batch) 得到 5 个列表分别代表所有状态、动作、奖励、下一个状态和 done 标志。
'''
state_batch, action_batch, reward_batch, next_state_batch, done_batch = self.memory.sample(self.batch_size)
# 将数据转换为tensor
'''
作用:
- 将采样到的数据转换为 PyTorch 张量,以便进行后续计算。
说明:
- state_batch 和 next_state_batch 通过 np.array() 转换为 NumPy 数组后,再转为 tensor。
- action_batch 用 unsqueeze(1) 使形状为 (batch_size, 1)。
- done_batch 也转换为浮点数张量(done 为 1.0 表示 True)。
'''
processed_state_batch = []
for s in state_batch:
if isinstance(s, tuple):
# 如果元素是元组,则取元组的第一个元素
processed_state_batch.append(s[0])
else:
processed_state_batch.append(s)
state_batch = torch.tensor(np.array(processed_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(np.array(next_state_batch), device=self.device, dtype=torch.float)
done_batch = torch.tensor(np.float32(done_batch), device=self.device)
'''
作用:
- 计算当前策略网络对于批量状态下所采取动作对应的 Q 值。
数学公式:
- 对于每个样本𝑖,表示从网络输出的每行中取出对应 action_batch 索引的元素。
数值例子:
- 假设对于某个状态 s,policy_net(state) 输出张量 [[0.2, 0.5, 0.1, 0.3]],
如果 action 为 1,则 gather 结果为 0.5。
'''
q_values = self.policy_net(state_batch).gather(dim=1, index=action_batch) # 计算当前状态(s_t,a)对应的Q(s_t, a)
'''
作用:
- 使用目标网络计算批量下一状态的最大 Q 值。
数学公式:
- 对于每个样本 𝑖 ,maxQ(s′i,a′),使用 max(1) 返回每行的最大值,并取 [0] 获取数值,detach() 表示不计算梯度。
数值例子:
- 如果 target_net(next_state) 对于一个样本返回 [[0.3, 0.4, 0.2, 0.1]], 则 max(1)[0] = 0.4。
'''
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
'''
作用:
- 根据贝尔曼方程计算每个样本的目标 Q 值
- 未终止:done_batch=0
- 终止:done_batch=1
'''
expected_q_values = reward_batch + self.gamma * next_q_values * (1-done_batch)
'''
作用:
- 计算均方误差(MSE)损失。
- 由于 q_values 的形状为 (batch_size, 1),而 expected_q_values 可能为 (batch_size,),
因此用 unsqueeze(1) 将其形状改为 (batch_size, 1)。
数值例子:
- 若某样本 q_value=0.5,expected_q_value=1.396,则误差 = (0.5 - 1.396)² ≈ 0.792² ≈ 0.627。
'''
loss = nn.MSELoss()(q_values, expected_q_values.unsqueeze(1)) # 计算均方根损失
# 优化更新模型
'''
作用:
- 清除之前计算的梯度,确保本次梯度计算不受累积影响。
- loss.backward() 反向传播计算所有模型参数的梯度。
'''
self.optimizer.zero_grad()
loss.backward()
# clip防止梯度爆炸
'''
作用:
- 对策略网络的所有梯度进行梯度裁剪,将梯度值限制在 [-1, 1] 范围内,以防止梯度爆炸。
数值例子:
- 如果某参数梯度值为 2.5,则经过 clamp_(-1, 1) 后变为 1;如果梯度为 -1.5,则变为 -1。
'''
for param in self.policy_net.parameters():
param.grad.data.clamp_(-1, 1)
'''
作用:
- 优化器更新策略网络参数,依据计算出的梯度进行一步参数更新。
'''
self.optimizer.step()
# 2. 定义训练
def train(cfg, env, agent):
''' 训练'''
"""作用:
定义一个训练函数,其参数包括配置字典 cfg(存储超参数)、环境 env 和智能体 agent。
"""
print("开始训练!")
rewards = [] # 记录所有回合的奖励
steps = []
for i_ep in range(cfg['train_eps']):
ep_reward = 0 # 记录一回合内的奖励
ep_step = 0
'''state: (array([-0.02859258, 0.03005529, -0.00388322, -0.02385169], dtype=float32), {})'''
state = env.reset() # 重置环境,返回初始状态
for _ in range(cfg['ep_max_steps']):
ep_step += 1
'''
假设 state = [0.05, 0.0, 0.02, 0.0],
智能体策略网络输出 Q 值为 [0.2, 0.5, 0.1, 0.3],
则贪婪选择会选动作 1(对应 0.5),如果随机,则可能选 0、2 或 3。
'''
action = agent.sample_action(state) # 选择动作
next_state, reward, done, truncated, info = env.step(action)
# next_state, reward, done, _ = env.step(action) # 更新环境,返回transition
'''将当前一步的经验(transition)存入智能体的经验回放 memory。'''
agent.memory.push((state, action, reward,next_state, done)) # 保存transition
state = next_state # 更新下一个状态
'''
作用:
- 调用智能体的 update() 方法,尝试利用当前经验池中的数据进行网络更新。
说明:
- update() 内部通常会判断是否达到更新频率,
然后从经验回放中采样一个批次数据进行梯度下降更新。
'''
agent.update() # 更新智能体
ep_reward += reward # 累加奖励
if done:
"""如果环境返回 done 为 True,表示回合结束,则退出内层循环。"""
break
if (i_ep + 1) % cfg['target_update'] == 0: # 智能体目标网络更新
"""
每经过一定回合数(例如每 target_update 回合),更新目标网络的参数,使其与策略网络同步。
数值例子:
- 如果 cfg['target_update'] = 10,当 i_ep+1 为 10、20、30……时,
目标网络的参数将更新为最新的策略网络参数。
state_dict() 方法:
- 对于一个 PyTorch 模型(例如 policy_net),调用 model.state_dict() 会返回一个 Python 字典,
键是参数名称,值是对应的参数张量。
- 例如,假设 policy_net 有一层权重 "fc1.weight",
那么 policy_net.state_dict()["fc1.weight"] 就是该层的权重张量。
load_state_dict() 方法:
- 对于一个模型(例如 target_net),
调用 target_net.load_state_dict(some_dict) 会将该字典中存储的参数值加载到 target_net 中,
覆盖目标网络当前的参数。
=============================================
结果就是目标网络的参数和策略网络的参数完全一致。
=============================================
为什么这么做?
在 DQN 等算法中,我们通常使用两个网络:
- 策略网络(policy_net):每步都更新,用来计算当前状态的 Q 值并指导动作选择。
- 目标网络(target_net):相对较慢更新,它在计算目标 Q 值时使用,避免训练过程中目标不断变化带来的不稳定。
"""
agent.target_net.load_state_dict(agent.policy_net.state_dict())
steps.append(ep_step)
rewards.append(ep_reward)
if (i_ep + 1) % 10 == 0:
print(f"回合:{i_ep+1}/{cfg['train_eps']},奖励:{ep_reward:.2f},Epislon:{agent.epsilon:.3f}")
print("完成训练!")
env.close()
return {'rewards':rewards}
def test(cfg, env, agent):
print("开始测试!")
rewards = [] # 记录所有回合的奖励
steps = []
for i_ep in range(cfg['test_eps']):
ep_reward = 0 # 记录一回合内的奖励
ep_step = 0
state = env.reset() # 重置环境,返回初始状态
for _ in range(cfg['ep_max_steps']):
ep_step+=1
action = agent.predict_action(state) # 选择动作
next_state, reward, done, truncated, info = env.step(action)
# next_state, reward, done, _ = env.step(action) # 更新环境,返回transition
state = next_state # 更新下一个状态
ep_reward += reward # 累加奖励
if done:
break
steps.append(ep_step)
rewards.append(ep_reward)
print(f"回合:{i_ep+1}/{cfg['test_eps']},奖励:{ep_reward:.2f}")
print("完成测试")
env.close()
return {'rewards':rewards}
# 3. 定义环境
import gym
import os
def all_seed(env,seed = 1):
''' 万能的seed函数'''
if not hasattr(env, 'seed'):
def seed_fn(self, seed=None):
env.reset(seed=seed)
return [seed]
env.seed = seed_fn.__get__(env, type(env))
# env.seed(seed) # env config
np.random.seed(seed)
random.seed(seed)
torch.manual_seed(seed) # config for CPU
torch.cuda.manual_seed(seed) # config for GPU
'''
设置环境变量 PYTHONHASHSEED,固定 Python 内部哈希函数的随机性,避免因哈希随机化导致数据结构顺序变化。
'''
os.environ['PYTHONHASHSEED'] = str(seed) # config for python scripts
# config for cudnn
torch.backends.cudnn.deterministic = True # 确保 CuDNN 使用确定性算法;
torch.backends.cudnn.benchmark = False # 关闭动态算法搜索;
torch.backends.cudnn.enabled = False # 禁用 CuDNN,保证计算确定性。
def env_agent_config(cfg):
env = gym.make(cfg['env_name']) # 创建环境
if cfg['seed'] !=0:
"""
如果配置中 seed 不为 0,则调用上面的 all_seed 函数,为环境以及其他随机模块固定随机种子。
"""
all_seed(env,seed=cfg['seed'])
'''从环境的 observation_space 中获取状态空间的维度。'''
n_states = env.observation_space.shape[0]
'''从环境的 action_space 获取离散动作数。'''
n_actions = env.action_space.n
print(f"状态空间维度:{n_states},动作空间维度:{n_actions}")
'''
将从环境中获取的 n_states 和 n_actions 更新到配置字典 cfg 中,以便后续构造智能体时使用。
'''
cfg.update({"n_states":n_states,"n_actions":n_actions}) # 更新n_states和n_actions到cfg参数中
'''根据更新后的状态数和动作数,使用一个多层感知机(MLP)构造模型作为 Q 网络。
如果 n_states=4, n_actions=2,且 cfg['hidden_dim']=128,
那么创建的模型是一个输入层维度为 4、隐藏层维度为 128、输出层维度为 2 的全连接网络。
'''
model = MLP(n_states, n_actions, hidden_dim = cfg['hidden_dim']) # 创建模型
'''使用配置中的 memory_capacity 构造一个经验回放池 ReplayBuffer。'''
memory = ReplayBuffer(cfg['memory_capacity'])
agent = DQN(model,memory,cfg)
return env,agent
# 4. 设置参数
import argparse
import matplotlib.pyplot as plt
import seaborn as sns
def get_args():
""" 超参数
"""
parser = argparse.ArgumentParser(description="hyperparameters")
parser.add_argument('--algo_name',default='DQN',type=str,help="name of algorithm")
parser.add_argument('--env_name',default='CartPole-v0',type=str,help="name of environment")
parser.add_argument('--train_eps',default=200,type=int,help="episodes of training")
parser.add_argument('--test_eps',default=20,type=int,help="episodes of testing")
parser.add_argument('--ep_max_steps',default = 100000,type=int,help="steps per episode, much larger value can simulate infinite steps")
parser.add_argument('--gamma',default=0.95,type=float,help="discounted factor")
parser.add_argument('--epsilon_start',default=0.95,type=float,help="initial value of epsilon")
parser.add_argument('--epsilon_end',default=0.01,type=float,help="final value of epsilon")
parser.add_argument('--epsilon_decay',default=500,type=int,help="decay rate of epsilon, the higher value, the slower decay")
parser.add_argument('--lr',default=0.0001,type=float,help="learning rate")
parser.add_argument('--memory_capacity',default=100000,type=int,help="memory capacity")
parser.add_argument('--batch_size',default=64,type=int)
parser.add_argument('--target_update',default=4,type=int)
parser.add_argument('--hidden_dim',default=256,type=int)
parser.add_argument('--device',default='cpu',type=str,help="cpu or cuda")
parser.add_argument('--seed',default=10,type=int,help="seed")
args = parser.parse_args([])
args = {**vars(args)} # 转换成字典类型
## 打印超参数
print("超参数")
print(''.join(['=']*80))
tplt = "{:^20}\t{:^20}\t{:^20}"
print(tplt.format("Name", "Value", "Type"))
for k,v in args.items():
print(tplt.format(k,v,str(type(v))))
print(''.join(['=']*80))
return args
def smooth(data, weight=0.9):
'''用于平滑曲线,类似于Tensorboard中的smooth曲线
对输入的数值序列 data 进行指数平滑处理,减少噪声,使得曲线更平滑。
'''
last = data[0]
smoothed = []
for point in data:
smoothed_val = last * weight + (1 - weight) * point # 计算平滑值
smoothed.append(smoothed_val)
last = smoothed_val
return smoothed
def plot_rewards(rewards,cfg, tag='train'):
''' 画图
'''
sns.set()
plt.figure() # 创建一个图形实例,方便同时多画几个图
plt.title(f"{tag}ing curve on {cfg['device']} of {cfg['algo_name']} for {cfg['env_name']}")
plt.xlabel('epsiodes')
plt.plot(rewards, label='rewards')
plt.plot(smooth(rewards), label='smoothed')
plt.legend()
plt.show()
# 5. 开始训练
# 获取参数
cfg = get_args()
# 训练
env, agent = env_agent_config(cfg)
res_dic = train(cfg, env, agent)
plot_rewards(res_dic['rewards'], cfg, tag="train")
# 测试
res_dic = test(cfg, env, agent)
plot_rewards(res_dic['rewards'], cfg, tag="test") # 画出结果