强化学习实践二 :理解gym的建模思想

2023-11-17

David Silver的强化学习公开课有几个特点,个人感觉首要的一个特点是偏重于讲解理论,而且有时候为了讲清楚一个理论的来龙去脉,也顺带讲了很多不常用的理论;还有一个特点是小例子很多,这些例子有时候不仅是为了讲清楚一个复杂的算法,而且通过例子会加深对一些概念的理解。同样我们在学习他的课程时,也应该注重实践,因为只有通过实践,才会对理论有更深的认识,有时候会在实践中纠正自己曾经一直自己以为正确其实是错误的认识。

本篇我们先简单尝试下怎么对强化学习中的个体、环境等进行建模,借此加深对一些概念的认识。随后我们简单介绍下热门强化学习库gym是怎么对环境及相关对象进行建模的。由于gym提供了许多强化学习经典的环境,学习者只需熟悉gym提供的接口,可以将主要精力放在个体解决强化学习问题的算法实现上,而不需自己搭建一个环境。

有了今天的基础,今后为了能够紧密贴合David的公开课,我们将模仿gym中环境类编写一个格子世界环境,来模拟其公开课中提到的一些示例,比如有风格子世界、随机行走、悬崖行走等小例子。通过这些贴合公开课的例子,相信对于初学者会相当直观。

一、强化学习问题需要描述那些内容

强化学习中最主要的两类对象是“个体”和“环境”,其次还有一些像“即时奖励”、“收获”、“状态”、“行为”、“价值”、“策略”、“学习”、“控制”等概念。这些概念把个体和环境联系起来。通过理论学习,我们知道:

1. 环境响应个体的行为。当个体执行一个行为时,它需要根据环境本身的动力学来更新环境,也包括更新个体状态,同时给以个体一个反馈信息:即时奖励

2. 对于个体来说,它并不掌握整个环境信息,它只能通过观测来获得其可以获得的信息,它能观测到哪些信息取决于问题的难度;同样个体需要一定的行为与环境进行交互,哪些行为是被允许的,也要由个体和环境协商好。因此环境要确定个体的观测空间和行为空间

3. 个体还应该有一个决策功能,该功能根据当前观测来判断下一时刻该采取什么行为,也就是决策过程。

4. 个体具有执行一个确定行为的功能。

5. 智能的个体应能从与环境的交互中学习到知识,进而在与环境交互时尽可能多的获取奖励,最终达到最大化累积奖励的目的。

6. 环境应该给个体设置一个(些)终止条件,即当个体处在这个状态或这些状态之一时,约定交互结束,即产生一个完整的Episode。随后重新开始一个Episode或者退出交互。

把上面的过程提炼成伪代码可以是下面这样:

class Environment():
    self.states  # 所有可能的状态集合
    self.agent_cur_state    # 记录个体当前的状态
    self.observation_space  # 个体的观测空间
    self.action_space  # 个体的行为空间

    def reward(self) -> reward # 根据状态确定个体的即时奖励
    def dynamics(self, action) -> None # 根据当前状态和个体的行为确定个体的新状态
    def is_episode_end(self) -> Bool # 判断是否一个Episode结束
    def obs_for_agent() -> obs  # 环境把个体当前状态做一定变换,作为个体的观测


class Agent(env: Environment):
    self.env = env  # 个体依附于一个环境存在
    self.obs # 个体的观测
    self.reward # 个体获得的即时奖励

    def performPolicy(self, obs) -> action # 个体执行一个策略产生一个行为

    def performAction(self, action) -> None  # 个体与环境交互,执行行为
        action = self.performPolicy(self.obs)
        self.env.dynamics(action)

    def observe(self) -> next_obs, reward # 个体得到从环境反馈来的观测和奖励
        self.obs = self.env.obs_for_agent()
        self.reward = self.env.reward()

按照上面的设计,可以写出一个不错的个体与环境的类。但是我们不打算按照这个写下去,我们看看gym库是如何描述环境,以及个体通过什么方式与环境进行交互。

 

二、gym库介绍

gym的官方网址在:这里,其库代码托管地址在:这里

gym库的在设计环境以及个体的交互时基本上也是解决上述问题,但是它有它的规范和接口。gym库的核心在文件core.py里,这里定义了两个最基本的类Env和Space。前者是所有环境类的基类,后者是所有空间类的基类。从Space基类衍生出几个常用的空间类,其中最主要的是Discrete类和Box类。通过其__init__方法的参数以及其它方法的实现可以看出前者对应于一维离散空间,后者对应于多维连续空间。它们既可以应用在行为空间中,也可以用来描述状态空间,具体怎么用看问题本身。例如如果我要描述上篇提到的一个4*4的格子世界,其一共有16个状态,每一个状态只需要用一个数字来描述,这样我可以把这个问题的状态空间用Discrete(16)对象来描述就可以了。对于另外一个经典的小车爬山的问题(如下图),小车的状态是用两个变量来描述的,一个是小车对应目标旗杆的水平距离,另一个是小车的速度(是沿坡度切线方向的速率还是速度在水平方向的分量这个没仔细研究),因此环境要描述小车的状态需要2个连续的变量。由于描述小车的状态数据对个体完全可见,因此小车的状态空间即是小车的观测空间,此时再用Discrete来描述就不行了,要用Box类,Box空间可以定义多维空间,每一个维度可以用一个最低值和最大值来约束。同时小车作为个体可以执行的行为只有3个:左侧加速、不加速、右侧加速。因此行为空间可以用Discrete来描述。最终,该环境类的观测空间和行为空间描述如下:

 

def __init__(self):
        self.min_position = -1.2
        self.max_position = 0.6
        self.max_speed = 0.07
        self.goal_position = 0.5

        self.low = np.array([self.min_position, -self.max_speed])
        self.high = np.array([self.max_position, self.max_speed])

        self.viewer = None

        self.action_space = spaces.Discrete(3)
        self.observation_space = spaces.Box(self.low, self.high, dtype=np.float32)

        self.seed()
        self.reset()

从这段代码可以看出,要定义一个Discrete类的空间只需要一个参数n就可以了,而定义一个多维的Box空间需要知道每一个维度的最小最大值,当然也要知道维数。

有了描述空间的对象,再来看环境类如何声明就简单的多了。先来看看代码中关于环境基类的一段解释:

class Env(object):
    """The main OpenAI Gym class. It encapsulates an environment with
    arbitrary behind-the-scenes dynamics. An environment can be
    partially or fully observed.
    The main API methods that users of this class need to know are:
        step
        reset
        render
        close
        seed
    And set the following attributes:
        action_space: The Space object corresponding to valid actions
        observation_space: The Space object corresponding to valid observations
        reward_range: A tuple corresponding to the min and max possible rewards
    Note: a default reward range set to [-inf,+inf] already exists. Set it if you want a narrower range.
    The methods are accessed publicly as "step", "reset", etc.. The
    non-underscored versions are wrapper methods to which we may add
    functionality over time.
    """

    # Set this in SOME subclasses
    metadata = {'render.modes': []}
    reward_range = (-float('inf'), float('inf'))
    spec = None

    # Set these in ALL subclasses
    action_space = None
    observation_space = None

    def step(self, action):
        """Run one timestep of the environment's dynamics. When end of
        episode is reached, you are responsible for calling `reset()`
        to reset this environment's state.
        Accepts an action and returns a tuple (observation, reward, done, info).
        Args:
            action (object): an action provided by the environment
        Returns:
            observation (object): agent's observation of the current environment
            reward (float) : amount of reward returned after previous action
            done (boolean): whether the episode has ended, in which case further step() calls will return undefined results
            info (dict): contains auxiliary diagnostic information (helpful for debugging, and sometimes learning)
        """
        raise NotImplementedError

    def reset(self):
        """Resets the state of the environment and returns an initial observation.
        Returns: observation (object): the initial observation of the
            space.
        """
        raise NotImplementedError

    def render(self, mode='human'):
        """Renders the environment.
        The set of supported modes varies per environment. (And some
        environments do not support rendering at all.) By convention,
        if mode is:
        - human: render to the current display or terminal and
          return nothing. Usually for human consumption.
        - rgb_array: Return an numpy.ndarray with shape (x, y, 3),
          representing RGB values for an x-by-y pixel image, suitable
          for turning into a video.
        - ansi: Return a string (str) or StringIO.StringIO containing a
          terminal-style text representation. The text can include newlines
          and ANSI escape sequences (e.g. for colors).
        Note:
            Make sure that your class's metadata 'render.modes' key includes
              the list of supported modes. It's recommended to call super()
              in implementations to use the functionality of this method.
        Args:
            mode (str): the mode to render with
            close (bool): close all open renderings
        Example:
        class MyEnv(Env):
            metadata = {'render.modes': ['human', 'rgb_array']}
            def render(self, mode='human'):
                if mode == 'rgb_array':
                    return np.array(...) # return RGB frame suitable for video
                elif mode is 'human':
                    ... # pop up a window and render
                else:
                    super(MyEnv, self).render(mode=mode) # just raise an exception
        """
        raise NotImplementedError

    def close(self):
        """Override _close in your subclass to perform any necessary cleanup.
        Environments will automatically close() themselves when
        garbage collected or when the program exits.
        """
        return

    def seed(self, seed=None):
        """Sets the seed for this env's random number generator(s).
        Note:
            Some environments use multiple pseudorandom number generators.
            We want to capture all such seeds used in order to ensure that
            there aren't accidental correlations between multiple generators.
        Returns:
            list<bigint>: Returns the list of seeds used in this env's random
              number generators. The first value in the list should be the
              "main" seed, or the value which a reproducer should pass to
              'seed'. Often, the main seed equals the provided 'seed', but
              this won't be true if seed=None, for example.
        """
        logger.warn("Could not seed environment %s", self)
        return

    @property
    def unwrapped(self):
        """Completely unwrap this env.
        Returns:
            gym.Env: The base non-wrapped gym.Env instance
        """
        return self

    def __str__(self):
        if self.spec is None:
            return '<{} instance>'.format(type(self).__name__)
        else:
            return '<{}<{}>>'.format(type(self).__name__, self.spec.id)

看得出,个体主要通过环境的一下几个方法进行交互:step,reset,render,close,seed。此外这段描述还指出,如果你要编写自己的环境类,也主要是重写这些私有方法,同时指定该环境的观测和行为空间。close方法可以不用重写。这几个方法主要完成的个性化功能如下:

step: 最核心的方法,定义环境的动力学,确定个体的下一个状态奖励信息是否Episode终止,以及一些额外的信息,按约定,额外的信息不被允许用来训练个体。

reset: 开启个体与环境交互前调用该方法,确定个体的初始状态以及其他可能的一些初始化设置

seed: 设置一些随机数的种子

render: 如果需要将个体与环境的交互以动画的形式展示出来的话,需要重写该方法。简单的UI设计可以用gym包装好了的pyglet方法来实现,这些方法在rendering.py文件里定义。具体使用这些方法进行UI绘制需要了解基本的OpenGL编程思想和接口,这里暂时不做细说。

可以看出,使用gym编写自己的Agent代码,需要在你的Agent类中声明一个env变量,指向对应的环境类,个体使用自己的代码产生一个行为,将该行为送入env的step方法中,同时得到观测状态、奖励值、Episode是否终止以及调试信息等四项信息组成的元组:

state, reward, is_done, info = env.step(a)

state 是一个元组或numpy数组,其提供的信息维度应与观测空间的维度一样、每一个维度的具体指在制定的low与high之间,保证state信息符合这些条件是env类的step方法负责的事情。

reward 则是根据环境的动力学给出的即时奖励,它就是一个数值。

is_done 是一个布尔变量,True或False,你可以根据具体的值来安排个体的后续动作。

info 提供的数据因环境的不同差异很大,通常它的结构是一个字典:

{"key1":data1,"key2":data2,...}

获取其中的信息应该不难。

最后一点,在自己的代码中如何建立个环境类对象呢?有两种情况,一种是在gym库里注册了的对象,你只要使用下面的语句:

import gym
env = gym.make("registered_env_name")

其中不同的环境类有不同的注册名,只要把make方法内的字符串改成对应的环境名就可以了。

另外一种使用自己编写的未注册的环境类,这种很简单,同一般的建立对象的语句没什么区别:

env = MyEnvClassName()

相信读者已经基本清楚了如何使用gym提供的环境类了。下一步就是如何编写自己的环境类了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

强化学习实践二 :理解gym的建模思想 的相关文章

  • 强化学习之GYM库

    GYM库 gym基础 GYM库版本 0 18 0 导入gym环境 env gym make CartPole v0 查看Gym库注册的环境 from gym import envs env specs env registry all 所有
  • Docker实操6——配置好的强化学习Dockerfile

    自定义专属的强化环境 环境概述 一 文件内容 1 1 sh文件 1 2 换源的txt文件 1 3 测试环境的py文件 终 完整的Dockerfile 小坑 环境概述 基本信息 具体版本 系统 Ubuntu 18 04 LTS 用户 密码 p
  • 多智能体强化学习与博弈论-博弈论基础2

    多智能体强化学习与博弈论 博弈论基础2 Repeated Games 重复博弈 之前我们介绍了一些单次博弈的例子 除了单次博弈外 重复博弈也是经常在我们生活中出现的 在重复博弈中智能体有机会在单次的博弈中占到对手的便宜 但是由于考虑到后来还
  • 强化学习代码实战入门

    这是一个易理解的 demo 300行左右 可以作为RL的入门代码 辅助基础公式的理解 这个是我自己的学习笔记 三连留下邮箱 可以直接发送完整的代码标注文件 如有错误 麻烦指出 我已经蛮久没写博了 上一篇RL博客也快一年半了 很久没做这一块了
  • 强化学习入门笔记

    强化学习 相关概念 我们先回忆一下童年 来看看超级玛丽这款游戏 在这款游戏里面的 我们需要控制超级玛丽进行左右行走 跳 攻击等动作 来躲避或攻击小动物 吃金币以及各种类型的增益道具 最终 获得的金币数量的多少以及通关代表我们玩游戏玩的好不好
  • 共轭梯度法详细推导分析

    共轭梯度法是一种经典的优化算法 算法求解速度较快 虽然比梯度下降法复杂 但是比二阶方法简单 一 引入 1 优化模型建立 假定待优化的问题如下所示 min x f x 1 2 x T A x b T x min x f x frac 1 2
  • 周志华《Machine Learning》学习笔记(17)--强化学习

    上篇主要介绍了概率图模型 首先从生成式模型与判别式模型的定义出发 引出了概率图模型的基本概念 即利用图结构来表达变量之间的依赖关系 接着分别介绍了隐马尔可夫模型 马尔可夫随机场 条件随机场 精确推断方法以及LDA话题模型 HMM主要围绕着评
  • 【强化学习】

    强化学习DQN 提示 写完文章后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 强化学习DQN DQN算法的简介 一 环境的介绍 二 DQN算法 1 DQN算法的关键技术 2 DQN代码 2 1 导入库 2 2 定义类 2 3
  • 【论文解读】滴滴智能派单-KDD2018 Large-Scale Order Dispatch in On-Demand Ride-Hailing

    Large Scale Order Dispatch in On Demand Ride Hailing Platforms A Learning and Planning Approach 一 简介 基于大量历史数据 构建一个大Q表 用于
  • Python 深度学习实战:75个有关神经网络建模、强化学习与迁移学习的解决方案

    深度学习正在为广泛的行业带来革命性的变化 对于许多应用来说 深度学习通过做出更快和更准确的预测 证明其已经超越人类的预测 本书提供了自上而下和自下而上的方法来展示深度学习对不同领域现实问题的解决方案 这些应用程序包括计算机视觉 自然语言处理
  • ML-Agents案例之双人足球

    本案例源自ML Agents官方的示例 Github地址 https github com Unity Technologies ml agents 本文是详细的配套讲解 本文基于我前面发的两篇文章 需要对ML Agents有一定的了解 详
  • Dyna-Q算法的理论基础及其代码实践【CliffWalking-v0】

    Dyna Q 理论基础 强化学习中 模型 通常指与智能体交互的环境模型 即对环境的状态转移概率和奖励函数进行建模 根据是否具有环境模型 强化学习算法分为两种 基于模型的强化学习 model based 无模型的强化学习根据智能体与环境交互采
  • 【华为诺亚方舟实验室】2022届毕业生招聘--决策(强化学习)推理方向

    深度强化学习实验室 官网 http www neurondance com 论坛 http deeprl neurondance com 来源 华为诺亚方舟实验室官微 诺亚方舟实验室 Noah s Ark Lab 是华为公司从事人工智能基础
  • 强化学习(Reinforcement Learning)知识整理

    马尔可夫决策过程 Markov Decision Processes MDPs MDPs 简单说就是一个智能体 Agent 采取行动 Action 从而改变自己的状态 State 获得奖励 Reward 与环境 Environment 发生
  • 强化学习入门《Easy RL》

    什么是强化学习 强化学习关注的是智能体 Agent 在复杂的环境 Environment 中如何最大化获得的奖励 Reward 智能体和环境两部分组成了强化学习 在强化学习过程中 智能体与环境一直在交互 智能体在环境中获取某个状态后 它会利
  • 强化学习——基本概念

    什么是强化学习 强化学习关注与智能体 agent 如何与环境交互中不断学习以完成特定的目标 与有监督学习相比 不需要告诉智能体数据以及对应的标签 学习相应的模型 而是需要智能体在环境中一次次学习 哪些数据对应哪些标签 从而学习规律知道策略
  • Deep Ensemble Bootstrapped Q-Learning (Deep-EBQL)【代码复现】

    Deep EBQL理论基础 原文链接 Ensemble Bootstrapping for Q Learning Deep EBQL是EBQL的深度学习版本 也即是在DQN的基础上 引入集成的思想 解决DQN过估计的问题 深度版本的EBQL
  • 强化学习基础三大优化方法:(一)动态规划

    文章目录 一 简介 二 动态规划 DP Dynamic Planning 方法 一 策略评估 二 策略迭代 1 策略改进 2 策略迭代 3 迭代算法 三 编程实践 一 环境介绍 二 策略编写 1 初始化 2 价值评估 3 策略改进 4 其他
  • 论文笔记:STMARL: A Spatio-Temporal Multi-AgentReinforcement Learning Approach for Cooperative Traffic

    0 abstract 智能交通灯控制系统的开发对于智能交通管理至关重要 虽然已经做出了一些努力以孤立的形式来优化单个红绿灯的使用 但相关研究在很大程度上忽略了多路口红绿灯的使用受到空间影响的事实 以及历史交通状态的时间依赖性 为此 在本文中
  • Pytorch中常用的损失函数

    Pytorch中常用的损失函数 回归 nn L1Loss nn MSELoss 分类 nn CrossEntropyLoss 回归 nn L1Loss 平均绝对误差 也称L1范数损失 计算预测值与真实值之间的误差绝对值 L 1 L o s

随机推荐

  • 文件上传能通过ajax上传吗,通过ajax上传文件

    我使用2个文件index js upload php尝试通过ajax上传文件 img 如果成功追加到div uploadfile show 但是它不起作用 有几个问题 下面我的代码有什么建议吗 谢谢 upload php 1 form en
  • Vue脚手架的创建以及Eslint的解决

    1 创建脚手架 win R cmd 打开命令行窗口 运行下面代码 npm i g vue cli 2 cd Desktop 转到桌面 创建项目 vue create 项目名字 例如 vue create test 3 在自己写的项目中 出现
  • MD5加密及随机数生成

    今天贴两个简单的方法 需要的时候不用自己再写 public static String md5 String plaintext MessageDigest m try m MessageDigest getInstance MD5 m r
  • grafana 表格自动刷新

  • POSIX线程:API

    一 线程创建与取消 1 线程创建 1 1 线程与进程 相对进程而言 线程是一个更加接近于执行体的概念 它可以与同进程中的其他线程共享数据 但拥有自己的栈空间 拥有独立的执行序列 在串行程序基础上引入线程和进程是为了提高程序的并发度 从而提高
  • [数学]齐次线性方程组的解、SVD、最小二乘法

    AX 0 这是一个齐次线性方程组 一般的非齐次线性方程组AX b其实也都可以化为齐次方程组的形式 所以比较普遍 先要说明在非齐次方程组中 A到底有没有解析解 可以由增广矩阵来判断 r A
  • 颠覆人才市场?区块链凭什么?

    原创 点宽学园 作者 王启瑞 全文字数2509字 建议阅读时长 8 分钟 数字化平台包括 ABCD 四大核心技术 分别是人工智能 A Artificial Intelligence 区块链 B BlockChain 云计算 C Cloud
  • 我的学习体验是这样的,关于

    话说 完全没有想到 自己在知乎发布的第一篇文章 我在知乎学写作 竟然收获到还不错的互动 坦率地说 已经比我的微信公众号推文的效果好太多 我想 这主要是得益于大家对于知乎写作课本身的兴趣吧 到目前为止 虽然还没有修完知乎写作课的全部课程 但是
  • 浮点数在计算机中存储方式、十六进制(HEX)和浮点类型(float、double)转换

    目录 浮点数在计算机中存储方式 举例 8 25和120 5在内存中真正的存储方式 浮点类型转换为十六进制 方法1 用地址用指针 方法2 用共用体 方法3 使用memcpy 十六进制转换为浮点类型 浮点数在计算机中存储方式 https www
  • 多家支付机构停发代理商分润

    分润是POS代理推广的主要收入来源 一旦分润被掐断 意味着POS代理失去了赚钱的来源 近段时间以来 多家支付公司因停发POS代理分润在支付行业内引起了较大争议 涉及十几家支付公司 近日 多家支付公司代理商在公开投诉平台发帖投诉称 后台分润提
  • 详述Java中的异常

    我是目录 一 异常的解决方案 二 异常的基本用法 三 Java异常体系 四 自定义异常 五 面试阐述 所谓 异常 指的就是程序在 运行时 出现错误时通知调用者的一种机制 我们平时把 System out println 拼写错了 写成了 s
  • 第一章 pandas基础-练习题

    第一章 pandas基础 练习题 首先要导入对应的模块 import pandas as pd import numpy as np Ex1 口袋妖怪数据集 现有一份口袋妖怪的数据集 下面进行一些背景说明 代表全国图鉴编号 不同行存在相同数
  • QT基础(三)之添加资源文件及界面美化

    QT基础之添加资源文件及界面美化 QT可以做出非常炫酷的图形界面 通过添加一些资源文件可以对我们的界面进行美化 下面以添加图片资源 美化标签为例 对QT Creator添加资源文件进行学习 一 添加资源文件 1 给工程添加一个新的资源文件
  • C++之标准库(STL)容器List的用法

    文章目录 list说明 list定义 list使用 list赋值操作 list数据元素插入和删除操作 list数据存取 list大小操作 list反转排序 list访问 list说明 链表是一种物理存储单元上非连续 非顺序的存储结构 数据元
  • QtCreator 打不开UI文件

    最近遇到了ubuntu下的QtCreator打开ui 文件时 QT Creator 界面变黑色 程序崩溃 然后自动退出 这儿软件我用了1年了 莫名其妙出现了这个问题 很是费解 重装了QtCreator和QtDesigner 还是不行 在网上
  • Endnote 导入参考文献的时候,格式错误太多了。et.al错误,国标GBT7714在endnote中的详细配置教程,适用于本科,硕士论文文献插入的模板

    文章目录 一 Endnote文献GBT7714下载 0 起因与发展 1 1进行chinese GBT7714 2015的下载 二 Endnote格式配置及参数的设置 2 1 开始修改配置 重要配置详细分解 英文期刊 中文期刊 三 实战插入文
  • 用matlab进行拉普拉斯滤波,matlab拉普拉斯算子锐化滤波

    一 本文主要是在给出拉普拉斯锐化算子公式的情况下 在matlab上实现代码设计 拉普拉斯算子是最简单的各向同性微分算子 有几种常用的滤波模板 本文使用的是八邻域模板 如下所示 image png 其对应的计算公式为 image png 因此
  • 应用程序,操作系统,驱动程序的关系

    硬件和软件 计算机资源分为硬件资源和软件资源 硬件资源包括cpu 内存 显卡 网卡 声卡 硬盘等等 软件资源包括各种程序 每个硬件完成特定的功能 比如显卡完成在显示设备上显示图形 声卡实现声音的处理 再比如 你用qq发送一段文字给一个同学
  • VUE3使用JSON编辑器

    1 先看看效果图 可以自行选择展示效果 2 这是我在vue3项目中使用的JSON编辑器 首先引入第三方插件 npm install json editor vue3 yarn add json editor vue3 3 引入到项目中 导入
  • 强化学习实践二 :理解gym的建模思想

    David Silver的强化学习公开课有几个特点 个人感觉首要的一个特点是偏重于讲解理论 而且有时候为了讲清楚一个理论的来龙去脉 也顺带讲了很多不常用的理论 还有一个特点是小例子很多 这些例子有时候不仅是为了讲清楚一个复杂的算法 而且通过