基于博弈树的开源五子棋AI教程[7 多线程搜索]

文章目录

  • 引子
  • 定义
  • 实现
  • 结果
  • 尾记

引子

多线程加快搜索速度这一认知是经受住实践考验的。博弈树搜索的并行搜索方式有很多种,例如叶子并行,根并行,树分裂等算法。笔者给出一种实现起来比较简单的根并行算法。
在是实现时需要注意两点,第一,怎么安全的剪枝;第二,如何进行线程间的通信。对于AB剪枝有三点发现可以指导我们设计多线程的并行算法:

  1. 当某一节点搜索完成,其分数才能安全的更新父亲节点的AB值。
  2. 一个节点的AB值可以安全的更新其所有子孙节点的AB值。
  3. 如果一个节点alpha >= beta, 这个节点可以安全的被剪枝

这样一来,就可以知道一个节点搜索完成后,如何更新博弈树所有节点的AB值,如何剪枝。通信方式使用的全局变量+读写锁控制的,全局变量保存所有节点状态的AB值。当搜索开始,从根节点沿着搜索路径开始更新沿路的所有节点AB值,然后从全局变量中读取该节点的AB值。搜索完成后,更新父亲节点AB值。

定义

struct parallelNABSearchNode{int alpha, beta;parallelNABSearchNode() : alpha(-INT_MAX), beta(INT_MAX){}parallelNABSearchNode(int aalpha, int abeta) : alpha(aalpha), beta(abeta){}QString str();//返回值:true已经更新,false表示没更新bool getAlphaBeta(int &aalpha, int &abeta);bool updateLeaf2RootAlphaBeta(int score);//返回值:true已经更新,false表示没更新bool updateRoot2LeafAlphaBeta(int aalpha, int abeta);
};
    //并行化搜索技术static QReadWriteLock parallelSearchTableLock;static QHash<quint64, parallelNABSearchNode> parallelSearchTable;

函数实现三个方法,一个getAlphaBeta(int &aalpha, int &abeta)是从全局变量中获取AB值,一个updateLeaf2RootAlphaBeta是从更新该节点的父亲的AB值,还有一个updateRoot2LeafAlphaBeta是更新儿子节点的AB值。

bool parallelNABSearchNode::getAlphaBeta(int &aalpha, int &abeta){if(!globalParam::utilGameSetting.IsOpenParallelSearch) return false;if(aalpha >= alpha && beta >= abeta) return false;if(aalpha < alpha){aalpha = alpha;}if(beta < abeta){abeta = beta;}return true;
}
bool parallelNABSearchNode::updateLeaf2RootAlphaBeta(int score){if(!globalParam::utilGameSetting.IsOpenParallelSearch) return false;if(score > alpha){alpha = score;return true;}return false;
}
bool parallelNABSearchNode::updateRoot2LeafAlphaBeta(int aalpha, int abeta){if(!globalParam::utilGameSetting.IsOpenParallelSearch) return false;if(alpha >= aalpha && abeta >= beta) return false;if(alpha < aalpha){alpha = aalpha;}if(abeta < beta){beta = abeta;}return true;
}

实现

现在已经实现了线程间通信的工具,只需要在搜索时调用这些利器就可以了,总体的实现思路和常规负极大搜索如出一撤。为了能后续兼容树分裂的算法,这里给出了并行化搜索指定深度的接口。

//fail-soft negMax Alpha-Beta pruning search
int GameAI::NABParallelSearch(int depth, int alpha, int beta, bool maximizingPlayer, quint8 searchSpaceType)
{int score = -INT_MAX;QWriteLocker writeLock(&globalParam::parallelSearchTableLock);// 更新根节点->当前节点搜索路径上AB值for(int pid = 0;pid < parallelSsearchHistoryPlayersHash.size() - 1; ++pid){//表项不存在会自动调用默认构造函数parallelNABSearchNode *curNode = &globalParam::parallelSearchTable[parallelSsearchHistoryPlayersHash[pid]];parallelNABSearchNode *sontNode = &globalParam::parallelSearchTable[parallelSsearchHistoryPlayersHash[pid + 1]];//更新下一层的AB值sontNode->updateRoot2LeafAlphaBeta(- curNode->beta, - curNode->alpha);}// 获取当前AB值globalParam::parallelSearchTable[zobristSearchHash.hash()].getAlphaBeta(alpha, beta);
//    // 更新AB值后可能引发剪枝
//    if(alpha >= beta){   // AB剪枝
//        aiCalInfo.cutTreeTimesCurrentTurn ++;
//        return beta;
//    }writeLock.unlock();//探查置换表中值if(zobristSearchHash.getNABTranspositionTable(score, depth, alpha, beta)) {return score;}// ??或 分数过大过小
//    if (qAbs(score) > globalParam::utilGameSetting.MaxScore){
//        //保存置换表
//        return score;
//    }int evalPlayer = globalParam::AIPlayer;MPlayerType searchPlayer = maximizingPlayer ? evalPlayer : UtilReservePlayer(evalPlayer);// 达到搜索深度if (depth == 0 || checkSearchBoardWiner() != PLAYER_NONE){//保存置换表score = evaluateBoard(evalPlayer);//负极大搜索中评估必须searchPlayerif(!maximizingPlayer) score *= -1;//        //VCF
//        QList<MPoint> vcf, vcfpath;
//        if(VCXSearch(globalParam::utilGameSetting.MaxVctSearchDepth, maximizingPlayer, VCT_SEARCH, vcf, vcfpath)){
//            qDebug() << "NABsearch : find vct";
//            if(maximizingPlayer) return globalParam::utilGameSetting.MaxScore;
//            else return -globalParam::utilGameSetting.MaxScore;
//        }return score;}// 着法生成QVector<MPoint> searchPoints;getSortedSearchSpace(searchPoints, evalPlayer, searchPlayer, searchSpaceType);int scoreBest = -INT_MAX;int hashf = hashfUperBound;MPoint moveBest(InvalidMPoint);quint16 savedSearchBoardPatternDirection[boardSize][boardSize];for (const auto &curPoint : searchPoints) {if (!searchBoardHasPiece(curPoint)) {setSearchBoard(curPoint, searchPlayer, savedSearchBoardPatternDirection);// searchPlayer落子score = -NABParallelSearch(depth - 1, -beta, -alpha, !maximizingPlayer,searchSpaceType);setSearchBoard(curPoint, PLAYER_NONE, savedSearchBoardPatternDirection);// 撤销落子if (score > scoreBest) {scoreBest = score;moveBest = curPoint;if (score >= beta) {hashf = hashfLowerBound;appendSearchKillerTable(curPoint, depth, hashf);aiCalInfo.cutTreeTimesCurrentTurn ++;break; // Alpha-Beta 剪枝}if (score > alpha) {alpha = score;hashf = hashfExact;//更新当前层的AB值writeLock.relock();parallelNABSearchNode *curNode = &globalParam::parallelSearchTable[zobristSearchHash.hash()];curNode->alpha = scoreBest;writeLock.unlock();}}}}//    writeLock.relock();
//    //更新当前层的AB值
//    parallelNABSearchNode *curNode = &globalParam::parallelSearchTable[zobristSearchHash.hash()];
//    curNode->alpha = scoreBest;
//    writeLock.unlock();writeLock.relock();//更新上一层的AB值:只有当前所有节点搜索完成后,得到的值才是可靠的,才能用来更新父亲节点的AB值if(parallelSsearchHistoryPlayersHash.size() >= 2){const quint64& fatherHash = parallelSsearchHistoryPlayersHash[parallelSsearchHistoryPlayersHash.size()-2];parallelNABSearchNode *fatherNode = &globalParam::parallelSearchTable[fatherHash];fatherNode->updateLeaf2RootAlphaBeta(-scoreBest);}writeLock.unlock();//更新历史表appendSearchHistoryTable(moveBest, depth, hashf);// 更新置换表zobristSearchHash.appendNABTranspositionTable(depth, scoreBest, hashf, moveBest, UtilReservePlayer(searchPlayer));return scoreBest;
}

结果

这里实现的并行化搜索效果并不出众,只能说是有一定效果。在深度为6搜索情况下,线程数为4的并行化搜索能加速2~3倍。这一点也是可以理解的,因为负极大搜索的节点如果排序较好,搜索量主要集中在PV路径的搜索上。简单的分裂根节点能提升的速度是可预见,只有动态的分裂树,把算力平摊到PV路径搜索,加速PV路径产生能提高博弈树搜索的瓶颈。

尾记

这里实现并行化搜索还存在一些值得思考的问题,如何能提高搜索的稳定性,在发生截断返回时,仍能正确的搜索到PV路径,而不是会因为提前的不安全的剪枝与PV路径失之交臂。后面也希望有时间继续研究下如何高效的分裂树,而不是盲目的根分裂。

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

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

相关文章

【LabVIEW FPGA入门】LabVIEW FPGA 实现SPI通信协议

该实现由两个组件组成&#xff1a;在 LabVIEW FPGA 中实现的 SPI 协议以及用于从主机 PC 或实时控制器与 FPGA 进行通信的 LabVIEW 主机接口。该架构允许从单个主机程序控制多个 SPI 端口&#xff0c;同时仍然允许定制 FPGA VI 以进行其他数据采集和处理。该实现不使用任何DMA&…

【期末不挂科-C++考前速过系列P4】大二C++实验作业-继承和派生(3道代码题)【解析,注释】

前言 大家好吖&#xff0c;欢迎来到 YY 滴C考前速过系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的《…

2_单列模式_懒汉式单例模式

二.懒汉式单例模式 (1) 饿汉式单例模式概念 为了解决饿汉式单例可能带来的内存浪费问题&#xff0c;于是就出现了懒汉式单例的写法。懒汉式单例模式的特点是&#xff0c;单例对象要在被使用时才会初始化。 (2) 代码实现 实现代码方式1&#xff1a;简单的饿汉式单例 /*** 优…

解密威胁:.kat6.l6st6r 勒索病毒的威胁与恢复

导言&#xff1a; 在当今数字化时代&#xff0c;勒索病毒已经成为网络安全威胁中的一大巨头。其中&#xff0c;.kat6.l6st6r 勒索病毒以其狡猾的传播方式和高级的加密算法备受关注。本文将深入介绍.kat6.l6st6r 勒索病毒的特点、应对措施以及如何预防此类威胁。如果您在面对被…

基于java的 aws s3文件上传

aws s3 文件上传代码 首先&#xff0c;确保您已经在AWS上创建了一个S3存储桶&#xff0c;并拥有相应的访问密钥和密钥ID。这些凭据将用于在Java代码中进行身份验证。 接下来&#xff0c;需要在Java项目中添加AWS SDK的依赖。可以使用Maven或Gradle进行依赖管理。以下是一个Ma…

Kubernetes实战(十七)-设置kubernetes允许master调度pod

1 taint节点调度策略 NoSchedule: 一定不能被调度PreferNoSchedule: 尽量不要调度NoExecute: 不仅不会调度, 还会驱逐Node上已有的Pod 2 查看节点调度 $ kubectl describe node|grep -E "Name:|Taints:" Name: ops-master-1 Taints: no…

Kotlin 委托

Kotlin 委托 委托模式 是一种 通过 对象组合 实现代码重用 的设计模式&#xff0c; 对象处理请求时 将其 委托给 其他对象&#xff08;委托&#xff09;处理。 Kotlin 委托 分为&#xff1a; 类的委托属性的委托 一、类的委托 委托 Delegate 是实现继承的一种替代方式。达到了…

流量预测中文文献阅读(郭郭专用)

目录 基于流量预测的超密集网络资源分配策略研究_2023_高雪亮_内蒙古大学&#xff08;1&#xff09;内容总结&#xff08;2&#xff09;流量预测部分1、数据集2、结果对其中的一个网格的CDR进行预测RMSE和R2近邻数据和周期数据对RMSE的影响 &#xff08;3&#xff09;基于流量预…

ElasticSearch概述+SpringBoot 集成 ES

ES概述 开源的、高扩展的、分布式全文检索引擎【站内搜索】 解决问题 1.搜索词是一个整体时&#xff0c;不能拆分&#xff08;mysql整体连续&#xff09; 2.效率会低&#xff0c;不会用到索引&#xff08;mysql索引失效&#xff09; 解决方式 进行数据的存储&#xff08;只存储…

无缝打通易快报与电子签章系统,合同管理也能如此简单!

客户介绍&#xff1a; 某股份有限公司是一家专注于高端装备制造和智能制造解决方案的高新技术企业。该公司的产品和服务广泛应用于汽车、航空、高铁、智能家居、电子电器、新能源等领域&#xff0c;为全球客户提供了高效、精准、可靠的制造解决方案。 添加图片注释&#xff0c…

剑指offer面试题4 替换空格

考察点 考察数据结构字符串知识点 不同于c语言&#xff0c;java语言有字符和字符串俩种数据类型&#xff0c;char类型(字符类型)是基本数据类型&#xff0c;string类型(字符串类型)是引用类型 java语言中的字符采用unicode编码在内存中用2个字节存储&#xff0c;因此一个英文…

js中console.log()的使用方法

console.log()是JavaScript中的一个内置函数&#xff0c;用于在控制台输出信息&#xff1b;该方法对于开发过程进行测试很有帮助。可以输出之前在其中定义的任何类型的变量&#xff0c;或者只输出需要显示给用户的任何消息。 语法是&#xff1a; console。log&#xff08;&am…

HCIA-Datacom题库(自己整理分类的)_12_其他网络协议多选【12道题】

1.下面哪些是路由协议? BGP IPX OSPF IP 2.网络管理员使用Ping来测试网络的连通性&#xff0c;在这个过程中下面哪个协议可能会被使用到? UDP ICMP ARP TCP 解析&#xff1a;UDP、TCP是四层协议。 3.以下哪些是指网络通信? 使用即时通信软件(如:QQ、微信)与好友…

​iOS 应用上架指南:资料填写及提交审核

目录 摘要 引言 打开appuploader工具&#xff0c;第二步&#xff1a;打开appuploader工具 第五步&#xff1a;交付应用程序&#xff0c;在iTunes Connect中查看应用程序 总结 摘要 本文提供了iOS新站上架资料填写及提交审核的详细指南&#xff0c;包括创建应用、资料填写-…

【JAVA语言-第12话】API中的工具类 之 Date,DateFormat,SimpleDateFormat,Calendar类的详细解析

目录 日期和时间 1.1 Date类 1.1.1 概述 1.1.2 常用方法 1.1.3 案例 1.2 DateFormat类 1.2.1 概述 1.2.2 常用方法 1.3 SimpleDateFormat类 1.3.1 概述 1.3.2 构造方法 1.3.3 模式字符 1.3.4 日期转字符串 1.3.5 字符串转日期 1.4 Calendar类 1.4.1 概述 1…

软件工程宠物管理系统详细计划示例

1&#xff0e;引言 1.1编写目的 本详细设计说明书旨在为宠物管理系统的开发提供一个清晰、全面的指导&#xff0c;确保项目顺利进行。本说明书详细阐述了系统的功能、架构、模块划分以及技术选型等方面的内容&#xff0c;旨在为项目开发团队、项目管理人员和感兴趣的读者提供…

Raspbian安装摄像头

Raspbian安装摄像头 1. 源由2. 摄像头2.1 选型2.2 系统2.3 安装 3. 配置&命令3.1 命令3.2 配置 4. 测试4.1 拍照4.1.1 libcamera-jpeg4.1.2 libcamera-still 4.2 视频流4.2.1 RTSP流4.2.2 TCP流 5. 参考资料 1. 源由 家里闲置两块树莓派&#xff0c;打算做个WiFi视频流RTS…

python - fastapi 之 Denpends

Depends 描述 在 FastAPI 中&#xff0c;Depends 是一个用于处理依赖关系的工具。 Depends允许开发者定义一个可复用函数&#xff0c;此函数作为参数传递给路由处理函数。 from fastapi import Dependsuser_router.post("/test") def test_api(data: UserTestSche…

【JaveWeb教程】(21) MySQL数据库开发之多表设计:一对多、一对一、多对多的表关系 详细代码示例讲解

目录 2. 多表设计2.1 一对多2.1.1 表设计2.1.2 外键约束 2.2 一对一2.3 多对多2.4 案例 2. 多表设计 关于单表的操作(单表的设计、单表的增删改查)我们就已经学习完了。接下来我们就要来学习多表的操作&#xff0c;首先来学习多表的设计。 项目开发中&#xff0c;在进行数据库…

Csiszár divergences

Csiszr divergences 熵函数 熵函数&#xff08;entropy function) φ : R → R \varphi: \mathbb{R}_{} \to \mathbb{R}_{} φ:R​→R​&#xff0c;他是凸函数&#xff0c;正的&#xff08;&#xff1f;&#xff09;&#xff0c;下半连续函数&#xff0c;并且 φ ( 1 ) …