Keras深度学习实战(43)——深度Q学习算法

0. 前言

《强化学习基础》一节中,我们学习了强化学习的基本概念,并且介绍了如何在给定状态下采取随机动作。此外,我们还使用自定义环境,计算下一个状态、动作和奖励。在本节中,我们首先介绍 Q 学习与深度 Q 学习的基本思想,然后利用 OpenAIGym 库模拟 Frozen LakeCartPole 问题,并使用 Keras 实现 Q 学习解决这两个问题。

1. Q 学习简介

状态-动作值函数 (State-Action Value Function),简称 Q 函数,定义为从状态 s t s_t st 同时执行动作 a t a_t at 的双重设定下,在策略 π π π 的控制下能获得的期望回报值:

Q π ( s t , a t ) = E τ ∼ p ( τ ) [ R ( τ t : T ) ∣ τ a t = a t , τ s t = s t ] Q^π(s_t, a_t) = E_{τ∼p(τ)}[R(τ_{t:T})|τ_{a_t} = a_t , τ_{s_t}= s_t ] Qπ(st,at)=Eτp(τ)[R(τt:T)τat=at,τst=st]

在最优策略 π ∗ ( a ∣ s ) π^*(a|s) π(as) 下,具有以下关系:

Q ∗ ( s t , a t ) = m a x π Q π ( s t , a t ) π ∗ = a r g m a x a t Q ∗ ( s t , a t ) \begin{split} Q^*(s_t, a_t)=\undersetπ{max}Q^π(s_t, a_t)\\ π^*=\underset{a_t}{argmax}Q^*(s_t, a_t) \end{split} Q(st,at)=πmaxQπ(st,at)π=atargmaxQ(st,at)

Q 学习 (Q Learning) 算法通过下式估计 Q ∗ ( s t , a t ) Q^*(s_t, a_t) Q(st,at) 函数,并利用 π ε ( s t ) \pi^{\varepsilon}(s_t) πε(st) 策略来获得策略改进:

Q ∗ ( s t , a t ) ← Q ∗ ( s t , a t ) + α ( r ( s t , a t ) + γ m a x a t + 1 Q ∗ ( s t + 1 , a t + 1 ) − Q ∗ ( s t , a t ) ) Q^*(s_t, a_t)\larr Q^*(s_t, a_t)+\alpha(r(s_t,a_t)+\gamma\underset{a_{t+1}}{max}Q^*(s_{t+1},a_{t+1})-Q^*(s_t, a_t)) Q(st,at)Q(st,at)+α(r(st,at)+γat+1maxQ(st+1,at+1)Q(st,at))

深度 Q 网络 (Deep Q Network, DQN) 用深层的神经网络来参数化 Q ∗ ( s t , a t ) Q^*(s_t, a_t) Q(st,at) 函数,并利用梯度下降算法循环更新 Q 网络。

2. 使用 Q 学习进行 FrozenLake 游戏

2.1 FrozenLake 环境分析

FrozenLake 环境如下所示:

扫描二维码关注公众号,回复: 14548625 查看本文章

FrozenLake 环境

智能体从状态 S 开始,目标是通过尽可能避免状态 H 来达到状态 G,智能体可以处于 16 种可能的状态。此外,智能体可以采取四种可能的动作(向上,向下,向右或向左移动)。
我们将定义一个 q-表,其中有 16 行对应于 16 个状态,四列对应于在每种状态下可以采取的四个动作:

  • 在当前状态下采取动作的值=在当前状态下采取动作的值+ 1*(奖励+折扣系数*在下一个状态下采取的最佳动作的值-在当前状态下采取动作的值)

我们将学习率设为 1,以使状态下某个动作的值更新不会急剧变化:

  • 在当前状态下采取动作的值=在当前状态下采取动作的值+学习率*(奖励+折扣系数*下一种状态采取的最佳可能行动的值-在当前状态下采取动作的值)

根据以上公式更新 q-表,以便确定在不同状态下可以采取的最佳动作。

2.2 模型分析

在实际解决问题之前,我们将首先探讨用于解决 FrozenLake 问题的模型策略:

  • 使用 OpenAI的Gym 实例化 FrozenLake 环境
  • 初始化形状为 16 x 4q-表,其中所有元素值为 0
  • 在给定状态下选择动作时,采用探索-利用法:
    • 在早期迭代中探索,因为我们不确定在游戏的最初几个回合中应采取的最佳动作。
    • 但是,随着我们对游戏的了解越来越多,我们在采取可能的最佳动作的同时,仍然采取一定概率的随机行动(随着回合的增加,概率逐渐降低)来利用所学到的知识
  • 在给定的回合中:
    • 根据我们是否尝试探索或利用来选择动作
    • 确定新的状态和奖励,并通过执行上一步中选择的动作来检查游戏是否结束
  • 初始化学习率参数和折扣因子
  • 通过使用上述公式来更新在 q-表中的状态下采取所选动作的值
  • 重复上述步骤,直到游戏结束
  • 重复进行 1000 回合以上游戏
  • 检查 q-表以识别在给定状态下要采取的最佳动作
  • 绘制智能体的路径,以使其根据 q-表在状态下执行动作

2.3 使用 Q 学习算法解决 FrozenLake 问题

(1) 导入 Gym 以及其他相关库,Gym 是用于开发和比较强化学习算法的工具包,它支持多种模拟环境,关于 Gym 更多介绍,可以参考《强化学习环境配置》

import gym
from gym.envs.registration import register
import random

(2) 注册并创建环境,为了简化游戏,我们修改了 FrozenLake 环境的默认设定,最重要的设置是令冰面并不滑,每一步动作的结果都是确定,而冰面滑时,即时指定向左的动作,智能体也可能会滑动到其他位置,例如向下滑:

register(
    id = 'FrozenLakeNotSlippery-v1',
    entry_point = 'gym.envs.toy_text:FrozenLakeEnv',
    kwargs = {
    
    'map_name': '4x4', 'is_slippery': False},
    max_episode_steps = 100,
    reward_threshold = 0.8196
)
env = gym.make('FrozenLakeNotSlippery-v1')

(3) 检查创建的环境,reset() 方法用于重置游戏环境:

env.reset()
env.render()

渲染出的 FrozenLake 环境如下:

FrozenLake 环境

(4) 我们也可以检查环境中状态空间与动作空间数量,由于我们使用的 FrozenLake 环境包括 4 x 4 的网格,共有 16 个状态。因此,共有 16 个观测值:

print(env.observation_space)

print(env.action_space.n)

输出结果如下所示:

Discrete(16)
4

(5) 接下来,从动作空间中采样一个动作,并使用 step() 方法采取该动作,以生成新状态、奖励、是否完成游戏的标记以及有关该步骤的其他信息:

action = env.action_space.sample()

new_state, reward, is_done, info = env.step(action)

(6) 初始化 q-表,因为有 16 种状态,每种状态有 4 种可能的动作,因此 q-表的形状为 (16, 4)

import numpy as np
qtable = np.zeros((16,4))

(7) 初始化超参数后,运行 FrozenLake 游戏多个回合:

total_episodes=15000
learning_rate=0.8
max_steps=99
gamma=0.95
epsilon=1
max_epsilon=1
min_epsilon=0.01
decay_rate=0.005

rewards=[]
for episode in range(total_episodes):
    state=env.reset()
    step=0
    done=False
    total_rewards=0

(8) 定义要采取的策略。如果 eps (一个介于 01 之间的随机数)小于 0.5,我们将进行探索;否则,我们会进行利用(考虑 q-表中的最佳操作):

rewards=[]
for episode in range(total_episodes):
    state=env.reset()
    step=0
    done=False
    total_rewards=0
    for step in range(max_steps):
        exp_exp_tradeoff=random.uniform(0,1)
        
        # Exploitation:
        if exp_exp_tradeoff>epsilon:
            action=np.argmax(qtable[state,:])
        else:
            # Exploration
            action=env.action_space.sample()

(9) 获取新状态和奖励,并通过在给定步骤中采取动作获取游戏是否完成:

        new_state,reward,done,info=env.step(action)

(10) 基于当前状态状态执行的操作更新 q-表,并使用在当前状态下执行动作后获得的新状态来更新状态:

        # Update the Q
        qtable[state,action]=qtable[state,action]+\
                learning_rate*(reward+gamma*np.max(qtable[new_state,:])-qtable[state,action])
        total_rewards+=reward
        state=new_state

(11) 当本次游戏回合结束后,我们继续进行下一个新的游戏回合,同时,我们更新用于决定要进行探索还是利用的随机因子 (eps):

        if done:
            break
    episode+=1
    epsilon=min_epsilon+(max_epsilon-min_epsilon)*np.exp(decay_rate*episode)
    rewards.append(total_rewards)

(12) 一旦建立了 q-表,我们就可以根据 q-表得到的最佳动作指导智能体以进行动作:

print("Score over Time:",sum(rewards)/total_episodes)
print(qtable)

打印出的 q-表如下所示:

[[0.73509189 0.77378094 0.77378094 0.73509189]
 [0.73509189 0.         0.81450625 0.77378094]
 [0.77378094 0.857375   0.77378094 0.81450625]
 [0.81450625 0.         0.77378094 0.77378094]
 [0.77378094 0.81450625 0.         0.73509189]
 [0.         0.         0.         0.        ]
 [0.         0.9025     0.         0.81450625]
 [0.         0.         0.         0.        ]
 [0.81450625 0.         0.857375   0.77378094]
 [0.81450625 0.9025     0.9025     0.        ]
 [0.857375   0.95       0.         0.857375  ]
 [0.         0.         0.         0.        ]
 [0.         0.         0.         0.        ]
 [0.         0.9025     0.95       0.857375  ]
 [0.9025     0.95       1.         0.9025    ]
 [0.         0.         0.         0.        ]]

(13) 继续使用一个新回合,使用得到的 q-表计算智能体到达最终目标经过的最佳路径:

env.reset()
for episode in range(1):
    state=env.reset()
    step=0
    done=False
    print("-----------------------")
    print("Episode",episode)
    for step in range(max_steps):
        env.render()
        action=np.argmax(qtable[state,:])
        print(action)
        new_state,reward,done,info=env.step(action)
        
        if done:
            env.render()
            print("Number of Steps",step+1)
            break
        state=new_state
env.close()

得到的最佳路径如下所示:

Episode 0

SFFF
FHFH
FFFH
HFFG
1
  (Down)
...
2
  (Right)
SFFF
FHFH
FFFH
HFFG
Number of Steps 6

除此之外,我们也可以使用冰面较滑的环境进行相同的实验。

3. 使用深度 Q 学习进行 CartPole 游戏

在前面的部分中,我们学习了基于 q-表值执行动作的方法。但是,达到最佳值需要耗费大量时间,因为智能体必须通过进行多次游戏才能得到最佳 q-表。接下来,我们将学习如何使用神经网络,以便比使用 q-表时更快地达到最佳值。

3.1 问题分析

在本节中,我们将使用 CartPole 环境,其中智能体可能的动作包括向右或向左移动推车,以便平衡推车上的杆。此外,环境中的状态信息包括推车位置,推车速度,杆与推车的夹角和杆顶端处的速度。关于 CartPole 环境更详细的说明可以参考官方网页《强化学习环境配置》
CartPole 环境中,杆通过一个未固定的接头连接到小推车,小推车沿着无摩擦的轨道移动。该系统通过向推车施加 +1-1 的力来控制推车移动。杆开始时垂直于推车,智能体的目标是防止杆跌落。杆保持直立的每个时间戳都可以得到 +1 的奖励。当杆与垂直方向的夹角超过 15 度或推车距离中心点超过 2.4 个单位以上时,回合结束。

3.2 模型分析

为了保持杆的平衡,我们使用深度 Q 学习算法,利用神经网络来预测智能体需要采取的最佳动作,训练神经网络的策略如下:

  • 存储有关状态值、采取动作和获得奖励的信息:
    • 如果游戏未结束,则奖励为 1,否则为 0
  • 开始时,神经网络基于随机初始化的权重进行预测,模型的输出层具有两个节点,对应于两个可能动作的新状态值
  • 新状态值基于最大化新状态值的动作
  • 如果游戏没有结束,使用奖励和新状态的最大状态值与折扣因子之积来更新当前状态值
  • 从先前获得的更新后的当前状态值中覆盖动作的值:
    • 如果当前步骤中执行的动作导致游戏结束,则当前状态下的动作值为 0
    • 否则,当前步骤中的值是一个正数
    • 令模型找出要采取的正确动作
  • 另外,可以使用这种方法来指定奖励为 0 时动作是错误的;但是,由于不确定在奖励为 1 时是否是正确的动作,因此仅针对执行的动作对其进行更新,并保持新状态的值不变
  • 将状态值追加到输入数组,以及在当前状态下采取动作的值作为输出数组
  • 对模型进行拟合,以使数据样本的均方误差最小
  • 最后,随着回合数的增加不断减少探索

3.3 使用深度 Q 学习算法解决 CartPole 问题

(1) 导入所需库,创建 CartPole 环境并存储动作空间大小和状态空间大小:

import gym
import numpy as np
import random
from keras.models import Sequential
from keras.layers import Dense
from keras.optimizers import Adam
from collections import deque

env = gym.make('CartPole-v0')
state_size = env.observation_space.shape[0]
action_size = env.action_space.n

CartPole 环境如下所示:

CartPole

(2) 构建并编译神经网络模型:

model=Sequential()
model.add(Dense(24,input_dim=state_size,activation='relu'))
model.add(Dense(24,activation='relu'))
model.add(Dense(2,activation='linear'))
model.compile(loss='mse',optimizer=Adam(lr=0.01))

(3) 初始化超参数以及所需列表:

memory = deque(maxlen=2000)
gamma = 0.95 # discount rate
epsilon = 1.0 # exploration rate
epsilon_min = 0.01
epsilon_decay = 0.995
done = False
batch_size=32

(4) 定义函数,该函数接受神经网络模型,批大小和 epsilon (用于确定探索还是利用)作为参数。首先,获取 batch_size 大小的随机样本,其中 memory 包括状态 (state)、动作 (action)、奖励 (reward) 和新状态 (next_state) 等。如果游戏未结束,更新所采取动作的奖励;否则,奖励为 0。此外,模型可以预测采取某项动作的值(因为该模型在输出中有 2 个节点——每个节点都提供对采取某一动作的输出)。函数返回更新的模型和探索/利用系数 (epsilon):

def replay(model, batch_size,epsilon):
    epsilon_min = 0.01
    epsilon_decay = 0.995
    minibatch = random.sample(memory, batch_size)
    for state, action, reward, next_state, done in minibatch:
        target = reward
        if not done:
            target = (reward + gamma *np.amax(model.predict(next_state)[0]))
        new_action_value = model.predict(state)
        new_action_value[0][action] = target
        model.fit(state,new_action_value, epochs=1, verbose=0)
    if epsilon > epsilon_min:
        epsilon *= epsilon_decay
    return model,epsilon

(5) 进行 CartPole 游戏多个回合,并记录智能体获得的分数。此外,确保模型根据 epsilon 值指示智能体采取的动作:

episodes=200
maxsteps=200
score_list = []
for e in range(episodes):
    state = env.reset()
    state = np.reshape(state, [1, state_size])

在以上代码中,共进行 200 个回合 CartPole 游戏,在每次回合开始时重置环境,我们还需要整形状态 state,以便可以将其传递给神经网络模型。

(6) 接下来,我们将基于探索参数 (epsilon) 采取行动,大约有 epsilon 的概率会采取随机动作 (env.actionspace.sample()),在其他情况下,会利用模型的预测采取相应动作:

    for step in range(maxsteps):
        if np.random.rand()<=epsilon:
            action=env.action_space.sample()
        else:
            action = np.argmax(model.predict(state)[0])

(7) 接下来,在每个时间戳执行一个动作并获取下一个状态、奖励以及有关游戏是否结束的信息。如果游戏结束,使用 -10 作为奖励值,表明执行了不正确的动作。此外,提取下一个状态并将其添加到 memory 中。这样,就为要训练的模型创建了一个数据集,该模型采用当前状态和奖励来计算两个可能动作的奖励:

        next_state, reward, done, _ = env.step(action)
        reward = reward if not done else -10
        next_state = np.reshape(next_state, [1, state_size])
        memory.append((state, action, reward, next_state, done))

(8) 如果游戏结束,我们将记录得分(游戏过程中的时间戳数);否则,将更新模型。此外,仅当 memory 中的数据样本数与大于预定义的批大小时,才会更新模型:

        state = next_state
        if done:
            print("episode: {}/{}, score: {}, exp prob: {:.2}".format(e, episodes, step, epsilon))
            score_list.append(step)
            break
        if len(memory) > batch_size:
            model,epsilon=replay(model, batch_size,epsilon)

(9) 绘制随着时间的增加,每个回合的得分情况:

import matplotlib.pyplot as plt
plt.plot(range(1,len(score_list)+1),score_list)
plt.title('Game score over increasing episodes')
plt.show()

游戏过程得分情况

小结

强化学习使用马尔可夫决策过程的形式化框架,使用状态、动作和奖励定义学习型智能体与环境的交互过程。Q 学习作为一种经典的强化学习算法,可以直接优化一个可迭代计算的 Q 函数,能够比较可用动作的预期效用,而不需要环境模型。在本节中,我们首先介绍 Q 学习与深度 Q 学习的基本思想,并使用 Keras 实现 Q 学习解决 Frozen LakeCartPole 问题。

系列链接

Keras深度学习实战(1)——神经网络基础与模型训练过程详解
Keras深度学习实战(2)——使用Keras构建神经网络
Keras深度学习实战(3)——神经网络性能优化技术
Keras深度学习实战(4)——深度学习中常用激活函数和损失函数详解
Keras深度学习实战(5)——批归一化详解
Keras深度学习实战(6)——深度学习过拟合问题及解决方法
Keras深度学习实战(7)——卷积神经网络详解与实现
Keras深度学习实战(8)——使用数据增强提高神经网络性能
Keras深度学习实战(9)——卷积神经网络的局限性
Keras深度学习实战(10)——迁移学习详解
Keras深度学习实战(11)——可视化神经网络中间层输出
Keras深度学习实战(12)——面部特征点检测
Keras深度学习实战(13)——目标检测基础详解
Keras深度学习实战(14)——从零开始实现R-CNN目标检测
Keras深度学习实战(15)——从零开始实现YOLO目标检测
Keras深度学习实战(16)——自编码器详解
Keras深度学习实战(17)——使用U-Net架构进行图像分割
Keras深度学习实战(18)——语义分割详解
Keras深度学习实战(19)——使用对抗攻击生成可欺骗神经网络的图像
Keras深度学习实战(20)——DeepDream模型详解
Keras深度学习实战(21)——神经风格迁移详解
Keras深度学习实战(22)——生成对抗网络详解与实现
Keras深度学习实战(23)——DCGAN详解与实现
Keras深度学习实战(24)——从零开始构建单词向量
Keras深度学习实战(25)——使用skip-gram和CBOW模型构建单词向量
Keras深度学习实战(26)——文档向量详解
Keras深度学习实战(27)——循环神经详解与实现
Keras深度学习实战(28)——利用单词向量构建情感分析模型
Keras深度学习实战(29)——长短时记忆网络详解与实现
Keras深度学习实战(30)——使用文本生成模型进行文学创作
Keras深度学习实战(31)——构建电影推荐系统
Keras深度学习实战(32)——基于LSTM预测股价
Keras深度学习实战(33)——基于LSTM的序列预测模型
Keras深度学习实战(34)——构建聊天机器人
Keras深度学习实战(35)——构建机器翻译模型
Keras深度学习实战(36)——基于编码器-解码器的机器翻译模型
Keras深度学习实战(37)——手写文字识别
Keras深度学习实战(38)——图像字幕生成
Keras深度学习实战(39)——音乐音频分类
Keras深度学习实战(40)——音频生成
Keras深度学习实战(41)——语音识别
Keras深度学习实战(42)——强化学习基础

猜你喜欢

转载自blog.csdn.net/LOVEmy134611/article/details/126774917
今日推荐