简体   繁体   English

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

[英]How should I store a sparse decision tree(move list) in a database?

I have been thinking of making an AI for a board game for a long time, and recently I've started to gather resources and algorithms. 很长时间以来,我一直在考虑为棋盘游戏制作AI,最近我开始收集资源和算法。 The game is non-random, and most of the time, there < 3 moves for a player, sometimes, there are >20 moves. 游戏是非随机的,并且在大多数情况下,玩家的棋步少于3个,有时棋手的步伐超过20个。 I would like to store critical moves, or ambiguous moves so that the AI learns from its mistakes and will not make a same mistake the next time. 我想存储关键动作或模棱两可的动作,以使AI从错误中吸取教训,并且下次不会再犯相同的错误。 Moves that surely win or lose need not be stored. 肯定会赢或输的动作无需存储。 So I actually have a sparse decision tree for the beginning of games. 所以我实际上在游戏开始时有一个稀疏的决策树。 I would like to know how I should store this decision tree in a database? 我想知道如何将这个决策树存储在数据库中? The database does not need to be SQL, and I do not know which database is suitable for this particular problem. 该数据库不必是SQL,并且我不知道哪个数据库适合此特定问题。

EDIT: Please do not tell me to parse the decision tree into memory, just imagine the game as complicated as chess. 编辑:请不要告诉我将决策树解析到内存中,只是想象一下像国际象棋这样复杂的游戏。

As you will be traversing the tree, neo4j seems like a good solution to me. 当您将遍历这棵树时,neo4j对我来说似乎是一个很好的解决方案。 SQL is no good choice because of the many joins you would need for queries. SQL并不是很好的选择,因为查询需要许多联接。 As i understand the question, you are asking for a way to store some graph in a database, and neo4j is a database explicitely for graphs. 据我了解的问题,您正在寻求一种在数据库中存储某些图形的方法,而neo4j是显式用于图形的数据库。 For the sparseness, you can attach arrays of primitives or Strings to the edges of your graph to encode sequences of moves, using PropertyContainers (am i right that by sparseness and skipping of nodes you mean your tree-edges are sequences of moves rather than single moves?). 对于稀疏性,您可以使用PropertyContainers将图元或字符串数​​组附加到图形的边缘以编码移动序列(正确的是,稀疏和跳过节点意味着树边缘是移动序列而不是单个动作?)。

Firstly what you are trying to do sounds like a case based reasoning(CBR) problem see: http://en.wikipedia.org/wiki/Case-based_reasoning#Prominent_CBR_systems . 首先,您尝试做的事情听起来像是基于案例的推理(CBR)问题,请参见: http : //en.wikipedia.org/wiki/Case-based_reasoning#Prominent_CBR_systems CBR will have a database of decisions, your system would in theory pick the best outcomes available. CBR将拥有一个决策数据库,理论上您的系统将选择可用的最佳结果。

Therefore I would suggest using neo4j which is a nosql graph database. 因此,我建议使用neo4j,它是一个nosql图形数据库。 http://neo4j.org/ http://neo4j.org/

So to represent your game each position is a node in the graph, and each node should contain potential moves from said position. 因此,为了表示您的游戏,每个位置都是图形中的一个节点,每个节点都应包含从该位置开始的潜在移动。 You can track scoring metrics which are learnt as games progress so that the AI is more informed. 您可以跟踪随着游戏的进展而学习的得分指标,从而使AI的信息更加丰富。

I would use a document database (NOSQL) like RavenDB because you can store any data structure in the database. 我将使用像RavenDB这样的文档数据库(NOSQL),因为您可以在数据库中存储任何数据结构。

Documents aren't flat like in a normal SQL database and that allows you to store hierarchical data like trees directly: 文档不是像普通SQL数据库中那样平坦,它使您可以直接存储树之类的分层数据:

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

Here you can see an example JSON tree which can be stored in RavenDB. 在这里,您可以看到示例JSON树,该树可以存储在RavenDB中。

RavenDB also has a built-in feature to query hierarchical data: http://ravendb.net/faq/hierarchies RavenDB还具有内置功能来查询分层数据: http ://ravendb.net/faq/hierarchies

Please look at the documentation to get more information how RavenDB works. 请查看文档以获取更多有关RavenDB如何工作的信息。

Resources: 资源:

You can use memory mapped file as storage. 您可以使用内存映射文件作为存储。 First, create "compiler". 首先,创建“编译器”。 This compiler will parse text file and convert it into compact binary representation. 该编译器将解析文本文件,并将其转换为紧凑的二进制表示形式。 Main application will map this binary optimized file into memory. 主应用程序会将这个二进制优化文件映射到内存中。 This will solve your problem with memory size limitation 这将解决您的内存大小限制问题

Start with a simple database table design. 从简单的数据库表设计开始。

Decisions: CurrentState BINARY(57) | 决策:CurrentState BINARY(57)| NewState BINARY(57) | NewState BINARY(57)| Score INT 得分INT

CurrentState and NewState are a serialized version of the game state. CurrentState和NewState是游戏状态的序列化版本。 Score is a weight given to the NewState (positive scores are good moves, negative scores are bad moves) your AI can update these scores appropriately. 分数是赋予NewState的权重(正分数是好举,负分数是坏举),您的AI可以适当地更新这些分数。

Renju, uses a 15x15 board, each location can be either black, white or empty so you need Ceiling( (2bits * 15*15) / 8 ) bytes to serialize the board. 人居使用15x15电路板,每个位置可以是黑色,白色或空白,因此您需要Ceiling((2bits * 15 * 15)/ 8)个字节来序列化该电路板。 In SQL that would be a BINARY(57) in T-SQL 在SQL中,这将是T-SQL中的BINARY(57)

Your AI would select the current moves it has stored like... 您的AI会选择它已存储的当前动作,例如...

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

You'll get a list of all the stored next moves from the current game state in order of best score to least score. 您将获得从当前游戏状态按最佳得分到最低得分顺序存储的所有下一步动作的列表。

Your table structure would have a Composite Unique Index (primary key) on (CurrentState, NewState) to facilitate searching and avoid duplicates. 您的表结构将在(CurrentState,NewState)上具有一个复合唯一索引(主键),以方便搜索并避免重复。

This isn't the best/most optimal solution, but because of your lack of DB knowledge I beleive it would be the easiest to implement and give you a good start. 这不是最佳/最佳的解决方案,但是由于您缺乏数据库知识,我相信这将是最容易实现并为您提供良好开端的方法。

If I compare with chess engines, those play from memory, maybe apart from opening libraries. 如果将我与国际象棋引擎进行比较,那是从内存中进行的,也许除了打开库以外。 Chess is too complicated to store a decinding decision tree. 国际象棋太复杂了,无法存储令人失望的决策树。 Chess engines play by assigning heuristic evaluations to potential and transient future positions (not moves). 国际象棋引擎通过将启发式评估分配给潜在的和暂时的未来位置 (而非移动)来发挥作用。 Future positions are found by some kind of limited depth search, may be cached for some time in memory, but often are plainly recalculated each turn as the search space is just too big to store in a way faster to look up than recalculating is possible. 将来的位置是通过某种有限的深度搜索找到的,可以在内存中缓存一段时间,但是由于搜索空间太大而无法以比重新计算可能的查找速度更快的方式存储,因此每次转弯时通常都会明确地重新计算。

Do you know Chinook — the AI that solves checkers? 您是否知道Chinook-解决跳棋的AI? It does this by compiling a database of every possible endgame. 它通过编译每个可能的残局数据库来做到这一点。 While this is not exactly what you are doing, you might learn from it. 尽管这并不是您正在做的事情,但是您可以从中学到东西。

I can't clearly conceive neither the data structures you handle in your tree nor their complexity. 我无法清楚地构思出您在树中处理的数据结构及其复杂性。

But here are some thoughts which may interest you : 但是以下是您可能感兴趣的一些想法:

  • Map your decision tree into sparse matrix, a tree is a graph after all 将您的决策树映射到稀疏矩阵中,树毕竟是图
  • Devise a storage/retrieval strategy taking advantage of sparse matrix properties. 设计利用稀疏矩阵属性的存储/检索策略。

I would approach this with the traditional way an opening book is handled in chess engines: 我会用国际象棋引擎中处理开场书的传统方式来解决这个问题:

  1. Generate all possible moves 产生所有可能的动作
  2. For each move: 对于每一步:
    1. Make that move 采取行动
    2. Look the resulting position up in your database 在数据库中向上查找结果位置
    3. Undo the move 撤消移动
  3. Make the move that had the highest score in your database 进行数据库中得分最高的举动

Looking up a move 抬头看

Chess engines usually compute a hash function of the current game state via Zobrist hashing , which is a simple way to construct a good hash function for gamestates. 国际象棋引擎通常通过Zobrist哈希计算当前游戏状态的哈希函数 ,这是一种为游戏状态构造良好哈希函数的简单方法。

The big advantage of this approach is that it takes care of transpositions , that is, if the same state can be reached via alternate paths, you don't need to worry about those alternate paths, only about the game states themselves. 这种方法的最大优点是它可以处理换位 ,也就是说,如果可以通过备用路径达到相同的状态,则无需担心这些备用路径,而只需担心游戏状态本身。

How chess engines do this 国际象棋引擎如何做到这一点

Most chess engines use static opening books that are compiled from recorded games and hence use a simple binary file that maps these hashes to a score; 大多数象棋引擎使用从录制的游戏中编译的静态开场书,因此使用简单的二进制文件将这些哈希映射到分数。 eg 例如

struct book_entry {
    uint64_t hash
    uint32_t score
}

The entries are then sorted by hash, and thanks to operating system caching, a simple binary search through the file will find the needed entries very quickly. 然后,这些条目将按哈希进行排序,并且由于使用了操作系统缓存,因此对文件进行简单的二进制搜索将很快找到所需的条目。

Updating the scores 更新分数

However, if you want the engine to learn continously, you will need a more complicated data structure; 但是,如果您希望引擎不断学习,则将需要更复杂的数据结构。 at this point it is usually not worth doing yourself, and you should use an available library. 在这一点上,通常不值得自己做,您应该使用可用的库。 I would probably use LevelDB , but anything that lets you store key-value pairs is fine (Redis, SQLite, GDBM, etc.) 我可能会使用LevelDB ,但是任何可以存储键值对的东西都很好(Redis,SQLite,GDBM等)

Learning the scores 学习分数

How exactly you update the scores depends on your game. 您如何精确更新分数取决于您的游戏。 In games with a lot of data available, a simple approach such as just storing the percentage of games won after the move that resulted in the position works; 在具有大量可用数据的游戏中,一种简单的方法,例如仅存储导致位置变化的移动后获胜游戏的百分比; if you have less data, you can store the result of a game tree search from the position in question as score. 如果数据较少,则可以将有关位置的游戏树搜索结果存储为得分。 Machine learning techniques such as Q learning are also a possibility, although I do not know of a program that actually does this in practice. 诸如Q学习之类的机器学习技术也是可能的,尽管我不知道实际中会执行此操作的程序。

I'm assuming your question is asking about how to convert a decision tree into a serial format that can be written to a location and later used to reconstruct the tree. 我假设您的问题是询问如何将决策树转换为可以写入位置并随后用于重构树的串行格式。

Try using a pre-order traversal of the tree, using a toString() function (or its equivalent) to convert the data stored at each node of the decision tree to a textual descriptor. 尝试使用树的预遍历,使用toString()函数(或其等效方法)将决策树每个节点上存储的数据转换为文本描述符。 By pre-order traversal, I mean implementing an algorithm that first performs the toString() operation on the node, and writes the output to a database or file, and then recursively performs the same operation on its child nodes, in a specified order. 通过预遍历,我的意思是实现一种算法,该算法首先在节点上执行toString()操作,然后将输出写入数据库或文件,然后以指定顺序在其子节点上递归执行相同的操作。 Because you are dealing with a sparse tree, your toString() operation should also include information about the existence or non-existence of subtrees. 因为您要处理稀疏树,所以您的toString()操作还应该包括有关子树存在或不存在的信息。

Reconstructing the tree is simple - the first stored value is the root node, the second is the root member of the left subtree, and so on. 重建树很简单-第一个存储的值是根节点,第二个存储的值是左子树的根成员,依此类推。 The serial data stored for each node should provide information as to which subtree the next inputted node should belong to. 为每个节点存储的串行数据应提供有关下一个输入节点应属于哪个子树的信息。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM