前文
本文使用 Deep Q-learning 强化学习算法
训练智能体,并在 BreakoutNoFrameskip-v4
游戏中取得高分。
游戏介绍
在 Breakout 游戏画面的底部有一个可以只能左右移动的木板,并且在画面中有一个一直反弹的球,我们训练智能体控制木板击球,以达到让该球尽量快尽量多地摧毁屏幕顶部的方块,直到消除所有方块,最终得到地分数越高越好。如下所示。

核心概念
以下是 Deep Q-learning 中的一些核心概念和公式。 Deep Q-learning 的主要目标是通过训练深度神经网络来学习最优的策略,使智能体能够在不同状态下做出最优决策以最大化预期的累积回报奖励。
-
Q-value(Q 值):
- 定义: Q-value 表示在给定状态和动作的情况下,智能体能够获得的预期累积回报。Q-value 是学习目标,智能体的目标是最大化 Q-value,即最大化预期累积回报。
- 公式: <math xmlns="http://www.w3.org/1998/Math/MathML"> Q ( s , a ) Q(s, a) </math>Q(s,a)
-
Bellman Equation(贝尔曼方程):
- 定义: 贝尔曼方程描述了 Q-value 之间的关系,将当前时刻的 Q-value 与下一时刻的 Q-value 相关联。当前状态和动作的 Q-value 等于即时奖励 R 加上下一时刻状态的最大 Q-value 乘以折扣因子 gamma。在本文中 gamma 设置为 0.99 。
- 公式: <math xmlns="http://www.w3.org/1998/Math/MathML"> Q ( s , a ) = R + γ ⋅ max a ′ Q ( s ′ , a ′ ) Q(s, a) = R + \gamma \cdot \max_{a'} Q(s', a') </math>Q(s,a)=R+γ⋅maxa′Q(s′,a′)
-
Experience Replay(经验回放):
- 定义: 为了稳定训练,Deep Q-learning 使用经验回放来存储先前的经验,然后从中随机抽样进行训练。 在智能体与环境的交互中,将状态、动作、奖励等信息存储在经验回放缓冲区中,并在训练时从中抽样进行学习。经验回放通过存储和重复利用先前的经验,可以提高样本的独立性,减少样本选择的偏差,增加了数据的使用效率,从而促进了深度强化学习算法的稳定性和学习效果。
-
Deep Q-Network(深度 Q 网络):
- 定义: 使用深度神经网络来近似 Q-value 函数,将状态作为输入,输出每个动作对应的 Q-value。 通过深度神经网络,Q-learning 能够处理更复杂的状态空间,提高对环境的建模能力。
-
Target Model(目标网络):
- 定义: 为了提高训练的稳定性,Deep Q-learning 使用两个神经网络,Model 用于计算 Q-value,Target Model 用于计算目标 Q-value 。 Target Model 的参数定期按照 Model 的参数进行更新,可以帮助稳定训练,减少训练中的 Q-value 目标的波动。
-
ε-Greedy Exploration(ε-贪心探索):
- 定义: 在训练中,为了平衡探索和利用,ε-贪心策略被引入,以一定概率随机选择动作,而以 1-epsilon 的概率选择当前估计的最优动作。
-
时态差分法(Temporal Difference, TD):
- 定义: TD 算法是一类在强化学习中广泛应用的算法,用于学习价值函数或策略。Sarsa 和 Q-learning 都是基于时态差分法的重要算法,用于解决马尔可夫决策过程中的强化学习问题。
环境准备
本文需要 tensorflow-gpu==2.10 深度学习框架,另外需要以下 python 包配合:baselines
, atari-py
, rows
,可以从下面的途径获取:
bash
git clone https://github.com/openai/baselines.git
cd baselines
pip install -e .
git clone https://github.com/openai/atari-py
wget http://www.atarimania.com/roms/Roms.rar
unrar x Roms.rar .
python -m atari_py.import_roms .
DQN 模型
ini
def create_q_model():
inputs = layers.Input(shape=(84, 84, 4))
layer1 = layers.Conv2D(32, 8, strides=4, activation="relu")(inputs)
layer2 = layers.Conv2D(64, 4, strides=2, activation="relu")(layer1)
layer3 = layers.Conv2D(64, 3, strides=1, activation="relu")(layer2)
layer4 = layers.Flatten()(layer3)
layer5 = layers.Dense(512, activation="relu")(layer4)
action = layers.Dense(num_actions, activation="linear")(layer5)
return keras.Model(inputs=inputs, outputs=action)
这段代码定义了一个近似 Deep Q-Learning(DQN)的模型,使用卷积神经网络(Convolutional Neural Network, CNN)来估计 Q-value。以下是对代码的详细解释:
-
输入层:
- 输入层的形状为 (84, 84, 4) ,表示输入的状态是一个 84x84 的图像,而深度为 4 表示四帧连续的图像,用于捕捉动态信息,以更好地理解物体的运动和变化趋势。
-
卷积层(Convolutional Layers):
- 模型包含三个卷积层,每个层都采用不同的卷积核大小和步幅。
- 第一个卷积层:32 个大小为 8 * 8 的卷积核,步幅为 4 。
- 第二个卷积层:64 个大小为 4 * 4 的卷积核,步幅为 2 。
- 第三个卷积层:64 个大小为 3 * 3 的卷积核,步幅为 1 。
-
展平层(Flatten Layer):
- 通过 Flatten 层将卷积层的输出展平,以便连接到全连接层。
-
全连接层(Dense Layers):
- 模型包含一个包含 512 个神经元的全连接层,使用 ReLU 激活函数。
-
输出层(输出动作值):
- 输出层是一个具有
num_actions
个神经元的全连接层,使用线性激活函数(linear activation),用于输出 Q-value。
- 输出层是一个具有
-
模型创建:
- 使用 Keras 的
Model
类,将输入和输出连接起来,形成最终的近似 DQN 模型。
- 使用 Keras 的
算法实现
ini
while True:
state = np.array(env.reset())
episode_reward = 0
for timestep in range(1, max_steps_per_episode):
frame_count += 1
if frame_count < epsilon_random_frames or epsilon > np.random.rand(1)[0]:
action = np.random.choice(num_actions)
else:
state_tensor = tf.convert_to_tensor(state)
state_tensor = tf.expand_dims(state_tensor, 0)
action_probs = model(state_tensor, training=False)
action = tf.argmax(action_probs[0]).numpy()
epsilon -= epsilon_interval / epsilon_greedy_frames
epsilon = max(epsilon, epsilon_min)
state_next, reward, done, _ = env.step(action)
state_next = np.array(state_next)
episode_reward += reward
action_history.append(action)
state_history.append(state)
state_next_history.append(state_next)
done_history.append(done)
rewards_history.append(reward)
state = state_next
if frame_count % update_after_actions == 0 and len(done_history) > batch_size:
indices = np.random.choice(range(len(done_history)), size=batch_size)
state_sample = np.array([state_history[i] for i in indices])
state_next_sample = np.array([state_next_history[i] for i in indices])
rewards_sample = np.array([rewards_history[i] for i in indices])
action_sample = [action_history[i] for i in indices]
done_sample = tf.convert_to_tensor([float(done_history[i]) for i in indices])
future_rewards = model_target.predict(state_next_sample)
update_q_values = rewards_sample + gamma * tf.reduce_max(future_rewards, axis=1)
update_q_values = update_q_values * (1 - done_sample) - done_sample
masks = tf.one_hot(action_sample, num_actions)
with tf.GradientTape() as tape:
q_values = model(state_sample)
q_action = tf.reduce_sum(tf.multiply(q_values, masks), axis=1)
loss = loss_function(update_q_values, q_action)
grads = tape.gradient(loss, model.trainable_variables)
optimizer.apply_gradients(zip(grads, model.trainable_variables))
if frame_count % update_target_network == 0:
model_target.set_weights(model.get_weights())
print("running reward: {:.2f} at episode {}, frame count {}".format(running_reward, episode_count,
frame_count))
if len(rewards_history) > max_memory_length:
del rewards_history[:1]
del state_history[:1]
del state_next_history[:1]
del action_history[:1]
del done_history[:1]
if done:
break
episode_reward_history.append(episode_reward)
if len(episode_reward_history) > 100:
del episode_reward_history[:1]
running_reward = np.mean(episode_reward_history)
episode_count += 1
if running_reward > 40:
print("Solved at {}".format(episode_count))
break
这段代码是一个基于 Deep Q-Learning 的强化学习算法的核心代码,下面是对代码中参数的详细解释:
env
: 强化学习环境对象max_steps_per_episode
: 每轮游戏最大步数epsilon_random_frames
: 随机贪心探索的帧数epsilon
: 贪心探索策略的初始概率epsilon_min
: 贪心探索策略的最小概率epsilon_greedy_frames
: 贪心探索策略逐渐减小的帧数epsilon_interval
: 贪心探索概率每帧减小的量num_actions
: 可选动作的数量batch_size
: 用于训练的批次大小gamma
: 折扣因子update_after_actions
: 每多少步后进行一次模型更新update_target_network
: 每多少步后更新目标网络max_memory_length
: 经验回放缓冲区的最大长度
以下是大码的整体实现思路过程,不断重复下面的过程以训练智能体:
-
环境重置: 通过
env.reset()
重置环境,以便进行新一轮游戏的开始。 -
探索与利用: 在每一步中,使用 ε-贪心策略进行动作选择,即以概率 ε 随机选择动作,否则选择当前估计的最优动作。
-
状态转换与奖励: 执行选定的动作,获得下一个状态、奖励和是否完成的信息。
-
经验回放缓冲区更新: 将状态、动作、奖励、下一个状态等信息存储在经验回放缓冲区中,用于后续训练。 每隔一定步数,从缓冲区中随机抽样一批经验进行训练。
-
计算 Q 值和梯度下降: 使用深度神经网络模型
model
计算 Q 值,使用目标网络model_target
计算目标 Q 值。 使用 Huber Loss 计算损失,进行梯度下降更新模型参数。 -
目标网络更新: 每隔一定步数,将当前模型的权重复制给目标网络。
-
性能指标和控制: 计算最近 100 轮地平均奖励值作为本轮的分数,当运行奖励超过一定预先设定的值时,输出 "Solved" 并结束训练。
训练过程
Deepmind 论文训练了总计约 5000 万帧,但本文将训练大约 100 万帧,旨在说明整个原理实现过程,耗时 30 小时左右,有兴趣的同学可以将模型完整地训练结束。训练过程日志如下,可以看出随着训练的进行获得的分数是在逐渐增多的:
yaml
...
running reward: 0.27 at episode 4203, frame count 140000
running reward: 0.37 at episode 4477, frame count 150000
running reward: 0.44 at episode 4727, frame count 160000
...
running reward: 0.92 at episode 8996, frame count 370000
running reward: 0.93 at episode 9162, frame count 380000
running reward: 1.11 at episode 9318, frame count 390000
...
running reward: 2.62 at episode 13483, frame count 730000
running reward: 2.19 at episode 13582, frame count 740000
running reward: 2.36 at episode 13676, frame count 750000
...
效果展示
未训练地时候,基本上智能体地动作毫无规律可言。

经过一定时间的训练,智能体学会了去用木板击球获取分数。如果再继续随着训练的深入,智能体还能学会在最短时间内获取最高分数的方法。
