概述
Deep Q-Learning(深度 Q 学习)是一种强化学习算法,用于解决决策问题,其中代理(agent)通过学习在不同环境中采取行动来最大化累积奖励。Lunar Lander 是一个经典的强化学习问题,其中代理的任务是控制一个着陆舱在月球表面着陆,最小化着陆过程中的燃料消耗。
以下是使用 Deep Q-Learning 解决 Lunar Lander 问题的基本步骤:
环境建模: 首先,需要对 Lunar Lander 环境进行建模。这包括定义状态空间、动作空间、奖励函数等。在 Lunar Lander 中,状态可以包括着陆舱的位置、速度、角度等信息,动作可以是推力引擎的火力等。
深度 Q 网络: 创建一个深度神经网络,该网络将输入状态,并输出每个可能动作的 Q 值。Q 值表示在给定状态下采取某个动作的累积奖励的估计。网络的目标是通过学习调整 Q 值以最大化累积奖励。
经验回放: 使用经验回放机制来改善学习稳定性。这涉及到存储代理先前的经验,并从中随机抽样进行训练。这有助于解决样本相关性问题,提高算法的收敛性。
ε-贪婪策略: 引入 ε-贪婪策略,以平衡探索和利用。在一部分情况下,代理将以高概率选择当前认为最佳的动作(贪婪),而在其他情况下,它会以较高概率选择一个随机动作(探索)。
目标 Q 值计算: 使用目标 Q 值来更新网络参数。目标 Q 值通过将当前状态的即时奖励与下一个状态的最大 Q 值相结合得到。这有助于更有效地传播奖励信号。
训练: 通过与环境的交互,不断地更新深度 Q 网络的参数。代理通过学习来优化其行为,以最大化预期的累积奖励。
调优: 对算法的超参数进行调优,包括学习率、折扣因子、神经网络结构等。这有助于提高算法的性能和稳定性。
使用 Deep Q-Learning 解决 Lunar Lander 问题是一个复杂的任务,需要仔细调整和实验。算法的性能可能受到许多因素的影响,包括网络结构的选择、超参数的设置以及环境的建模等。
Lunar Lander环境
这个环境是 Box2D 环境的一部分,其中包含有关环境的一般信息。
描述 Description
该环境是一个经典的火箭轨迹优化问题。根据庞特里亚金最大值原理,最佳的方法是全速点火或关闭引擎。这就是为什么这个环境具有离散动作的原因:引擎开启或关闭。
有两个环境版本:离散或连续。着陆点始终位于坐标 (0,0)。坐标是状态向量中的前两个数字。在着陆点外着陆是可能的。燃料是无限的,因此代理可以学会飞行,然后在第一次尝试时着陆。
要查看启发式着陆,请运行:
python gymnasium/envs/box2d/lunar_lander.py
动作空间 Action Space
有四个可用的离散动作:
0:什么都不做
1:点火左定向引擎
2:点火主引擎
3:点火右定向引擎
观察空间 Observation Space
状态是一个8维向量:着陆器在 x 和 y 上的坐标,它在 x 和 y 上的线速度,它的角度,它的角速度,以及两个表示每条腿是否与地面接触的布尔值。
奖励 Rewards
每一步都会获得一个奖励。一个 episode(回合)总奖励是该 episode 中所有步骤的奖励之和。
对于每一步,奖励:
着陆器离着陆点越近/远,奖励增加/减少。
着陆器移动越慢/快,奖励增加/减少。
着陆器倾斜度越大,奖励减少(角度非水平)。
每个与地面接触的腿奖励增加10分。
每帧侧引擎点火,奖励减少0.03分。
每帧主引擎点火,奖励减少0.3分。
episode 因坠毁或安全着陆而额外获得-100或+100分的奖励。
如果一个 episode 得分至少为200分,则认为它是一个解决方案。
起始状态 Starting State
着陆器位于视口的顶部中心,对其质心施加随机初始力。
回合终止条件 Episode Termination
如果满足以下条件回合结束:
着陆器坠毁(着陆器本体与月球接触);
着陆器超出视口范围(x 坐标大于1);
着陆器处于非唤醒状态。根据 Box2D 文档,处于非唤醒状态的身体是不移动且不与任何其他身体碰撞的身体:
当 Box2D 确定一个身体(或一组身体)已经停止时,该身体进入了一种几乎没有 CPU 开销的休眠状态。如果一个身体醒着并与休眠中的身体发生碰撞,那么休眠中的身体会醒来。如果连接到它们的关节或接触被销毁,身体也会醒来。
参数 Arguments
要使用连续环境,您需要指定 continuous=True 参数,如下所示:
import gymnasium as gym
env = gym.make(
"LunarLander-v2",
continuous: bool = False,
gravity: float = -10.0,
enable_wind: bool = False,
wind_power: float = 15.0,
turbulence_power: float = 1.5,
)
如果传递 continuous=True,将使用连续动作(对应于引擎的油门),并且动作空间将是 Box(-1, +1, (2,), dtype=np.float32)。动作的第一个坐标确定主引擎的油门,而第二个坐标指定侧推器的油门。给定一个动作 np.array([main, lateral]),如果 main < 0,则主引擎将完全关闭,并且油门在 0 <= main <= 1 时按比例从 50% 缩放到 100%(特别是,主引擎在功率低于 50% 时不起作用)。同样,如果 -0.5 < lateral < 0.5,则侧推器将不会点火。如果 lateral < -0.5,则左推进器将点火,如果 lateral > 0.5,则右推进器将点火。同样,油门在 -1 到 -0.5 之间(以及 0.5 到 1 之间)按比例从 50% 缩放到 100%。
gravity 确定了重力常数,它被限制在 0 和 -12 之间。
如果传递 enable_wind=True,则着陆器将受到风的影响。风是使用函数 tanh(sin(2 k (t+C)) + sin(pi k (t+C))) 生成的。k 设置为 0.01。C 在 -9999 到 9999 之间随机抽样。
wind_power 确定了施加在飞行器上的线性风的最大幅度。wind_power 的推荐值在 0.0 到 20.0 之间。turbulence_power 确定了施加在飞行器上的旋转风的最大幅度。turbulence_power 的推荐值在 0.0 到 2.0 之间。
版本历史 Version History
v2:计算能量消耗,并在 v0.24 版本中,添加了具有风力和 turbulence_power 参数的湍流
v1:在状态向量中添加了与地面接触的腿;与地面接触会奖励 +10 分,如果失去接触则减去 -10 分;奖励重新调整为 200 分;更难的初始随机推力。
v0:初始版本
备注 Notes
在环境实现中存在一些意外的错误。
着陆器身体上侧推器的位置会随着着陆器的方向而变化。这反过来导致对着陆器施加方向依赖性扭矩。
状态的单位不一致。即:
角速度以每秒 0.4 弧度为单位。为了转换为每秒弧度,需要将该值乘以 2.5 的因子。
对于 VIEWPORT_W、VIEWPORT_H、SCALE 和 FPS 的默认值,比例因子相等:'x': 10 'y': 6.666 'vx': 5 'vy': 7.5 'angle': 1 'angular velocity': 2.5
在进行更正后,状态的单位如下:'x':(单位) 'y':(单位) 'vx':(单位/秒) 'vy':(单位/秒) 'angle':(弧度) 'angular velocity':(弧度/秒)
示例代码
################ 完整代码
import gymnasium as gym
from gym.wrappers.monitoring.video_recorder import VideoRecorder
from IPython.display import HTML, display
import imageio
import base64
import io
import glob
import os
import random
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import torch.autograd as autograd
from torch.autograd import Variable
from collections import deque, namedtuple
######## 创建网络架构
class Network(nn.Module):
def __init__(self, state_size, action_size, seed=42):
super(Network, self).__init__()
self.seed = torch.manual_seed(seed)
self.fc1 = nn.Linear(state_size, 64)
self.fc2 = nn.Line