强化学习算法概述
强化学习(Reinforcement Learning, RL)是机器学习的一个重要分支,通过智能体与环境的交互来学习最优策略。近年来,随着深度学习的发展,深度强化学习算法取得了巨大的进展,在游戏、机器人、自动驾驶等领域展现出强大的潜力。本文将系统介绍几种主流的强化学习算法,包括它们的原理、优缺点以及实现细节。
强化学习的基本概念
在介绍具体算法之前,我们先回顾一下强化学习的基本概念:
- 智能体(Agent):学习和决策的主体
- 环境(Environment):智能体所处的外部世界
- 状态(State):环境在某一时刻的描述
- 动作(Action):智能体可以采取的行为
- 奖励(Reward):环境反馈给智能体的数值信号
- 策略(Policy):智能体的行为准则,决定在某状态下应该采取什么动作
- 价值函数(Value Function):评估某状态或某状态-动作对的长期价值
强化学习的目标是学习一个最优策略,使得从初始状态开始,智能体能获得最大的累积奖励。
强化学习算法的分类
强化学习算法可以从多个维度进行分类:
-
基于值函数与基于策略
- 基于值函数:学习动作价值函数,如Q-learning、DQN
- 基于策略:直接学习策略函数,如策略梯度、PPO
- Actor-Critic:同时学习值函数和策略函数
-
在线学习与离线学习
- 在线学习:边交互边学习,如SARSA
- 离线学习:先收集数据再学习,如DQN的经验回放
-
基于模型与无模型
- 基于模型:学习或利用环境模型,如Dyna-Q
- 无模型:直接从经验中学习,如Q-learning
-
确定性策略与随机策略
- 确定性策略:策略是状态到动作的确定映射,如DDPG
- 随机策略:输出动作的概率分布,如策略梯度
接下来,我们将详细介绍几种经典的强化学习算法。
深度Q网络(DQN)
深度Q网络(Deep Q-Network, DQN)是将深度学习与Q学习相结合的算法,它在多个Atari游戏上取得了超越人类水平的表现,是深度强化学习领域的里程碑工作。
DQN的核心思想
DQN的核心是用神经网络来近似Q函数。传统的Q学习使用表格来存储每个状态-动作对的Q值,但在状态空间很大的问题中这是不可行的。DQN使用深度神经网络作为函数逼近器,输入状态,输出每个动作的Q值估计。
DQN引入了两个关键的技巧来稳定训练:
-
经验回放(Experience Replay):将与环境交互得到的经验(状态、动作、奖励、下一状态)存储在一个回放缓冲区中,训练时随机采样batch进行学习。这打破了样本间的相关性,使训练更稳定。
-
目标网络(Target Network):维护一个单独的目标Q网络,用于计算目标Q值。目标网络的参数定期从主网络复制,这减少了目标的变化,有助于算法收敛。
DQN算法流程
DQN的主要步骤如下:
- 初始化主Q网络和目标Q网络,参数相同
- 对于每个回合:
- 初始化环境,获得初始状态s
- 对于每个时间步t:
- 用ε-greedy策略选择动作a
- 执行动作a,观察奖励r和新状态s'
- 将经验(s,a,r,s')存入回放缓冲区
- 从回放缓冲区采样mini-batch
- 计算目标Q值:y = r + γ * max_a' Q_target(s',a')
- 更新主Q网络,最小化(y - Q(s,a))^2
- 每C步更新一次目标网络
- s = s'
- 重复步骤2直到收敛
DQN的优缺点
优点:
- 能处理高维状态空间
- 样本效率高,可以重复利用历史经验
- 训练稳定性好
缺点:
- 只适用于离散动作空间
- 容易过高估计Q值
- 难以处理随机环境
DQN的实现要点
- 网络结构:常用CNN处理图像输入,FC层输出每个动作的Q值
- 探索策略:ε-greedy,随训练进程逐渐减小ε
- 目标网络更新频率:通常每1000步左右更新一次
- 回放缓冲区大小:通常设置为100,000左右
- 折扣因子γ:一般设为0.99
- 学习率:可以使用Adam优化器,初始学习率设为0.0001左右
class DQN(nn.Module):
def __init__(self, n_states, n_actions):
super(DQN, self).__init__()
self.fc1 = nn.Linear(n_states, 128)
self.fc2 = nn.Linear(128, 128)
self.fc3 = nn.Linear(128, n_actions)
def forward(self, x):
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
return self.fc3(x)
class DQNAgent:
def __init__(self, n_states, n_actions):
self.q_net = DQN(n_states, n_actions)
self.target_net = DQN(n_states, n_actions)
self.target_net.load_state_dict(self.q_net.state_dict())
self.optimizer = optim.Adam(self.q_net.parameters(), lr=0.0001)
self.memory = ReplayBuffer(100000)
def select_action(self, state, epsilon):
if random.random() > epsilon:
with torch.no_grad():
return self.q_net(state).max(1)[1].item()
else:
return random.randrange(self.n_actions)
def update(self, batch_size):
state, action, reward, next_state, done = self.memory.sample(batch_size)
q_values = self.q_net(state).gather(1, action)
next_q_values = self.target_net(next_state).max(1)[0].unsqueeze(1)
expected_q_values = reward + (1 - done) * 0.99 * next_q_values
loss = F.mse_loss(q_values, expected_q_values)
self.optimizer.zero_grad()
loss.backward()
self.optimizer.step()
DQN是一个里程碑式的工作,它为将深度学习应用于强化学习开辟了道路。在DQN之后,又出现了许多改进版本,如Double DQN、Dueling DQN、Prioritized Experience Replay等,进一步提升了算法的性能。
策略梯度算法
策略梯度(Policy Gradient)算法是另一类重要的强化学习方法。与基于值函数的方法不同,策略梯度直接对策略进行参数化,通过优化目标函数来更新策略参数。
策略梯度的基本原理
策略梯度的核心思想是:好的动作应该在未来出现的概率更大,而不好的动作在未来出现的概率应该更小。算法的目标是最大化期望累积奖励:
J(θ) = E_τ~π_θ [R(τ)]
其中τ表示轨迹,π_θ是参数化的策略,R(τ)是轨迹τ的累积奖励。
通过对目标函数求梯度,我们可以得到著名的策略梯度定理:
∇_θ J(θ) = E_τ~π_θ [∇_θ log π_θ(a|s) Q^π(s,a)]
这个公式告诉我们,策略梯度的方向是使得好的动作(Q值高的动作)在未来更可能被选择。
REINFORCE算法
REINFORCE是最基本的策略梯度算法,它使用整个轨迹的回报来估计Q值。算法流程如下:
- 初始化策略参数θ
- 对于每个回合:
- 使用当前策略π_θ采样一个轨迹τ = (s_0, a_0, r_1, s_1, ..., s_T-1, a_T-1, r_T)
- 对于轨迹中的每一步t:
- 计算回报G_t = Σ_k=t^T γ^(k-t) r_k
- 更新策略参数:θ = θ + α ∇_θ log π_θ(a_t|s_t) G_t
- 重复步骤2直到收敛
Actor-Critic算法
Actor-Critic算法是策略梯度的一个重要变体,它结合了策略梯度和值函数逼近。算法维护两个网络:
- Actor网络:输出动作的概率分布,即策略π(a|s)
- Critic网络:估计状态值函数V(s)或动作值函数Q(s,a)
Actor-Critic的优势在于,它使用Critic网络的估计来减小策略梯度的方差,从而使训练更加稳定。典型的Actor-Critic更新公式为:
θ = θ + α (r + γV(s') - V(s)) ∇_θ log π_θ(a|s)
其中(r + γV(s') - V(s))称为TD误差,可以看作是对优势函数A(s,a)的估计。
策略梯度算法的实现要点
- 策略网络结构:对于连续动作空间,通常输出高斯分布的均值和标准差;对于离散动作空间,输出各动作的概率
- 基线减小方差:使用状态值函数V(s)作为基线,减小回报的方差
- 重要性采样:处理离线数据或异步更新时使用重要性权重
- 熵正则化:在目标函数中加入策略熵,鼓励探索
- 自然策略梯度:使用Fisher信息矩阵来修正更新方向
class PolicyNet(nn.Module):
def __init__(self, n_states, n_actions):
super(PolicyNet, self).__init__()
self.fc1 = nn.Linear(n_states, 128)
self.fc2 = nn.Linear(128, 128)
self.fc3 = nn.Linear(128, n_actions)
def forward(self, x):
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
return F.softmax(self.fc3(x), dim=1)
class ValueNet(nn.Module):
def __init__(self, n_states):
super(ValueNet, self).__init__()
self.fc1 = nn.Linear(n_states, 128)
self.fc2 = nn.Linear(128, 128)
self.fc3 = nn.Linear(128, 1)
def forward(self, x):
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
return self.fc3(x)
class ActorCritic:
def __init__(self, n_states, n_actions):
self.actor = PolicyNet(n_states, n_actions)
self.critic = ValueNet(n_states)
self.actor_optimizer = optim.Adam(self.actor.parameters(), lr=0.001)
self.critic_optimizer = optim.Adam(self.critic.parameters(), lr=0.001)
def select_action(self, state):
probs = self.actor(state)
m = Categorical(probs)
action = m.sample()
return action.item(), m.log_prob(action)
def update(self, state, action, reward, next_state, done):
# Compute TD error
value = self.critic(state)
next_value = self.critic(next_state)
td_error = reward + (1 - done) * 0.99 * next_value - value
# Update critic
critic_loss = td_error.pow(2)
self.critic_optimizer.zero_grad()
critic_loss.backward()
self.critic_optimizer.step()
# Update actor
log_prob = self.actor(state).log_prob(action)
actor_loss = -log_prob * td_error.detach()
self.actor_optimizer.zero_grad()
actor_loss.backward()
self.actor_optimizer.step()
策略梯度算法在连续动作空间的问题上表现优秀,也更容易处理随机策略。然而,它们通常样本效率较低,需要精心的超参数