【行为树】py_trees 学习笔记

学习资料

I. 《Introduction to behavior trees》

II. Intro to BTs part 14: Behavior Trees and Reinforcement Learning

  • 如何将BT跟RL结合

RL模型可以用来替换Action、Subtree或者CompositeNode

在这里插入图片描述

1. 常用提示词

请问,这里提到的“Teleo-reactive approach”是什么呀?
我是一名高中生,希望你用浅显形象的语言解释一下

2. 常见术语

ACC:Active Constraint Condition.

3. 概念说明

3.1 Action:pt.behaviour.Behaviour

在一个行为树中,Action 是一个叶节点(Leaf Node),它实际执行某种行为或任务,并返回成功、失败或运行中的状态。

Lifecycle:生命周期

  1. setup(): 这个方法在行为树开始执行之前被调用,用于进行一些初始化工作。例如,打开网络连接或初始化硬件。
  2. initialise(): 当行为从非运行状态(Status.SUCCESSStatus.FAILURE)转换到运行状态(Status.RUNNING)时,这个方法会被调用。
  3. update(): 这是行为的主要执行方法,它在每个行为树的“tick()”中被调用。这个方法应返回一个状态,表示该行为的执行结果(成功、失败或运行中)。
  4. terminate(): 当行为从运行状态(Status.RUNNING)转换到非运行状态(Status.SUCCESSStatus.FAILURE)时,这个方法会被调用。通常用于进行一些清理工作。
  5. shutdown(): 这个方法在行为树停止执行后被调用,用于释放资源。

Memory:从上一次返回running的节点处继续执行

南溪觉得memory是一种实现可靠性的机制,意味着上一步骤的作用是稳定的;而设置memory=False时,意味着环境变化十分频繁,需要每次都重头开始执行当前策略链以保证应对时常变化的情况;

Note:所以对于【检测-执行】型的Selector节点我们一般都会设置memory=False

1.2 Statuspy_trees.common.Status

Note:
在需要等待的动作函数中使用Runnning,可以使得当前子节点停止运行并返回父节点,结合memory=True设置,可以使得行为树自动重复当前子节点的执行,从而避免显式的编写循环来实现重复等待操作,可以简化代码的逻辑,使得前置检测跟动作执行解耦

1.3 Intent:意识

有时候,下一步的行动是一系列动作的集合,例如要先:判断香蕉🍌是否剥好;如果已经剥好,就可以直接吃了;这里就会涉及到多个动作;
所以下列组件都可以作为Intent:

  • Sequence

1.4 Selector:“如果-则-否则”

在这里插入图片描述

root = py_trees.composites.Sequence(name="NameString", memory=False)

1.5 Tree

BT基于树型结构,它通过高层节点和底层节点的有序组合来实现智能体的行为。高层节点通常表示智能体的目标或者策略,底层节点通常表示智能体的具体动作或者反应。

“反应式”

BT的“反应式”来自于它能够快速地根据当前的世界状态来选择合适的行为。当世界状态发生变化时,BT会重新评估每个节点的条件,并选择最合适的行为来执行。这种快速反应能力使得BT能够适应动态环境的变化,从而实现智能体的目标。

1.6 CBF: Control Barrier Function

Perplexity:
Control Barrier Functions (CBFs) 对于确保一个动作不违反子目标是有帮助的。CBFs 是一种在控制系统中用于实现安全性和稳定性的方法。它们可以用来约束系统的行为,以确保系统在执行动作时不会违反早期行为所实现的子目标。

CBFs 可以通过在系统的控制器中引入约束项来实现。这些约束项可以是系统状态的函数,通过优化这些函数可以实现对系统行为的约束。通过使用CBFs,可以在系统的控制器中引入对子目标的约束,从而确保系统在执行动作时不会违反这些子目标。

因此,CBFs 可以作为一种方法来确保动作不会违反子目标,从而实现更加可靠和高效的行为。

4. 设计思想

4.1 Decision Tree design

参考资料

Petter Ögren在教程【Creating Behavior Trees using Decision Tree design (BT intro part 3)】中讲到如何将DT转换成BT;南溪觉得这是一种很有趣的设计方法,因为DT的示意图会比较清晰,有点像程序执行的逻辑图
在这里插入图片描述

2.2 Optimal Policy from Reinforcement Learning

作者在【Behavior Trees vs Finite State Machines (BT intro part 4)】中,提到了一个很有趣的、关于强化学习(Reinforcement Learning)和最优策略(optimal policy)观点:

“在强化学习中,最优策略是一种根据当前世界状态来选择最佳动作的方法。这意味着,理论上,你不需要记住之前做了什么(即不需要内部状态),你只需要看当前的世界状态就能做出最佳决策。”

南溪觉得这一点给我们很深刻的启发:RL的思想有一种很重要的特性,也就是使用端到端的模型来自动输出agent下一步的动作,这给工程维护带来了很大的便利性;

2.3 Subsumption Architecture

首先我们来看看NewBing对 Subsumption Architecture 的介绍:

NewBing:

  • Subsumption architecture是一种用来控制智能体行为的方法,它是一种基于行为的机器人学的重要分支,它在1980年代和90年代非常流行。这个术语是由Rodney Brooks和他的同事在1986年提出的。
  • Subsumption architecture的核心思想是,智能体不需要用符号来表示世界,而是直接用感知信息来选择行动,这是一种自下而上的方式。它通过把智能体的行为分解成不同的子行为,然后把这些子行为组织成一个层次结构。每一层实现了一种特定的行为能力,高层可以取代低层来创造可行的行为。例如,一个机器人的最低层可以是“避开障碍物”,第二层可以是“随机游走”,第三层可以是“探索世界”。因为一个机器人要想有效地“随机游走”,就必须有“避开障碍物”的能力,所以 Subsumption Architecture 创建了一个系统,让高层利用低层的能力。
  • Subsumption Architecture的目标是与传统的人工智能相对立。传统的人工智能通常用一个中央“大脑”来包含一个对世界的符号表示,然后根据感知信息来操作这个模型。大脑根据这个虚拟模型来决定如何使用它的执行器和末端效应器。传统的人工智能架构是计算密集型的,而且太慢了,无法跟上动态环境的变化。事实上,无论传统人工智能机器人有多少计算能力,它永远也无法创建一个比世界本身更准确或更及时的表示。Subsumption Architecture认为“世界是它自己最好的模型”,也就是说,适当的【感知 - 行动】设置可以直接与世界交互,而不需要对其建模。

南溪觉得 Subsumption Architecture 很像人类层次化的思维方式,可以帮助我们在编程中快速地进行思考和构建机器人的行为模式;

2.4 Teleo-reactive Approach

我们先来看看NewBing对 Teleo-reactive Approach 的介绍:

你好,这是Bing。我很高兴能帮助你。😊

  • “Teleo”是一个希腊词根,它的意思是“目的”或者“完成”。它和另一个希腊词根“reactive”组合起来,就形成了“Teleo-reactive approach”的名称。
  • “Teleo-reactive approach”的核心思想是,智能体不需要用复杂的规划来决定如何达成目标,而是用一系列的条件/动作规则来指导行动,这是一种自上而下的方式。它通过把智能体的目标分解成不同的子目标,然后把这些子目标组织成一个优先级列表。每一个子目标对应一个条件/动作规则,当条件满足时,就执行相应的动作,直到达成目标或者条件不再满足为止。
  • “Teleo-reactive approach”的特点是,它可以让智能体快速地根据当前的世界状态来选择合适的行为。当世界状态发生变化时,智能体会重新评估每个规则的条件,并选择最合适的行为来执行。这种快速反应能力使得智能体能够适应动态环境的变化,从而实现智能体的目标。

不过,需要注意的是:

Bing:
BT和Teleo-reactive approach都具有快速反应能力,它们都能够快速地根据当前的世界状态来选择合适的行为。
因此,可以说BT在某种程度上契合了 Teleo-reactive Approach 的核心思想。但是,它们也有一些不同之处。例如,BT通常用树型结构来表示智能体的行为,而 Teleo-reactive Approach 通常用优先级列表来表示智能体的目标。

由此可知,BT虽然借鉴了 Teleo-reactive Approach 的思想,但是在实现上使用的是自己与众不同的树形结构;

5. 编程哲学

5.1 Keeping Previous Subgoals

这里我们引用 Petter Ögren 对于保持subgoals的描述:

Petter:
We often want to make sure that an action does not violate subgoals that was achieved by earlier actions (such as making sure we stay in the safe part of the state space). CBFs are great for doing precisely this, by guaranteeing that some parts of the state space are invariant (we do not leave them).

5.2 Naming Subtrees Enable Tracing Running Path

通过给子树或者节点命名,可以用来追踪BT运行的路径;

Note:py_trees 在定义子树时需要定义name属性做为当前子树的名称。

5.3 Try to Use Non-memory Sequences [video]

Note:
Non-memory Sequences 在运行时会自动保存中断位置,并在下一次运行时从中断处开始执行;可以看到,这里的“中断点”信息其实是隐式的,也就是说是由框架内部来维护的,而作为调用者,我们一般无法显式地观测到这个状态信息,于是在调试时,可能会无法全面地分析行为树出现当前执行路径的原因。

因此,Petter Ögren 在视频中建议【Make sure your BT software includes non-memory sequences!】,或者将 Memory-based Sequence 转化为 Reactive Sequence(Blackboard),从而会更加直观,如图所示:
在这里插入图片描述

6. 设计模式

6.1 Belief

Belief模式是可以用来解决,由于在条件判断耗时的情况下,行为树(BT)执行速度慢问题。这个模式的关键是将条件判断的结果作为Belief(信念)存储在位向量(Bit Vector)中。

问题描述

BT的一个缺点是它往往需要不断地检查许多条件(确认环境信息),这会影响执行效率。

Bit Vector:用来记录状态信息的数据结构

Note:可以考虑使用Blackboard来实现Belief。

6.2 Utility Behavior Tree:效用行为树

Petter Ögren:
The core idea of Utility BT is to add a “utility fallback node”, that gets information regarding how useful all its children are, and then reorders them based on this utility, ticking the one with highest utility first.

6.3 PPA-BT: Postcondition-Precondition-Action BT

在这里插入图片描述

7. 常用词汇

Deal:表示处理事件;
Assure:表示确认事件因素是否存在。

8. 数据共享:blackboard

blackboard.Client:数据权限视图 —— main()

可以使用blackboard.Client来实现对不同数据读写权限的控制;

Note

  • 尽管客户端的名称不同,但是如果多个客户端对相同的key进行操作,它们实际上是在读取或修改同一个变量。
  • py_treesClient最主要的作用是对变量参数进行权限控制。每个客户端可以为特定的键(即变量)注册读取或写入权限。

self.attach_blackboard_client():获得黑板client —— Behaviour

代码模板:

self.blackboard = self.attach_blackboard_client()

9. 常见内置behaviours

py_trees.behaviours.CheckBlackboardVariableValue:检查黑板变量值 [demo]

10. 运行调试

10.1 Unicode_tree

py_trees.display.unicode_tree(root, show_status=True):显示当前树的运行状态 [demo]

10.2 Visitor

10.2.1 DebugVisitor

behaviour_tree.visitors.append(py_trees.visitors.DebugVisitor()):显示调试信息 [demo]

调试信息示例如下:

--------- Run 9 ---------

[DEBUG] Root                 : Parallel.tick()
[DEBUG] Reset                : Sequence.tick()
[DEBUG] Joy1 - Disabled      : SetBlackboardVariable.stop(Status.SUCCESS->Status.INVALID)
[DEBUG] Joy2 - Disabled      : SetBlackboardVariable.stop(Status.SUCCESS->Status.INVALID)
[DEBUG] Joy1 - Disabled      : SetBlackboardVariable.tick()
[DEBUG] Joy1 - Disabled      : SetBlackboardVariable.stop(Status.INVALID->Status.SUCCESS)
[DEBUG] Joy1 - Disabled      : DebugVisitor.run() [Status.SUCCESS]
[DEBUG] Joy2 - Disabled      : SetBlackboardVariable.tick()
[DEBUG] Joy2 - Disabled      : SetBlackboardVariable.stop(Status.INVALID->Status.SUCCESS)
[DEBUG] Joy2 - Disabled      : DebugVisitor.run() [Status.SUCCESS]
[DEBUG] Reset                : Sequence.stop()[Status.SUCCESS->Status.SUCCESS]
[DEBUG] Reset                : DebugVisitor.run() [Status.SUCCESS]
[DEBUG] Joy1 Events          : Sequence.tick()
[DEBUG] FisR                 : FailureIsRunning.tick()
[DEBUG] Joystick 1           : SuccessEveryN.tick()
[DEBUG] Joystick 1           : SuccessEveryN.update()][10]
[DEBUG] Joystick 1           : SuccessEveryN.stop(Status.FAILURE)
[DEBUG] Joystick 1           : DebugVisitor.run() [not yet][Status.FAILURE]
[DEBUG] FisR                 : DebugVisitor.run() [failure is running [not yet]][Status.RUNNING]
[DEBUG] Joy1 Events          : DebugVisitor.run() [Status.RUNNING]
[DEBUG] Joy2 Events          : Sequence.tick()
[DEBUG] FisR                 : FailureIsRunning.tick()
[DEBUG] Joystick 2           : SuccessEveryN.tick()
[DEBUG] Joystick 2           : SuccessEveryN.update()][10]
[DEBUG] Joystick 2           : SuccessEveryN.stop(Status.FAILURE)
[DEBUG] Joystick 2           : DebugVisitor.run() [not yet][Status.FAILURE]
[DEBUG] FisR                 : DebugVisitor.run() [failure is running [not yet]][Status.RUNNING]
[DEBUG] Joy2 Events          : DebugVisitor.run() [Status.RUNNING]
[DEBUG] Tasks                : Selector.tick()
[DEBUG] Tasks                : Selector.tick() [!RUNNING->reset current_child]
[DEBUG] ...

10.2.2 SnapshotVisitor:快照访问者

snapshot_visitor = py_trees.visitors.SnapshotVisitor()
示例代码
snapshot_visitor = py_trees.visitors.SnapshotVisitor()
behaviour_tree.add_post_tick_handler(
    functools.partial(post_tick_handler, snapshot_visitor)
)
behaviour_tree.visitors.append(snapshot_visitor)

Note:
py_trees.display.unicode_treepreviously_visited用于指定上一次的访问列表,使得 unicode_tree 可以更加全面地显示之前地历史信息。

调试信息示例如下:

--------- Run 3 ---------


[-] Eternal Guard []
    --> Condition 1 []
    --> Condition 2 []
    {-} Task Sequence [-]
        --> Worker 1
        --> Worker 2 [-]

Blackboard Data

10.3 Decorator

显示行为叶节点的运行时间

def timed_update_decorator(method):
    def timed_update(self):
        start_time = time.time()
        result = method()
        end_time = time.time()
        print(f"{self.name} execution time: {end_time - start_time:.4f} seconds")
        return result

    return timed_update

def add_timing_to_leaf_behaviours(behaviour):
    if hasattr(behaviour, 'update') and not getattr(behaviour, 'children', []):
        behaviour.update = MethodType(timed_update_decorator(behaviour.update), behaviour)
    for child in getattr(behaviour, 'children', []):
        add_timing_to_leaf_behaviours(child)

add_timing_to_leaf_behaviours(tree.root)

11. 可视化

Dot Graph

示例图像

在这里插入图片描述

Xdot:可视化 dot graph

Xdot相比静态图片提供了更好的图形交互体验,主要包括以下几点:

  • 放大缩小:可以通过鼠标滚轮或手势放大缩小图形,方便查看图形细节。
  • 平移:鼠标拖动可以平移视图,快速定位到图形不同区域。
  • 节点定位:可以搜索并快速跳转到特定节点。
  • 节点高亮:悬停在节点上可以高亮显示,更清晰的查看节点信息。
  • 节点详情:点击节点可以弹出窗口显示节点所有属性信息。
  • 子图折叠:可以折叠隐藏子图,只关注当前层次结构。

12. 官方文档学习

南溪觉得,py_trees的官方文档书写得十分优秀,示例多样而且提供了许多测试API;
推荐阅读的材料列表:

  • Release Notes
  • Demos
  • Module API
  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值