我应该如何在数据库中存储稀疏决策树(移动列表)?

Posted

技术标签:

【中文标题】我应该如何在数据库中存储稀疏决策树(移动列表)?【英文标题】:How should I store a sparse decision tree(move list) in a database? 【发布时间】:2011-10-21 18:37:10 【问题描述】:

我一直在想为棋盘游戏做一个人工智能,最近我开始收集资源和算法。游戏是非随机的,大多数时候,一个玩家有 20 步。我想存储关键动作或模棱两可的动作,以便人工智能从错误中吸取教训,下次不会犯同样的错误。无需存储肯定会赢或输的动作。所以我实际上有一个用于游戏开始的稀疏决策树。 我想知道我应该如何将这个决策树存储在数据库中?数据库不需要是 SQL,我不知道哪个数据库适合这个特定的问题。

编辑:请不要告诉我将决策树解析到内存中,想象一下游戏就像国际象棋一样复杂。

【问题讨论】:

为什么是数据库?只需在运行时从文本文件加载它。您只会看到拥有数十万条目的数据库的好处。保持简单,不要打扰数据库。 为什么是数据库?因为这棵树太大而无法存储在内存中。我知道该游戏的已解决总是获胜分支非稀疏决策树为 200mbs,该游戏至少有 14 个以上的此类分支。 你听说过 Redis 吗?这是一个简单的键值存储,您可以使用它来实现您的树逻辑。在不知道您的决策树是如何设置的情况下,这可能是一个合适的解决方案。 @tjameson Redis 是一个选项,但正如你所说,它是一个 simple 键值存储,实际上我想要一个更适合游戏树的数据库. @TiansHUo- 你可以做一些基本的查询。您可能感兴趣的是钥匙。这可以进行一些非常基本的键搜索,如果您组织键以使其遵循树状结构 (top:next:next:next:next),它可能会非常快。除了严格的键值存储之外,它还具有其他功能。我不会将此作为解决方案提交,因为我不知道您的游戏是如何设置的。 【参考方案1】:

当您将遍历树时,neo4j 对我来说似乎是一个很好的解决方案。 SQL 不是一个好的选择,因为查询需要很多连接。据我了解,您正在寻求一种将一些图形存储在数据库中的方法,而 neo4j 是一个专门用于图形的数据库。对于稀疏性,您可以使用 PropertyContainers 将基元数组或字符串附加到图形的边缘以对移动序列进行编码(我说得对,通过稀疏和跳过节点意味着您的树边是移动序列而不是单个移动?)。

【讨论】:

不完全一样,很多招都可以达到同样的情况。 好的,在这种情况下,它更容易,因为树只是变成了一个“正常”图。只需用移动(以及带有分数的节点/状态)注释图形的边缘,并确保您的边缘是定向的。然后您可以像遍历相应的树一样遍历您的图表。如果您知道稀疏树的样子,您可以以这种方式存储它。例如,您可以随后查询您的数据库以获取状态,并查看您是否为它保存了移动。【参考方案2】:

首先,您尝试做的事情听起来像是基于案例的推理(CBR)问题,请参阅:http://en.wikipedia.org/wiki/Case-based_reasoning#Prominent_CBR_systems。 CBR 将有一个决策数据库,您的系统理论上会选择可用的最佳结果。

因此,我建议使用 nosql 图形数据库 neo4j。 http://neo4j.org/

因此,为了表示您的游戏,每个位置都是图中的一个节点,每个节点都应该包含从所述位置开始的潜在移动。您可以跟踪随着游戏进度学习的得分指标,以便 AI 更了解情况。

【讨论】:

请注意,在我的问题中,我要求使用 sparse 树,这意味着可能会跳过许多节点。但是neo4j 使用了一棵完整的树,这意味着必须保存每一步。这意味着数据库将太大。 您希望在什么情况下跳过步骤?在 node4j 中,您只需设计底层数据模型,以便在某些情况下它只是跳到相关的决策过程。【参考方案3】:

我会使用像 RavenDB 这样的文档数据库 (NOSQL),因为您可以在数据库中存储任何数据结构。

文档不像普通 SQL 数据库那样平坦,它允许您直接存储分层数据,如树:

 
   decision: 'Go forward', 
   childs: [ 
       decision: 'Go backwards' ,
       
         decision: 'Stay there',
         childs: [
             decision: 'Go backwards' 
         ]
      
   ]

在这里您可以看到一个可以存储在 RavenDB 中的示例 JSON 树。

RavenDB 还具有查询分层数据的内置功能: http://ravendb.net/faq/hierarchies

请查看documentation 以获取有关 RavenDB 工作原理的更多信息。

资源:

What type of NoSQL database is best suited to store hierarchical data?

【讨论】:

【参考方案4】:

您可以使用memory mapped file 作为存储。 首先,创建“编译器”。该编译器将解析文本文件并将其转换为紧凑的二进制表示。主应用程序会将这个二进制优化文件映射到内存中。这将解决您的内存大小限制问题

【讨论】:

我希望文件是增量的,而不是预先计算的。所以最好有数据库。【参考方案5】:

从简单的数据库表设计开始。

决定: 当前状态 BINARY(57) | NewState BINARY(57) |得分INT

CurrentState 和 NewState 是游戏状态的序列化版本。分数是赋予 NewState 的权重(正分数是好动作,负分数是坏动作)您的 AI 可以适当地更新这些分数。

Renju,使用 15x15 板,每个位置可以是黑色、白色或空白,因此您需要 Ceiling( (2bits * 15*15) / 8 ) 字节来序列化板。在 SQL 中,这将是 T-SQL 中的 BINARY(57)

你的 AI 会选择它存储的当前动作......

SELECT NewState FROM Decisions WHERE CurrentState = @SerializedState ORDER BY Score DESC

您将获得从当前游戏状态中存储的所有下一步移动的列表,按照得分从高到低的顺序排列。

您的表结构将在 (CurrentState, NewState) 上有一个复合唯一索引(主键),以便于搜索并避免重复。

这不是最佳/最佳解决方案,但由于您缺乏数据库知识,我相信这将是最容易实施的,并且会给您一个良好的开端。

【讨论】:

【参考方案6】:

如果我与国际象棋引擎进行比较,那些是凭记忆玩的,也许除了打开图书馆。国际象棋太复杂,无法存储决定性的决策树。国际象棋引擎通过将启发式评估分配给潜在和瞬态未来位置(而不是移动)来进行游戏。未来的位置是通过某种有限的深度搜索找到的,可能会在内存中缓存一段时间,但由于搜索空间太大而无法以比重新计算更快的查找方式存储,因此通常每轮都会重新计算。

【讨论】:

【参考方案7】:

你知道Chinook——解决跳棋的人工智能吗?它通过编译每个可能的残局的数据库来做到这一点。虽然这不是您正在做的事情,但您可以从中学习。

【讨论】:

【参考方案8】:

我无法清楚地设想您在树中处理的数据结构及其复杂性。

但这里有一些你可能感兴趣的想法:

将决策树映射为稀疏矩阵,树毕竟是图 利用稀疏矩阵属性设计存储/检索策略。

【讨论】:

【参考方案9】:

我会用国际象棋引擎中处理开局的传统方式来处理这个问题:

    生成所有可能的移动 对于每一步:
      采取行动 在您的数据库中查找结果位置 撤消移动
    进行数据库中得分最高的移动

查找动作

国际象棋引擎通常通过Zobrist hashing计算当前游戏状态的哈希函数,这是为游戏状态构造良好哈希函数的简单方法。

这种方法的最大优势在于它处理了transpositions,也就是说,如果可以通过备用路径到达相同的状态,则无需担心那些备用路径,只需关心游戏状态自己。

国际象棋引擎如何做到这一点

大多数国际象棋引擎使用从记录的游戏编译而来的静态开局书,因此使用简单的二进制文件将这些哈希映射到分数;例如

struct book_entry 
    uint64_t hash
    uint32_t score

然后这些条目按哈希排序,并且由于操作系统缓存,一个简单的binary search 通过文件将很快找到所需的条目。

更新分数

但是,如果您希望引擎持续学习,您将需要更复杂的数据结构;此时通常不值得自己做,您应该使用可用的库。我可能会使用LevelDB,但任何可以让您存储键值对的东西都可以(Redis、SQLite、GDBM 等)

学习分数

您如何准确地更新分数取决于您的游戏。在有大量可用数据的游戏中,一种简单的方法(例如仅存储导致位置的移动后获胜的游戏百分比)是有效的;如果您的数据较少,您可以将来自相关位置的游戏树搜索结果存储为分数。诸如Q learning 之类的机器学习技术也是一种可能,尽管我不知道有什么程序在实践中真正做到这一点。

【讨论】:

【参考方案10】:

我假设您的问题是询问如何将决策树转换为可以写入某个位置并随后用于重建树的串行格式。

尝试使用树的前序遍历,使用 toString() 函数(或其等效函数)将存储在决策树每个节点的数据转换为文本描述符。通过前序遍历,我的意思是实现一种算法,首先在节点上执行 toString() 操作,并将输出写入数据库或文件,然后在其子节点上递归执行相同的操作,以指定的顺序。因为您正在处理稀疏树,所以您的 toString() 操作还应该包括有关子树存在或不存在的信息。

重建树很简单——第一个存储的值是根节点,第二个是左子树的根成员,以此类推。为每个节点存储的串行数据应提供有关下一个输入节点应属于哪个子树的信息。

【讨论】:

投反对票 - 这不是答案,我不想将树解析到内存中,我至少需要一个内存索引文件,它可以尽快找到分支。 这也不对,你需要先序+中序遍历才能重建二叉树。更不用说 OP 的树不是二叉树了。

以上是关于我应该如何在数据库中存储稀疏决策树(移动列表)?的主要内容,如果未能解决你的问题,请参考以下文章

如何在决策树中获取特征重要性?

如何在matlab中存储和检索多个决策树

如何存储决策树

制作决策树时如何处理数据

机器学习之路--决策树

决策树分类器,多标签输出