从0开始机器学习--8.强化学习(超详细概念、策略、算法大全,含代码)

写在前面

从这篇博客开始,我们将进入到一个全新的机器学习方式--强化学习。这是一种近几年越来越流行的学习方式,新兴意味着困难与机会并存。不同于之前提到的八大基础机器学习模型和神经网络模型,对于初学的学习任务来说,强化学习方式很多情况下不存在现存的库来帮助我们完成任务,需要我们自己编写环境以及智能体与环境交互的代码。下面我们就一起来解开强化学习的面纱。

1.python基础;
2.ai模型概念+基础;
3.数据预处理;
4.机器学习模型--1.聚类;2.降维;3.回归(预测);4.分类;
5.正则化技术;
6.神经网络模型--1.概念+基础;2.几种常见的神经网络模型;
7.对回归、分类模型的评价方式;
8.简单强化学习概念;
9.几种常见的启发式算法及应用场景;
10.机器学习延申应用-数据分析相关内容--1.A/B Test;2.辛普森悖论;3.蒙特卡洛模拟;
以及其他的与人工智能相关的学习经历,如数据挖掘、计算机视觉-OCR光学字符识别等。


本文目录

写在前面

总目标

蒙特卡洛树搜索(Monte Carlo Tree Search, MCTS)

MCTS的基本步骤

MCTS的优点

应用场景

基本概念

对象

探索(Exploration)与利用(Exploitation)

ϵ-greedy策略

代码

Boltzmann策略 / Softmax策略

优先级回放(Prioritized Experience Replay)

马尔科夫决策过程(Markov Decision Process, MDP)

马尔可夫性质

马尔可夫过程 

马尔可夫奖励过程

马尔可夫决策过程

贝尔曼方程

强化学习算法

model-based

策略迭代(Policy Iteration)

值迭代(Value Iteration)

对比

代码 基于网格环境的最优路径规划问题

model-free

Q学习(Q-Learning)

基本原理

特点

代码 简单例子:往右走寻找宝藏(一维)

SARSA

基本原理

特点

SARSA(λ)

基本原理

特点

资格迹(Eligibility Trace)

Q学习和SARSA代码、对比 

在线策略(On-policy)与离线策略(Off-policy)学习效果比较

深度 Q 网络(DQN, Deep Q-Network) 

基本原理

关键技术

优势

局限性

代码

 

Actor-Critic (AC) 方法

基本原理

主要流程

A3C(Asynchronous Actor-Critic)

A3C的主要特点

A3C的工作流程

PD算法(Policy Distillation,策略蒸馏)

基本原理

应用场景

与其他方法的比较

分桶方法

什么时候使用分桶方法

分桶方法的步骤

分桶方法的优点与缺点

分桶方法的类型

分桶方法的应用场景:CartPole 问题

总结


强化学习是一种让智能体通过与环境的交互来学习最优策略的方法,王者荣耀的觉悟人机理论上就有可能可以通过强化学习实现。通过试错法,智能体根据奖励信号调整其行为策略,以最大化累积奖励。常用于试错成本不高的游戏、机器人控制等领域。

总目标

强化学习的总目标就是找到一个策略,使得智能体从开始到结束所获得的期累积望奖励和最高。强化学习对于策略的寻找用了蒙特卡洛方法中的随机采样和模拟的思想。

蒙特卡洛树搜索(Monte Carlo Tree Search, MCTS)

是一种用于决策过程的强化学习算法,特别适用于那些具有庞大状态空间和复杂决策树的问题,例如围棋、国际象棋等棋类游戏。MCTS结合了树状搜索和蒙特卡洛模拟(会在《10.数据分析》中具体介绍)的优点,通过模拟和统计的方法逐步构建决策树,从而找到最优策略。

MCTS的基本步骤

1. 选择(Selection):根节点开始,根据某种策略(如UCB1,Upper Confidence Bound 1)选择子节点,逐步深入到一个尚未完全扩展的节点。UCB1的公式为:

UCB1 = \frac{w_i}{n_i} + c \sqrt{\frac{\ln N}{n_i}}

其中,w_i是节点的累积奖励,n_i是节点被访问的次数,N是父节点被访问的总次数,c是一个平衡探索与利用的常数。

2. 扩展(Expansion):在选择的节点上扩展一个或多个子节点(即可能的行动或状态)。如果该节点表示的状态还没有被完全扩展,则从中随机选择一个未探索的子节点进行扩展。

3. 模拟(Simulation):从新扩展的节点开始,进行一次模拟(即在该节点的状态下,随机选择动作直到终止状态)。这一步通常是通过随机策略或者一些简化策略来进行的,用于估计从该节点开始的预期奖励

4. 回溯(Backpropagation):将模拟的结果(通常是胜负或奖励值)沿着路径回传到根节点,更新每个节点的访问次数和累积奖励。

MCTS的优点

  • 无需完整的状态空间:MCTS不需要完全探索所有的状态,而是通过模拟和部分探索来估计策略的效果,非常适合于状态空间非常庞大的问题。
  • 适应性强:MCTS可以灵活地调整探索和利用的平衡,通过调整探索参数来适应不同问题的需求。
  • 渐进式优化:MCTS的性能随着计算时间的增加而提高。更多的模拟和回溯会逐步提升决策的质量。

应用场景

MCTS广泛应用于各种游戏AI(如AlphaGo),决策支持系统以及需要高效搜索和优化策略的复杂问题。它在处理具有高维状态空间、不可预测性和非确定性的问题中表现尤其优越。通过结合多次模拟结果,MCTS可以在有限时间内提供一个近似最优的策略,并且能够随着计算时间的增加而逐步逼近最优解。

基本概念

对象

  1. 智能体(Agent):操作的对象
  2. 环境(Environment):智能体与之交互的外部世界。
  3. 状态(State, S): 智能体在某一时刻的环境状况。
  4. 动作(Action, A): 智能体在某一状态下可以执行的行为。
  5. 奖励(Reward, R): 智能体执行某一动作后环境给予的反馈。
  6. 策略(Policy, π): 决定智能体在给定状态下选择哪个动作的规则。智能体的行为函数。是静态的,且只与当前状态有关。
  7. 价值函数(Value Function, V): 用来估计在某一状态下未来能够获得的总奖励。评判每个动作或状态的好坏。
  8. 折扣因子:避免马尔可夫过程中的无限收益。立即奖励会赢得更多利息比延迟奖励。未来的不确定性。r=0,目光短浅;r=1,目光长远。
  9. Q值(Q-Value, Q): 用来估计在某一状态执行某一动作后未来能够获得的总奖励。
  10. 模型(Model):智能体对环境的理解。
  11. 马尔科夫决策过程(Markov Decision Process, MDP): 强化学习的研究领域。

接下来,将通过简单的代码示例和算法介绍来理清这些对象。

探索(Exploration)利用(Exploitation)

强化学习中的探索利用是智能体面临的关键决策。探索是指尝试不同的动作来发现潜在的更高奖励,而利用则是指选择当前已知的最优动作常用的策略是ϵ-greedy策略

ϵ-greedy策略

ϵ(介于 0 和 1 之间的概率去贪心的尝试未知的、与当前经验不同的动作,ϵ通常是一个小值,例如 0.1 或 0.01,这意味着大部分时间算法会选择当前最优动作,但仍有一定的概率随机探索其他动作,以避免陷入局部最优解

假设在状态 s 下,动作集合为 A(s),我们有一个估计动作价值的函数 Q(s,a)。根据 ϵ-greedy 策略,选择动作 a 的概率如下:

在实际应用中,ϵ 可以是一个固定值,也可以随着时间逐渐减小(如线性递减或指数递减),以使算法在训练的早期阶段更多地探索,而在后期逐渐收敛到最优解。常用于值函数法的强化学习算法中,如 Q-learningSARSA。在这些算法中,动作的 Q 值在不断更新,而 ϵ-greedy 策略能够通过合理的探索和利用,使模型在训练过程中逐渐收敛到一个更优的策略。但是它缺乏对状态的敏感性,不够灵活。

以下是一个简单的例子,多臂老虎机,左右两个老虎机符合不同参数的正态分布,一共操作1000次,求解如何操作这两台机器能获得收益最大化。

代码
import numpy as np

class Bandit:
    def __init__(self, true_mean, true_std):
        """
        Agent: Bandit 类代表强化学习中的智能体,每个 Bandit 实例代表一个老虎机臂,其属性 estimated_mean 可以看作是智能体对动作(拉动臂)价值的估计。
        Environment: 每个 Bandit 的 true_mean 和 true_std 分别表示老虎机臂的真实均值和标准差,代表环境的真实情况。
        State: 在这个多臂老虎机问题中,状态可以看作是每个 Bandit 实例的 estimated_mean 和 num_pulls 的组合,它们随着时间和行为的不同而变化。

        Parameters:
        true_mean (float): 老虎机臂的真实均值
        true_std (float): 老虎机臂的真实标准差
        """
        self.true_mean = true_mean  # 老虎机的真实均值
        self.true_std = true_std  # 老虎机的真实标准差
        self.estimated_mean = (
            998  # 初始估计的价值系数,可以理解为初始的“行动价值”或“策略评估”
        )
        self.num_pulls = 0  # 记录每个臂被拉动的次数,即臂的“访问次数”

    def pull(self):
        """
        Action: 每次调用 pull() 方法即为执行一个动作,即拉动一个老虎机的臂.
        Reward: 每次调用 pull() 方法返回一个奖励,奖励是从 Bandit 的真实分布中随机生成的.

        Returns:
        reward (float): 从老虎机臂中拉动得到的奖励
        """
        return np.random.randn() * self.true_std + self.true_mean

    def update(self, reward):
        """
        Value: estimated_mean 可以看作是智能体对动作价值的估计,即策略评估的一部分.

        Parameters:
        reward (float): 拉动臂后得到的奖励
        """
        self.num_pulls += 1
        self.estimated_mean = (
            (self.num_pulls - 1) * self.estimated_mean + reward
        ) / self.num_pulls


def epsilon_greedy(bandits, epsilon):
    """
    Policy: ε-greedy 算法用于选择动作(选择哪个老虎机的臂进行拉动)的策略,根据当前的估计价值决定是探索(explore)还是利用(exploit).

    Parameters:
    bandits (list): Bandit 对象的列表,每个 Bandit 代表一个老虎机臂
    epsilon (float): 探索率,介于0和1之间的小数

    Returns:
    chosen_bandit_idx (int): 选择的老虎机臂的索引
    """
    if np.random.rand() < epsilon:
        # 探索:随机选择一个臂
        return np.random.choice(len(bandits))
    else:
        # 利用:选择当前估计值最高的臂
        max_mean = max([bandit.estimated_mean for bandit in bandits])
        return np.random.choice(
            [i for i, bandit in enumerate(bandits) if bandit.estimated_mean == max_mean]
        )


# 创建两个多臂老虎机,左边符合(500, 50)的正态分布,右边符合(550, 110)的正态分布
bandit_left = Bandit(500, 50)
bandit_right = Bandit(550, 110)
bandits = [bandit_left, bandit_right]
# 参数设定
epsilon = 0.1
num_steps = 1000
# 运行epsilon-greedy算法
for _ in range(num_steps):
    chosen_bandit_idx = epsilon_greedy(bandits, epsilon)
    reward = bandits[chosen_bandit_idx].pull()
    bandits[chosen_bandit_idx].update(reward)
# 输出结果
print(
    f"Bandit Left - True Mean: {bandit_left.true_mean}, Estimated Mean: {bandit_left.estimated_mean}, Num Pulls: {bandit_left.num_pulls}"
)
print(
    f"Bandit Right - True Mean: {bandit_right.true_mean}, Estimated Mean: {bandit_right.estimated_mean}, Num Pulls: {bandit_right.num_pulls}"
)

输出:

Bandit Left - True Mean: 500, Estimated Mean: 483.070069816252, Num Pulls: 56
Bandit Right - True Mean: 550, Estimated Mean: 548.9987767765474, Num Pulls: 944

Boltzmann策略 / Softmax策略

Boltzmann策略(或称Softmax策略)是一种用于决策时平衡探索和利用的策略。与ϵ-greedy策略的随机探索不同,Boltzmann策略通过概率分布选择动作,基于动作的Q值大小。动作选择的概率与Q值成正比: 

优先级回放(Prioritized Experience Replay)

优先级回放是一种增强经验回放机制的技术。传统的经验回放在DQN等算法(见下)中,随机从经验池中采样数据用于训练,而优先级回放则根据经验的重要性来调整采样的概率,优先选择那些误差较大的经验进行训练。

Boltzmann策略通过软决策概率代替ϵ-greedy的随机选择,提供平滑的探索方式,而优先级回放则使得重要的经验优先用于训练,提升算法学习效率。两者结合能够帮助强化学习算法更好地平衡探索与利用,同时加速收敛。 

马尔科夫决策过程(Markov Decision Process, MDP)

强化学习的研究领域。

马尔可夫性质

未来的状态仅与当前状态有关,与之前状态无关。P(S_{t+1} | S_t, S_{t-1}, \dots, S_0) = P(S_{t+1} | S_t)

马尔可夫过程 

马尔可夫过程是一个没有记忆的随机过程,定义为\{S_0, S_1, S_2, \dots\},其状态转移依赖于状态转移概率 P(S_{t+1} | S_t)

马尔可夫奖励过程

马尔可夫奖励过程为带有价值的马尔可夫过程(链),G_t = R_{t+1} + \gamma R_{t+2} + \gamma^2 R_{t+3} + \dots = \sum_{k=0}^{\infty} \gamma^k R_{t+k+1}

马尔可夫决策过程

马尔可夫决策过程为带有决策的马尔可夫奖励过程,状态S_t和动作 A_t的联合决定状态转移。贝尔曼方程是解决马尔可夫决策过程的核心工具之一。

贝尔曼方程

贝尔曼方程是一组递归关系,它刻画了在一个给定状态下,策略的 价值函数动作价值函数 如何与其后续状态的价值相关联。

 贝尔曼方程为解决 MDP 提供了递归框架,能有效地计算每个状态或动作的价值。这种递归关系是多种强化学习算法(如 动态规划值迭代策略迭代(见下))的基础。通过求解贝尔曼方程,我们可以找到 MDP 的最优策略。

强化学习算法

model-based

动态规划算法中的策略迭代值迭代是求解马尔可夫决策过程(MDP)最优策略的两种经典方法。两者虽然目标相同,但其实现方式有所不同。

策略迭代(Policy Iteration)

策略迭代是一种通过交替执行策略评估和策略改进来找到最优策略的过程。

步骤:

策略迭代是显式地评估策略,然后再改进策略,通常收敛速度较快,因为每次策略改进后都会显著改进当前的策略。 

  • 优点:每次策略改进后通常能大幅提升策略质量,迭代次数相对较少。
  • 缺点:策略评估需要完全收敛,可能在大型状态空间中耗时较长。

值迭代(Value Iteration)

值迭代是通过反复更新价值函数,直接计算最优值函数 V,从而间接得到最优策略。

步骤:

值迭代通过单步更新价值函数,并在最后提取最优策略,因此它避免了每次策略评估中多次计算价值函数的步骤。

  • 优点:直接更新价值函数,适用于大型状态空间,因为每次迭代只需更新一次价值函数,而不是像策略迭代那样完全评估策略。
  • 缺点:可能需要更多的迭代次数才能达到收敛。

对比

代码 基于网格环境的最优路径规划问题
import numpy as np
from copy import copy

def policy_eval(env, values, policies, upper_bound):
    print('\n===== Policy Evalution =====')
    delta = upper_bound
    iteration = 0

    while delta >= upper_bound:
        delta = 0.

        for s in env.states:
            v = values.get(s)
            env.set_state(s)

            action_index = policies.sample(s)
            action = env.actions[action_index]
            _, _, rewards, next_states = env.step(action)

            next_values = values.get(list(next_states))
            td_values = list(map(lambda x, y: x + env.gamma * y, rewards, next_values))

            exp_value = np.mean(td_values)
            values.update(s, exp_value)

            # update delta
            delta = max(delta, abs(v - exp_value))
            
        iteration += 1
        print('\r> iteration: {} delta: {}'.format(iteration, delta), flush=True, end="")
    return iteration  # 返回策略评估的迭代次数

def policy_improve(env, values, policies):
    print('\n===== Policy Improve =====')
    policy_stable = True
    
    for state in env.states:
        old_act = policies.sample(state)

        # calculate new policy execution
        actions = env.actions
        value = [0] * len(env.actions)
        
        for i, action in enumerate(actions):
            env.set_state(state)
            _, _, rewards, next_states = env.step(action)
            next_values = values.get(list(next_states))
            td_values = list(map(lambda x, y: x + env.gamma * y, rewards, next_values))
            prob = [1 / len(next_states)] * len(next_states)

            value[i] = sum(map(lambda x, y: x * y, prob, td_values))

        # action selection
        new_act = actions[np.argmax(value)]

        # greedy update policy
        new_policy = [0.] * env.action_space
        new_policy[new_act] = 1.
        policies.update(state, new_policy)

        if old_act != new_act:
            policy_stable = False

    return policy_stable

def value_iter(env, values, upper_bound):
    print('===== Value Iteration =====')
    delta = upper_bound + 1.
    states = copy(env.states)
    
    iteration = 0

    while delta >= upper_bound:
        delta = 0

        for s in states:
            v = values.get(s)

            # get new value
            actions = env.actions
            vs = [0] * len(actions)

            for i, action in enumerate(actions):
                env.set_state(s)
                _, _, rewards, next_states = env.step(action)
                td_values = list(map(lambda x, y: x + env.gamma * y, rewards, values.get(next_states)))

                vs[i] = np.mean(td_values)

            values.update(s, max(vs))
            delta = max(delta, abs(v - values.get(s)))
        
        iteration += 1
        print('\r> iteration: {} delta: {}'.format(iteration, delta), end="", flush=True)
    return iteration  # 返回价值迭代的迭代次数

class Env:
    def __init__(self):
        self._states = set()
        self._state = None
        self._actions = []
        self._gamma = None
        
    @property
    def states(self):
        return self._states
    
    @property
    def state_space(self):
        return self._state_shape
    
    @property
    def actions(self):
        return self._actions
    
    @property
    def action_space(self):
        return len(self._actions)
    
    @property
    def gamma(self):
        return self._gamma
    
    def _world_init(self):
        raise NotImplementedError
        
    def reset(self):
        raise NotImplementedError
    
    def step(self, state, action):
        """Return distribution and next states"""
        raise NotImplementedError
        
    def set_state(self, state):
        self._state = state


class MatrixEnv(Env):
    def __init__(self, height=4, width=4):
        super().__init__()
        
        self._action_space = 4
        self._actions = list(range(4))
        
        self._state_shape = (2,)
        self._state_shape = (height, width)
        self._states = [(i, j) for i in range(height) for j in range(width)]
        
        self._gamma = 0.9
        self._height = height
        self._width = width

        self._world_init()
        
    @property
    def state(self):
        return self._state
    
    @property
    def gamma(self):
        return self._gamma
    
    def set_gamma(self, value):
        self._gamma = value
        
    def reset(self):
        self._state = self._start_point
        
    def _world_init(self):
        # start_point
        self._start_point = (0, 0)
        self._end_point = (self._height - 1, self._width - 1)
        
    def _state_switch(self, act):
        # 0: h - 1, 1: w + 1, 2: h + 1, 3: w - 1
        if act == 0:  # up
            self._state = (max(0, self._state[0] - 1), self._state[1])
        elif act == 1:  # right
            self._state = (self._state[0], min(self._width - 1, self._state[1] + 1))
        elif act == 2:  # down
            self._state = (min(self._height - 1, self._state[0] + 1), self._state[1])
        elif act == 3:  # left
            self._state = (self._state[0], max(0, self._state[1] - 1))

    def step(self, act):
        assert 0 <= act <= 3
        
        done = False
        reward = 0.

        self._state_switch(act)
        
        if self._state == self._end_point:
            reward = 1.
            done = True

        return None, done, [reward], [self._state]

class ValueTable:
    def __init__(self, env):
        self._values = np.zeros(env.state_space)
        
    def update(self, s, value):
        self._values[s] = value
        
    def get(self, state):
        if type(state) == list:
            # loop get
            res = [self._values[s] for s in state]
            return res
        elif type(state) == tuple:
            # return directly
            return self._values[state]

from collections import namedtuple


Pi = namedtuple('Pi', 'act, prob')


class Policies:
    def __init__(self, env: Env):
        self._actions = env.actions
        self._default_policy = [1 / env.action_space] * env.action_space
        self._policies = dict.fromkeys(env.states, Pi(self._actions, self._default_policy))
    
    def sample(self, state):
        if self._policies.get(state, None) is None:
            self._policies[state] = Pi(self._actions, self._default_policy)

        policy = self._policies[state]
        return np.random.choice(policy.act, p=policy.prob)
    
    def retrieve(self, state):
        return self._policies[state].prob
    
    def update(self, state, policy):
        self._policies[state] = self._policies[state]._replace(prob=policy)

import time

env = MatrixEnv(width=8, height=8)  # TODO(ming): try different word size
policies = Policies(env)
values = ValueTable(env)
upper_bound = 1e-2

stable = False
policy_eval_iterations = 0
policy_improve_iterations = 0

start = time.time()
while not stable:
    policy_eval(env, values, policies, upper_bound)
    stable = policy_improve(env, values, policies)
    policy_improve_iterations += 1
end = time.time()

print(f'\n[Policy Evaluation Iterations]: {policy_eval_iterations}')
print(f'[Policy Improvement Iterations]: {policy_improve_iterations}')
print('\n[time consumpution]: {} s'.format(end - start))

done = False
rewards = 0
env.reset()
step = 0

while not done:
    act_index = policies.sample(env.state)
    _, done, reward, next_state = env.step(env.actions[act_index])
    rewards += sum(reward)
    step += 1

print('Evaluation: [reward] {} [step] {}'.format(rewards, step))

env = MatrixEnv(width=8, height=8)  # try different word size
policies = Policies(env)
values = ValueTable(env)
upper_bound = 1e-4

start = time.time()
value_iter_iterations = value_iter(env, values, upper_bound)
_ = policy_improve(env, values, policies)
end = time.time()

print(f'\n[Value Iteration Iterations]: {value_iter_iterations}')
print('\n[time consumption] {}s'.format(end - start))
# print("===== Render =====")
env.reset()
done = False
rewards = 0
step = 0
while not done:
    act_index = policies.sample(env.state)
    _, done, reward, next_state = env.step(env.actions[act_index])
    rewards += sum(reward)
    step += 1

print('Evaluation: [reward] {} [step] {}'.format(rewards, step))



输出:

===== Policy Evalution =====
> iteration: 1 delta: 0.0
===== Policy Improve =====

===== Policy Evalution =====
> iteration: 45 delta: 0.009697737297875264
===== Policy Improve =====

===== Policy Evalution =====
> iteration: 2 delta: 0.007855167211278768
===== Policy Improve =====

===== Policy Evalution =====
> iteration: 2 delta: 0.006362685441136051
===== Policy Improve =====

===== Policy Evalution =====
> iteration: 2 delta: 0.005153775207320521
===== Policy Improve =====

===== Policy Evalution =====
> iteration: 2 delta: 0.004174557917929533
===== Policy Improve =====

===== Policy Evalution =====
> iteration: 2 delta: 0.0033813919135239345
===== Policy Improve =====

===== Policy Evalution =====
> iteration: 2 delta: 0.002738927449954076
===== Policy Improve =====

===== Policy Evalution =====
> iteration: 2 delta: 0.002218531234462695
===== Policy Improve =====

===== Policy Evalution =====
> iteration: 2 delta: 0.0017970102999154136
===== Policy Improve =====

===== Policy Evalution =====
> iteration: 2 delta: 0.0014555783429317515
===== Policy Improve =====

===== Policy Evalution =====
> iteration: 2 delta: 0.0011790184577753493
===== Policy Improve =====

===== Policy Evalution =====
> iteration: 2 delta: 0.000955004950798255
===== Policy Improve =====

===== Policy Evalution =====
> iteration: 2 delta: 0.0007735540101467819
===== Policy Improve =====

===== Policy Evalution =====
> iteration: 2 delta: 0.0006265787482195861
===== Policy Improve =====

[Policy Evaluation Iterations]: 0
[Policy Improvement Iterations]: 15

[time consumpution]: 0.19951701164245605 s
Evaluation: [reward] 1.0 [step] 14
===== Value Iteration =====
> iteration: 89 delta: 9.404610870067387e-059
===== Policy Improve =====

[Value Iteration Iterations]: 89

[time consumption] 0.20746064186096191s
Evaluation: [reward] 1.0 [step] 14

策略迭代比价值迭代大多数情况下迭代次数确实更少、计算成本确实更大(消耗的时间更多)。随着折扣因子的增大、状态空间的增大、收敛阈值减小,两种迭代策略的迭代次数和所需要的时间也会跟着增加

model-free

但状态空间、环境过大,无法事先建立一个马尔可夫模型刻画。大数定律,通过不断交互刻画环境是一种方法,但会太慢。

蒙特卡洛(MC)策略评估使用经验的平均回报代替期望回报,不需要假设状态是马尔可夫。从完整的轨迹中学习,没有使用bootstrapping(自举:通过当前估计值来更新值函数或策略的技术)

Q学习(Q-Learning)

Q学习是一种无模型的强化学习算法,旨在通过与环境的交互来学习最优策略。它采用一种简单而高效的方式,通过维护一个Q值表来评估状态-动作对的价值,并逐步更新这个表,以便在未来做出更好的决策。这种方式只适用于离散环境

基本原理

Q学习的核心在于更新 Q 值,具体的更新公式为:

这个公式的含义是:通过当前 Q 值和获得的奖励来调整 Q 值,逐渐逼近真实的状态-动作价值。

特点
  • 无模型:Q学习不需要对环境的模型进行建模,而是通过实际与环境的交互来更新 Q 值。这使得它在复杂或不确定的环境中表现良好。
  • 离策略学习:Q学习允许代理使用与当前学习策略不同的策略来生成动作和学习更新。这意味着代理可以探索新的策略,即使这些策略并不是最优的。这种灵活性使得代理能够在未知的环境中更好地学习。
  • 渐进性:通过不断更新 Q 值,Q学习算法在经过足够的探索后,可以逐渐收敛到最优策略。
代码 简单例子:往右走寻找宝藏(一维)
# 2.简单例子:往右走寻找宝藏(一维)   Q-learning 算法(只适用于离散的环境)
import time
import numpy as np
import pandas as pd

np.random.seed(2)

N_STATES = 6                 # 状态。也是最开始位置距离宝藏的距离
ACTIONS = ["left", "right"]  # 行动。往左走还是往右走
MAX_EPISODES = 10            # 只跑10个episode,episode是指一次完整的游戏过程
FRESH_TIME = 1               # 每次刷新时间,即每次刷新屏幕的时间为0.3秒
EPSILON = 0.9                # 探索率。10%的概率随机探索,90%的概率选择最佳动作
ALPHA = 0.1                  # learning rate,即学习率,控制更新的幅度
GAMMA = 0.9                  # discount factor, 即折扣因子, 0.9表示在下一个状态的奖励值乘以0.9,作为当前状态的奖励值, 对未来奖励的衰减值。

# 创建环境
def update_env(S, episode, step_counter):
    env_list = ["-"] * (N_STATES - 1) + ["T"]
    if S == "terminal":
        interaction = "Episode %s: total_steps = %s" % (episode + 1, step_counter)
        print("\r{}".format(interaction), end="")
        time.sleep(2)
        print("\r                                ", end="")
    else:
        env_list[S] = "o"
        interaction = "".join(env_list)
        print("\r{}".format(interaction), end="")
        time.sleep(FRESH_TIME)


# 环境反馈
def get_env_feedback(S, A):
    if A == "right":
        if (
            S == N_STATES - 2
        ):  # 因为从0开始,N-1是终点,所以如果向右走。n-2在往右走一步即可
            S_ = "terminal"
            R = 1
        else:
            S_ = S + 1
            R = 0
    else:
        R = 0
        if S == 0:
            S_ = S
        else:
            S_ = S - 1
    return S_, R

# 构建Q表格
# Q 值表示在特定状态下执行特定动作的预期长期回报(即预计的总奖励)
def build_q_table(n_states, actions):
    table = pd.DataFrame(
        np.zeros((n_states, len(actions))), columns=actions
    )  # 全0初始化
    # 纵轴是状态,横轴是动作,值是Q值
    return table

# 根据状态和Q选择动作
def choose_action(state, q_table):
    state_actions = q_table.iloc[
        state, :
    ]  # 获取该状态(即当前q_table的行)的所有动作的Q值
    if (np.random.uniform() > EPSILON) or ((state_actions == 0).all()):
        action_name = np.random.choice(ACTIONS)  # 10%的概率随机选择动作
    else:
        action_name = state_actions.idxmax()  # 90%的概率选择Q值最大的动作
    return action_name

# 主循环
def rl():
    q_table = build_q_table(N_STATES, ACTIONS)
    for episode in range(MAX_EPISODES):  # 从第一个回合开始,到最后一个回合结束
        print("episode: ", episode)
        # print("q_table: ")
        # print(q_table)
        step_counter = 0
        S = 0  # 初始状态
        is_terminated = False
        update_env(S, episode, step_counter)  # 更新环境
        while not is_terminated:
            A = choose_action(S, q_table)  # 根据当前的初始状态和Q表格选择动作
            S_, R = get_env_feedback(S, A)  # 根据动作反馈环境(下一个状态和奖励)
            q_predict = q_table.loc[S, A]  # 获取当前状态和动作的Q值的估计值
            # .loc[S, A]:用于定位 Q 表格中特定状态 S 和动作 A 所对应的 Q 值。即当前的 Q 表格中记录的值。
            # 也就是上一个episode中,在状态 S 执行动作 A 得到的值,即q-table[S,A]。
            if S_ != "terminal":
                q_target = (
                    R + GAMMA * q_table.iloc[S_, :].max()
                )  # 下一步不是终点的:真实值=奖励+折扣因子*下一个状态的Q值最大值
                # R:即时奖励,表示在执行动作 A 后获得的即时回报。来自get_env_feedback。
                # 这里使用 .iloc 是 Pandas DataFrame 的位置索引方式 .max() 是获取最大值
            else:
                q_target = R  # 下一步是终点的:真实值=奖励
                is_terminated = True

            q_table.loc[S, A] += ALPHA * (q_target - q_predict)  # 更新Q表格
            # 这行代码的结果等同于q_table.loc[S, A] = q_predict + ALPHA * (q_target - q_predict)
            print()
            print("q_table: ")
            print(q_table)
            S = S_  # 更新状态
            update_env(S, episode, step_counter + 1)  # 更新环境
            step_counter += 1
    return q_table

q_table = rl()
print("\r\nQ-table:\n")
print(q_table)
# 每个 episode 中可以同时更新多个状态的 Q 表格。这是因为 Q-learning 算法的更新是基于每个动作执行后的反馈,而不是限制在单一状态的更新过程中。
# 分析qtable:
# 第0个回合:在第一个回合后,我们开始看到Q表中的一些更新:
# 前几次因为表中全为0,无法学习。
# 在第0个回合中,状态4采取右行动的Q值为0.1,表明代理已经学会了从状态4向右移动可能是有益的。
# 第1至第3个回合:随着回合的进行,Q值开始发生变化:
# 例如,到了第3个回合,从状态4采取右行动的Q值增加到了0.271,表明强化学习正在强化这一行动为有利的行为。

# 更新的模式:
# 更新是逐渐增加的,反映了代理在学习过程中探索和利用行动的过程,基于收到的奖励。
# 某些状态和行动可能会更快地收敛到更高的Q值,如果它们在多个回合中导致更高的累积奖励。

# 学习状态:
# Q值是根据从状态采取行动后收到的奖励进行更新的,遵循Q学习算法或类似的强化学习方法。
# 每个回合可能包括多个步骤,代理与环境交互,更新其Q值,并调整其策略以最大化未来的奖励。

# 结论:提供的Q表快照显示了强化学习任务中学习进程的进展。Q值的更新反映了代理如何根据其在环境中的经验来做出决策,以最大化长期奖励。

输出:

episode:  0
o----T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
-o---T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
--o--T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
-o---T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
--o--T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
---o-T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
----oT
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
---o-T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
----oT
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
---o-T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
----oT
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
---o-T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
--o--T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
---o-T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
--o--T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
---o-T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
--o--T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
-o---T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
o----T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
-o---T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
--o--T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
-o---T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
--o--T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
-o---T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
o----T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
-o---T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
--o--T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
-o---T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
o----T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
o----T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
o----T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
o----T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
-o---T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
o----T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
-o---T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
--o--T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
---o-T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
----oT
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.1
5   0.0    0.0
                                episode:  1
o----T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.1
5   0.0    0.0
o----T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.1
5   0.0    0.0
-o---T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.1
5   0.0    0.0
o----T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.1
5   0.0    0.0
-o---T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.1
5   0.0    0.0
o----T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.1
5   0.0    0.0
-o---T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.1
5   0.0    0.0
--o--T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.1
5   0.0    0.0
-o---T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.1
5   0.0    0.0
--o--T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.1
5   0.0    0.0
-o---T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.1
5   0.0    0.0
--o--T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.1
5   0.0    0.0
---o-T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.1
5   0.0    0.0
--o--T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.1
5   0.0    0.0
-o---T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.1
5   0.0    0.0
--o--T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.1
5   0.0    0.0
-o---T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.1
5   0.0    0.0
o----T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.1
5   0.0    0.0
-o---T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.1
5   0.0    0.0
--o--T
q_table:
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.1
5   0.0    0.0
---o-T
q_table:
   left  right
0   0.0  0.000
1   0.0  0.000
2   0.0  0.000
3   0.0  0.009
4   0.0  0.100
5   0.0  0.000
----oT
q_table:
   left  right
0   0.0  0.000
1   0.0  0.000
2   0.0  0.000
3   0.0  0.009
4   0.0  0.190
5   0.0  0.000
                                episode:  2
o----T
q_table:
   left  right
0   0.0  0.000
1   0.0  0.000
2   0.0  0.000
3   0.0  0.009
4   0.0  0.190
5   0.0  0.000
-o---T
q_table:
   left  right
0   0.0  0.000
1   0.0  0.000
2   0.0  0.000
3   0.0  0.009
4   0.0  0.190
5   0.0  0.000
o----T
q_table:
   left  right
0   0.0  0.000
1   0.0  0.000
2   0.0  0.000
3   0.0  0.009
4   0.0  0.190
5   0.0  0.000
-o---T
q_table:
   left  right
0   0.0  0.000
1   0.0  0.000
2   0.0  0.000
3   0.0  0.009
4   0.0  0.190
5   0.0  0.000
o----T
q_table:
   left  right
0   0.0  0.000
1   0.0  0.000
2   0.0  0.000
3   0.0  0.009
4   0.0  0.190
5   0.0  0.000
-o---T
q_table:
   left  right
0   0.0  0.000
1   0.0  0.000
2   0.0  0.000
3   0.0  0.009
4   0.0  0.190
5   0.0  0.000
--o--T
q_table:
   left    right
0   0.0  0.00000
1   0.0  0.00000
2   0.0  0.00081
3   0.0  0.00900
4   0.0  0.19000
5   0.0  0.00000
---o-T
q_table:
   left    right
0   0.0  0.00000
1   0.0  0.00000
2   0.0  0.00081
3   0.0  0.02520
4   0.0  0.19000
5   0.0  0.00000
----oT
q_table:
   left    right
0   0.0  0.00000
1   0.0  0.00000
2   0.0  0.00081
3   0.0  0.02520
4   0.0  0.27100
5   0.0  0.00000
                                episode:  3
o----T
q_table:
   left    right
0   0.0  0.00000
1   0.0  0.00000
2   0.0  0.00081
3   0.0  0.02520
4   0.0  0.27100
5   0.0  0.00000
-o---T
q_table:
   left     right
0   0.0  0.000000
1   0.0  0.000073
2   0.0  0.000810
3   0.0  0.025200
4   0.0  0.271000
5   0.0  0.000000
--o--T
q_table:
   left     right
0   0.0  0.000000
1   0.0  0.000073
2   0.0  0.002997
3   0.0  0.025200
4   0.0  0.271000
5   0.0  0.000000
---o-T
q_table:
   left     right
0   0.0  0.000000
1   0.0  0.000073
2   0.0  0.002997
3   0.0  0.047070
4   0.0  0.271000
5   0.0  0.000000
----oT
q_table:
   left     right
0   0.0  0.000000
1   0.0  0.000073
2   0.0  0.002997
3   0.0  0.047070
4   0.0  0.343900
5   0.0  0.000000
                                episode:  4
o----T
q_table:
   left     right
0   0.0  0.000007
1   0.0  0.000073
2   0.0  0.002997
3   0.0  0.047070
4   0.0  0.343900
5   0.0  0.000000
-o---T
q_table:
   left     right
0   0.0  0.000007
1   0.0  0.000335
2   0.0  0.002997
3   0.0  0.047070
4   0.0  0.343900
5   0.0  0.000000
--o--T
q_table:
      left     right
0  0.00000  0.000007
1  0.00000  0.000335
2  0.00003  0.002997
3  0.00000  0.047070
4  0.00000  0.343900
5  0.00000  0.000000
-o---T
q_table:
      left     right
0  0.00000  0.000007
1  0.00000  0.000572
2  0.00003  0.002997
3  0.00000  0.047070
4  0.00000  0.343900
5  0.00000  0.000000
--o--T
q_table:
      left     right
0  0.00000  0.000007
1  0.00000  0.000572
2  0.00003  0.006934
3  0.00000  0.047070
4  0.00000  0.343900
5  0.00000  0.000000
---o-T
q_table:
      left     right
0  0.00000  0.000007
1  0.00000  0.000572
2  0.00003  0.006934
3  0.00000  0.073314
4  0.00000  0.343900
5  0.00000  0.000000
----oT
q_table:
      left     right
0  0.00000  0.000007
1  0.00000  0.000572
2  0.00003  0.006934
3  0.00000  0.073314
4  0.00000  0.409510
5  0.00000  0.000000
                                episode:  5
o----T
q_table:
      left     right
0  0.00000  0.000057
1  0.00000  0.000572
2  0.00003  0.006934
3  0.00000  0.073314
4  0.00000  0.409510
5  0.00000  0.000000
-o---T
q_table:
      left     right
0  0.00000  0.000057
1  0.00000  0.001138
2  0.00003  0.006934
3  0.00000  0.073314
4  0.00000  0.409510
5  0.00000  0.000000
--o--T
q_table:
      left     right
0  0.00000  0.000057
1  0.00000  0.001138
2  0.00003  0.012839
3  0.00000  0.073314
4  0.00000  0.409510
5  0.00000  0.000000
---o-T
q_table:
      left     right
0  0.00000  0.000057
1  0.00000  0.001138
2  0.00003  0.012839
3  0.00000  0.102839
4  0.00000  0.409510
5  0.00000  0.000000
----oT
q_table:
      left     right
0  0.00000  0.000057
1  0.00000  0.001138
2  0.00003  0.012839
3  0.00000  0.102839
4  0.00000  0.468559
5  0.00000  0.000000
                                episode:  6
o----T
q_table:
      left     right
0  0.00000  0.000154
1  0.00000  0.001138
2  0.00003  0.012839
3  0.00000  0.102839
4  0.00000  0.468559
5  0.00000  0.000000
-o---T
q_table:
      left     right
0  0.00000  0.000154
1  0.00000  0.002180
2  0.00003  0.012839
3  0.00000  0.102839
4  0.00000  0.468559
5  0.00000  0.000000
--o--T
q_table:
      left     right
0  0.00000  0.000154
1  0.00000  0.002180
2  0.00003  0.020810
3  0.00000  0.102839
4  0.00000  0.468559
5  0.00000  0.000000
---o-T
q_table:
      left     right
0  0.00000  0.000154
1  0.00000  0.002180
2  0.00003  0.020810
3  0.00000  0.134725
4  0.00000  0.468559
5  0.00000  0.000000
----oT
q_table:
      left     right
0  0.00000  0.000154
1  0.00000  0.002180
2  0.00003  0.020810
3  0.00000  0.134725
4  0.00000  0.521703
5  0.00000  0.000000
                                episode:  7
o----T
q_table:
      left     right
0  0.00000  0.000335
1  0.00000  0.002180
2  0.00003  0.020810
3  0.00000  0.134725
4  0.00000  0.521703
5  0.00000  0.000000
-o---T
q_table:
      left     right
0  0.00000  0.000335
1  0.00000  0.003835
2  0.00003  0.020810
3  0.00000  0.134725
4  0.00000  0.521703
5  0.00000  0.000000
--o--T
q_table:
      left     right
0  0.00000  0.000335
1  0.00000  0.003835
2  0.00003  0.030854
3  0.00000  0.134725
4  0.00000  0.521703
5  0.00000  0.000000
---o-T
q_table:
      left     right
0  0.00000  0.000335
1  0.00000  0.003835
2  0.00003  0.030854
3  0.00000  0.168206
4  0.00000  0.521703
5  0.00000  0.000000
----oT
q_table:
      left     right
0  0.00000  0.000335
1  0.00000  0.003835
2  0.00003  0.030854
3  0.00000  0.168206
4  0.00000  0.569533
5  0.00000  0.000000
                                episode:  8
o----T
q_table:
      left     right
0  0.00000  0.000647
1  0.00000  0.003835
2  0.00003  0.030854
3  0.00000  0.168206
4  0.00000  0.569533
5  0.00000  0.000000
-o---T
q_table:
      left     right
0  0.00000  0.000647
1  0.00000  0.006228
2  0.00003  0.030854
3  0.00000  0.168206
4  0.00000  0.569533
5  0.00000  0.000000
--o--T
q_table:
      left     right
0  0.00000  0.000647
1  0.00000  0.006228
2  0.00003  0.042907
3  0.00000  0.168206
4  0.00000  0.569533
5  0.00000  0.000000
---o-T
q_table:
      left     right
0  0.00000  0.000647
1  0.00000  0.006228
2  0.00003  0.042907
3  0.00000  0.202643
4  0.00000  0.569533
5  0.00000  0.000000
----oT
q_table:
      left     right
0  0.00000  0.000647
1  0.00000  0.006228
2  0.00003  0.042907
3  0.00000  0.202643
4  0.00000  0.612580
5  0.00000  0.000000
                                episode:  9
o----T
q_table:
      left     right
0  0.00000  0.001142
1  0.00000  0.006228
2  0.00003  0.042907
3  0.00000  0.202643
4  0.00000  0.612580
5  0.00000  0.000000
-o---T
q_table:
      left     right
0  0.00000  0.001142
1  0.00000  0.009467
2  0.00003  0.042907
3  0.00000  0.202643
4  0.00000  0.612580
5  0.00000  0.000000
--o--T
q_table:
      left     right
0  0.00000  0.001142
1  0.00000  0.009467
2  0.00003  0.056855
3  0.00000  0.202643
4  0.00000  0.612580
5  0.00000  0.000000
---o-T
q_table:
      left     right
0  0.00000  0.001142
1  0.00000  0.009467
2  0.00003  0.056855
3  0.00000  0.237511
4  0.00000  0.612580
5  0.00000  0.000000
----oT
q_table:
      left     right
0  0.00000  0.001142
1  0.00000  0.009467
2  0.00003  0.056855
3  0.00000  0.237511
4  0.00000  0.651322
5  0.00000  0.000000

Q-table:

      left     right
0  0.00000  0.001142
1  0.00000  0.009467
2  0.00003  0.056855
3  0.00000  0.237511
4  0.00000  0.651322
5  0.00000  0.000000

SARSA

SARSA 是一种强化学习算法,类似于 Q 学习,但它使用 "on-policy" 策略来更新 Q 值。这意味着 SARSA 更新时所依赖的动作是基于当前策略选择的,而不是像 Q 学习那样假设总是选择最优动作。这使得 SARSA 更加保守,相比 Q 学习更少倾向于冒险。on-policy Temporal Difference Control Algorithm在策略时序差分控制算法

SARSA也同样是基于Q-table的,不要以为Q-table是Q 学习的专利。

基本原理

其算法公式和代码相比Q-learning方式其实只有对Q表的更新方式不一样,其他都是一样的。

特点
  • On-policy(依策略)学习:SARSA 依赖当前策略(如 ε-greedy 策略)生成的动作进行学习,更新时使用的动作 a′ 来自当前的策略执行。这意味着 SARSA 会根据其实际选择的动作来更新 Q 值,而不是总是假设选取最优动作
  • 更保守:由于 SARSA 更新依赖的是当前策略而不是最优动作,它在面对高风险的选择时表现得更为保守。例如,在一个可能包含负奖励的路径上,SARSA 可能会避免探索高风险的动作。
  • 收敛性:在一定条件下,SARSA 的收敛性是可以证明的,特别是在 ε-greedy 策略下,随着探索的逐渐减少,SARSA 可以收敛到最优策略。
SARSA(λ)

SARSA(λ) 是 SARSA 的一种改进版本,它结合了 时序差分(TD)学习资格迹(Eligibility Trace) 的思想,用于加速学习过程。

基本原理

SARSA(λ) 的基本思路是在更新 Q 值时,不仅更新当前状态-动作对的 Q 值,还会根据资格迹的权重对之前访问的状态-动作对进行更新。通过这种方式,SARSA(λ) 能够在策略执行过程中“回溯”地更新多个状态-动作对的 Q 值,进一步加快学习速度。

SARSA(λ) 的 Q 值更新公式如下:

特点
  • 权衡短期和长期更新:通过参数 λ 控制是仅更新最近访问的状态,还是回溯更新更多过去的状态。较大的 λ 允许更多的状态-动作对被更新,有助于加速学习。
  • 加速收敛:相比标准 SARSA,SARSA(λ) 能更快收敛到最优策略,因为它能同时更新多个状态-动作对,而不是仅仅更新当前的状态-动作对。
资格迹(Eligibility Trace)
  • 资格迹是用来记录每个状态-动作对的“资格”,即该状态-动作对最近是否被访问过以及它们的重要性
  • 当某个状态-动作对被访问时,它的资格迹值会增加(通常初始化为1),随着时间的推移,未被访问的状态-动作对的资格迹值会逐渐衰减。
  • 资格迹的衰减由一个参数 λ 控制, λ 的取值在 [0,1] 之间, λ=0 相当于普通的 SARSA,而 λ=1 则表示会完全更新每一步的所有状态-动作对。λ取值越大,离宝藏越近的步越重视

Q学习和SARSA代码、对比 

学习如何在迷宫中找到最佳路径,使得玩家能够以最优策略到达目标点,并避免陷入障碍物。

maze_env.py

  • 使用Tkinter构建了一个8x8的网格作为迷宫,每个格子有40个像素。网格中的红色矩形代表玩家,黄色椭圆代表目标点,黑色方块为障碍物。
  • 玩家可以采取四个动作(上、下、左、右),通过调用step()函数执行动作并获取当前状态、奖励以及是否到达终点的反馈。
import numpy as np
import time
import tkinter as tk


UNIT = 40  # pixels每个格子的像素大小
MAZE_H = 8  # grid height迷宫的高度(格子数)
MAZE_W = 8  # grid width迷宫的宽度(格子数)


class Maze(tk.Tk, object):
    # Maze 类继承自 tk.Tk,这是 Tkinter 创建窗口的类。__init__ 方法初始化迷宫的基本属性,包括动作空间(上下左右),窗口的标题和大小,并调用 _build_maze 方法创建迷宫。
    def __init__(self):
        super(Maze, self).__init__()
        self.action_space = ["u", "d", "l", "r"]
        self.n_actions = len(self.action_space)
        self.title("maze")
        self.geometry("{0}x{1}".format(MAZE_H * UNIT, MAZE_H * UNIT))
        self._build_maze()

    def _build_maze(self):
        self.canvas = tk.Canvas(
            self, bg="white", height=MAZE_H * UNIT, width=MAZE_W * UNIT
        )

        # create grids创建网格线
        for c in range(0, MAZE_W * UNIT, UNIT):
            # 表示从 0 到 MAZE_W * UNIT(即迷宫宽度的像素数),步长为 UNIT(每个格子的像素大小)。
            x0, y0, x1, y1 = (
                c,
                0,
                c,
                MAZE_H * UNIT,
            )  # x0, y0 是线的起点坐标,x1, y1 是线的终点坐标。
            self.canvas.create_line(x0, y0, x1, y1)  # 画出竖线。
        for r in range(0, MAZE_H * UNIT, UNIT):
            x0, y0, x1, y1 = 0, r, MAZE_W * UNIT, r
            self.canvas.create_line(x0, y0, x1, y1)

        # create origin创建起点
        origin = np.array([20, 20])

        # hell创建障碍物
        hell1_center = origin + np.array([UNIT * 2, UNIT])
        # 计算“地狱”方块的中心坐标,相对于起点的偏移量是 UNIT * 2 和 UNIT
        self.hell1 = self.canvas.create_rectangle(
            hell1_center[0] - 15,
            hell1_center[1] - 15,
            hell1_center[0] + 15,
            hell1_center[1] + 15,
            fill="black",
        )
        # (hell1_center[0] - 15, hell1_center[1] - 15) 和 (hell1_center[0] + 15, hell1_center[1] + 15),即以 hell1_center 为中心,向四个方向各延伸 15 像素。
        # hell
        hell2_center = origin + np.array([UNIT, UNIT * 2])
        self.hell2 = self.canvas.create_rectangle(
            hell2_center[0] - 15,
            hell2_center[1] - 15,
            hell2_center[0] + 15,
            hell2_center[1] + 15,
            fill="black",
        )
        # hell
        hell3_center = origin + np.array([UNIT * 2, UNIT * 6])
        self.hell3 = self.canvas.create_rectangle(
            hell3_center[0] - 15,
            hell3_center[1] - 15,
            hell3_center[0] + 15,
            hell3_center[1] + 15,
            fill="black",
        )
        # hell
        hell4_center = origin + np.array([UNIT * 6, UNIT * 2])
        self.hell4 = self.canvas.create_rectangle(
            hell4_center[0] - 15,
            hell4_center[1] - 15,
            hell4_center[0] + 15,
            hell4_center[1] + 15,
            fill="black",
        )
        # hell
        hell5_center = origin + np.array([UNIT * 4, UNIT * 4])
        self.hell5 = self.canvas.create_rectangle(
            hell5_center[0] - 15,
            hell5_center[1] - 15,
            hell5_center[0] + 15,
            hell5_center[1] + 15,
            fill="black",
        )
        # hell
        hell6_center = origin + np.array([UNIT * 4, UNIT * 1])
        self.hell6 = self.canvas.create_rectangle(
            hell6_center[0] - 15,
            hell6_center[1] - 15,
            hell6_center[0] + 15,
            hell6_center[1] + 15,
            fill="black",
        )
        # hell
        hell7_center = origin + np.array([UNIT * 1, UNIT * 3])
        self.hell7 = self.canvas.create_rectangle(
            hell7_center[0] - 15,
            hell7_center[1] - 15,
            hell7_center[0] + 15,
            hell7_center[1] + 15,
            fill="black",
        )
        # hell
        hell8_center = origin + np.array([UNIT * 2, UNIT * 4])
        self.hell8 = self.canvas.create_rectangle(
            hell8_center[0] - 15,
            hell8_center[1] - 15,
            hell8_center[0] + 15,
            hell8_center[1] + 15,
            fill="black",
        )
        # hell
        hell9_center = origin + np.array([UNIT * 3, UNIT * 2])
        self.hell9 = self.canvas.create_rectangle(
            hell9_center[0] - 15,
            hell9_center[1] - 15,
            hell9_center[0] + 15,
            hell9_center[1] + 15,
            fill="black",
        )

        # create oval创建目标点
        oval_center = origin + UNIT * 3  # 相当于origin + np.array([UNIT * 3, UNIT * 3])
        self.oval = self.canvas.create_oval(
            oval_center[0] - 15,
            oval_center[1] - 15,
            oval_center[0] + 15,
            oval_center[1] + 15,
            fill="yellow",
        )

        # create red rect创建红色矩形(玩家)
        self.rect = self.canvas.create_rectangle(
            origin[0] - 15, origin[1] - 15, origin[0] + 15, origin[1] + 15, fill="red"
        )

        # pack all将所有元素添加到画布
        self.canvas.pack()

    def reset(self):  # 用于重置迷宫,将玩家移回起点,并返回玩家的当前位置。
        self.update()
        time.sleep(0.5)
        self.canvas.delete(self.rect)
        origin = np.array([20, 20])
        self.rect = self.canvas.create_rectangle(
            origin[0] - 15, origin[1] - 15, origin[0] + 15, origin[1] + 15, fill="red"
        )
        # return observation
        return self.canvas.coords(self.rect)

    def step(self, action):  # 进行一步动作,并返回下一个状态、奖励和是否结束的布尔值。
        s = self.canvas.coords(self.rect)
        base_action = np.array([0, 0])
        if action == 0:  # up
            if s[1] > UNIT:
                base_action[1] -= UNIT
        elif action == 1:  # down
            if s[1] < (MAZE_H - 1) * UNIT:
                base_action[1] += UNIT
        elif action == 2:  # right
            if s[0] < (MAZE_W - 1) * UNIT:
                base_action[0] += UNIT
        elif action == 3:  # left
            if s[0] > UNIT:
                base_action[0] -= UNIT

        self.canvas.move(self.rect, base_action[0], base_action[1])  # move agent

        s_ = self.canvas.coords(self.rect)  # next state

        # reward function奖励函数
        if s_ == self.canvas.coords(self.oval):
            reward = 1
            done = True
            s_ = "terminal"
        elif s_ in [
            self.canvas.coords(self.hell1),
            self.canvas.coords(self.hell2),
            self.canvas.coords(self.hell3),
            self.canvas.coords(self.hell4),
            self.canvas.coords(self.hell5),
            self.canvas.coords(self.hell6),
            self.canvas.coords(self.hell7),
            self.canvas.coords(self.hell8),
            self.canvas.coords(self.hell9),
        ]:
            reward = -1
            done = True
            s_ = "terminal"
        else:
            reward = 0
            done = False

        return s_, reward, done

    def render(self):  # 渲染迷宫
        time.sleep(0.1)
        self.update()


def update():  # 测试迷宫运行,重置迷宫并进行一系列动作
    for t in range(10):
        s = env.reset()
        while True:
            env.render()
            a = 1
            s, r, done = env.step(a)
            if done:
                break


if __name__ == "__main__":
    env = Maze()
    env.after(100, update)
    env.mainloop()

Q学习-RL_brain.py

import numpy as np
import pandas as pd


# Q-learning:off-policy TD control algorithm离策略时序差分控制算法
class QLearningTable:
    def __init__(
        self, actions, learning_rate=0.01, reward_decay=0.9, e_greedy=0.9
    ):  # reward_decay:即gamma,即衰减率,即下一个状态的奖励值与当前状态的奖励值的比值,越接近1,说明当前状态的价值越高,越接近0,说明当前状态的价值越低。e_greedy:即epsilon-greedy,即随机探索的概率。
        self.actions = actions
        self.lr = learning_rate
        self.gamma = reward_decay
        self.epsilon = e_greedy
        self.q_table = pd.DataFrame(
            columns=self.actions, dtype=np.float64
        )  # 初始化空的qtable,check_state_exist函数会将没遇到过的状态添加到qtable中

    def choose_action(self, observation):
        self.check_state_exist(
            observation
        )  # 检验观察值是否在qtable中,如果不在,则添加到qtable中
        # epsilon-greedy策略
        if np.random.uniform() < self.epsilon:
            state_action = self.q_table.loc[observation, :]
            action = np.random.choice(
                state_action[state_action == np.max(state_action)].index
            )  # 若qtable中存在值相同的状态,则随机选取其中一个
        else:
            action = np.random.choice(self.actions)
        return action

    def learn(self, s, a, r, s_):
        self.check_state_exist(
            s_
        )  # 检验观察值是否在qtable中,如果不在,则添加到qtable中
        # q-learning算法,更新qtable,和简单例子一样
        q_predict = self.q_table.loc[s, a]
        if s_ != "terminal":
            q_target = r + self.gamma * self.q_table.loc[s_, :].max()
        else:
            q_target = r
        self.q_table.loc[s, a] += self.lr * (q_target - q_predict)

    def check_state_exist(self, state):
        if state not in self.q_table.index:
            new_row = pd.Series(
                [0] * len(self.actions), index=self.q_table.columns, name=state
            )
            self.q_table = pd.concat([self.q_table, pd.DataFrame([new_row])])

 Q学习-run_this.py

from maze_env import Maze
from RL_brain import QLearningTable
import time

def update():  # openai gym环境中也适用如下这种训练方式
    for episode in range(150):
        observation = env.reset()  # 给出初始状态和玩家的初始位置信息
        print(episode)
        while True:  # 玩家一直在环境中行动,直到游戏结束,一整步的状态变换
            env.render()  # 与环境互动,重新渲染环境
            action = RL.choose_action(str(observation))  # 基于观测值选择动作
            # print("observation: {}".format(observation))
            observation_, reward, done = env.step(
                action
            )  # 在环境中执行动作并得到下一个观测值和奖励
            RL.learn(
                str(observation), action, reward, str(observation_)
            )  # 若回合没完,就学习上一次观测值、动作、奖励和下一次观测值之间的关系
            # print(RL.q_table)
            observation = observation_  # 更新当前状态为下一个状态
            if done:
                break
    print("game over")
    env.destroy()


if __name__ == "__main__":
    env = Maze()
    # print("env.n_actions: {}".format(env.n_actions))
    RL = QLearningTable(actions=list(range(env.n_actions)))
    start_time = time.time()
    env.after(100, update)
    env.mainloop()
    end_time = time.time()
    print(end_time-start_time)

 SARSA-RL_brain.py

import numpy as np
import pandas as pd


# SARSA:on-policy Temporal Difference Control Algorithm在策略时序差分控制算法
class SarsaTable:
    def __init__(
        self, actions, learning_rate=0.01, reward_decay=0.9, e_greedy=0.9
    ):  # reward_decay:即gamma,即衰减率,即下一个状态的奖励值与当前状态的奖励值的比值,越接近1,说明当前状态的价值越高,越接近0,说明当前状态的价值越低。e_greedy:即epsilon-greedy,即随机探索的概率。
        self.actions = actions
        self.lr = learning_rate
        self.gamma = reward_decay
        self.epsilon = e_greedy
        self.q_table = pd.DataFrame(
            columns=self.actions, dtype=np.float64
        )  # 初始化空的qtable,check_state_exist函数会将没遇到过的状态添加到qtable中

    def choose_action(self, observation):
        self.check_state_exist(
            observation
        )  # 检验观察值是否在qtable中,如果不在,则添加到qtable中
        # epsilon-greedy策略
        if np.random.uniform() < self.epsilon:
            state_action = self.q_table.loc[observation, :]
            action = np.random.choice(
                state_action[state_action == np.max(state_action)].index
            )  # 若qtable中存在值相同的状态,则随机选取其中一个
        else:
            action = np.random.choice(self.actions)
        return action

    def learn(self, s, a, r, s_, a_):
        self.check_state_exist(
            s_
        )  # 检验观察值是否在qtable中,如果不在,则添加到qtable中
        # Sarsa算法,更新qtable,q-target = r + gamma * q_table(s_,a_)
        q_predict = self.q_table.loc[s, a]
        if s_ != "terminal":
            q_target = r + self.gamma * self.q_table.loc[s_, a_]

        else:
            q_target = r
        self.q_table.loc[s, a] += self.lr * (q_target - q_predict)  # 当前实际-当前估计

    def check_state_exist(self, state):
        if state not in self.q_table.index:
            new_row = pd.Series(
                [0] * len(self.actions), index=self.q_table.columns, name=state
            )
            self.q_table = pd.concat([self.q_table, pd.DataFrame([new_row])])

 SARSA-run_this.py

from maze_env import Maze
from RL_brain import SarsaTable
import time

# Q-Learning 使用下一个状态的最大 Q 值进行更新,是离策略算法。
# SARSA 使用当前策略选择的下一个动作的 Q 值进行更新,是在策略算法。
def update():
    for episode in range(150):
        observation = env.reset()
        action = RL.choose_action(
            str(observation)
        )  # 这里是与Q-learning不同的地方,不在动作循环内选择action
        print(episode)
        while True:
            env.render()
            observation_, reward, done = env.step(action)
            action_ = RL.choose_action(
                str(observation_)
            )  # 这里是与Q-learning不同的地方
            RL.learn(str(observation), action, reward, str(observation_), action_)
            observation = observation_
            action = action_

            if done:
                break

    print("game over")
    env.destroy()


if __name__ == "__main__":
    env = Maze()
    RL = SarsaTable(actions=list(range(env.n_actions)))
    start_time = time.time()
    env.after(100, update)
    env.mainloop()
    end_time = time.time()
    print(end_time-start_time)

Q-learning的效率高于SARSA,分别使用Q-learning和SARSA训练150个回合并记录时间。Q-learning大致能在50-60个回合时首次找到黄点,但由于离策略,大致在90个回合后才能稳定找到黄点,150个回合用时约487s;SARSA由于在策略,在探索阶段更为谨慎,大致在30个回合起会陷在左上角局部,大致在65-75个回合时首次找到黄点,80个回合后便几乎能稳定找到黄点,150个回合用时约1300s。 

在线策略(On-policy)与离线策略(Off-policy)学习效果比较

1. 学习目标与策略更新

  • 在线策略:在线策略直接从当前使用的策略(行为策略)生成经验并更新策略,即当前的策略会被用来生成样本,并且这些样本直接用于更新同一个策略,如SARSA
  • 离线策略:离线策略则从不同于当前执行策略的行为策略中学习。离线策略使用从其他策略或历史经验中采集的数据来更新当前策略,如Q-learning使用一个固定的策略(如 ε-greedy)生成数据,但学习的是最优策略。

2. 样本效率

  • 在线策略:样本效率通常较低,因为每次更新都依赖于新生成的经验。尤其在高维或复杂环境下,收集新的交互数据可能非常耗时。
  • 离线策略:可以重复利用历史数据进行训练,从而提高样本效率,尤其适合那些通过经验重放或者批量训练的算法。故Off-policy方法往往比On-policy更具数据效率。

3. 收敛性与稳定性

  • 在线策略:由于策略改进直接基于当前执行的策略,在线策略通常收敛更稳定,尤其在噪声较多或环境不确定性较高的场景中。然而,由于它只能逐步更新当前策略,可能需要更多的时间来达到全局最优。
  • 离线策略:理论上,Off-policy 的收敛性可能会更快,因为它能够从不同的策略中学习。然而由于目标策略和行为策略不同,容易引入偏差,可能导致不稳定或发散。

4. 探索与利用

  • 在线策略:因为直接从执行的策略中学习,在线策略往往会受限于当前策略的探索能力,特别是当使用贪婪策略时,可能会过早地陷入局部最优。
  • 离线策略:由于行为策略和目标策略可以不同,离线策略往往允许更广泛的探索。例如,可以使用一个较为随机的行为策略来收集数据,同时学习一个接近最优的目标策略,增强探索能力。

5. 适用场景

  • 在线策略:更适合于交互成本较低且策略与环境可以同步更新的场景。例如,实时的机器人控制、游戏中的策略学习等,它们可以在每一步进行策略更新。
  • 离线策略:更适合于数据采集代价较高或需要从历史数据中学习的场景。例如,自动驾驶、推荐系统等领域,由于采集数据成本高,往往需要利用大量历史经验进行学习。

6. 实际应用中的选择

  • 在线策略:在一些实时互动系统中,尤其是那些需要立即采取行动并更新策略的场景,在线策略可能表现更稳定。
    • 优点:在策略改进的过程中,能够确保所学的策略与正在执行的策略一致,通常在某些随机环境下有更稳定的表现。
    • 缺点:需要不断采集新的数据进行更新,不能利用过去的经验,样本效率较低,特别是当采样代价较高或环境变化较大时,效果可能受到影响。
  • 离线策略:在需要处理大量数据、可以重复利用过去经验的环境中,比如深度Q网络(DQN)中经验回放的使用,Off-policy 是更合适的选择。
    • 优点:可以利用以前的经验进行学习,样本效率更高,同时允许从多种行为策略中学习。这种方法适合复杂、昂贵或变化频繁的环境,或者需要从大规模经验池中学习的场景。
    • 缺点:离线策略更新时由于行为策略和目标策略不一致,可能会导致策略偏差或不稳定性(如过估计问题)。

两者各有优缺点,实际应用中通常根据具体需求选择或结合使用(如A3C(见下)结合On-policy和Off-policy的思想)。

深度 Q 网络(DQN, Deep Q-Network) 

一种基于 Q 学习的强化学习算法,通过使用神经网络近似 Q 值函数,来解决具有高维状态空间的复杂任务问题。DQN 的出现标志着深度强化学习的成功,尤其在像 Atari 游戏等复杂环境中取得了突破性的成果。

在传统 Q 学习中,Q 值表用于存储每个状态-动作对的 Q 值,但当状态空间和动作空间变得非常大或连续时,存储所有状态-动作对的 Q 值是不可行的。DQN 通过引入深度神经网络来逼近 Q 值,解决了这一问题。

基本原理

DQN 的基本思想是使用一个深度神经网络来近似 Q 值函数,即对于每个状态 s 和动作 a,使用神经网络计算出 Q 值 Q(s,a;θ),其中 θ 是网络的参数。

关键技术
  • 经验回放(Experience Replay): 经验回放机制用于解决强化学习中数据高度相关性的问题。代理(智能体)在环境中生成的经验(即状态、动作、奖励、下一个状态的四元组)被存储在一个经验池中,然后从经验池中随机抽取小批量的经验来训练神经网络打破了时间上相邻经验的相关性,提高了训练的稳定性。
  • 目标网络(Target Network): DQN 使用了两个神经网络一个是用于学习和更新的 Q 网络,另一个是用于计算目标值的目标网络。目标网络的参数 θ− 以较低的频率从 Q 网络的参数 θ 进行复制更新,减缓了目标值的变化,进而稳定了训练过程。
  • ε-贪心策略(ε-Greedy Policy): DQN 使用 ε-贪心策略来平衡探索和利用。当以概率 ε 选择随机动作(探索),以 1−ε 的概率选择当前 Q 值最大的动作(利用)。
优势
  • 适用于高维状态空间:由于使用神经网络逼近 Q 值,DQN 能够处理复杂的、具有高维度的状态空间问题。
  • 经验回放的使用:通过存储和重用经验,DQN 提高了数据利用率,并减少了时间相关性对学习的影响。
  • 目标网络的引入:目标网络通过减缓 Q 值的变化,提高了算法的稳定性。
局限性
  • 动作空间维度问题:DQN 无法直接处理连续的动作空间,通常适用于离散动作问题。
  • 过估计问题:由于使用 max⁡aQ(s′,a) 更新 Q 值,可能会导致过估计问题。
代码

DQN模型控制一个智能体(红色方块)在迷宫中找到黄色终点,同时避免黑色障碍物。迷宫是一个4x4的网格,智能体在每个格子上可以选择向上、下、左、右四个方向移动。智能体的目标是在最短步数内到达目标点(黄色终点),并避免陷入“陷阱”(黑色障碍物)。

maze_env.py

Maze类构建了迷宫的界面,定义了智能体(红色方块)、目标点(黄色圆形)、障碍物(黑色方块),并提供了重置、移动和渲染功能。

import numpy as np
import time
import tkinter as tk

UNIT = 40  # pixels
MAZE_H = 4  # grid height
MAZE_W = 4  # grid width


class Maze(tk.Tk, object):
    def __init__(self):
        super(Maze, self).__init__()
        self.action_space = ["u", "d", "l", "r"]
        self.n_actions = len(self.action_space)
        self.n_features = 2
        self.title("maze")
        self.geometry("{0}x{1}".format(MAZE_H * UNIT, MAZE_H * UNIT))
        self._build_maze()

    def _build_maze(self):
        self.canvas = tk.Canvas(
            self, bg="white", height=MAZE_H * UNIT, width=MAZE_W * UNIT
        )

        # create grids
        for c in range(0, MAZE_W * UNIT, UNIT):
            x0, y0, x1, y1 = c, 0, c, MAZE_H * UNIT
            self.canvas.create_line(x0, y0, x1, y1)
        for r in range(0, MAZE_H * UNIT, UNIT):
            x0, y0, x1, y1 = 0, r, MAZE_W * UNIT, r
            self.canvas.create_line(x0, y0, x1, y1)

        # create origin
        origin = np.array([20, 20])

        # hell
        hell1_center = origin + np.array([UNIT * 2, UNIT])
        self.hell1 = self.canvas.create_rectangle(
            hell1_center[0] - 15,
            hell1_center[1] - 15,
            hell1_center[0] + 15,
            hell1_center[1] + 15,
            fill="black",
        )
        # hell
        # hell2_center = origin + np.array([UNIT, UNIT * 2])
        # self.hell2 = self.canvas.create_rectangle(
        #     hell2_center[0] - 15, hell2_center[1] - 15,
        #     hell2_center[0] + 15, hell2_center[1] + 15,
        #     fill='black')

        # create oval
        oval_center = origin + UNIT * 2
        self.oval = self.canvas.create_oval(
            oval_center[0] - 15,
            oval_center[1] - 15,
            oval_center[0] + 15,
            oval_center[1] + 15,
            fill="yellow",
        )

        # create red rect
        self.rect = self.canvas.create_rectangle(
            origin[0] - 15, origin[1] - 15, origin[0] + 15, origin[1] + 15, fill="red"
        )

        # pack all
        self.canvas.pack()

    def reset(self):
        self.update()
        time.sleep(0.1)
        self.canvas.delete(self.rect)
        origin = np.array([20, 20])
        self.rect = self.canvas.create_rectangle(
            origin[0] - 15, origin[1] - 15, origin[0] + 15, origin[1] + 15, fill="red"
        )
        # return observation
        return (
            np.array(self.canvas.coords(self.rect)[:2])
            - np.array(self.canvas.coords(self.oval)[:2])
        ) / (MAZE_H * UNIT)

    def step(self, action):
        s = self.canvas.coords(self.rect)
        base_action = np.array([0, 0])
        if action == 0:  # up
            if s[1] > UNIT:
                base_action[1] -= UNIT
        elif action == 1:  # down
            if s[1] < (MAZE_H - 1) * UNIT:
                base_action[1] += UNIT
        elif action == 2:  # right
            if s[0] < (MAZE_W - 1) * UNIT:
                base_action[0] += UNIT
        elif action == 3:  # left
            if s[0] > UNIT:
                base_action[0] -= UNIT

        self.canvas.move(self.rect, base_action[0], base_action[1])  # move agent

        next_coords = self.canvas.coords(self.rect)  # next state

        # reward function
        if next_coords == self.canvas.coords(self.oval):
            reward = 1
            done = True
        elif next_coords in [self.canvas.coords(self.hell1)]:
            reward = -1
            done = True
        else:
            reward = 0
            done = False
        s_ = (
            np.array(next_coords[:2]) - np.array(self.canvas.coords(self.oval)[:2])
        ) / (MAZE_H * UNIT)
        return s_, reward, done

    def render(self):
        time.sleep(0.01)
        self.update()

RL_brain.py

智能体在每个状态下可以执行四种动作(上下左右),根据动作的选择更新智能体的位置,计算奖励(到达目标点得1分,撞上障碍物扣1分,其他情况得0分)。

  • 状态是迷宫中智能体和目标点的位置差值。
  • 动作是智能体可以选择的四个方向(上下左右)。
  • 奖励是根据智能体在迷宫中的表现给定的。
  • DQN使用了两个神经网络:一个用于评估当前的Q值,另一个是目标网络,用于固定一段时间内的Q值目标。通过经验回放和固定Q值更新机制,智能体可以逐渐学习出最优的策略。
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
import matplotlib.pyplot as plt

# q-table的形式的弊端:如果状态有很多,那么更新起来会很慢,而且容易出现状态空间爆炸的问题。
# DQN:将状态和动作当作神经网络的输入值,将Q值作为输出值,通过训练神经网络来学习状态-动作值函数,从而得到最优的动作。
# 或DQN可以只输入一个状态,计算出不同动作的Q值,然后根据qlearning,即选择Q值最大的动作。
# 新NN=老NN+α(Q现实-Q估计) α:学习率,Q现实:神经网络输出的Q值,Q估计:Q-table中存储的Q值。
# 此外,DQN还有experience replay机制,即将经验存储在记忆库中,然后随机抽取一批经验进行训练。以及fixed q-target机制,即每隔一定的步数更新Q-target网络。预测Q估计使用最新的参数,而预测Q现实使用很久以前的参数。
# 记忆库,即建立两个相同的神经网络,一个是Q估计网络,另一个是Q现实网络。Q估计网络用于预测Q值,Q现实网络用于更新Q值。
np.random.seed(1)
torch.manual_seed(1)


# define the network architecture
class Net(nn.Module):
    def __init__(self, n_feature, n_hidden, n_output):
        super(Net, self).__init__()
        self.el = nn.Linear(n_feature, n_hidden)
        self.q = nn.Linear(n_hidden, n_output)

    def forward(self, x):
        x = self.el(x)
        x = F.relu(x)
        x = self.q(x)
        return x


class DeepQNetwork:
    def __init__(
        self,
        n_actions,  # 每个action的qvalue
        n_features,  # 接受的状态的特征数
        n_hidden=20,
        learning_rate=0.01,
        reward_decay=0.9,
        e_greedy=0.9,
        replace_target_iter=200,
        memory_size=500,
        batch_size=32,
        e_greedy_increment=None,
    ):
        self.n_actions = n_actions
        self.n_features = n_features
        self.n_hidden = n_hidden
        self.lr = learning_rate
        self.gamma = reward_decay
        self.epsilon_max = e_greedy
        self.replace_target_iter = replace_target_iter
        self.memory_size = memory_size
        self.batch_size = batch_size
        self.epsilon_increment = e_greedy_increment
        self.epsilon = 0 if e_greedy_increment is not None else self.epsilon_max

        # total learning step
        self.learn_step_counter = 0

        # initialize zero memory [s, a, r, s_]
        self.memory = np.zeros((self.memory_size, n_features * 2 + 2))

        self.loss_func = nn.MSELoss()
        self.cost_his = []

        self._build_net()

    def _build_net(self):
        # 建造评估网络和目标网络
        self.q_eval = Net(self.n_features, self.n_hidden, self.n_actions)
        self.q_target = Net(self.n_features, self.n_hidden, self.n_actions)
        # qt(q现实)-qe(q估计)=loss,loss反向传播更新q估计网络的参数。
        self.optimizer = torch.optim.RMSprop(self.q_eval.parameters(), lr=self.lr)

    def store_transition(self, s, a, r, s_):
        if not hasattr(self, "memory_counter"):
            self.memory_counter = 0
        transition = np.hstack((s, [a, r], s_))
        # replace the old memory with new memory
        index = self.memory_counter % self.memory_size
        self.memory[index, :] = transition
        self.memory_counter += 1

    def choose_action(self, observation):
        observation = torch.Tensor(observation[np.newaxis, :])
        if np.random.uniform() < self.epsilon:
            actions_value = self.q_eval(observation)

            action = np.argmax(actions_value.data.numpy())
        else:
            action = np.random.randint(0, self.n_actions)
        return action

    def learn(self):
        # check to replace target parameters
        if self.learn_step_counter % self.replace_target_iter == 0:
            self.q_target.load_state_dict(self.q_eval.state_dict())
            print("\ntarget params replaced\n")

        # sample batch memory from all memory
        if self.memory_counter > self.memory_size:
            sample_index = np.random.choice(self.memory_size, size=self.batch_size)
        else:
            sample_index = np.random.choice(self.memory_counter, size=self.batch_size)
        batch_memory = self.memory[sample_index, :]

        # q_next is used for getting which action would be choosed by target network in state s_(t+1)
        q_next, q_eval = self.q_target(
            torch.Tensor(batch_memory[:, -self.n_features :])
        ), self.q_eval(torch.Tensor(batch_memory[:, : self.n_features]))
        # used for calculating y, we need to copy for q_eval because this operation could keep the Q_value that has not been selected unchanged,
        # so when we do q_target - q_eval, these Q_value become zero and wouldn't affect the calculation of the loss
        q_target = torch.Tensor(q_eval.data.numpy().copy())

        batch_index = np.arange(self.batch_size, dtype=np.int32)
        eval_act_index = batch_memory[:, self.n_features].astype(int)
        reward = torch.Tensor(batch_memory[:, self.n_features + 1])
        q_target[batch_index, eval_act_index] = (
            reward + self.gamma * torch.max(q_next, 1)[0]
        )

        loss = self.loss_func(q_eval, q_target)
        self.optimizer.zero_grad()
        loss.backward()
        self.optimizer.step()

        # increase epsilon
        self.cost_his.append(loss)
        self.epsilon = (
            self.epsilon + self.epsilon_increment
            if self.epsilon < self.epsilon_max
            else self.epsilon_max
        )
        self.learn_step_counter += 1

    def plot_cost(self):
        plt.plot(np.arange(len(self.cost_his)), self.cost_his)
        plt.ylabel("Cost")
        plt.xlabel("training steps")
        plt.show()

run_this.py

每一轮训练,智能体在迷宫中执行一系列动作,存储状态转移(经验回放),并通过DQN模型更新Q值来学习最优策略。训练300轮后,智能体应该能够学会如何在迷宫中高效到达目标点。

from maze_env import Maze
from RL_brain import DeepQNetwork


def run_maze():
    step = 0
    for episode in range(300):
        print("episode: {}".format(episode))
        observation = env.reset()  # 重置环境,类似qlearning中的状态初始化
        while True:
            print("step: {}".format(step))
            env.render()
            action = RL.choose_action(observation)
            observation_, reward, done = env.step(action)
            RL.store_transition(
                observation, action, reward, observation_
            )  # 记忆库存储记忆
            if (step > 200) and (step % 5 == 0):
                RL.learn()  # 记忆库中有东西,再开始每5步学习一次
            observation = observation_
            if done:
                break
            step += 1
    print("game over")
    env.destroy()


if __name__ == "__main__":
    env = Maze()
    RL = DeepQNetwork(
        env.n_actions,
        env.n_features,
        learning_rate=0.01,
        reward_decay=0.9,
        e_greedy=0.9,
        replace_target_iter=200,
        memory_size=2000,
    )
    env.after(100, run_maze)
    env.mainloop()
    RL.plot_cost()

 输出:

episode: 286
step: 10036
step: 10037
step: 10038
step: 10039
episode: 287
step: 10039
step: 10040
step: 10041
step: 10042
step: 10043
step: 10044
step: 10045
step: 10046
episode: 288
step: 10046
step: 10047
step: 10048
step: 10049
step: 10050
step: 10051
episode: 289
step: 10051
step: 10052
step: 10053
step: 10054
episode: 290
step: 10054
step: 10055
step: 10056
step: 10057
episode: 291
step: 10057
step: 10058
step: 10059
step: 10060
episode: 292
step: 10060
step: 10061
step: 10062
step: 10063
episode: 293
step: 10063
step: 10064
step: 10065
step: 10066
episode: 294
step: 10066
step: 10067
step: 10068
step: 10069
episode: 295
step: 10069
step: 10070
step: 10071
step: 10072
episode: 296
step: 10072
step: 10073
step: 10074
episode: 297
step: 10074
step: 10075
step: 10076
step: 10077
episode: 298
step: 10077
step: 10078
step: 10079
step: 10080
episode: 299
step: 10080
step: 10081
step: 10082
step: 10083
game over

最后几次很快就能抵达终点。 

Actor-Critic (AC) 方法

基本原理

Actor-Critic(AC)是一种结合了策略优化和价值函数估计的强化学习算法。它同时使用两个组件:

  • Actor:负责选择动作,代表策略 π(a∣s)。Actor根据当前策略生成动作并与环境交互
  • Critic:评估Actor所采取的动作的价值,通常使用值函数 V(s) 或状态-动作值函数 Q(s,a)。
主要流程
  1. 初始化:初始化Actor和Critic网络的参数。
  2. 策略选择:Actor根据当前策略选择动作并与环境进行交互。
  3. 计算回报:Critic评估当前状态的价值,并计算TD误差(Temporal-Difference error),用于更新Actor和Critic。
  4. 参数更新:使用TD误差来更新Critic网络,并通过Critic的反馈来更新Actor网络。

优点

  • 通过使用Critic来减小Actor的方差,从而提高学习效率。
  • 可以处理高维状态空间。

缺点

  • 收敛速度可能受到Actor和Critic之间不平衡的影响。

A3C(Asynchronous Actor-Critic)

A3CActor-Critic方法的一种变种,它通过异步并行的方式提高学习效率。A3C的核心思想是使用多个独立的Agent同时与环境进行交互,分别更新它们的Actor和Critic,并在一定频率下同步参数

A3C的主要特点
  1. 异步更新

    • 多个Agent(线程)在多个环境中独立学习,每个Agent的参数更新都是独立的,且同时进行。
    • 通过异步的方式,能够更快地收集经验,减少样本的相关性。
  2. 全局网络

    • 每个Agent使用全局的Actor和Critic网络进行训练,并在每个Agent完成一定数量的步骤后,将本地网络的参数更新到全局网络。
    • 这使得所有Agent可以共享经验,提高样本效率。
  3. 减少方差

    • 通过多个Agent的异步学习,A3C减少了估计方差,提升了策略更新的稳定性。
A3C的工作流程
  1. 多个Agent同时在各自环境中独立训练
  2. 每个Agent收集经验并使用本地的Critic计算TD误差。
  3. 定期将本地的Actor和Critic更新到全局网络,从而提升全局策略。

优点

  • 利用多线程的方式加速学习,提高样本效率。
  • 在复杂的环境中表现优异。

缺点

  • 实现复杂度较高,需要处理多线程的同步和更新。
  • 对计算资源的要求较高。

PD算法(Policy Distillation,策略蒸馏)

是一种深度强化学习方法,用于将多个策略组合成一个更加精简且性能接近的策略模型。这种方法类似于知识蒸馏(Knowledge Distillation),其中一个复杂或多个模型的知识被“蒸馏”到一个更小、更高效的模型中,也便于提高泛化能力

基本原理
  1. 多策略训练:首先通过不同的方法(如Q-learning、Actor-Critic等)或在不同环境下训练多个策略模型。
  2. 软目标输出:将这些策略的输出作为软目标,通过引入熵正则化等方式,生成一种概率分布(通常是更平滑的分布,而不是确定的动作)。
  3. 蒸馏策略:通过训练一个新的学生策略模型,使其模仿这些软目标输出。目标是让学生策略学会接近这些原始策略的动作选择,但只需一个简化的模型。
应用场景

PD算法通常用于多任务学习、模型压缩、模型加速等情境下,特别是在计算资源有限时使用。

与其他方法的比较
  • 与Q-learning、AC 等单策略方法不同,PD算法是一个后处理步骤,不直接训练策略,而是对现有策略进行整合。
  • 与A3C等多线程并行方法相比,PD更专注于策略的知识提取和精简。

分桶方法

为了解决之前提到的只能解决离散问题以及若状态空间实在过大的问题,引入了分桶方法。

分桶方法可以算一种用于强化学习的是一种数据预处理技术(在《3.数据预处理》中有具体介绍),是将连续的状态值划分为一系列的区间(“桶”或“箱”),将每个连续状态值映射到它所落入的桶中,目的是将连续状态空间转化为离散状态空间。通过这种方式,可以在强化学习中应用标准的离散算法(如 Q-learning、SARSA 等),因为这些算法通常无法直接处理连续状态。

什么时候使用分桶方法

  • 状态空间非常大或是连续的:例如在物理控制问题中,状态空间可以是物体的速度、位置等连续变量。在这些场景中,直接处理所有可能的状态是不可行的。
  • 使用离散化方法能有效简化问题:通过将连续状态离散化,能够减少状态空间的大小,使得算法更易于处理。例如下棋,棋盘非常大,直接计算计算量很大。

分桶方法的步骤

  1. 确定状态空间的范围:首先,需要确定每个状态变量的范围。例如,假设我们有一个物体的速度状态,范围是[−5,5]。
  2. 划分区间:将状态空间划分为若干个区间。例如,将[−5,5] 划分为 10 个区间,即每个区间的宽度为 1。
  3. 映射状态到桶:根据当前的状态值,确定它所属的区间(或桶)。例如,如果当前速度是 2.3,那么它落入[2,3] 的区间中。
  4. 使用离散化后的状态:将映射到的桶作为离散状态,然后在标准强化学习算法中使用这些离散的状态进行学习和更新。

分桶方法的优点与缺点

优点

  • 减少状态空间规模:将连续的状态空间离散化为有限数量的桶,能够减少状态空间的规模,使得强化学习算法可以在可行的时间和内存范围内工作
  • 适用性广泛:这种方法适用于很多连续状态的强化学习问题,尤其是在没有复杂模型或函数逼近器(如神经网络)时,分桶是简化问题的有效方式。

缺点

  • 精度受限:分桶方法会丢失精度,因为连续的状态被离散化为有限个区间。桶的数量越少,模型的精度可能越差;桶的数量越多,问题的复杂度又会提升
  • 维度灾难:状态变量较多时(即状态空间维数很高),即使每个状态变量的区间数目有限,整体的离散状态空间规模仍然可能非常大,从而导致计算和存储上的挑战。
  • 人为选择桶数:如何划分桶的数量(区间的数量)可能是一个挑战,选择太多或太少都会影响性能。

分桶方法的类型

1. 等宽分桶(Equal Width Binning):

  • 将数据按照固定的区间宽度进行分组,每个区间的宽度相同。
  • 例如,将年龄数据分成3个区间:[0, 20], [20, 40], [40, 60]
  • 优点:实现简单,适合数据均匀分布的情况。
  • 缺点:当数据分布不均匀时,可能会导致某些桶中的数据量过少或过多。

2. 等频分桶(Equal Frequency Binning):

  • 将数据按照相同数量的样本数进行分桶,每个桶中的数据量相同。
  • 例如,假设有10个样本,分成2个桶,那么每个桶包含5个样本。
  • 优点:适合数据分布不均的情况,能保证每个桶有相似的样本数。
  • 缺点:区间宽度可能会不均匀,导致每个桶的范围大小不同。

3. 自定义分桶(Custom Binning):

  • 根据业务逻辑或先验知识,将数据划分为特定的区间。
  • 例如,在信用评分模型中,可能将收入分为[0, 5000], [5000, 10000], [10000, ∞]这样的区间。
  • 优点:灵活性高,能够根据具体业务需求进行分组。
  • 缺点:需要领域专家的经验,可能对数据有一定的偏见。

4. 决策树分桶

  • 通过决策树模型(在《4.4分类》中有具体介绍)来自动生成分桶,依据特征与目标变量之间的关系进行划分。
  • 优点:能够根据数据的内在结构自动选择分组。
  • 缺点:复杂度较高,且需要训练模型来确定分桶。

分桶方法的应用场景:CartPole 问题

在经典的 CartPole 问题中,杆子有四个连续状态变量:小车的位置、小车的速度、杆子的角度和杆子的角速度。我们可以通过分桶方法将这些连续的状态变量离散化。

import gym
import numpy as np

# 创建CartPole环境
env = gym.make('CartPole-v1')

# 分别为每个状态变量设置分桶数量
buckets = (10, 10, 10, 10)  # 位置、速度、角度、角速度的分桶数量

state_bounds = list(zip(env.observation_space.low, env.observation_space.high))

# 手动限制状态范围,避免无穷大(env中默认会给出极大的值)
state_bounds[1] = [-1, 1]   # 限制小车速度
state_bounds[3] = [-5, 5]   # 限制杆子角速度

# 将连续状态离散化
def discretize_state(state):
    discretized = []
    for i in range(len(state)):
        # 对每个状态变量离散化
        ratio = (state[i] - state_bounds[i][0]) / (state_bounds[i][1] - state_bounds[i][0])
        # 确保 ratio 在 0 到 1 之间
        ratio = min(max(ratio, 0), 1)
        new_state = int((buckets[i] - 1) * ratio)
        discretized.append(new_state)
    return tuple(discretized)

# 初始化Q表
q_table = np.zeros(buckets + (env.action_space.n,))

# 超参数设置
learning_rate = 0.1
discount_factor = 0.99
epsilon = 1.0
epsilon_decay = 0.99
min_epsilon = 0.01
num_episodes = 1000

# Q-learning算法
for episode in range(num_episodes):
    # 初始化状态
    state = discretize_state(env.reset()[0])  # 修改这里,env.reset() 返回 (state, info)
    done = False
    total_reward = 0  # 用于记录每个回合的总奖励
    steps = 0  # 用于记录每个回合的步数
    while not done:
        if np.random.random() < epsilon:
            action = env.action_space.sample()  # 随机探索
        else:
            action = np.argmax(q_table[state])  # 贪婪选择
        next_state, reward, done, _, _ = env.step(action)  # 修改这里,env.step() 返回 (next_state, reward, done, truncated, info)
        next_state_discrete = discretize_state(next_state)

        # Q值更新
        best_next_action = np.argmax(q_table[next_state_discrete])
        q_table[state + (action,)] += learning_rate * (reward + discount_factor * q_table[next_state_discrete + (best_next_action,)] - q_table[state + (action,)])

        state = next_state_discrete
        total_reward += reward
        steps += 1

    # 输出每个回合的结果
    print(f"Episode: {episode+1}, Total reward: {total_reward}, Steps: {steps}, Epsilon: {epsilon:.4f}")
    # 衰减探索率
    epsilon = max(min_epsilon, epsilon * epsilon_decay)

env.close()
  • 分桶设置:每个状态变量(如小车的位置、速度等)被分成 10 个桶(区间)。
  • 状态离散化:discretize_state 函数将连续状态转换为离散的桶索引。
  • Q-learning:基于离散状态和动作空间,我们使用 Q-learning 算法来更新策略。通过 q_table 存储每个离散状态下的 Q 值。
  • 在运行过程中,模型会通过分桶后的状态学习如何平衡杆子,并逐步改进策略。尽管通过分桶方法可以简化状态空间,但需要根据具体问题调整桶的数量,以确保模型能够有效学习。

输出:

Episode: 871, Total reward: 55.0, Steps: 55, Epsilon: 0.0100
Episode: 872, Total reward: 47.0, Steps: 47, Epsilon: 0.0100
Episode: 873, Total reward: 53.0, Steps: 53, Epsilon: 0.0100
Episode: 874, Total reward: 63.0, Steps: 63, Epsilon: 0.0100
Episode: 875, Total reward: 74.0, Steps: 74, Epsilon: 0.0100
Episode: 876, Total reward: 53.0, Steps: 53, Epsilon: 0.0100
Episode: 877, Total reward: 56.0, Steps: 56, Epsilon: 0.0100
Episode: 878, Total reward: 79.0, Steps: 79, Epsilon: 0.0100
Episode: 879, Total reward: 46.0, Steps: 46, Epsilon: 0.0100
Episode: 880, Total reward: 130.0, Steps: 130, Epsilon: 0.0100
Episode: 881, Total reward: 57.0, Steps: 57, Epsilon: 0.0100
Episode: 882, Total reward: 92.0, Steps: 92, Epsilon: 0.0100
Episode: 883, Total reward: 70.0, Steps: 70, Epsilon: 0.0100
Episode: 884, Total reward: 65.0, Steps: 65, Epsilon: 0.0100
Episode: 885, Total reward: 62.0, Steps: 62, Epsilon: 0.0100
Episode: 886, Total reward: 54.0, Steps: 54, Epsilon: 0.0100
Episode: 887, Total reward: 64.0, Steps: 64, Epsilon: 0.0100
Episode: 888, Total reward: 66.0, Steps: 66, Epsilon: 0.0100
Episode: 889, Total reward: 50.0, Steps: 50, Epsilon: 0.0100
Episode: 890, Total reward: 54.0, Steps: 54, Epsilon: 0.0100
Episode: 891, Total reward: 61.0, Steps: 61, Epsilon: 0.0100
Episode: 892, Total reward: 51.0, Steps: 51, Epsilon: 0.0100
Episode: 893, Total reward: 106.0, Steps: 106, Epsilon: 0.0100
Episode: 894, Total reward: 63.0, Steps: 63, Epsilon: 0.0100
Episode: 895, Total reward: 52.0, Steps: 52, Epsilon: 0.0100
Episode: 896, Total reward: 62.0, Steps: 62, Epsilon: 0.0100
Episode: 897, Total reward: 46.0, Steps: 46, Epsilon: 0.0100
Episode: 898, Total reward: 67.0, Steps: 67, Epsilon: 0.0100
Episode: 899, Total reward: 101.0, Steps: 101, Epsilon: 0.0100
Episode: 900, Total reward: 76.0, Steps: 76, Epsilon: 0.0100
Episode: 901, Total reward: 53.0, Steps: 53, Epsilon: 0.0100
Episode: 902, Total reward: 54.0, Steps: 54, Epsilon: 0.0100
Episode: 903, Total reward: 89.0, Steps: 89, Epsilon: 0.0100
Episode: 904, Total reward: 76.0, Steps: 76, Epsilon: 0.0100
Episode: 905, Total reward: 78.0, Steps: 78, Epsilon: 0.0100
Episode: 906, Total reward: 68.0, Steps: 68, Epsilon: 0.0100
Episode: 907, Total reward: 48.0, Steps: 48, Epsilon: 0.0100
Episode: 908, Total reward: 82.0, Steps: 82, Epsilon: 0.0100
Episode: 909, Total reward: 54.0, Steps: 54, Epsilon: 0.0100
Episode: 910, Total reward: 73.0, Steps: 73, Epsilon: 0.0100
Episode: 911, Total reward: 62.0, Steps: 62, Epsilon: 0.0100
Episode: 912, Total reward: 61.0, Steps: 61, Epsilon: 0.0100
Episode: 913, Total reward: 52.0, Steps: 52, Epsilon: 0.0100
Episode: 914, Total reward: 103.0, Steps: 103, Epsilon: 0.0100
Episode: 915, Total reward: 51.0, Steps: 51, Epsilon: 0.0100
Episode: 916, Total reward: 55.0, Steps: 55, Epsilon: 0.0100
Episode: 917, Total reward: 74.0, Steps: 74, Epsilon: 0.0100
Episode: 918, Total reward: 66.0, Steps: 66, Epsilon: 0.0100
Episode: 919, Total reward: 60.0, Steps: 60, Epsilon: 0.0100
Episode: 920, Total reward: 61.0, Steps: 61, Epsilon: 0.0100
Episode: 921, Total reward: 76.0, Steps: 76, Epsilon: 0.0100
Episode: 922, Total reward: 57.0, Steps: 57, Epsilon: 0.0100
Episode: 923, Total reward: 79.0, Steps: 79, Epsilon: 0.0100
Episode: 924, Total reward: 53.0, Steps: 53, Epsilon: 0.0100
Episode: 925, Total reward: 85.0, Steps: 85, Epsilon: 0.0100
Episode: 926, Total reward: 55.0, Steps: 55, Epsilon: 0.0100
Episode: 927, Total reward: 54.0, Steps: 54, Epsilon: 0.0100
Episode: 928, Total reward: 56.0, Steps: 56, Epsilon: 0.0100
Episode: 929, Total reward: 54.0, Steps: 54, Epsilon: 0.0100
Episode: 930, Total reward: 32.0, Steps: 32, Epsilon: 0.0100
Episode: 931, Total reward: 52.0, Steps: 52, Epsilon: 0.0100
Episode: 932, Total reward: 35.0, Steps: 35, Epsilon: 0.0100
Episode: 933, Total reward: 79.0, Steps: 79, Epsilon: 0.0100
Episode: 934, Total reward: 32.0, Steps: 32, Epsilon: 0.0100
Episode: 935, Total reward: 72.0, Steps: 72, Epsilon: 0.0100
Episode: 936, Total reward: 53.0, Steps: 53, Epsilon: 0.0100
Episode: 937, Total reward: 58.0, Steps: 58, Epsilon: 0.0100
Episode: 938, Total reward: 60.0, Steps: 60, Epsilon: 0.0100
Episode: 939, Total reward: 101.0, Steps: 101, Epsilon: 0.0100
Episode: 940, Total reward: 60.0, Steps: 60, Epsilon: 0.0100
Episode: 941, Total reward: 48.0, Steps: 48, Epsilon: 0.0100
Episode: 942, Total reward: 53.0, Steps: 53, Epsilon: 0.0100
Episode: 943, Total reward: 58.0, Steps: 58, Epsilon: 0.0100
Episode: 944, Total reward: 97.0, Steps: 97, Epsilon: 0.0100
Episode: 945, Total reward: 58.0, Steps: 58, Epsilon: 0.0100
Episode: 946, Total reward: 98.0, Steps: 98, Epsilon: 0.0100
Episode: 947, Total reward: 94.0, Steps: 94, Epsilon: 0.0100
Episode: 948, Total reward: 89.0, Steps: 89, Epsilon: 0.0100
Episode: 949, Total reward: 50.0, Steps: 50, Epsilon: 0.0100
Episode: 950, Total reward: 63.0, Steps: 63, Epsilon: 0.0100
Episode: 951, Total reward: 82.0, Steps: 82, Epsilon: 0.0100
Episode: 952, Total reward: 53.0, Steps: 53, Epsilon: 0.0100
Episode: 953, Total reward: 48.0, Steps: 48, Epsilon: 0.0100
Episode: 954, Total reward: 47.0, Steps: 47, Epsilon: 0.0100
Episode: 955, Total reward: 89.0, Steps: 89, Epsilon: 0.0100
Episode: 956, Total reward: 49.0, Steps: 49, Epsilon: 0.0100
Episode: 957, Total reward: 58.0, Steps: 58, Epsilon: 0.0100
Episode: 958, Total reward: 55.0, Steps: 55, Epsilon: 0.0100
Episode: 959, Total reward: 48.0, Steps: 48, Epsilon: 0.0100
Episode: 960, Total reward: 79.0, Steps: 79, Epsilon: 0.0100
Episode: 961, Total reward: 69.0, Steps: 69, Epsilon: 0.0100
Episode: 962, Total reward: 56.0, Steps: 56, Epsilon: 0.0100
Episode: 963, Total reward: 38.0, Steps: 38, Epsilon: 0.0100
Episode: 964, Total reward: 64.0, Steps: 64, Epsilon: 0.0100
Episode: 965, Total reward: 45.0, Steps: 45, Epsilon: 0.0100
Episode: 966, Total reward: 56.0, Steps: 56, Epsilon: 0.0100
Episode: 967, Total reward: 46.0, Steps: 46, Epsilon: 0.0100
Episode: 968, Total reward: 62.0, Steps: 62, Epsilon: 0.0100
Episode: 969, Total reward: 62.0, Steps: 62, Epsilon: 0.0100
Episode: 970, Total reward: 56.0, Steps: 56, Epsilon: 0.0100
Episode: 971, Total reward: 66.0, Steps: 66, Epsilon: 0.0100
Episode: 972, Total reward: 73.0, Steps: 73, Epsilon: 0.0100
Episode: 973, Total reward: 60.0, Steps: 60, Epsilon: 0.0100
Episode: 974, Total reward: 64.0, Steps: 64, Epsilon: 0.0100
Episode: 975, Total reward: 75.0, Steps: 75, Epsilon: 0.0100
Episode: 976, Total reward: 56.0, Steps: 56, Epsilon: 0.0100
Episode: 977, Total reward: 52.0, Steps: 52, Epsilon: 0.0100
Episode: 978, Total reward: 51.0, Steps: 51, Epsilon: 0.0100
Episode: 979, Total reward: 60.0, Steps: 60, Epsilon: 0.0100
Episode: 980, Total reward: 88.0, Steps: 88, Epsilon: 0.0100
Episode: 981, Total reward: 57.0, Steps: 57, Epsilon: 0.0100
Episode: 982, Total reward: 84.0, Steps: 84, Epsilon: 0.0100
Episode: 983, Total reward: 62.0, Steps: 62, Epsilon: 0.0100
Episode: 984, Total reward: 26.0, Steps: 26, Epsilon: 0.0100
Episode: 985, Total reward: 73.0, Steps: 73, Epsilon: 0.0100
Episode: 986, Total reward: 42.0, Steps: 42, Epsilon: 0.0100
Episode: 987, Total reward: 60.0, Steps: 60, Epsilon: 0.0100
Episode: 988, Total reward: 34.0, Steps: 34, Epsilon: 0.0100
Episode: 989, Total reward: 99.0, Steps: 99, Epsilon: 0.0100
Episode: 990, Total reward: 21.0, Steps: 21, Epsilon: 0.0100
Episode: 991, Total reward: 68.0, Steps: 68, Epsilon: 0.0100
Episode: 992, Total reward: 64.0, Steps: 64, Epsilon: 0.0100
Episode: 993, Total reward: 52.0, Steps: 52, Epsilon: 0.0100
Episode: 994, Total reward: 53.0, Steps: 53, Epsilon: 0.0100
Episode: 995, Total reward: 87.0, Steps: 87, Epsilon: 0.0100
Episode: 996, Total reward: 51.0, Steps: 51, Epsilon: 0.0100
Episode: 997, Total reward: 58.0, Steps: 58, Epsilon: 0.0100
Episode: 998, Total reward: 63.0, Steps: 63, Epsilon: 0.0100
Episode: 999, Total reward: 62.0, Steps: 62, Epsilon: 0.0100
Episode: 1000, Total reward: 73.0, Steps: 73, Epsilon: 0.0100

总结

强化学习是一种通过与环境交互来学习最优策略的技术,涉及到多种算法和策略。动态规划是基于已知的环境模型,依赖状态转移矩阵,典型方法包括策略迭代和价值迭代。Q-learning和SARSA都是基于值函数的算法,前者是离线学习,不依赖当前策略,而后者是在线学习,依赖当前策略。DQN通过引入神经网络对Q值进行估计,结合经验回放和目标网络,提升了处理复杂环境的能力。AC(Actor-Critic)算法结合了策略优化和值函数估计,分为Actor和Critic两部分,是基于策略的强化学习算法。A3C是AC的并行扩展版,多个智能体并行训练,具有更好的收敛性。Boltzmann和ϵ-greedy策略是常用的探索-利用策略,前者通过温度参数控制探索与利用的平衡,后者通过随机选择与贪婪选择相结合。优先级回放技术通过对经验样本的优先级排序,提高了经验回放的效率。PD算法则是通过将多个策略模型的知识整合并蒸馏到一个更小的模型中,有助于模型压缩和提高泛化能力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值