计算机系统应用教程网站

网站首页 > 技术文章 正文

强化学习4--QLearning的实现例子和原理

btikc 2024-10-14 08:52:05 技术文章 12 ℃ 0 评论

0、主要参考

1、代码:https://github.com/lansinuote/More_Simple_Reinforcement_Learning

2、原理:https://zhuanlan.zhihu.com/p/626998396

1、什么是Q-Learning?

  • Q-learning是一种无模型(model-free)的强化学习算法。它通过估计动作值函数(action-value function),即Q函数(Q-function),来学习智能体在环境中采取各种动作所能获得的期望奖励。Q-learning算法可以应用于有限MDP(Markov Decision Process)问题。
  • Q-learning算法的核心是基于TD算法来训练其动作值函数,所以可以称之为一种TD算法。
  • 在代码中,我们的 Q 函数由 Q 表编码,Q 表中的每个单元格对应于一个状态-动作对值。将此 Q 表视为我们 Q 函数。

2、什么是Q表

转载了:https://zhuanlan.zhihu.com/p/626998396

(1)以下用mouse吃奶酪举例:

这个图中,一共有6个格子,每个格子有上下左右四种动作。而对应Q 表已初始化,所有值都 = 0(初始未训练。此表包含每个状态和动作的相应状态动作值:

所以:Q 函数使用一个 Q 表,该表具有每个状态-动作对的值。给定一个状态和动作,我们的 Q 函数将在其 Q 表中搜索以输出值。

3、Q_learning基本流程

所以Q_learning算法包含两个阶段:训练和推理

(1)在训练阶段,我们不断Exploration(探索)和 Exploitation(利用),以生成一个最优的Q函数

(2)在推理阶段,我们在Q表中搜索相应的值,然后采用最优策略(选择最大Q值的动作)来进行下一步动作。

3.1训练

3.2推理

3.3Q_learning算法具体实现

(1)第一步,初始化 Q 表,我们需要为每个状态-动作对初始化 Q 表。大多数情况下,我们使用值 0 进行初始化。

(2)第二步,使用 epsilon 贪婪策略选择操作

(3)第 3 步:在执行操作,获得奖励 Rt+1 和下一个状态 St+1

(4)第 4 步:更新 Q(St, at)

在TD学习中,我们会在交互的一个步骤后更新我们的价值函数。

为了制定我们的TD目标,我们这里的价值函数是Q,因为我们推理的策略是找Q表最大值,所以公式通过查找在下一个状态下最大化当前 Q 函数的动作来计算。

因此,我们的更新公式如下所示:

在此episode结束时,会不断开始新的episode的训练,经过足够多次迭代后,Q值表会收敛,智能体可以通过查询Q值表来确定在每个状态下应该采取的最优动作。

4、代码实现和测试

4.1定义一个环境类

(1)代码如下

#定义环境
class MyWrapper(gym.Wrapper):
    def __init__(self):
        #is_slippery控制会不会滑
        env = gym.make('FrozenLake-v1',
                       render_mode='rgb_array',
                       is_slippery=False)

        super().__init__(env)
        self.env = env

    def reset(self):
        state, _ = self.env.reset()
        return state

    def step(self, action):
        state, reward, terminated, truncated, info = self.env.step(action)
        over = terminated or truncated

        #走一步扣一份,逼迫机器人尽快结束游戏
        if not over:
            reward = -1

        #掉坑扣100分
        if over and reward == 0:
            reward = -100

        return state, reward, over

    #打印游戏图像
    def show(self):
        plt.figure(figsize=(3, 3))
        plt.imshow(self.env.render())
        plt.show()

(2)注意:该类定义了一个FrozenLake-v1对象,该类的step方法返回的是:当前的状态state,该步骤的奖励reward(正常走路-1分,掉坑里-100分),以及是否结束了over的标记(True为结束了)

(3)可以简单测试一下这个环境

import gymnasium as gym
from matplotlib import pyplot as plt

#定义环境
class MyWrapper(gym.Wrapper):
    def __init__(self):
        #is_slippery控制会不会滑
        env = gym.make('FrozenLake-v1',
                       render_mode='rgb_array',
                       is_slippery=False)

        super().__init__(env)
        self.env = env

    def reset(self):
        state, _ = self.env.reset()
        return state

    def step(self, action):
        state, reward, terminated, truncated, info = self.env.step(action)
        over = terminated or truncated

        #走一步扣一份,逼迫机器人尽快结束游戏
        if not over:
            reward = -1

        #掉坑扣100分
        if over and reward == 0:
            reward = -100

        return state, reward, over

    #打印游戏图像
    def show(self):
        plt.figure(figsize=(3, 3))
        plt.imshow(self.env.render())
        plt.show()

if __name__ == '__main__':
    #(1)创建环境
    env = MyWrapper()
    env.reset()
    state, reward, over =env.step(2) #先前一步
    print(state, reward, over)
    state, reward, over =env.step(2) #先前一步
    print(state, reward, over)
    env.show()

(4)测试结果如下:

4.2建立一个空的Q表

(1)初始化Q表,定义了每个状态下每个动作的价值,因为我们定义的空间大小为4*4=16,而且每个坐标state的可能动作为4个(上下左右),所以定义的Q表大小为16*4

Q = np.zeros((16, 4))

4.3 下一步如何走使用系统随机采样方法

gym提供了下一步如何走没有很好的选择情况下的随机走(值)的获取方法,函数使用方法如下

action = env.action_space.sample()

注意:上面获取的action是随机走的,对于我们这个游戏

action 为:2向右,1向下,0向左,3向上

{0: "←", 1: "↓", 2: "→", 3: "↑"}

4.4 贪婪策略跑一轮的实现,直到失败(掉坑里了)

(1)小概率e概率(下面为0.1)下,随机空间获取如何走

(2)剩下的都从Q表里面取值(虽然一开始都是0),后面会训练出来的,不用愁

4.4.1主要过程

  • (1)如果没结束,一直尝试下去
  • (2)大部分情况下从Q值获取动作
  • (3)贪婪策略,小概率情况下,随机
  • (4)输入下一步动作,获取下一步状态state,奖励reward,以及是否结束over
  • (5)把每一步的状态state,动作action,奖励reward,下一状态next_state,是否结束over都记下来
  • (6)计算累积的奖励reward_sum
  • (7)更新状态

4.4.2主要代码

(1)代码如下

#贪婪策略玩一局游戏并记录数据
def play(show=False):
    data = []
    reward_sum = 0

    state = env.reset()
    over = False
    #(1)如果没结束,一直尝试下去
    while not over:
        #(2)大部分情况下从Q值获取动作
        action = Q[state].argmax()
        #(3)贪婪策略,小概率情况下,随机
        if random.random() < 0.1:
            action = env.action_space.sample()
            print("random.random() env.action_space.sample()")
        #(4)输入下一步动作,获取下一步状态state,奖励reward,以及是否结束over
        next_state, reward, over = env.step(action)

        #(5)把每一步的状态state,动作action,奖励reward,下一状态next_state,是否结束over都记下来
        data.append((state, action, reward, next_state, over))
        #(6)计算累积的奖励reward_sum
        reward_sum += reward
        #(7)更新状态
        state = next_state
        print(action,over)
        if show:
            display.clear_output(wait=True)
            env.show()

    return data, reward_sum

(2)简单测试

    data, reward_sum = play()
    print(data,reward_sum)

(3)注意返回的值,为一些列的过程数据state, action, reward, next_state, over和,reward_sum

4.5经验池子Pool

4.5.1池子的定义

  • (1)经验池子的作用是记录以前走过的路,记下来再说,供后续参考。
  • (2)具体记录的数据为上图中data返回的值,包含了(state, action, reward, next_state, over)五个数据。
  • (3)上述贪婪的play步骤一次方法的data记录中可能包含了几十个甚至上百个尝试的步骤。
  • (4)当然pool中记录的数据不会无限的长,太多也没有,比如说记录个1万条数据或者一百万条数据(咔咔,哈哈)。

具体池子的简单代码定义如下

#数据池
class Pool:
    #初始化一个空的元组
    def __init__(self):
        self.pool = []
    #返回长度
    def __len__(self):
        return len(self.pool)
    #获取第i个元素
    def __getitem__(self, i):
        return self.pool[i]

    #更新动作池
    def update(self):
        #每次更新不少于N条新数据
        old_len = len(self.pool)
        # 下述while中len(pool)至少会增加200条
        while len(pool) - old_len < 200:
            #下面的操作一下子可能会添加好多元素,
            # 比如100条,而且有可能重复的
            self.pool.extend(play()[0])

        #只保留最新的N条数据
        self.pool = self.pool[-1_0000:]

    #从Pool中获取一批数据样本
    def sample(self):
        return random.choice(self.pool)

4.5.2池子的简单测试

(1)测试代码

    #(4)数据池
    pool = Pool()
    pool.update()

(2)注意,Pool中的数据简单如下图所示

4.6训练一轮

(1)逻辑如下

  • (1)先更新一下池子Pool,也就是跑了最小N条数据
  • (2)每次更新数据后,训练M次,可修改
  • (3)随机从Pool中抽一条数据
  • (4)Q矩阵当前估计的state下action的价值,最开始Q都是0
  • (5)实际玩了之后得到的reward+下一个状态的价值*0.9
  • (6)value和target应该是相等的,说明Q矩阵的评估准确。如果有误差,则应该以target为准更新Q表,修正它的偏差。这就是TD误差,指评估值之间的偏差,以实际成分高的评估为准进行修正。
  • (7)更新Q表

(2)训练一次的代码如下所示

#训练一轮的函数
def trainOneEpoch( ):
    #(1)先更新一下池子Pool,也就是跑了最小N条数据
    pool.update()
    #(2)每次更新数据后,训练M次,可修改
    for i in range(200):
        #(3)随机从Pool中抽一条数据
        state, action, reward, next_state, over = pool.sample()
        #(4)Q矩阵当前估计的state下action的价值
        # 最Q开始都是0
        value = Q[state, action]

        #(5)实际玩了之后得到的reward+下一个状态的价值*0.9
        target = reward + Q[next_state].max() * 0.9

        #(6)value和target应该是相等的,说明Q矩阵的评估准确
        #如果有误差,则应该以target为准更新Q表,修正它的偏差
        #这就是TD误差,指评估值之间的偏差,以实际成分高的评估为准进行修正
        update = (target - value) * 0.1

        #(7)更新Q表
        Q[state, action] += update

(3)注意,简单训练一轮后,Q值就不全是0

4.7多轮训练

(1)多轮简要代码

    #(6)多轮训练
    for epoch in range(1000):
        trainOneEpoch()
        if epoch % 100 == 0:
            print(epoch, len(pool), play()[-1])

(2)训练过程每100步后的结果

(4)训练后的Q值,随意标注的几个最优动作,马上可以看出来了

{0: "←", 1: "↓", 2: "→", 3: "↑"}

4.8训练结果的使用

(1)训练得到的Q值可以直接使用,简单测试代码

    #(7)最后演示10次训练结果的行走
    for i in range(10):
        play(True)

(2)结果很完美

4.9完整测试代码

(1)完整代码如下

import gymnasium as gym
from matplotlib import pyplot as plt
import numpy as np
import random
from IPython import display

#定义环境
class MyWrapper(gym.Wrapper):
    def __init__(self):
        #is_slippery控制会不会滑
        env = gym.make('FrozenLake-v1',
                       render_mode='rgb_array',
                       is_slippery=False)

        super().__init__(env)
        self.env = env

    def reset(self):
        state, _ = self.env.reset()
        return state

    def step(self, action):
        state, reward, terminated, truncated, info = self.env.step(action)
        over = terminated or truncated

        #走一步扣一份,逼迫机器人尽快结束游戏
        if not over:
            reward = -1

        #掉坑扣100分
        if over and reward == 0:
            reward = -100

        return state, reward, over

    #打印游戏图像
    def show(self):
        plt.figure(figsize=(3, 3))
        plt.imshow(self.env.render())
        plt.show()


#贪婪策略玩一局游戏并记录数据
def play(show=False):
    data = []
    reward_sum = 0

    state = env.reset()
    over = False
    #(1)如果没结束,一直尝试下去
    while not over:
        #(2)大部分情况下从Q值获取动作
        action = Q[state].argmax()
        #(3)贪婪策略,小概率情况下,随机
        if random.random() < 0.1:
            action = env.action_space.sample()
            # print("random.random() env.action_space.sample()")
        #(4)输入下一步动作,获取下一步状态state,奖励reward,以及是否结束over
        next_state, reward, over = env.step(action)

        #(5)把每一步的状态state,动作action,奖励reward,下一状态next_state,是否结束over都记下来
        data.append((state, action, reward, next_state, over))
        #(6)计算累积的奖励reward_sum
        reward_sum += reward
        #(7)更新状态
        state = next_state
        # print(action,over)
        if show:
            display.clear_output(wait=True)
            env.show()

    return data, reward_sum

#数据池
class Pool:
    #初始化一个空的元组
    def __init__(self):
        self.pool = []
    #返回长度
    def __len__(self):
        return len(self.pool)
    #获取第i个元素
    def __getitem__(self, i):
        return self.pool[i]

    #更新动作池
    def update(self):
        #每次更新不少于N条新数据
        old_len = len(self.pool)
        # 下述while中len(pool)至少会增加200条
        while len(pool) - old_len < 200:
            #下面的操作一下子可能会添加好多元素,
            # 比如100条,而且有可能重复的
            self.pool.extend(play()[0])

        #只保留最新的N条数据
        self.pool = self.pool[-1_0000:]

    #从Pool中获取一批数据样本
    def sample(self):
        return random.choice(self.pool)


#训练一轮的函数
def trainOneEpoch( ):
    #(1)先更新一下池子Pool,也就是跑了最小N条数据
    pool.update()
    #(2)每次更新数据后,训练M次,可修改
    for i in range(200):
        #(3)随机从Pool中抽一条数据
        state, action, reward, next_state, over = pool.sample()
        #(4)Q矩阵当前估计的state下action的价值
        # 最Q开始都是0
        value = Q[state, action]

        #(5)实际玩了之后得到的reward+下一个状态的价值*0.9
        target = reward + Q[next_state].max() * 0.9

        #(6)value和target应该是相等的,说明Q矩阵的评估准确
        #如果有误差,则应该以target为准更新Q表,修正它的偏差
        #这就是TD误差,指评估值之间的偏差,以实际成分高的评估为准进行修正
        update = (target - value) * 0.1

        #(7)更新Q表
        Q[state, action] += update


if __name__ == '__main__':
    #(1)创建环境
    env = MyWrapper()
    env.reset()
    # state, reward, over =env.step(2) #先前一步
    # print(state, reward, over)
    # state, reward, over =env.step(2) #先前一步
    # print(state, reward, over)
    # env.show()

    #(2)初始化Q表,定义了每个状态下每个动作的价值    
    Q = np.zeros((16, 4))

    #(3)贪婪策略往往看
    # play(show=True)
    # data, reward_sum = play()
    # print(data,reward_sum)

    #(4)数据池
    pool = Pool()
    # pool.update()

    #(5) 训练1轮
    trainOneEpoch()
    #print(Q)

    #(6)多轮训练
    for epoch in range(1000):
        trainOneEpoch()
        if epoch % 100 == 0:
            print(epoch, len(pool), play()[-1])
    print(Q)

    #(7)最后演示10次训练结果的行走
    for i in range(10):
        play(True)

5、Q-learning算法的基本原理总结

Q-learning算法的核心思想是通过学习一个Q值函数(Q-value Function)来估计在某个状态下采取某个行动的长期回报。Q值函数记作:Q(s, a),其中s表示状态,a表示行动。

Q-learning算法的更新公式为:

其中: - s:当前状态。 - a:当前行动。 - r:获得的即时奖励。 - s':下一个状态。 - a':下一个行动。 - \alpha:学习率,控制更新的步长。 - \gamma:折扣因子,用于控制未来奖励的重要性。

在每个时刻,智能体(Agent)根据当前状态选择一个行动,并根据环境的反馈获得奖励和下一个状态,然后根据上述公式更新Q值函数。智能体的行动选择可以采用贪婪策略(Greedy Policy)或ε-贪婪策略(ε-Greedy Policy)。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表