基于博弈树的开源五子棋AI教程[6 置换表]

文章目录

  • 引子
  • 定义
  • 实现
  • 讨论与尾记

引子

置换表是记忆化搜索技术的应用,置换表保存了某一盘面的搜索结果。当博弈树搜索遇到相同的局面时可以调用这些信息来减少重复搜索。那么如何设计一个置换表的节点就显得比较重要,本文在经典的置换表节点增加一个显示当前玩家的字段,这一字段补足了zobrist hash单向函数的缺点,如果节点需要使用更浅深度的信息,可以通过迭代的方式来求解,丰富了置换表的信息。

定义

置换表中包换了搜索状态的散列值,剪枝信息,PV信息以及用于迭代获取浅层深度的玩家信息。

//EXACT :表示记录中存储的分数是一个精确的估值分数。
//hashfBETA :表示记录中存储的分数是一个下界(lower bound)。这意味着在搜索中发现了一个分数,它至少是某一分支的分数下限,可能更高。
//hashfALPHA :表示记录中存储的分数是一个上界(upper bound)。这意味着在搜索中发现了一个分数,它至多是某一分支的分数上限,可能更低。
struct NABtransportTableNode {quint64 key;int value;quint8 depth;quint8 flags;MPoint bestMove;MPlayerType lastPlayer; //当前hash状态下,最后一个落子类型NABtransportTableNode() : key(InitialZobristHash), value(0), depth(InitialDepth), flags(hashfEmpty),bestMove(InvalidMPoint), lastPlayer(PLAYER_NONE){}
};

实现

置换表需要实现三个最主要的功能,获取,添加,清除。根据获取信息的需求不同给出三个函数getNABTranspositionTable用于获取并修改搜索中的剪枝信息,getPVNABTranspositionTable更关注获取某一指定盘面下的搜索结果,getBestMoveNABTranspositionTable更暴力,我只需要当前状下最佳着法即可。

    //置换表[用于负极大搜索,对于其他搜索方式需要修改,未做测试]bool getNABTranspositionTable(int &score, int depth, int &alpha, int &beta) const;bool getPVNABTranspositionTable(int&score, MPoint& bestMove, int depth, const quint64 &curhash) const;bool getBestMoveNABTranspositionTable(MPoint &bestMove) const;void appendNABTranspositionTable(int depth, int val, int hashf, MPoint bestMove, MPlayerType lastPlayer);void clearNABTranspositionTable(quint8 minCutDepth = InitialDepth);

这里只给出置换表中使用最多的获取节点剪枝信息到置换表中实现。追加表项的方法和清除置换表的方法相对简单,可以移步源代码一观。

//置换表
/*不用标记玩家或者交换上下界:因为评估是根据当前层的玩家决定的,无论是否交换手,
该层玩家始终没有变化,区别只不过是Max还是Min玩家罢了*/
bool ZobristHash::getNABTranspositionTable(int& score, int depth, int &alpha, int &beta) const
{if(!globalParam::utilGameSetting.IsOpenTranspositionTable) return false;QReadLocker locker(&globalParam::transportTableLock);NABtransportTableNode *hashNode = &globalParam::transportTable[m_hash & globalParam::transportTableSizeMask];if (hashNode->key == m_hash) {//因为不同棋子数目的棋盘分数没有可比性if(hashNode->depth < depth) return false;int storedScore(-INT_MAX);if(hashNode->depth == depth){storedScore = hashNode->value;}else{MPoint nextMove = hashNode->bestMove;MPlayerType nextPlayer = UtilReservePlayer(hashNode->lastPlayer);if(nextMove == InvalidMPoint || nextPlayer == PLAYER_NONE) return false;quint64 nextHash = generateHash(m_hash, nextMove, PLAYER_NONE, nextPlayer);for (int curDepth = 1; curDepth < depth; ++curDepth) {NABtransportTableNode *nextHashNode = &globalParam::transportTable[nextHash & globalParam::transportTableSizeMask];if (nextHashNode->key == nextHash) {nextMove = nextHashNode->bestMove;nextPlayer = UtilReservePlayer(nextPlayer);nextHash = generateHash(nextHash, nextMove, PLAYER_NONE, nextPlayer);}}if(getLeafTable(globalParam::AIPlayer, storedScore, nextHash)){if(nextPlayer != globalParam::AIPlayer) storedScore *= -1;}}if(hashNode->flags == hashfExact) {//精确值score = storedScore;return true;}else if((hashNode->flags == hashfLowerBound) && score > alpha) alpha = score;//更新alphaelse if ((hashNode->flags == hashfUperBound) && score < beta)  beta = score;//更新betaif(alpha >= beta){//剪枝score = hashNode->value;return true;}}return false;
}

这里实现区别于网上经典的实现方法,一般而言置换表中某一节点的深度越深,得到的信息越可靠。然而在五子棋中并不能一概而论,深度越深,着法越可靠,但是得分就不可靠了。深度不同,叶子棋盘的落子数必然不同,在得分没有归一化的情况下盲目使用是非常不安全的。基于这点发现,实现是通过迭代寻找指定深度,然后通过查找叶子表来求解分数,这样避免了子数不一致进行尴尬局面。
这种实现方式也可以认为是一种时间换空间策略,如果保存某一盘面的所有深度下搜索信息,置换表将成倍数递增,增加了程序的内存负担。

讨论与尾记

通过对极大极小的讨论我们可以知道一点,剪枝算法是安全可靠的,无论剪枝与否,PV路径始终是可以被搜索出来,然而置换表的引入虽加快了搜索,但搜索的不稳定性问题便会凸显出来。在对弈基本技术-置换表篇中指出

当你用置换表时,如果你允许搜索过程根据散列项来截断,那就会产生另一个问题,你的搜索会受“不稳定性”的捆扰。
  不稳定性至少是由以下因素引起的:
  1. 你可能在做6层的搜索,但是如果你在散列项中得到10层搜索的结果,就可能根据这个值来截断。在后来的搜索中,这个散列项被覆盖了,因此你在这个结点上得到了两个不同的值。
  2. Zobrist键值无法记录到达结点的线路,这个结点上不是每条线路都有相同结果的。如果某条线路遇到重复局面,那么散列项的值就会跟路线有关。因为重复局面会导致和局的分值,或者至少不一样的分值。

还有一个原因已经在搜索篇章点出,评分视角的不同,评分会有差异,如果直接使用这些信息截断,极有可能将PV路径拒之门外。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/620838.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

NUS CS1101S:SICP JavaScript 描述:一、使用函数构建抽象

原文&#xff1a;1 Building Abstractions with Functions 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 心灵的行为&#xff0c;其中它对简单的想法施加其力量&#xff0c;主要有以下三种&#xff1a;1.将几个简单的想法组合成一个复合的想法&#xff0c;从而形成所…

Scipy 高级教程——稀疏矩阵

Python Scipy 高级教程&#xff1a;稀疏矩阵 Scipy 提供了处理稀疏矩阵的工具&#xff0c;这对于处理大规模数据集中的稀疏数据是非常有效的。本篇博客将深入介绍 Scipy 中的稀疏矩阵功能&#xff0c;并通过实例演示如何应用这些工具。 1. 稀疏矩阵的表示 在 Scipy 中&#…

【重点】【DP】300. 最长递增子序列

题目 更好的方法是耐心排序&#xff0c;参见《算法小抄》的内容&#xff01;&#xff01;&#xff01; 法1&#xff1a;DP 基础解法必须掌握&#xff01;&#xff01;&#xff01; class Solution {public int lengthOfLIS(int[] nums) {if (nums null || nums.length 0) …

【深度学习】RTX2060 2080如何安装CUDA,如何使用onnx runtime

文章目录 如何在Python环境下配置RTX 2060与CUDA 101. 安装最新的NVIDIA显卡驱动2. 使用conda安装CUDA Toolkit3. 验证onnxruntime与CUDA版本4. 验证ONNX需求版本5. 安装ONNX与onnxruntime6. 编写ONNX推理代码 如何在Python环境下配置RTX 2060与CUDA 10 RTX 2060虽然是一款较早…

等保测评是什么

等保测评的全称是信息安全等级保护测评&#xff0c;是经公安部认证的具有资质的测评机构&#xff0c;依据国家信息安全等级保护规范规定&#xff0c;受有关单位委托&#xff0c;按照有关管理规范和技术标准&#xff0c;对信息系统安全等级保护状况进行检测评估的活动。 《信息…

Ps:何时需要转换为智能对象

智能对象 Smart Objects提供了广泛的灵活性和控制能力&#xff0c;特别是在处理复杂的合成、重复元素或需要非破坏性编辑的项目中。 ◆ ◆ ◆ 何时需要转换为智能对象 1、当需要对图像进行缩放、旋转等变换时。 涉及到的 Photoshop 命令包括&#xff1a;变换、自由变换、操控…

windows下如何搭建Yapi环境

今天使用YApi时发现原网址无法访问。这下只能本地部署了&#xff08;官方文档&#xff09;。 第一步&#xff1a;安装node.js 获取资源 nodejs: https://nodejs.org/en/downloadLinux安装yum install -y nodejs查看node版本node -v查看npm版本npm -v第二步&#xff1a;安装mo…

【论文阅读笔记】MobileSal: Extremely Efficient RGB-D Salient Object Detection

1.介绍 MobileSal: Extremely Efficient RGB-D Salient Object Detection MobileSal&#xff1a;极其高效的RGB-D显著对象检测 2021年发表在 IEEE Transactions on Pattern Analysis and Machine Intelligence。 Paper Code 2.摘要 神经网络的高计算成本阻碍了RGB-D显着对象…

Pandas实战100例 | 案例 31: 转换为分类数据

案例 31: 转换为分类数据 知识点讲解 在处理包含文本数据的 DataFrame 时&#xff0c;将文本列转换为分类数据类型通常是一个好主意。这可以提高性能并节省内存。Pandas 允许将列转换为 category 类型。 分类数据类型: category 类型适用于那些只包含有限数量不同值的列&…

【LeetCode】27. 移除元素(简单)——代码随想录算法训练营Day01

题目链接&#xff1a;27. 移除元素 题目描述 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 元素的顺…

Promise基础详细介绍(一),resolve,reject

Promise的含义 就是一个对象&#xff0c;用来传递异步操作的消息。 基本用法 resolve,reject是javascript引擎提供的。 const promise new Promise(function(resolve, reject) {const result {success: truevalue: 777} //伪代码&#xff0c;比如接口返回的参数if(result.su…

Vue 自定义仿word表单录入之日期输入组件

因项目需要&#xff0c;要实现仿word方式录入数据&#xff0c;要实现鼠标经过时才显示编辑组件&#xff0c;预览及离开后则显示具体的文字。 鼠标经过时显示 正常显示及离开时显示 组件代码 <template ><div class"paper-input flex flex-col border-box "…

物流实时数仓DWD层——1.准备工作

目录 1.创建主程序——DwdOrderRelevantApp类 2.创建DWD层的事实表——来源于订单表和订单明细表 (1)创建订单表实体类 (2)创建订单明细表实体类 (3)创建交易域&#xff1a;下单事务事实表实体类&#xff0c;并整合(1)与(2)&#xff0c;采用下单时间 (4)创建交易域&#…

【C++】STL(标准模板库)

文章目录 1. 基本概念2. 容器2.1. 容器的分类 1. 基本概念 STL&#xff08;Standard Template Library&#xff0c;标准模板库)是惠普实验室开发的一系列软件的统称&#xff0c;现在已经成为C标准库的重要组成部分。STL的从广义上讲分为三类&#xff1a;algorithm&#xff08;…

Matlab字符识别实验

Matlab 字符识别OCR实验 图像来源于屏幕截图&#xff0c;要求黑底白字。数据来源是任意二进制文件&#xff0c;内容以16进制打印输出&#xff0c;0-9a-f’字符被16个可打印字符替代&#xff0c;这些替代字符经过挑选&#xff0c;使其相对容易被识别。 第一步进行线分割和字符…

AI编程可视化Java项目拆解第一弹,解析本地Java项目

之前分享过一篇使用 AI 可视化 Java 项目的文章&#xff0c;同步在 AI 破局星球、知乎、掘金等地方都分享了。 原文在这里AI 编程&#xff1a;可视化 Java 项目 有很多人感兴趣&#xff0c;我打算写一个系列文章拆解这个项目&#xff0c;大家多多点赞支持~ 今天分享的是第一…

如何巧妙处理情绪化,让生活工作保持积极心态分享

目录 前言 1、首先&#xff0c;要认识到情绪化对我们生活工作带来的负面影响。 2、要处理情绪化&#xff0c;首先要学会观察自己的情绪。 3、其次&#xff0c;我们要学会换位思考。 4、与亲朋好友分享心情。 5、最后&#xff0c;培养自己的兴趣爱好。 前言 在这个快节奏、…

nestjs中@Injectable()的实现原理

以下是对@Injectable()的简单实现 import reflect-metadata;function Injectable() {return function (constructor: Function) {Reflect.defineMetadata(injectable, true, constructor);}; }class DependencyInjectionContainer {private instances = new Map();getInstance&…

alibaba学习笔记03(小滴课堂)

自定义Ribbon负载均衡策略实战 启动3个视频服务和一个订单服务&#xff1a; 我们可以看到它是随机调用的。 也可以使用其他负载均衡策略。 讲解新一代负载均衡组件feign介绍 这种方式去写死接口肯定是不妥当的。 于是我们使用feign负载均衡组件&#xff1a; 改造微服务 集成F…

【Linux】 系统目录结构

进入到根目录 cd /ls目录名具体作用/存放系统系统相关的目录文件/boot放置linux系统内核文件和启动时用到的一些引导文件/home包含linux系统上各用户的主目录&#xff0c;子目录名称默认以该用户名命名/root系统管理员root的家目录/bin包含常用的命令文件&#xff08;如ls 等&a…