c++中基于大型决策树的人工智能设计模式

Posted

技术标签:

【中文标题】c++中基于大型决策树的人工智能设计模式【英文标题】:Design pattern for large decision tree based AI in c++ 【发布时间】:2011-04-11 01:39:38 【问题描述】:

我目前正在为用 c++ 编写的游戏编写 AI。 AI 在概念上相当简单,它只是遍历决策树并选择适当的动作。我以前使用 prolog 作为决策引擎,但由于其他开发人员使用 c++ 以及集成 prolog 代码的一些问题,我现在正尝试将其移植到 c++。

目前我在 prolog (100+) 中有一堆事实和规则。许多表达形式的东西,如果game_state然后做动作xyz。大多数规则都相当简单,有些则相当复杂。我研究了一种有限状态机方法,但这似乎并不能很好地适应更大的情况。 我第一次尝试用 c++ 编写代码是一个巨大的噩梦,即 if then else case 语句。我到处都是这种代码:

    if( this->current_game_state->some_condition == true )
        if( this->current_game_state->some_other_condition == false )      
                //some code
        else
            return do_default_action();
        
    else if( this->current_game->another_condition )
        //more code
    

复杂性很快变得难以控制。

如果有一种好的方法可以用 C++ 编写这类问题?有没有好的设计模式来处理这种情况?不需要逻辑必须包含在源代码中,它只需要可以从 c++ 访问。唯一真正的要求是它相当快。

我还研究了规则引擎,如果速度足够快,它们可能是合适的。你知道是否有合适的开源 c++ 规则引擎?

【问题讨论】:

【参考方案1】:

代码就是数据,数据就是代码。你已经有了工作代码——你只需要将它以一种可以编译的方式暴露给 C++,然后你就可以实现一个最小的解释器来评估它。

一种可能性是采用您的 Prolog 规则并以最直接的方式将它们转换为数据结构。也许您可以设计一个简单的表格,例如:

struct 
    State coming_from;
    Event event;
    void (*func)(some, args);
    State going_to;
 rules[] = 
     WANDERING_AROUND, HEAR_SOUND, look_around, ENEMY_SEEN ,
     ENEMY_SEEN,       GUN_LOADED, fire_gun,    SNEEK_AWAY ,
     next, rule, goes, here ,
    etc... 

同样,函数调用可以填充数据结构,使其看起来类似于您的原始 Prolog:

void init_rules () 
    rule("Parent", "Bill", "John");
    rule("Parent", "Paul", "Bill");
    // 99 more rules go here...

然后您实现一个简单的解释器来遍历该数据结构并找到您需要的答案。如果规则少于 1000 条,蛮力搜索方法可能会足够快,但您以后总是可以变得聪明,并在时机成熟时尝试按照真正的 Prolog 环境的方式做事。

【讨论】:

那是一个有限状态机,这正是他所说的他首先尝试并炸毁了他的脸。 并不是说有限状态机不是我想要的,而是有限状态机的幼稚实现太复杂而无法管理。这个建议似乎有助于更好地管理复杂性。如果我要遵循这种方法,使用解释器似乎正是我所需要的。但是,我仍然没有完全接受使用有限状态机方法 第一个块当然是一个状态机,但我的意思是你可以将它实现为一个表驱动的算法,而不是一堆嵌套的 if-then-elses 或一个大的讨厌的 switch 语句。第二个部分是尝试仅使用 C++ 语法显示 DSL。这不仅仅是一个简单的状态机。您已经使用了 Prolog,因此与其尝试将其转换为 C++,我认为教 C++ 如何解释您现有的代码/数据可能更简单、更清晰。也许您可以发布您的规则/事实的一个子集,以便我们对其进行更好的处理并做出合理的示例。 boost::function 是否可以替代此处建议的使用函数指针? 如果您的项目已经在使用 Boost,我认为采用任何一种方式都是合理的。【参考方案2】:

您可以使用多态性。调用虚函数实际上是由编译器为您完成和优化的大型开关/案例。

class GameState 
    virtual void do_something()  std::cout << "GameState!"; 
    // some functions
    virtual ~GameState() 
;
class SomeOtherState : public GameState 
    // some other functions
    virtual void do_something()  std::cout << "SomeOtherState!"; 
;
class MyFinalState : public GameState 
    virtual void do_something()  std::cout << "MyOtherState!"; 
;
class StateMachine 
    std::auto_ptr<GameState> curr_state;
public:
    StateMachine()
        : curr_state(NULL) 
    void DoSomething()  curr_state->DoSomething(); 
    void SetState(GameState* ptr)  curr_state = ptr; 
    template<typename T> void SetState()  curr_state = new T; 
;
int main() 
    StateMachine sm;
    sm.SetState(new SomeOtherState());
    sm.SetState<SomeOtherState>();
    sm.DoSomething(); // prints "SomeOtherState!"
    sm.SetState<MyFinalState>();
    sm.DoSomething(); // prints "MyFinalState!"

在上面的例子中,我不需要切换任何状态,甚至不需要知道不同的状态存在或它们做什么(在 StateMachine 类中,无论如何),选择逻辑是由编译器完成的。

【讨论】:

这似乎是减少使用一堆函数指针的好方法。对于未来的项目,我一定会牢记这一点。【参考方案3】:

如果您想将您的 prolog 代码转换为 c++ 代码, 查看在 C++ 中启用逻辑编程的 Castor 库 (C++): http://www.mpprogramming.com/Cpp/Default.aspx

我自己没有试过,所以对它的性能一无所知。

如果您想使用状态机,请查看 Boost.Meta State Machine

【讨论】:

【参考方案4】:

我真的不明白为什么有限状态机不能满足您的游戏需求。这是做你想做的事的常用方法。您可以使其数据驱动,以使您的代码从具体操作中保持干净。有限状态 m。在“AI for Game Dev”中也有描述 O'Reilly (David M. Bourg & Glenn Seemann) 您可能希望将您的规则拆分为几个较小的规则集,以保持机器小巧且易于理解。

【讨论】:

【参考方案5】:

使用水银怎么样?它基本上是为了与 C 代码交互而构建的。

【讨论】:

是否有专门针对水星的 c++ 接口?此外,我在从源代码编译水银时遇到了很多麻烦。 与 C++ 的接口是 ez 游戏。但是是的,除非你能让编译器工作,否则它有点没用:P【参考方案6】:

试图将 Prolog 的表达能力与状态机相匹配,就像试图用自行车超越汽车。

蓖麻可能是要走的路。它非常轻量级,允许逻辑编程和其他 C++ 之间的平滑互操作。看看http://www.mpprogramming.com/cpp上的教程视频

【讨论】:

以上是关于c++中基于大型决策树的人工智能设计模式的主要内容,如果未能解决你的问题,请参考以下文章

模式识别 - 决策树问题研究

模式识别(Pattern Recognition)学习笔记(二十九)--决策树的剪枝

MIT计算机科学与人工智能实验室开发AI系统决策未来全新转诊模式

基于数据仓库及决策树算法的电网事故事件信息智能检索方法研究

大数据的常用算法(分类回归分析聚类关联规则神经网络方法web数据挖掘)

人工智能(模式识别)学习笔记:目录