第6章 Dyna-Q算法

6.1简介

在强化学习中,“模型”通常指与智能体交互的环境模型,即对环境的状态转移概率和奖励函数进行建模。根据是否具有环境模型,强化学习分为两种:基于模型的强化学习和无模型的强化学习。无模型的强化学习根据智能体与环境交互采样得到的数据直接进行策略提升或者价值估计。前面提到的Sarsa和Q-learning算法都是无模型的强化学习方法,后续这个系列主要讨论的也都是无模型的强化学习方法。第四章讨论的策略迭代和价值迭代就是基于模型的强化学习方法,在这两个算法里面环境模型是已知的,这章介绍的Dyna-Q也是基础的基于模型的强化学习算法,不过其模型是通过采样数据估计得到的。

强化学习算法有两个比较重要的评价指标:一是算法收敛后的策略在初始状态下的期望回报,另一个是样本复杂度,即算法达到收敛结果需要在真实环境中采样的样本数量。基于模型的强化学习算法由于具有一个环境模型,智能体可以额外和环境模型进行交互,对真实环境中的需求量往往会减少,因此通常会比无模型的强化学习具有更低的样本复杂度。但是,环境模型可能并不准确,不能完全替代真实环境,因此基于模型的强化学习算法收敛后其策略的期望回报可能还不如无模型的强化学习算法。

6.2 Dyna-Q

Dyna-Q算法是一个经典的基于模型的强化学习算法。如图6-1所示,Dyna-Q使用一种叫做Q-planning的方法来给予模型生成一些模拟数据,然后用模拟数据和真实数据一起改进策略。Q-planning每次选取一个曾经访问过的状态s,采取一个曾经在该状态下执行过的动作a,通过模型得到转移后的状态s'和奖励r,并根据这个模拟数据(s,a,r,s'),用Q-learning的更新方式来更新动作价值函数。

下面可以来观察一下Dyna-Q算法的具体流程:

  • 初始化Q(s,a),初始化模型M(s,a)
  • for 序列e = 1\leftarrow E \quad do
  •         得到初始状态s
  •         for \quad t = 1 \rightarrow T \quad do:
  •                 用\epsilon -贪婪策略根据Q选择当前状态s下的动作a
  •                 得到环境反馈的r,s'
  •                 Q(s,a) \leftarrow Q(s,a) + \alpha [r + \gamma max_{a'} Q(s',a') - Q(s,a)]
  •                 M(s,a) \leftarrow r,s'
  •                 for 次数 n = 1 \rightarrow N \quad do
  •                         随机选择一个曾经访问过的状态s_m
  •                         采取一个曾经在状态s_m下执行过的动作a_m
  •                         r_m, s'_m \leftarrow M(s_m, a_m)
  •                         Q(s_m, a_m) \leftarrow Q(s,m) + \alpha[r_m + \gamma max_{a'}Q(s'_m,a') - Q(s_m, a_m)]
  •                 end \quad for
  •                 s \leftarrow s'
  •         end for 
  • end for

    在每次与环境进行交互执行一次Q-learning之后,Dyna-Q会做n次Q-planning。其中Q-planning的次数N是一个实现可以选择的超参数,当其为0时就是普通的Q-learning。值得注意的是,上述Dyna-Q算法实质性在一个离散且确定的环境中,所以当看到一条经验数据(s,a,r,s')时,可以直接对模型做出更新,即M(s,a) \leftarrow r,s'.

6.3 Dyna-Q 代码实践

    在悬崖漫步环境中执行过Q-learning算法,现在也在这个环境中实现Dyna-Q,以方便比较。首先还是需要实现悬崖慢步的环境代码:

# -*- coding: utf-8 -*-
"""
@File    : CliffWalkingEnv.py
@Author  : Administrator
@Time    : 2024-08-16 10:13
@IDE     : PyCharm
@Version : 
@comment : ···
"""
import matplotlib.pyplot as plt
import numpy as np
from tqdm import tqdm
import random
import time


class CliffWalkingEnv:
    def __init__(self, ncol, nrow):
        self.nrow = nrow
        self.ncol = ncol
        self.x = 0  # 记录当前智能体位置的横坐标
        self.y = self.nrow - 1  # 记录当前智能体位置的纵坐标

    def step(self, action):  # 外部调用这个函数来改变当前位置
        # 4种动作, change[0]:上, change[1]:下, change[2]:左, change[3]:右。坐标系原点(0,0)
        # 定义在左上角
        change = [[0, -1], [0, 1], [-1, 0], [1, 0]]
        self.x = min(self.ncol - 1, max(0, self.x + change[action][0]))
        self.y = min(self.nrow - 1, max(0, self.y + change[action][1]))
        next_state = self.y * self.ncol + self.x
        reward = -1
        done = False
        if self.y == self.nrow - 1 and self.x > 0:  # 下一个位置在悬崖或者目标
            done = True
            if self.x != self.ncol - 1:
                reward = -100
        return next_state, reward, done

    def reset(self):  # 回归初始状态,起点在左上角
        self.x = 0
        self.y = self.nrow - 1
        return self.y * self.ncol + self.x

  然后我们再Q-learning‘的代码上进行简单修改,实现Dyna-Q的主要代码。最主要的修改时加入了环境模型model,用一个字典表是,每次在真实环境中收集到新的数据,就把他加入字典。根据字典的性质,若该数据本身存在于字典中,便不会再一次进行添加。在Dyna-Q的更新中,执行完Q-learning后,会立即执行Q-planning。

class DynaQ:
    """Dyna-Q算法"""
    def __init__(self, ncol, nrow, epsilon, alpha, gamma, n_planning, n_action=4):
        self.Q_table = np.zeros([nrow * ncol, n_action]) #初始化Q(s,a)表格
        self.n_action = n_action #动作个数
        self.alpha = alpha #学习率
        self.gamma = gamma #折扣因子
        self.epsilon = epsilon #epsilon-贪婪策略中的参数
        self.n_planning = n_planning #执行Q-planning的次数,对应一次Q-learning
        self.model = dict() #环境模型

    def take_action(self, state):#选取下一步的操作
        if np.random.random() < self.epsilon:
            action = np.random.randint(self.n_action)
        else:
            action = np.argmax(self.Q_table[state])
        return action
    def q_learning(self, s0, a0, r, s1):
        td_error = r + self.gamma * self.Q_table[s1].max() - self.Q_table[s0, a0]
        self.Q_table[s0, a0] += self.alpha * td_error

    def update(self, s0, a0, r, s1):
        self.q_learning(s0, a0, r, s1)
        self.model[(s0, a0)] = r, s1 #将数据添加到模型中
        for _ in range(self.n_planning):#Q-planing循环
            #随机选择曾经遇到过的状态动作对
            (s, a), (r, s_) = random.choice(list(self.model.items()))
            self.q_learning(s, a, r, s_)

        下面是Dyna-Q算法在悬崖漫步环境中的训练函数,它的输入参数是Q-planning的步数

def DynaQ_CliffWalking(n_planning):
    ncol = 12
    nrow = 4
    env = CliffWalkingEnv(ncol, nrow)
    epsilon = 0.01
    alpha = 0.1
    gamma = 0.9
    agent = DynaQ(ncol, nrow, epsilon, alpha, gamma, n_planning)
    num_episodes = 300  # 智能体在环境中运行多少条序列

    return_list = []  # 记录每一条序列的回报
    for i in range(10):  # 显示10个进度条
        # tqdm的进度条功能
        with tqdm(total=int(num_episodes / 10),
                  desc='Iteration %d' % i) as pbar:
            for i_episode in range(int(num_episodes / 10)):  # 每个进度条的序列数
                episode_return = 0
                state = env.reset()
                done = False
                while not done:
                    action = agent.take_action(state)
                    next_state, reward, done = env.step(action)
                    episode_return += reward  # 这里回报的计算不进行折扣因子衰减
                    agent.update(state, action, reward, next_state)
                    state = next_state
                return_list.append(episode_return)
                if (i_episode + 1) % 10 == 0:  # 每10条序列打印一下这10条序列的平均回报
                    pbar.set_postfix({
                        'episode':
                        '%d' % (num_episodes / 10 * i + i_episode + 1),
                        'return':
                        '%.3f' % np.mean(return_list[-10:])
                    })
                pbar.update(1)
    return return_list

接下来试对结果进行可视化,通过调整参数,可以观察Q-planing步数对结果的影响。

np.random.seed(0)
random.seed(0)
n_planning_list = [0, 2, 20]
for n_planning in n_planning_list:
    print('Q-planning步数为:%d' % n_planning)
    time.sleep(0.5)
    return_list = DynaQ_CliffWalking(n_planning)
    episodes_list = list(range(len(return_list)))
    plt.plot(episodes_list,
             return_list,
             label=str(n_planning) + ' planning steps')
plt.legend()
plt.xlabel('Episodes')
plt.ylabel('Returns')
plt.title('Dyna-Q on {}'.format('Cliff Walking'))
plt.show()

运行结果:

Q-planning步数为:0
Iteration 0: 100%|██████████| 30/30 [00:00<00:00, 810.09it/s, episode=30, return=-138.400]
Iteration 1: 100%|██████████| 30/30 [00:00<00:00, 1363.65it/s, episode=60, return=-64.100]
Iteration 2: 100%|██████████| 30/30 [00:00<00:00, 1875.00it/s, episode=90, return=-46.000]
Iteration 3: 100%|██████████| 30/30 [00:00<00:00, 2306.93it/s, episode=120, return=-38.000]
Iteration 4: 100%|██████████| 30/30 [00:00<00:00, 3000.72it/s, episode=150, return=-28.600]
Iteration 5: 100%|██████████| 30/30 [00:00<00:00, 3331.72it/s, episode=180, return=-25.300]
Iteration 6: 100%|██████████| 30/30 [00:00<00:00, 3750.27it/s, episode=210, return=-23.600]
Iteration 7: 100%|██████████| 30/30 [00:00<00:00, 3766.21it/s, episode=240, return=-20.100]
Iteration 8: 100%|██████████| 30/30 [00:00<00:00, 3745.25it/s, episode=270, return=-17.100]
Iteration 9: 100%|██████████| 30/30 [00:00<00:00, 4291.73it/s, episode=300, return=-16.500]
Q-planning步数为:2
Iteration 0: 100%|██████████| 30/30 [00:00<00:00, 365.97it/s, episode=30, return=-53.800]
Iteration 1: 100%|██████████| 30/30 [00:00<00:00, 416.67it/s, episode=60, return=-37.100]
Iteration 2: 100%|██████████| 30/30 [00:00<00:00, 697.64it/s, episode=90, return=-23.600]
Iteration 3: 100%|██████████| 30/30 [00:00<00:00, 1111.16it/s, episode=120, return=-18.500]
Iteration 4: 100%|██████████| 30/30 [00:00<00:00, 1250.02it/s, episode=150, return=-16.400]
Iteration 5: 100%|██████████| 30/30 [00:00<00:00, 1428.58it/s, episode=180, return=-16.400]
Iteration 6: 100%|██████████| 30/30 [00:00<00:00, 1200.11it/s, episode=210, return=-13.400]
Iteration 7: 100%|██████████| 30/30 [00:00<00:00, 1666.68it/s, episode=240, return=-13.200]
Iteration 8: 100%|██████████| 30/30 [00:00<00:00, 1428.37it/s, episode=270, return=-13.200]
Iteration 9: 100%|██████████| 30/30 [00:00<00:00, 1363.94it/s, episode=300, return=-13.500]
Q-planning步数为:20
Iteration 0: 100%|██████████| 30/30 [00:00<00:00, 232.54it/s, episode=30, return=-18.500]
Iteration 1: 100%|██████████| 30/30 [00:00<00:00, 370.36it/s, episode=60, return=-13.600]
Iteration 2: 100%|██████████| 30/30 [00:00<00:00, 416.54it/s, episode=90, return=-13.000]
Iteration 3: 100%|██████████| 30/30 [00:00<00:00, 384.56it/s, episode=120, return=-13.500]
Iteration 4: 100%|██████████| 30/30 [00:00<00:00, 370.37it/s, episode=150, return=-13.500]
Iteration 5: 100%|██████████| 30/30 [00:00<00:00, 411.17it/s, episode=180, return=-13.000]
Iteration 6: 100%|██████████| 30/30 [00:00<00:00, 416.51it/s, episode=210, return=-22.000]
Iteration 7: 100%|██████████| 30/30 [00:00<00:00, 379.69it/s, episode=240, return=-23.200]
Iteration 8: 100%|██████████| 30/30 [00:00<00:00, 375.00it/s, episode=270, return=-13.000]
Iteration 9: 100%|██████████| 30/30 [00:00<00:00, 379.58it/s, episode=300, return=-13.400]

从上述结果可以很容易看出,随着Q-planning步数的增多,Dyna-Q算法收敛速度也随之变快。当然,并不是在所有的环境中,都是Q-planning步数越大算法收敛越快,这取决于换进是否是确定性的以及环境模型的精度。

  • 16
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值