<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-Hans-CN">
	<id>http://www.pikafish.com/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=New</id>
	<title>皮卡鱼 Wiki - 用户贡献 [zh-cn]</title>
	<link rel="self" type="application/atom+xml" href="http://www.pikafish.com/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=New"/>
	<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E7%89%B9%E6%AE%8A:%E7%94%A8%E6%88%B7%E8%B4%A1%E7%8C%AE/New"/>
	<updated>2026-04-30T20:46:36Z</updated>
	<subtitle>用户贡献</subtitle>
	<generator>MediaWiki 1.40.0</generator>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E7%9A%AE%E5%8D%A1%E9%B1%BC%E5%92%8C%E5%95%86%E4%B8%9A%E4%BB%98%E8%B4%B9%E5%BC%95%E6%93%8E%E8%B0%81%E6%9B%B4%E5%BC%BA%3F&amp;diff=603</id>
		<title>皮卡鱼和商业付费引擎谁更强?</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E7%9A%AE%E5%8D%A1%E9%B1%BC%E5%92%8C%E5%95%86%E4%B8%9A%E4%BB%98%E8%B4%B9%E5%BC%95%E6%93%8E%E8%B0%81%E6%9B%B4%E5%BC%BA%3F&amp;diff=603"/>
		<updated>2025-06-27T15:23:52Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
对于部分言论“付费的一定比免费更强”，首先，象棋的强引擎(不论免费付费)全都是改自国际象棋的最强引擎“鳕鱼”，而鳕鱼是开源免费的，也是国际象棋毫无疑问的第一引擎，所以该言论从根本上就不成立。&lt;br /&gt;
&lt;br /&gt;
另外，象棋商业引擎“象棋旋风”于2025年初非法使用皮卡鱼的nnue。&lt;br /&gt;
&lt;br /&gt;
截至2025年6月底，象棋引擎中棋力最强的是皮卡鱼，测试结果如下：&lt;br /&gt;
[[文件:象棋引擎天梯图.png|缩略图|居中|象棋引擎天梯图（卡卡制作）|800px]]&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E7%9A%AE%E5%8D%A1%E9%B1%BC%E5%92%8C%E5%95%86%E4%B8%9A%E4%BB%98%E8%B4%B9%E5%BC%95%E6%93%8E%E8%B0%81%E6%9B%B4%E5%BC%BA%3F&amp;diff=602</id>
		<title>皮卡鱼和商业付费引擎谁更强?</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E7%9A%AE%E5%8D%A1%E9%B1%BC%E5%92%8C%E5%95%86%E4%B8%9A%E4%BB%98%E8%B4%B9%E5%BC%95%E6%93%8E%E8%B0%81%E6%9B%B4%E5%BC%BA%3F&amp;diff=602"/>
		<updated>2025-06-27T15:23:41Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
对于部分言论“付费的一定比免费更强”，首先，象棋的强引擎(不论免费付费)全都是改自国际象棋的最强引擎“鳕鱼”，而鳕鱼是开源免费的，也是国际象棋毫无疑问的第一引擎，所以该言论从根本上就不成立。&lt;br /&gt;
&lt;br /&gt;
另外，象棋商业引擎“象棋旋风”于2025年初非法使用皮卡鱼的nnue。&lt;br /&gt;
&lt;br /&gt;
截止2025年6月底，象棋引擎中棋力最强的是皮卡鱼，测试结果如下：&lt;br /&gt;
[[文件:象棋引擎天梯图.png|缩略图|居中|象棋引擎天梯图（卡卡制作）|800px]]&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E7%9A%AE%E5%8D%A1%E9%B1%BC%E5%92%8C%E5%95%86%E4%B8%9A%E4%BB%98%E8%B4%B9%E5%BC%95%E6%93%8E%E8%B0%81%E6%9B%B4%E5%BC%BA%3F&amp;diff=601</id>
		<title>皮卡鱼和商业付费引擎谁更强?</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E7%9A%AE%E5%8D%A1%E9%B1%BC%E5%92%8C%E5%95%86%E4%B8%9A%E4%BB%98%E8%B4%B9%E5%BC%95%E6%93%8E%E8%B0%81%E6%9B%B4%E5%BC%BA%3F&amp;diff=601"/>
		<updated>2025-06-27T15:23:16Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
对于部分言论“付费的一定比免费更强”，首先，象棋的强引擎(不论免费付费)全都是改自国际象棋的最强引擎“鳕鱼”，而鳕鱼是开源免费的，也是国际象棋毫无疑问的第一引擎，所以该言论从根本上就不成立。&lt;br /&gt;
&lt;br /&gt;
另外，象棋商业引擎“象棋旋风”于2025年初非法使用皮卡鱼的nnue。&lt;br /&gt;
&lt;br /&gt;
截止2025年初，象棋引擎中棋力最强的是皮卡鱼，测试结果如下&lt;br /&gt;
[[文件:象棋引擎天梯图.png|缩略图|居中|象棋引擎天梯图（卡卡制作）|800px]]&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E6%96%87%E4%BB%B6:%E8%B1%A1%E6%A3%8B%E5%BC%95%E6%93%8E%E5%A4%A9%E6%A2%AF%E5%9B%BE.png&amp;diff=600</id>
		<title>文件:象棋引擎天梯图.png</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E6%96%87%E4%BB%B6:%E8%B1%A1%E6%A3%8B%E5%BC%95%E6%93%8E%E5%A4%A9%E6%A2%AF%E5%9B%BE.png&amp;diff=600"/>
		<updated>2025-06-27T15:22:35Z</updated>

		<summary type="html">&lt;p&gt;New：​New上传文件:象棋引擎天梯图.png的新版本&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=598</id>
		<title>首页</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=598"/>
		<updated>2025-06-17T03:37:19Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;皮卡鱼（Pikafish）是一个免费、开源的象棋引擎，用于分析象棋局面并计算最优的走法。目前皮卡鱼是棋力最强的象棋引擎。&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
皮卡鱼没有界面（GUI），需要自行下载。可见→ [[不会用皮卡鱼，怎么使用？|新手如何使用皮卡鱼]]&lt;br /&gt;
&lt;br /&gt;
另外官方发布的皮卡鱼可能不支持Windows 7（使用gcc自行编译的可以支持）。&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
皮卡鱼源自国际象棋引擎鳕鱼（Stockfish），并继承GPL-3.0开源协议。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''棋类软件的一些常识，可点击→[[棋软知识]]'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''关于皮卡鱼的引擎选项设置，可点击→[[UCI选项]]'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''关于皮卡鱼引擎的选择（avx2 bmi2 vnni512等等）可点击→[[指令集（如bmi2 avx2) 是什么？]]'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''皮卡鱼网页版说明 → [[皮卡鱼网页版说明]]'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''想了解引擎搜索算法，可点击→[[搜索算法]]'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''关于皮卡鱼的UCI协议，可点击→[[UCI协议]]'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''想了解比如象棋界面的具体使用方法或其他疑问，可进入皮卡鱼官方群询问→ 1019666453 (群可能满员，可根据群介绍寻找其他皮卡鱼官方群进入)'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
想要更深入地使用皮卡鱼，可查看Pikafish的官方GitHub。&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9CNNUE%E2%80%9D%EF%BC%9F&amp;diff=596</id>
		<title>什么是“NNUE”？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9CNNUE%E2%80%9D%EF%BC%9F&amp;diff=596"/>
		<updated>2025-06-05T17:43:22Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
NNUE（高效可更新神经网络）是一种神经网络的架构，适合CPU处理器推理。&lt;br /&gt;
&lt;br /&gt;
现在象棋顶级引擎都使用NNUE，NNUE仅是评估网络，只负责评估局面。（关于评估是什么，请看[[什么是“评估”？]] ）&lt;br /&gt;
&lt;br /&gt;
而现在象棋顶级引擎使用的搜索算法，仍然是传统的ab剪枝搜索。&lt;br /&gt;
&lt;br /&gt;
== 具体介绍 ==&lt;br /&gt;
NNUE的全称是Efficiently Updatable Neural Network（倒过来写的，鵺（Nue）是日本的妖怪，这里使用了Nue的谐音），它的核心思想，就是设计一种特别适合棋类游戏这种输入变化微小场景的神经网络结构，比如我们下一步棋，棋盘的变化比较小，这时候我们就可以做增量更新，不需要把整个棋盘算一遍，大大提高了效率。&lt;br /&gt;
&lt;br /&gt;
NNUE最初是给日本将棋设计的，作者是那須悠（Yu Nasu），2018年被应用于Motohiro Isozaki的YaneuraOu，2019年6月NNUE又被Nodchip（Hisayori Noda）移植到了Stockfish上。&lt;br /&gt;
&lt;br /&gt;
=== 输入特征集 ===&lt;br /&gt;
首先我们需要把棋盘局面转换为神经网络能够理解的输入，这就需要特征集了，这里以Stockfish为例进行讲解&amp;lt;ref&amp;gt;皮卡鱼与Stockfish略有不同，但大体上是一样的，皮卡鱼NNUE的特征总数是25920（=6×6×90×8）。25920-&amp;gt;2048(L1)-&amp;gt;15(L2)-&amp;gt;32(L3)-&amp;gt;1&amp;lt;/ref&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
常见的特征集有HalfKP和HalfKA(v2)，Stockfish早期使用的是HalfKP，现在使用的是HalfKAv2。&lt;br /&gt;
&lt;br /&gt;
HalfKP的K是King（王）的意思，P是Piece（棋子，但不包括王）的意思，Half强调是某一方的王，其特征组合是 (我方王的位置 S&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;, 某个棋子的位置 S&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;, 该棋子的类型 T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;, 该棋子的相对颜色 C&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;)。&lt;br /&gt;
&lt;br /&gt;
对于国象来说，棋子有64个Squares可走，棋子类型去掉王只有5种，棋子颜色有两种，因此特征总数为64×64×5×2=40960。&lt;br /&gt;
&lt;br /&gt;
HalfKA的A代表All，也就是说棋子的类型中也包括了王，把王也纳入进普通棋子里面，因此HalfKA的特征总数有64×64×6×2=49152，这样设计有利于NNUE捕捉到王相关的价值。&lt;br /&gt;
&lt;br /&gt;
HalfKAv2相比于HalfKA优化了特征数量。这12种棋子中包括了我方王和对方王，但我方王的第一个特征S&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;必然对应我方王的T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;，对方王的第一个特征必然对应对方的T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;王，可以直接合二为一，不需要区分是我方王还是对方王，这样特征总数就减少到了64×64×11=45056。&lt;br /&gt;
&lt;br /&gt;
考虑到国象可以水平镜像，又有了HalfKAv2_hm，hm是horizontally mirrored的简称。我方王在棋盘左侧和在棋盘右侧是等效的，这样王的位置总数就从64减少到了32（特征总数32×64×11=22528）。&lt;br /&gt;
&lt;br /&gt;
=== 累加器 ===&lt;br /&gt;
累加器是NNUE增量更新的核心，NNUE第一层（22528-&amp;gt;2560&amp;lt;ref&amp;gt;2560是L1的大小，这里用的是SFNNv8的数据，皮卡鱼目前是2048。&amp;lt;/ref&amp;gt;）的输出会被存储到累加器中，当棋盘发生变化（如走了一步棋），累加器也相应地进行变化（只需要在累加器中加上/减去该特征对应的第一层权重列），这样我们可以更加快速地更新累加器，不需要重新计算第一层。累加器会区分我方视角和对方视角，有两套不同的累加器。&lt;br /&gt;
&lt;br /&gt;
=== 更多内容 ===&lt;br /&gt;
参见NNUE.md：https://github.com/official-stockfish/nnue-pytorch/blob/master/docs/nnue.md&lt;br /&gt;
&lt;br /&gt;
== 脚注 ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9CNNUE%E2%80%9D%EF%BC%9F&amp;diff=595</id>
		<title>什么是“NNUE”？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9CNNUE%E2%80%9D%EF%BC%9F&amp;diff=595"/>
		<updated>2025-06-05T17:42:45Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
NNUE（高效可更新神经网络）是一种神经网络的架构，适合CPU处理器推理。&lt;br /&gt;
&lt;br /&gt;
现在象棋顶级引擎都使用NNUE，NNUE仅是评估网络，只负责评估局面。（关于评估是什么，请看[[什么是“评估”？]] ）&lt;br /&gt;
&lt;br /&gt;
而现在象棋顶级引擎使用的搜索算法，仍然是传统的ab剪枝搜索。&lt;br /&gt;
&lt;br /&gt;
== 具体介绍 ==&lt;br /&gt;
NNUE的全称是Efficiently Updatable Neural Network（倒过来写的，鵺（Nue）是日本的妖怪，这里使用了Nue的谐音），它的核心思想，就是设计一种特别适合棋类游戏这种输入变化微小场景的神经网络结构，比如我们下一步棋，棋盘的变化比较小，这时候我们就可以做增量更新，不需要把整个棋盘算一遍，大大提高了效率。&lt;br /&gt;
&lt;br /&gt;
NNUE最初是给日本将棋设计的，作者是那須悠（Yu Nasu），2018年被应用于Motohiro Isozaki的YaneuraOu，2019年6月NNUE又被Nodchip（Hisayori Noda）移植到了Stockfish上。&lt;br /&gt;
&lt;br /&gt;
=== 输入特征集 ===&lt;br /&gt;
首先我们需要把棋盘局面转换为神经网络能够理解的输入，这就需要特征集了，这里以Stockfish为例进行讲解&amp;lt;ref&amp;gt;皮卡鱼与Stockfish略有不同，但大体上是一样的，皮卡鱼NNUE的特征总数是25920（=6×6×90×8）。&amp;lt;/ref&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
常见的特征集有HalfKP和HalfKA(v2)，Stockfish早期使用的是HalfKP，现在使用的是HalfKAv2。&lt;br /&gt;
&lt;br /&gt;
HalfKP的K是King（王）的意思，P是Piece（棋子，但不包括王）的意思，Half强调是某一方的王，其特征组合是 (我方王的位置 S&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;, 某个棋子的位置 S&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;, 该棋子的类型 T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;, 该棋子的相对颜色 C&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;)。&lt;br /&gt;
&lt;br /&gt;
对于国象来说，棋子有64个Squares可走，棋子类型去掉王只有5种，棋子颜色有两种，因此特征总数为64×64×5×2=40960。&lt;br /&gt;
&lt;br /&gt;
HalfKA的A代表All，也就是说棋子的类型中也包括了王，把王也纳入进普通棋子里面，因此HalfKA的特征总数有64×64×6×2=49152，这样设计有利于NNUE捕捉到王相关的价值。&lt;br /&gt;
&lt;br /&gt;
HalfKAv2相比于HalfKA优化了特征数量。这12种棋子中包括了我方王和对方王，但我方王的第一个特征S&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;必然对应我方王的T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;，对方王的第一个特征必然对应对方的T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;王，可以直接合二为一，不需要区分是我方王还是对方王，这样特征总数就减少到了64×64×11=45056。&lt;br /&gt;
&lt;br /&gt;
考虑到国象可以水平镜像，又有了HalfKAv2_hm，hm是horizontally mirrored的简称。我方王在棋盘左侧和在棋盘右侧是等效的，这样王的位置总数就从64减少到了32（特征总数32×64×11=22528）。25920-&amp;gt;2048(L1)-&amp;gt;15(L2)-&amp;gt;32(L3)-&amp;gt;1&lt;br /&gt;
&lt;br /&gt;
=== 累加器 ===&lt;br /&gt;
累加器是NNUE增量更新的核心，NNUE第一层（22528-&amp;gt;2560&amp;lt;ref&amp;gt;2560是L1的大小，这里用的是SFNNv8的数据，皮卡鱼目前是2048。&amp;lt;/ref&amp;gt;）的输出会被存储到累加器中，当棋盘发生变化（如走了一步棋），累加器也相应地进行变化（只需要在累加器中加上/减去该特征对应的第一层权重列），这样我们可以更加快速地更新累加器，不需要重新计算第一层。累加器会区分我方视角和对方视角，有两套不同的累加器。&lt;br /&gt;
&lt;br /&gt;
=== 更多内容 ===&lt;br /&gt;
参见NNUE.md：https://github.com/official-stockfish/nnue-pytorch/blob/master/docs/nnue.md&lt;br /&gt;
&lt;br /&gt;
== 脚注 ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9C%E6%AE%8B%E5%B1%80%E5%BA%93%E2%80%9D%EF%BC%9F&amp;diff=594</id>
		<title>什么是“残局库”？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9C%E6%AE%8B%E5%B1%80%E5%BA%93%E2%80%9D%EF%BC%9F&amp;diff=594"/>
		<updated>2025-06-05T17:35:52Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
残局库是穷尽某些子力组合的库。&lt;br /&gt;
&lt;br /&gt;
  例如穷尽“帅VS将”，以此为基础再开始穷尽“帅兵 vs 将”.....。如果要搞出车对双马的残局库，那么必须先搞好帅对将、帅对马、帅对双马、帅车对将、帅车对马的残局库。&lt;br /&gt;
  残局库先穷尽一个子力组合的所有局面，然后提取所有取胜局面，再提取被将死方无法阻止将死方将死的所有局面.....如此倒推，推出所有的理论必胜必负局面，最终推不到的局面都是和棋。&lt;br /&gt;
  在残局库的规则下，如果没有bug，那么残局库结论是绝对准确的，残局库只有胜 负 和三种结论，除了胜负局面所需的步数外，不存在任何分数。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
象棋残局库主要有DTM（Depth to mate）和DTC（Depth to conversion）两种。&lt;br /&gt;
  DTM的胜方目标只有将死和困毙，败方目标是尽力拖延将死和困毙。&lt;br /&gt;
  而DTC，这里以云库DTC说明。胜方目标是将死、困毙、子力变化(胜方自己的子被吃也算)，取最短的一项作为目标。还有一个特殊的循环犯规次数，胜方会尽力防止负方循环犯规，如果无法防止，则胜方会走负方犯规次数最少的路径，并且拖延负方犯规。而负方会拖延被将死、被困毙、场上子力变化，并且尽力犯规。&lt;br /&gt;
  但如果没有犯规次数，不代表胜方真的可以完全防止对手犯规。假设某个局面红有个相挡着黑车，黑车可以吃掉它并且进行长将，这个局面仍然可能显示没有犯规次数，因为负方对拖延子力变化是更优先的，所以着法中负方不会优先吃相。&lt;br /&gt;
  DTC因为对目标的步数更短，所以体积比DTM小很多。&lt;br /&gt;
&lt;br /&gt;
部分象棋引擎可以配合自己格式的残局库进行搜索，例如旋风、小虫等。&lt;br /&gt;
&lt;br /&gt;
残局库的终极目标是全子残局库。&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AFbench%EF%BC%9Fbench%E7%9A%84%E7%BB%93%E6%9E%9C%E8%AF%B4%E6%98%8E%E4%BA%86%E4%BB%80%E4%B9%88%EF%BC%9F&amp;diff=593</id>
		<title>什么是bench？bench的结果说明了什么？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AFbench%EF%BC%9Fbench%E7%9A%84%E7%BB%93%E6%9E%9C%E8%AF%B4%E6%98%8E%E4%BA%86%E4%BB%80%E4%B9%88%EF%BC%9F&amp;diff=593"/>
		<updated>2025-06-05T17:32:39Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
bench是皮卡鱼的一个UCI指令。在执行bench时，皮卡鱼会对内置的48个局面进行1核心计算（默认是13层），计算完后，引擎会输出总时间、搜索的总节点数和每秒搜索的节点数。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;===========================&lt;br /&gt;
Total time (ms) : 1561&lt;br /&gt;
Nodes searched  : 1879868&lt;br /&gt;
Nodes/second    : 1204271&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
例如，执行bench指令后，最终输出如上信息，“Total time (ms)”即为总时间（毫秒），“Nodes searched”为搜索的总节点数，“Nodes/second”（简称nps）为每秒搜索的节点数。&lt;br /&gt;
&lt;br /&gt;
'''如果要进行多线程测速，可以输入bench 128 线程数&lt;br /&gt;
例如8核16线程的机器，输入bench 128 8 来测全内核的速度，也可以bench 128 16 来测全超线程的速度'''&lt;br /&gt;
&lt;br /&gt;
== bench输出的Nodes/second值越大，引擎一定越强吗？ ==&lt;br /&gt;
答案是不一定。具体解析可以参考[[NPS（K值）是什么？代表棋力吗？]]条目。&lt;br /&gt;
&lt;br /&gt;
== 拓展功能：speedtest ==&lt;br /&gt;
最新的皮卡鱼亦引入了speedtest指令，可用于测速，但用于测速的局面比bench多得多，能够较为稳定地反映计算机性能，默认使用全部线程进行测速，自动根据线程数分配置换表。&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9C%E4%BA%91%E5%BA%93%E2%80%9D%EF%BC%9F&amp;diff=592</id>
		<title>什么是“云库”？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9C%E4%BA%91%E5%BA%93%E2%80%9D%EF%BC%9F&amp;diff=592"/>
		<updated>2025-06-05T17:22:07Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;云库一个免费查询的在线数据库，网址为[http://chessdb.cn chessdb.cn]&lt;br /&gt;
&lt;br /&gt;
云库收录了极多的局面并用后台服务器计算打分，也有部分残局库。&lt;br /&gt;
&lt;br /&gt;
除残局库外，云库所收录的局面分数不一定准确，但计算延伸越多的局面也就越可能准确。&lt;br /&gt;
&lt;br /&gt;
此外，一些商业开局库也会使用云库的方式调用以防止破解。&amp;lt;ref&amp;gt;商业开局库仅采用了这种方式调用，并未使用云库官方的地址，而是利用自己的云库地址替代掉官方的云库地址。&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 脚注 ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E6%B7%B1%E5%BA%A6%EF%BC%88%E5%B1%82%E6%95%B0%EF%BC%89%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F%E4%B8%8A%E5%B1%82%E9%80%9F%E5%BA%A6%E4%BB%A3%E8%A1%A8%E6%A3%8B%E5%8A%9B%E5%90%97%EF%BC%9F&amp;diff=591</id>
		<title>深度（层数）是什么？上层速度代表棋力吗？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E6%B7%B1%E5%BA%A6%EF%BC%88%E5%B1%82%E6%95%B0%EF%BC%89%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F%E4%B8%8A%E5%B1%82%E9%80%9F%E5%BA%A6%E4%BB%A3%E8%A1%A8%E6%A3%8B%E5%8A%9B%E5%90%97%EF%BC%9F&amp;diff=591"/>
		<updated>2025-06-05T17:19:12Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;对目前的强引擎来说，深度(层数)已经不是真实的“深度”，'''深度（层数）和算几步棋已经几乎没有联系'''，因为现在的剪枝延伸策略让不同着法的搜索深度很不平衡。&lt;br /&gt;
现在显示的深度(层数)只代表搜索按照搜索步骤完整迭代了几次，有的着法深度会超过这个数字，大多着法深度会低于这个数字。&lt;br /&gt;
&lt;br /&gt;
只是由于历史遗留，深度这个词被保留了下来。&lt;br /&gt;
&lt;br /&gt;
相同的引擎无论什么指令集，即使在不同机器上无论是否超线程，只要能影响引擎搜索路径的设置相同（例如哈希表大小、棋规设置、multipv等相同）、哈希表被清空的情况下，那么1线程每层的分析结果都是相同的，只有nps和时间不同。（而多线程有随机性）因此单线程深度大确实可以反映棋力强弱，因为深度大了说明搜索的时间更长；但到了多线程，这种“深度”就无法再用于比较引擎棋力，多线程既不能互相比较，也不能与单线程比较。&lt;br /&gt;
&lt;br /&gt;
'''上层速度不代表棋力。棋力的唯一指标是科学的测试数据。&lt;br /&gt;
'''同引擎下深度越高越强，但本质是计算时间越久越强。多线程的情况下，主要提升搜索树宽度，上层速度可以视为附带产物，甚至有的时候上层速度可能不如更低的线程。&lt;br /&gt;
&lt;br /&gt;
对一般人来说，如果上层速度和棋局前面比起来明显变慢，可能后续会有比较复杂的变化，或者后续局面优势太大分数没有明显的区分。&lt;br /&gt;
除此之外，大部分意义在于看着舒服。&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9C%E5%AE%A1%E5%B1%80%E5%BA%93%E2%80%9D%EF%BC%9F&amp;diff=590</id>
		<title>什么是“审局库”？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9C%E5%AE%A1%E5%B1%80%E5%BA%93%E2%80%9D%EF%BC%9F&amp;diff=590"/>
		<updated>2025-06-05T17:12:21Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;审局库就是残局库的一种，但没有着法。&lt;br /&gt;
&lt;br /&gt;
残局库会告诉你局面的结论和胜局的取胜着法，而审局库只会告诉你局面的结论(胜 负 和)。&lt;br /&gt;
但因此，使得审局库的体积比残局库小很多。&lt;br /&gt;
&lt;br /&gt;
'''常见误区是审局库是关于中局的，这是被&amp;quot;审局&amp;quot;的名字误导了，其实作用和残局库是一样的。&lt;br /&gt;
'''&lt;br /&gt;
因为审局库只有结果信息没有着法信息，所以某些复杂定式必胜残局，即使引擎和审局库配合搜索也难以取胜，例如一个复杂定式必胜残局，引擎搜索的局面绝大多数都是审局库的“胜”，那么这和没有审局库也差不多，只不过引擎不需要把搜索算力浪费在那些“和”“负”的局面上。&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C%E7%9A%84%E2%80%9C%E8%87%AA%E6%88%91%E5%AD%A6%E4%B9%A0%E2%80%9D%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&amp;diff=589</id>
		<title>神经网络的“自我学习”是什么？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C%E7%9A%84%E2%80%9C%E8%87%AA%E6%88%91%E5%AD%A6%E4%B9%A0%E2%80%9D%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&amp;diff=589"/>
		<updated>2025-06-05T17:10:16Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
在主流棋类引擎中，神经网络都属于离线监督学习。&lt;br /&gt;
&lt;br /&gt;
作者会先让引擎自对弈，从而生成数据（棋谱）。&lt;br /&gt;
&lt;br /&gt;
以NNUE跑谱为例，这些数据里面有每步的局面、分数、和这局游戏的结果等等信息，通常是以每步几层或者几千、几万节点自对弈生成。&lt;br /&gt;
&lt;br /&gt;
生成了足够多的数据后，便拿去训练，训练过程可以简单理解为去调整神经网络里的海量参数，使得网络的输出更接近数据。比如一个局面100分，训练就会改变神经网络参数让评估分数去接近这个100分。&lt;br /&gt;
&lt;br /&gt;
因为是离线学习，依赖作者训练发布，所以你使用引擎是无法让引擎“学习”的(但棋类也完全不适合在线学习)，而引擎有[[置换表（哈希）是什么？设置多少好？|哈希表]]这种暂时的信息储存，所以拆棋时会感觉引擎有记忆功能，但重新加载后便失去了记忆。&lt;br /&gt;
&lt;br /&gt;
所以强引擎目前不可能一边使用一边学习，而是依赖作者训练好之后再发布出来的。&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9C%E8%AF%84%E4%BC%B0%E2%80%9D%EF%BC%9F&amp;diff=588</id>
		<title>什么是“评估”？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9C%E8%AF%84%E4%BC%B0%E2%80%9D%EF%BC%9F&amp;diff=588"/>
		<updated>2025-06-05T16:52:47Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;评估就是对局面的评估。类似于人类对一个局面的直觉(红好走或者红优势、黑优势等等)，不进行任何棋局计算，就是纯感觉局面的优劣，也可以叫“审局”。只不过引擎的评估会按照它的评估参数，来计算出局面的分数。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
在nnue之前，评估都是靠人类写的，并且调整参数。例如写最基础的子力价值:车x分、马x分....，然后再调整参数的数值，例如车1000分。另外还有子力位置分、子力机动性分数、特殊棋型的分数等等。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
nnue完全代替了传统的手工评估，nnue相当于有海量的评估参数，但是人类不知道那些参数代表什么。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
nnue会导致引擎的搜索速度变慢，但是因为利远大于弊（评估准确造成的提升远大于速度减慢带来的削弱），所以棋力依然是飞跃提升。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
nnue的简单解释详见[[什么是“nnue”？]]&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9CNNUE%E2%80%9D%EF%BC%9F&amp;diff=587</id>
		<title>什么是“NNUE”？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9CNNUE%E2%80%9D%EF%BC%9F&amp;diff=587"/>
		<updated>2025-06-05T16:50:25Z</updated>

		<summary type="html">&lt;p&gt;New：​/* 输入特征集 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
NNUE（高效可更新神经网络）是一种神经网络的架构，适合CPU处理器推理。&lt;br /&gt;
&lt;br /&gt;
现在象棋顶级引擎都使用NNUE，NNUE仅是评估网络，只负责评估局面。（关于评估是什么，请看[[什么是“评估”？]] ）&lt;br /&gt;
&lt;br /&gt;
而现在象棋顶级引擎使用的搜索算法，仍然是传统的ab剪枝搜索。&lt;br /&gt;
&lt;br /&gt;
== 具体介绍 ==&lt;br /&gt;
NNUE的全称是Efficiently Updatable Neural Network（倒过来写的，鵺（Nue）是日本的妖怪，这里使用了Nue的谐音），它的核心思想，就是设计一种特别适合棋类游戏这种输入变化微小场景的神经网络结构，比如我们下一步棋，棋盘的变化比较小，这时候我们就可以做增量更新，不需要把整个棋盘算一遍，大大提高了效率。&lt;br /&gt;
&lt;br /&gt;
NNUE最初是给日本将棋设计的，作者是那須悠（Yu Nasu），2018年被应用于Motohiro Isozaki的YaneuraOu，2019年6月NNUE又被Nodchip（Hisayori Noda）移植到了Stockfish上。&lt;br /&gt;
&lt;br /&gt;
=== 输入特征集 ===&lt;br /&gt;
首先我们需要把棋盘局面转换为神经网络能够理解的输入，这就需要特征集了，这里以Stockfish为例进行讲解&amp;lt;ref&amp;gt;皮卡鱼与Stockfish略有不同，但大体上是一样的，皮卡鱼NNUE的特征总数是25920（=6×6×90×8）。&amp;lt;/ref&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
常见的特征集有HalfKP和HalfKA(v2)，Stockfish早期使用的是HalfKP，现在使用的是HalfKAv2。&lt;br /&gt;
&lt;br /&gt;
HalfKP的K是King（王）的意思，P是Piece（棋子，但不包括王）的意思，Half强调是某一方的王，其特征组合是 (我方王的位置 S&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;, 某个棋子的位置 S&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;, 该棋子的类型 T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;, 该棋子的相对颜色 C&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;)。&lt;br /&gt;
&lt;br /&gt;
对于国象来说，棋子有64个Squares可走，棋子类型去掉王只有5种，棋子颜色有两种，因此特征总数为64×64×5×2=40960。&lt;br /&gt;
&lt;br /&gt;
HalfKA的A代表All，也就是说棋子的类型中也包括了王，把王也纳入进普通棋子里面，因此HalfKA的特征总数有64×64×6×2=49152，这样设计有利于NNUE捕捉到王相关的价值。&lt;br /&gt;
&lt;br /&gt;
HalfKAv2相比于HalfKA优化了特征数量。这12种棋子中包括了我方王和对方王，但我方王的第一个特征S&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;必然对应我方王的T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;，对方王的第一个特征必然对应对方的T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;王，可以直接合二为一，不需要区分是我方王还是对方王，这样特征总数就减少到了64×64×11=45056。&lt;br /&gt;
&lt;br /&gt;
考虑到国象可以水平镜像，又有了HalfKAv2_hm，hm是horizontally mirrored的简称。我方王在棋盘左侧和在棋盘右侧是等效的，这样王的位置总数就从64减少到了32（特征总数32×64×11=22528）。&lt;br /&gt;
&lt;br /&gt;
=== 累加器 ===&lt;br /&gt;
累加器是NNUE增量更新的核心，NNUE第一层（22528-&amp;gt;2560&amp;lt;ref&amp;gt;2560是L1的大小，这里用的是SFNNv8的数据，皮卡鱼目前是2048。&amp;lt;/ref&amp;gt;）的输出会被存储到累加器中，当棋盘发生变化（如走了一步棋），累加器也相应地进行变化（只需要在累加器中加上/减去该特征对应的第一层权重列），这样我们可以更加快速地更新累加器，不需要重新计算第一层。累加器会区分我方视角和对方视角，有两套不同的累加器。&lt;br /&gt;
&lt;br /&gt;
=== 更多内容 ===&lt;br /&gt;
参见NNUE.md：https://github.com/official-stockfish/nnue-pytorch/blob/master/docs/nnue.md&lt;br /&gt;
&lt;br /&gt;
== 脚注 ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9CNNUE%E2%80%9D%EF%BC%9F&amp;diff=586</id>
		<title>什么是“NNUE”？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9CNNUE%E2%80%9D%EF%BC%9F&amp;diff=586"/>
		<updated>2025-06-05T16:50:02Z</updated>

		<summary type="html">&lt;p&gt;New：​撤销New（讨论）的版本585&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
NNUE（高效可更新神经网络）是一种神经网络的架构，适合CPU处理器推理。&lt;br /&gt;
&lt;br /&gt;
现在象棋顶级引擎都使用NNUE，NNUE仅是评估网络，只负责评估局面。（关于评估是什么，请看[[什么是“评估”？]] ）&lt;br /&gt;
&lt;br /&gt;
而现在象棋顶级引擎使用的搜索算法，仍然是传统的ab剪枝搜索。&lt;br /&gt;
&lt;br /&gt;
== 具体介绍 ==&lt;br /&gt;
NNUE的全称是Efficiently Updatable Neural Network（倒过来写的，鵺（Nue）是日本的妖怪，这里使用了Nue的谐音），它的核心思想，就是设计一种特别适合棋类游戏这种输入变化微小场景的神经网络结构，比如我们下一步棋，棋盘的变化比较小，这时候我们就可以做增量更新，不需要把整个棋盘算一遍，大大提高了效率。&lt;br /&gt;
&lt;br /&gt;
NNUE最初是给日本将棋设计的，作者是那須悠（Yu Nasu），2018年被应用于Motohiro Isozaki的YaneuraOu，2019年6月NNUE又被Nodchip（Hisayori Noda）移植到了Stockfish上。&lt;br /&gt;
&lt;br /&gt;
=== 输入特征集 ===&lt;br /&gt;
首先我们需要把棋盘局面转换为神经网络能够理解的输入，这就需要特征集了，这里以Stockfish为例进行讲解&amp;lt;ref&amp;gt;皮卡鱼与Stockfish略有不同，但大体上是一样的，皮卡鱼NNUE的特征总数是25920（=6×6×90×8）。&amp;lt;/ref&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
常见的特征集有HalfKP和HalfKA(v2)，Stockfish早期使用的是HalfKP，现在使用的是HalfKAv2。&lt;br /&gt;
&lt;br /&gt;
HalfKP的K是King（王）的意思，P是Piece（棋子，但不包括王）的意思，Half强调是某一方的王，其特征组合是 (我方王的位置 S&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;, 某个棋子的位置 S&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;, 该棋子的类型 T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;, 该棋子的相对颜色 C&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;)。&lt;br /&gt;
&lt;br /&gt;
对于国象来说，棋子有64个Squares可走，棋子类型去掉王只有5种，棋子颜色有两种，因此特征总数为64×64×5×2=40960。&lt;br /&gt;
&lt;br /&gt;
HalfKA的A代表All，也就是说棋子的类型中也包括了王，把王也纳入进普通棋子里面，因此HalfKA的特征总数有64×64×6×2=49152，这样设计有利于NNUE捕捉到王相关的价值。&lt;br /&gt;
&lt;br /&gt;
HalfKAv2相比于HalfKA优化了特征数量。这12种棋子中包括了我方王和对方王，但我方王的第一个特征S&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;必然对应我方王的T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;，对方王的第一个特征必然对应对方的T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;王，可以直接合二为一，不需要区分是我方王还是对方王，这样特征总数就减少到了64×64×11=45056。&lt;br /&gt;
&lt;br /&gt;
考虑到象棋可以水平镜像，又有了HalfKAv2_hm，hm是horizontally mirrored的简称。我方王在棋盘左侧和在棋盘右侧是等效的，这样王的位置总数就从64减少到了32（特征总数32×64×11=22528）。&lt;br /&gt;
&lt;br /&gt;
=== 累加器 ===&lt;br /&gt;
累加器是NNUE增量更新的核心，NNUE第一层（22528-&amp;gt;2560&amp;lt;ref&amp;gt;2560是L1的大小，这里用的是SFNNv8的数据，皮卡鱼目前是2048。&amp;lt;/ref&amp;gt;）的输出会被存储到累加器中，当棋盘发生变化（如走了一步棋），累加器也相应地进行变化（只需要在累加器中加上/减去该特征对应的第一层权重列），这样我们可以更加快速地更新累加器，不需要重新计算第一层。累加器会区分我方视角和对方视角，有两套不同的累加器。&lt;br /&gt;
&lt;br /&gt;
=== 更多内容 ===&lt;br /&gt;
参见NNUE.md：https://github.com/official-stockfish/nnue-pytorch/blob/master/docs/nnue.md&lt;br /&gt;
&lt;br /&gt;
== 脚注 ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9CNNUE%E2%80%9D%EF%BC%9F&amp;diff=585</id>
		<title>什么是“NNUE”？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9CNNUE%E2%80%9D%EF%BC%9F&amp;diff=585"/>
		<updated>2025-06-05T16:49:19Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
NNUE（高效可更新神经网络）是一种神经网络的架构，适合CPU处理器推理。&lt;br /&gt;
&lt;br /&gt;
现在象棋顶级引擎都使用NNUE，NNUE仅是评估网络，只负责评估局面。（关于评估是什么，请看[[什么是“评估”？]] ）&lt;br /&gt;
&lt;br /&gt;
而现在象棋顶级引擎使用的搜索算法，仍然是传统的ab剪枝搜索。&lt;br /&gt;
&lt;br /&gt;
== 具体介绍 ==&lt;br /&gt;
NNUE的全称是Efficiently Updatable Neural Network（倒过来写的，鵺（Nue）是日本的妖怪，这里使用了Nue的谐音），它的核心思想，就是设计一种特别适合棋类游戏这种输入变化微小场景的神经网络结构，比如我们下一步棋，棋盘的变化比较小，这时候我们就可以做增量更新，不需要把整个棋盘算一遍，大大提高了效率。&lt;br /&gt;
&lt;br /&gt;
NNUE最初是给日本将棋设计的，作者是那須悠（Yu Nasu），2018年被应用于Motohiro Isozaki的YaneuraOu，2019年6月NNUE又被Nodchip（Hisayori Noda）移植到了Stockfish上。&lt;br /&gt;
&lt;br /&gt;
=== 输入特征集 ===&lt;br /&gt;
首先我们需要把棋盘局面转换为神经网络能够理解的输入，这就需要特征集了。&lt;br /&gt;
&lt;br /&gt;
常见的特征集有HalfKP和HalfKA(v2)，Stockfish早期使用的是HalfKP，现在使用的是HalfKAv2。&lt;br /&gt;
&lt;br /&gt;
HalfKP的K是King（王）的意思，P是Piece（棋子，但不包括王）的意思，Half强调是某一方的王，其特征组合是 (我方王的位置 S&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;, 某个棋子的位置 S&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;, 该棋子的类型 T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;, 该棋子的相对颜色 C&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;)。&lt;br /&gt;
&lt;br /&gt;
对于国象来说，棋子有64个Squares可走，棋子类型去掉王只有5种，棋子颜色有两种，因此特征总数为64×64×5×2=40960。&lt;br /&gt;
&lt;br /&gt;
HalfKA的A代表All，也就是说棋子的类型中也包括了王，把王也纳入进普通棋子里面，因此HalfKA的特征总数有64×64×6×2=49152，这样设计有利于NNUE捕捉到王相关的价值。&lt;br /&gt;
&lt;br /&gt;
HalfKAv2相比于HalfKA优化了特征数量。这12种棋子中包括了我方王和对方王，但我方王的第一个特征S&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;必然对应我方王的T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;，对方王的第一个特征必然对应对方的T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;王，可以直接合二为一，不需要区分是我方王还是对方王，这样特征总数就减少到了64×64×11=45056。&lt;br /&gt;
&lt;br /&gt;
考虑到象棋可以水平镜像，又有了HalfKAv2_hm，hm是horizontally mirrored的简称。我方王在棋盘左侧和在棋盘右侧是等效的，这样王的位置总数就从64减少到了32（特征总数32×64×11=22528）。&lt;br /&gt;
&lt;br /&gt;
=== 累加器 ===&lt;br /&gt;
累加器是NNUE增量更新的核心，NNUE第一层（22528-&amp;gt;2560&amp;lt;ref&amp;gt;2560是L1的大小，这里用的是SFNNv8的数据。&amp;lt;/ref&amp;gt;）的输出会被存储到累加器中，当棋盘发生变化（如走了一步棋），累加器也相应地进行变化（只需要在累加器中加上/减去该特征对应的第一层权重列），这样我们可以更加快速地更新累加器，不需要重新计算第一层。累加器会区分我方视角和对方视角，有两套不同的累加器。&lt;br /&gt;
&lt;br /&gt;
=== 更多内容 ===&lt;br /&gt;
参见NNUE.md：https://github.com/official-stockfish/nnue-pytorch/blob/master/docs/nnue.md&lt;br /&gt;
&lt;br /&gt;
== 脚注 ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9CNNUE%E2%80%9D%EF%BC%9F&amp;diff=584</id>
		<title>什么是“NNUE”？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9CNNUE%E2%80%9D%EF%BC%9F&amp;diff=584"/>
		<updated>2025-06-05T16:41:13Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
NNUE（高效可更新神经网络）是一种神经网络的架构，适合CPU处理器推理。&lt;br /&gt;
&lt;br /&gt;
现在象棋顶级引擎都使用NNUE，NNUE仅是评估网络，只负责评估局面。（关于评估是什么，请看[[什么是“评估”？]] ）&lt;br /&gt;
&lt;br /&gt;
而现在象棋顶级引擎使用的搜索算法，仍然是传统的ab剪枝搜索。&lt;br /&gt;
&lt;br /&gt;
== 具体介绍 ==&lt;br /&gt;
NNUE的全称是Efficiently Updatable Neural Network（倒过来写的），它的核心思想，就是设计一种特别适合棋类游戏这种输入变化微小场景的神经网络结构，比如我们下一步棋，棋盘的变化比较小，这时候我们就可以做增量更新，不需要把整个棋盘算一遍，大大提高了效率。&lt;br /&gt;
&lt;br /&gt;
NNUE最初是给日本将棋设计的，作者是那須悠（Yu Nasu），2018年被应用于Motohiro Isozaki的YaneuraOu，2019年6月NNUE又被Nodchip（Hisayori Noda）移植到了Stockfish上。&lt;br /&gt;
&lt;br /&gt;
=== 输入特征集 ===&lt;br /&gt;
首先我们需要把棋盘局面转换为神经网络能够理解的输入，这就需要特征集了，这里以Stockfish为例进行讲解&amp;lt;ref&amp;gt;皮卡鱼与Stockfish略有不同，但大体上是一样的，皮卡鱼NNUE的特征总数是25920（=6×6×90×8）。&amp;lt;/ref&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
常见的特征集有HalfKP和HalfKA(v2)，Stockfish早期使用的是HalfKP，现在使用的是HalfKAv2。&lt;br /&gt;
&lt;br /&gt;
HalfKP的K是King（王）的意思，P是Piece（棋子，但不包括王）的意思，Half强调是某一方的王，其特征组合是 (我方王的位置 S&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;, 某个棋子的位置 S&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;, 该棋子的类型 T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;, 该棋子的相对颜色 C&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;)。&lt;br /&gt;
&lt;br /&gt;
对于国象来说，棋子有64个Squares可走，棋子类型去掉王只有5种，棋子颜色有两种，因此特征总数为64×64×5×2=40960。&lt;br /&gt;
&lt;br /&gt;
HalfKA的A代表All，也就是说棋子的类型中也包括了王，把王也纳入进普通棋子里面，因此HalfKA的特征总数有64×64×6×2=49152，这样设计有利于NNUE捕捉到王相关的价值。&lt;br /&gt;
&lt;br /&gt;
HalfKAv2相比于HalfKA优化了特征数量。这12种棋子中包括了我方王和对方王，但我方王的第一个特征S&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;必然对应我方王的T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;，对方王的第一个特征必然对应对方的T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;王，可以直接合二为一，不需要区分是我方王还是对方王，这样特征总数就减少到了64×64×11=45056。&lt;br /&gt;
&lt;br /&gt;
考虑到象棋可以水平镜像，又有了HalfKAv2_hm，hm是horizontally mirrored的简称。我方王在棋盘左侧和在棋盘右侧是等效的，这样王的位置总数就从64减少到了32（特征总数32×64×11=22528）。&lt;br /&gt;
&lt;br /&gt;
=== 累加器 ===&lt;br /&gt;
累加器是NNUE增量更新的核心，NNUE第一层（22528-&amp;gt;2560&amp;lt;ref&amp;gt;2560是L1的大小，这里用的是SFNNv8的数据，皮卡鱼目前是2048。&amp;lt;/ref&amp;gt;）的输出会被存储到累加器中，当棋盘发生变化（如走了一步棋），累加器也相应地进行变化（只需要在累加器中加上/减去该特征对应的第一层权重列），这样我们可以更加快速地更新累加器，不需要重新计算第一层。累加器会区分我方视角和对方视角，有两套不同的累加器。&lt;br /&gt;
&lt;br /&gt;
=== 更多内容 ===&lt;br /&gt;
参见NNUE.md：https://github.com/official-stockfish/nnue-pytorch/blob/master/docs/nnue.md&lt;br /&gt;
&lt;br /&gt;
== 脚注 ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9CNNUE%E2%80%9D%EF%BC%9F&amp;diff=583</id>
		<title>什么是“NNUE”？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9CNNUE%E2%80%9D%EF%BC%9F&amp;diff=583"/>
		<updated>2025-06-05T16:39:40Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
NNUE（高效可更新神经网络）是一种神经网络的架构，适合CPU处理器推理。&lt;br /&gt;
&lt;br /&gt;
现在象棋顶级引擎都使用NNUE，NNUE仅是评估网络，只负责评估局面。（关于评估是什么，请看[[什么是“评估”？]] ）&lt;br /&gt;
&lt;br /&gt;
而现在象棋顶级引擎使用的搜索算法，仍然是传统的ab剪枝搜索。&lt;br /&gt;
&lt;br /&gt;
== 具体介绍 ==&lt;br /&gt;
NNUE的全称是Efficiently Updatable Neural Network（倒过来写的），它的核心思想，就是设计一种特别适合棋类游戏这种输入变化微小场景的神经网络结构，比如我们下一步棋，棋盘的变化比较小，这时候我们就可以做增量更新，不需要把整个棋盘算一遍，大大提高了效率。&lt;br /&gt;
&lt;br /&gt;
NNUE最初是给日本将棋设计的，作者是那須悠（Yu Nasu），2018年被应用于Motohiro Isozaki的YaneuraOu，2019年6月NNUE又被Nodchip（Hisayori Noda）移植到了Stockfish上。&lt;br /&gt;
&lt;br /&gt;
=== 输入特征集 ===&lt;br /&gt;
首先我们需要把棋盘局面转换为神经网络能够理解的输入，这就需要特征集了，这里以Stockfish为例进行讲解&amp;lt;ref&amp;gt;皮卡鱼与Stockfish略有不同，但大体上是一样的，皮卡鱼NNUE的特征总数是25920（=6×6×90×8）。&amp;lt;/ref&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
常见的特征集有HalfKP和HalfKA(v2)，Stockfish早期使用的是HalfKP，现在使用的是HalfKAv2。&lt;br /&gt;
&lt;br /&gt;
HalfKP的K是King（王）的意思，P是Piece（棋子，但不包括王）的意思，Half强调是某一方的王，其特征组合是 (我方王的位置 S&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;, 某个棋子的位置 S&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;, 该棋子的类型 T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;, 该棋子的相对颜色 C&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;)。&lt;br /&gt;
&lt;br /&gt;
对于国象来说，棋子有64个Squares可走，棋子类型去掉王只有5种，棋子颜色有两种，因此特征总数为64×64×5×2=40960。&lt;br /&gt;
&lt;br /&gt;
HalfKA的A代表All，也就是说棋子的类型中也包括了王，把王也纳入进普通棋子里面，因此HalfKA的特征总数有64×64×6×2=49152，这样设计有利于NNUE捕捉到王相关的价值。&lt;br /&gt;
&lt;br /&gt;
HalfKAv2相比于HalfKA优化了特征数量。这12种棋子中包括了我方王和对方王，但我方王的第一个特征S&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;必然对应我方王的T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;，对方王的第一个特征必然对应对方的T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;王，可以直接合二为一，不需要区分是我方王还是对方王，这样特征总数就减少到了64×64×11=45056。&lt;br /&gt;
&lt;br /&gt;
考虑到象棋可以水平镜像，又有了HalfKAv2_hm，hm是horizontally mirrored的简称。我方王在棋盘左侧和在棋盘右侧是等效的，这样王的位置总数就从64减少到了32（特征总数32×64×11=22528）。&lt;br /&gt;
&lt;br /&gt;
=== 累加器 ===&lt;br /&gt;
累加器是NNUE增量更新的核心，NNUE第一层（22528-&amp;gt;2560&amp;lt;ref&amp;gt;2560是L1的大小，这里用的是SFNNv8的数据。&amp;lt;/ref&amp;gt;）的输出会被存储到累加器中，当棋盘发生变化（如走了一步棋），累加器也相应地进行变化（只需要在累加器中加上/减去该特征对应的第一层权重列），这样我们可以更加快速地更新累加器，不需要重新计算第一层。累加器会区分我方视角和对方视角，有两套不同的累加器。&lt;br /&gt;
&lt;br /&gt;
=== 更多内容 ===&lt;br /&gt;
参见NNUE.md：https://github.com/official-stockfish/nnue-pytorch/blob/master/docs/nnue.md&lt;br /&gt;
&lt;br /&gt;
== 脚注 ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9CNNUE%E2%80%9D%EF%BC%9F&amp;diff=582</id>
		<title>什么是“NNUE”？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9CNNUE%E2%80%9D%EF%BC%9F&amp;diff=582"/>
		<updated>2025-06-05T16:37:29Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
NNUE（高效可更新神经网络）是一种神经网络的架构，适合CPU处理器推理。&lt;br /&gt;
&lt;br /&gt;
现在象棋顶级引擎都使用NNUE，NNUE仅是评估网络，只负责评估局面。（关于评估是什么，请看[[什么是“评估”？]] ）&lt;br /&gt;
&lt;br /&gt;
而现在象棋顶级引擎使用的搜索算法，仍然是传统的ab剪枝搜索。&lt;br /&gt;
&lt;br /&gt;
== 具体介绍 ==&lt;br /&gt;
NNUE的全称是Efficiently Updatable Neural Network（倒过来写的），它的核心思想，就是设计一种特别适合棋类游戏这种输入变化微小场景的神经网络结构，比如我们下一步棋，棋盘的变化比较小，这时候我们就可以做增量更新，不需要把整个棋盘算一遍，大大提高了效率。&lt;br /&gt;
&lt;br /&gt;
NNUE最初是给日本将棋设计的，作者是那須悠（Yu Nasu），2018年被应用于Motohiro Isozaki的YaneuraOu，2019年6月NNUE又被Nodchip（Hisayori Noda）移植到了Stockfish上。&lt;br /&gt;
&lt;br /&gt;
=== 输入特征集 ===&lt;br /&gt;
首先我们需要把棋盘局面转换为神经网络能够理解的输入，这就需要特征集了，这里以Stockfish为例进行讲解&amp;lt;ref&amp;gt;皮卡鱼与Stockfish略有不同，但大体上是一样的，皮卡鱼NNUE的特征总数是25920。&amp;lt;/ref&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
常见的特征集有HalfKP和HalfKA(v2)，Stockfish早期使用的是HalfKP，现在使用的是HalfKAv2。&lt;br /&gt;
&lt;br /&gt;
HalfKP的K是King（王）的意思，P是Piece（棋子，但不包括王）的意思，Half强调是某一方的王，其特征组合是 (我方王的位置 S&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;, 某个棋子的位置 S&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;, 该棋子的类型 T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;, 该棋子的相对颜色 C&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;)。&lt;br /&gt;
&lt;br /&gt;
对于国象来说，棋子有64个Squares可走，棋子类型去掉王只有5种，棋子颜色有两种，因此特征总数为64×64×5×2=40960。&lt;br /&gt;
&lt;br /&gt;
HalfKA的A代表All，也就是说棋子的类型中也包括了王，把王也纳入进普通棋子里面，因此HalfKA的特征总数有64×64×6×2=49152，这样设计有利于NNUE捕捉到王相关的价值。&lt;br /&gt;
&lt;br /&gt;
HalfKAv2相比于HalfKA优化了特征数量。这12种棋子中包括了我方王和对方王，但我方王的第一个特征S&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;必然对应我方王的T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;，对方王的第一个特征必然对应对方的T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;王，可以直接合二为一，不需要区分是我方王还是对方王，这样特征总数就减少到了64×64×11=45056。&lt;br /&gt;
&lt;br /&gt;
考虑到象棋可以水平镜像，又有了HalfKAv2_hm，hm是horizontally mirrored的简称。我方王在棋盘左侧和在棋盘右侧是等效的，这样王的位置总数就从64减少到了32（特征总数32×64×11=22528）。&lt;br /&gt;
&lt;br /&gt;
=== 累加器 ===&lt;br /&gt;
累加器是NNUE增量更新的核心，NNUE第一层（22528-&amp;gt;2560&amp;lt;ref&amp;gt;2560是L1的大小，这里用的是SFNNv8的数据。&amp;lt;/ref&amp;gt;）的输出会被存储到累加器中，当棋盘发生变化（如走了一步棋），累加器也相应地进行变化（只需要在累加器中加上/减去该特征对应的第一层权重列），这样我们可以更加快速地更新累加器，不需要重新计算第一层。累加器会区分我方视角和对方视角，有两套不同的累加器。&lt;br /&gt;
&lt;br /&gt;
=== 更多内容 ===&lt;br /&gt;
参见NNUE.md：https://github.com/official-stockfish/nnue-pytorch/blob/master/docs/nnue.md&lt;br /&gt;
&lt;br /&gt;
== 脚注 ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9CNNUE%E2%80%9D%EF%BC%9F&amp;diff=581</id>
		<title>什么是“NNUE”？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9CNNUE%E2%80%9D%EF%BC%9F&amp;diff=581"/>
		<updated>2025-06-05T16:36:30Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
NNUE（高效可更新神经网络）是一种神经网络的架构，适合CPU处理器推理。&lt;br /&gt;
&lt;br /&gt;
现在象棋顶级引擎都使用NNUE，NNUE仅是评估网络，只负责评估局面。（关于评估是什么，请看[[什么是“评估”？]] ）&lt;br /&gt;
&lt;br /&gt;
而现在象棋顶级引擎使用的搜索算法，仍然是传统的ab剪枝搜索。&lt;br /&gt;
&lt;br /&gt;
== 具体介绍 ==&lt;br /&gt;
NNUE的全称是Efficiently Updatable Neural Network（倒过来写的），它的核心思想，就是设计一种特别适合棋类游戏这种输入变化微小场景的神经网络结构，比如我们下一步棋，棋盘的变化比较小，这时候我们就可以做增量更新，不需要把整个棋盘算一遍，大大提高了效率。&lt;br /&gt;
&lt;br /&gt;
NNUE最初是给日本将棋设计的，作者是那須悠（Yu Nasu），2018年被应用于Motohiro Isozaki的YaneuraOu，2019年6月NNUE又被Nodchip（Hisayori Noda）移植到了Stockfish上。&lt;br /&gt;
&lt;br /&gt;
=== 输入特征集 ===&lt;br /&gt;
首先我们需要把棋盘局面转换为神经网络能够理解的输入，这就需要特征集了，这里以Stockfish为例进行讲解（Pikafish略有不同，但大体上是一样的）。&lt;br /&gt;
&lt;br /&gt;
常见的特征集有HalfKP和HalfKA(v2)，Stockfish早期使用的是HalfKP，现在使用的是HalfKAv2。&lt;br /&gt;
&lt;br /&gt;
HalfKP的K是King（王）的意思，P是Piece（棋子，但不包括王）的意思，Half强调是某一方的王，其特征组合是 (我方王的位置 S&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;, 某个棋子的位置 S&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;, 该棋子的类型 T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;, 该棋子的相对颜色 C&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;)。&lt;br /&gt;
&lt;br /&gt;
对于国象来说，棋子有64个Squares可走，棋子类型去掉王只有5种，棋子颜色有两种，因此特征总数为64×64×5×2=40960。&lt;br /&gt;
&lt;br /&gt;
HalfKA的A代表All，也就是说棋子的类型中也包括了王，把王也纳入进普通棋子里面，因此HalfKA的特征总数有64×64×6×2=49152，这样设计有利于NNUE捕捉到王相关的价值。&lt;br /&gt;
&lt;br /&gt;
HalfKAv2相比于HalfKA优化了特征数量。这12种棋子中包括了我方王和对方王，但我方王的第一个特征S&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;必然对应我方王的T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;，对方王的第一个特征必然对应对方的T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;王，可以直接合二为一，不需要区分是我方王还是对方王，这样特征总数就减少到了64×64×11=45056。&lt;br /&gt;
&lt;br /&gt;
考虑到象棋可以水平镜像，又有了HalfKAv2_hm，hm是horizontally mirrored的简称。我方王在棋盘左侧和在棋盘右侧是等效的，这样王的位置总数就从64减少到了32（特征总数32×64×11=22528）。&lt;br /&gt;
&lt;br /&gt;
=== 累加器 ===&lt;br /&gt;
累加器是NNUE增量更新的核心，NNUE第一层（22528-&amp;gt;2560&amp;lt;ref&amp;gt;2560是L1的大小，这里用的是SFNNv8的数据。&amp;lt;/ref&amp;gt;）的输出会被存储到累加器中，当棋盘发生变化（如走了一步棋），累加器也相应地进行变化（只需要在累加器中加上/减去该特征对应的第一层权重列），这样我们可以更加快速地更新累加器，不需要重新计算第一层。累加器会区分我方视角和对方视角，有两套不同的累加器。&lt;br /&gt;
&lt;br /&gt;
=== 更多内容 ===&lt;br /&gt;
参见NNUE.md：https://github.com/official-stockfish/nnue-pytorch/blob/master/docs/nnue.md&lt;br /&gt;
&lt;br /&gt;
== 脚注 ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9CNNUE%E2%80%9D%EF%BC%9F&amp;diff=580</id>
		<title>什么是“NNUE”？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9CNNUE%E2%80%9D%EF%BC%9F&amp;diff=580"/>
		<updated>2025-06-05T14:33:54Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
NNUE（高效可更新神经网络）是一种神经网络的架构，适合CPU处理器推理。&lt;br /&gt;
&lt;br /&gt;
现在象棋顶级引擎都使用NNUE，NNUE仅是评估网络，只负责评估局面。（关于评估是什么，请看[[什么是“评估”？]] ）&lt;br /&gt;
&lt;br /&gt;
而现在象棋顶级引擎使用的搜索算法，仍然是传统的ab剪枝搜索。&lt;br /&gt;
&lt;br /&gt;
== 具体介绍 ==&lt;br /&gt;
NNUE的全称是Efficiently Updatable Neural Network（倒过来写的），它的核心思想，就是设计一种特别适合棋类游戏这种输入变化微小场景的神经网络结构，比如我们下一步棋，棋盘的变化比较小，这时候我们就可以做增量更新，不需要把整个棋盘算一遍，大大提高了效率。&lt;br /&gt;
&lt;br /&gt;
NNUE最初是给日本将棋设计的，作者是那須悠（Yu Nasu），2018年被应用于Motohiro Isozaki的YaneuraOu，2019年6月NNUE又被Nodchip（Hisayori Noda）移植到了Stockfish上。&lt;br /&gt;
&lt;br /&gt;
=== 输入特征集 ===&lt;br /&gt;
首先我们需要把棋盘局面转换为神经网络能够理解的输入，这就需要特征集了。&lt;br /&gt;
&lt;br /&gt;
常见的特征集有HalfKP和HalfKA(v2)，Stockfish早期使用的是HalfKP，现在使用的是HalfKAv2。&lt;br /&gt;
&lt;br /&gt;
HalfKP的K是King（王）的意思，P是Piece（棋子，但不包括王）的意思，Half强调是某一方的王，其特征组合是 (我方王的位置 S&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;, 某个棋子的位置 S&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;, 该棋子的类型 T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;, 该棋子的相对颜色 C&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;)。&lt;br /&gt;
&lt;br /&gt;
对于国象来说，棋子有64个Squares可走，棋子类型去掉王只有5种，棋子颜色有两种，因此特征总数为64×64×5×2=40960。&lt;br /&gt;
&lt;br /&gt;
HalfKA的A代表All，也就是说棋子的类型中也包括了王，把王也纳入进普通棋子里面，因此HalfKA的特征总数有64×64×6×2=49152，这样设计有利于NNUE捕捉到王相关的价值。&lt;br /&gt;
&lt;br /&gt;
HalfKAv2相比于HalfKA优化了特征数量。这12种棋子中包括了我方王和对方王，但我方王的第一个特征S&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;必然对应我方王的T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;，对方王的第一个特征必然对应对方的T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;王，可以直接合二为一，不需要区分是我方王还是对方王，这样特征总数就减少到了64×64×11=45056。&lt;br /&gt;
&lt;br /&gt;
考虑到象棋可以水平镜像，又有了HalfKAv2_hm，hm是horizontally mirrored的简称。我方王在棋盘左侧和在棋盘右侧是等效的，这样王的位置总数就从64减少到了32（特征总数32×64×11=22528）。&lt;br /&gt;
&lt;br /&gt;
=== 累加器 ===&lt;br /&gt;
累加器是NNUE增量更新的核心，NNUE第一层（22528-&amp;gt;2560&amp;lt;ref&amp;gt;2560是L1的大小，这里用的是SFNNv8的数据。&amp;lt;/ref&amp;gt;）的输出会被存储到累加器中，当棋盘发生变化（如走了一步棋），累加器也相应地进行变化（只需要在累加器中加上/减去该特征对应的第一层权重列），这样我们可以更加快速地更新累加器，不需要重新计算第一层。累加器会区分我方视角和对方视角，有两套不同的累加器。&lt;br /&gt;
&lt;br /&gt;
=== 更多内容 ===&lt;br /&gt;
参见NNUE.md：https://github.com/official-stockfish/nnue-pytorch/blob/master/docs/nnue.md&lt;br /&gt;
&lt;br /&gt;
== 脚注 ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9CNNUE%E2%80%9D%EF%BC%9F&amp;diff=579</id>
		<title>什么是“NNUE”？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E4%BB%80%E4%B9%88%E6%98%AF%E2%80%9CNNUE%E2%80%9D%EF%BC%9F&amp;diff=579"/>
		<updated>2025-06-05T14:33:00Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
NNUE（高效可更新神经网络）是一种神经网络的架构，适合CPU处理器推理。&lt;br /&gt;
&lt;br /&gt;
现在象棋顶级引擎都使用NNUE，NNUE仅是评估网络，只负责评估局面。（关于评估是什么，请看[[什么是“评估”？]] ）&lt;br /&gt;
&lt;br /&gt;
而现在象棋顶级引擎使用的搜索算法，仍然是传统的ab剪枝搜索。&lt;br /&gt;
&lt;br /&gt;
== 具体介绍 ==&lt;br /&gt;
NNUE的全称是Efficiently Updatable Neural Network（倒过来写的），它的核心思想，就是设计一种特别适合棋类游戏这种输入变化微小场景的神经网络结构，比如我们下一步棋，棋盘的变化比较小，这时候我们就可以做增量更新，不需要把整个棋盘算一遍，大大提高了效率。&lt;br /&gt;
&lt;br /&gt;
NNUE最初是给日本将棋设计的，作者是那須悠（Yu Nasu），2018年被应用于Motohiro Isozaki的YaneuraOu，2019年6月NNUE又被Nodchip（Hisayori Noda）移植到了Stockfish上。&lt;br /&gt;
&lt;br /&gt;
=== 输入特征集 ===&lt;br /&gt;
首先我们需要把棋盘局面转换为神经网络能够理解的输入，这就需要特征集了。&lt;br /&gt;
&lt;br /&gt;
常见的特征集有HalfKP和HalfKA(v2)，Stockfish早期使用的是HalfKP，现在使用的是HalfKAv2。&lt;br /&gt;
&lt;br /&gt;
HalfKP的K是King（王）的意思，P是Piece（棋子，但不包括王）的意思，Half强调是某一方的王，其特征组合是 (我方王的位置 S&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;, 某个棋子的位置 S&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;, 该棋子的类型 T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;, 该棋子的相对颜色 C&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;)。&lt;br /&gt;
&lt;br /&gt;
对于国象来说，棋子有64个Squares可走，棋子类型去掉王只有5种，棋子颜色有两种，因此特征总数为64×64×5×2=40960。&lt;br /&gt;
&lt;br /&gt;
HalfKA的A代表All，也就是说棋子的类型中也包括了王，把王也纳入进普通棋子里面，因此HalfKA的特征总数有64×64×6×2=49152，这样设计有利于NNUE捕捉到王相关的价值。&lt;br /&gt;
&lt;br /&gt;
HalfKAv2相比于HalfKA优化了特征数量。这12种棋子中包括了我方王和对方王，但我方王的第一个特征S&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;必然对应我方王的T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;，对方王的第一个特征必然对应对方的T&amp;lt;sub&amp;gt;p&amp;lt;/sub&amp;gt;王，可以直接合二为一，不需要区分是我方王还是对方王，这样特征总数就减少到了64×64×11=45056。&lt;br /&gt;
&lt;br /&gt;
考虑到象棋可以水平镜像，又有了HalfKAv2_hm，hm是horizontally mirrored的简称。我方王在棋盘左侧和在棋盘右侧是等效的，这样王的位置总数就从64减少到了32（特征总数32×64×11=22528）。&lt;br /&gt;
&lt;br /&gt;
=== 累加器 ===&lt;br /&gt;
累加器是NNUE增量更新的核心，NNUE第一层（22528-&amp;gt;2560&amp;lt;ref&amp;gt;2560是L1的大小，这里用的是SFNNv8的数据。&amp;lt;/ref&amp;gt;）的输出会被存储到累加器中，当棋盘发生变化（如走了一步棋），累加器也相应地进行变化（只需要在累加器中加上/减去该特征对应的第一层权重列），这样我们可以更加快速地更新累加器，不需要重新计算第一层。累加器会区分我方视角和对方视角，有两套不同的累加器。&lt;br /&gt;
&lt;br /&gt;
== 脚注 ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E4%B8%BA%E4%BB%80%E4%B9%88%E5%BC%95%E6%93%8E%E8%A7%A3%E4%B8%8D%E5%BC%80%E6%9F%90%E4%BA%9B%E5%B1%80%E9%9D%A2%EF%BC%9F&amp;diff=578</id>
		<title>为什么引擎解不开某些局面？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E4%B8%BA%E4%BB%80%E4%B9%88%E5%BC%95%E6%93%8E%E8%A7%A3%E4%B8%8D%E5%BC%80%E6%9F%90%E4%BA%9B%E5%B1%80%E9%9D%A2%EF%BC%9F&amp;diff=578"/>
		<updated>2025-06-05T12:33:21Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;引擎不是神，它只是非常强大而已，不代表没有弱点，更不代表可以解开任意局面。&lt;br /&gt;
&lt;br /&gt;
最常见的是排局(排局：摆出来的局，大多排局棋子比较少，经常被叫作残局，排局通常要求人类能掌握)，有部分一方必胜的排局，引擎极难算到正解。&lt;br /&gt;
&lt;br /&gt;
所以是可能出现某些局面，人类比引擎强的。打个比喻：这就好像人类和汽车比速度，绝大多数赛道都是汽车更强，但是有的赛道上会出现矮墙，那么就是人类优势。&lt;br /&gt;
&lt;br /&gt;
引擎极难计算的排局更让人印象深刻，因为这些排局是人类能掌握并且知道结论的，还有一种类型的局面经常被忽视，但引擎也可能极难算到的，那就是优势开局。&lt;br /&gt;
&lt;br /&gt;
在引擎测试中，经常用到优势开局，比如红高优开局，然后两个引擎各执红执黑一次进行对战，会经常出现一胜一和的情况，这些局面引擎可能无法稳定和棋或者稳定取胜，所以也可以是引擎“解不出来”的局面，人类更不可能知道这些优势局究竟是胜还是和。&lt;br /&gt;
&lt;br /&gt;
'''另外，引擎的棋力水平标准只有科学的测试数据，只比较部分局面无法代表引擎的棋力水平。'''&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=577</id>
		<title>首页</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=577"/>
		<updated>2025-06-05T12:24:09Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;皮卡鱼（Pikafish）是一个免费、开源的象棋引擎，用于分析象棋局面并计算最优的走法。目前皮卡鱼是棋力最强的象棋引擎。&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
皮卡鱼没有界面（GUI），需要自行下载。可见→ [[不会用皮卡鱼，怎么使用？|新手如何使用皮卡鱼]]&lt;br /&gt;
&lt;br /&gt;
另外皮卡鱼不支持Windows 7（使用gcc自行编译的可以支持）。&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
皮卡鱼源自国际象棋引擎鳕鱼（Stockfish），并继承GPL-3.0开源协议。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''棋类软件的一些常识，可点击→[[棋软知识]]'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''关于皮卡鱼的引擎选项设置，可点击→[[UCI选项]]'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''关于皮卡鱼引擎的选择（avx2 bmi2 vnni512等等）可点击→[[指令集（如bmi2 avx2) 是什么？]]'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''皮卡鱼网页版说明 → [[皮卡鱼网页版说明]]'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''想了解引擎搜索算法，可点击→[[搜索算法]]'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''关于皮卡鱼的UCI协议，可点击→[[UCI协议]]'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''想了解比如象棋界面的具体使用方法或其他疑问，可进入皮卡鱼官方群询问→ 1019666453 (群可能满员，可根据群介绍寻找其他皮卡鱼官方群进入)'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
想要更深入地使用皮卡鱼，可查看Pikafish的官方GitHub。&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=UCI%E9%80%89%E9%A1%B9&amp;diff=576</id>
		<title>UCI选项</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=UCI%E9%80%89%E9%A1%B9&amp;diff=576"/>
		<updated>2025-06-05T12:23:12Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
目前部分界面支持UCI选项， 例如：&lt;br /&gt;
&lt;br /&gt;
 在鲨鱼界面中（免费鲨鱼至少1.7.9版本，商业鲨鱼至少2.3.1版本），点击引擎栏中的🔧扳手图标(线程和哈希设置的旁边)即可进入引擎选项设置。如下图所示&lt;br /&gt;
[[文件:Screenshot 20240320 112109 QQ.jpg|缩略图|居中|鲨鱼界面设置引擎UCI选项的地方]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 而手机安卓系统，在进入优步app后，点击最右上角，就能弹出菜单，然后点击“管理UCI引擎”→“引擎设置”，即可进入部分选项设置。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 皮卡鱼的部分选项说明 ==&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;big&amp;gt;Clear Hash&amp;lt;/big&amp;gt;&lt;br /&gt;
&lt;br /&gt;
清除引擎目前的哈希记忆。不过最常见的是直接重新加载引擎达到这个效果。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;big&amp;gt;MultiPV&amp;lt;/big&amp;gt;&lt;br /&gt;
&lt;br /&gt;
多主变思考。引擎分析局面时默认是1个主变。当改变此选项，引擎分析时会增加当前局面的主变数量，各主变的深度一致，并同时显示(即同一层会显示多个变化)。&lt;br /&gt;
&lt;br /&gt;
或者说让着法按“主变的待遇”去计算，原本默认只有一个着法享受主变待遇。&lt;br /&gt;
&lt;br /&gt;
调高数字将会''降低棋力''，仅供拆棋分析使用，'''实战和引擎测试请不要使用'''。并且只是增加当前的局面主变数量，在思考后续局面时照旧(显而易见否则指数爆炸)。&lt;br /&gt;
&lt;br /&gt;
设置范围1~500，默认是1，即只有一个主变。&lt;br /&gt;
&lt;br /&gt;
注：主变是主要变例（Principle Variation）的缩写。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;big&amp;gt;Skill Level&amp;lt;/big&amp;gt;&lt;br /&gt;
&lt;br /&gt;
限制引擎的棋力水平，设置非20时，有一定概率在出招时选择劣变(同时会开启MultiPV＝4)可用作人软对弈。注意仅仅是引擎出招选择劣变，引擎分析和MultiPV＝4相同。&lt;br /&gt;
&lt;br /&gt;
设置范围0~20，默认为不限制棋力的20。&lt;br /&gt;
&lt;br /&gt;
'''但如果开启UCI_LimitStrength，将会使Skill Level无效。'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;big&amp;gt;UCI_LimitStrength&amp;lt;/big&amp;gt;&lt;br /&gt;
&lt;br /&gt;
默认关闭。'''开启后将会使Skill Level无效，让UCI_Elo生效。'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;big&amp;gt;UCI_Elo&amp;lt;/big&amp;gt;&lt;br /&gt;
&lt;br /&gt;
更细致地限制引擎的棋力水平。'''只有开启UCI_LimitStrength才会生效'''，设置范围1280~3133，越低越弱。如果不满足Skill Level的21个级别划分，想要更加细致地划分引擎棋力水平，使用UCI_Elo即可。和Skill Level的限制棋力方式没有区别，只是更加细分。&lt;br /&gt;
其中Elo=1280等于Skill Level中的0，最高值3133等于Skill Level中的19，2850=13，2568=10，2268=7，1777=4。&lt;br /&gt;
&lt;br /&gt;
UCI_Elo的值已经和[https://bbs.pikafish.org/forum.php?mod=viewthread&amp;amp;tid=179 象棋引擎联赛天梯图]校准。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
皮卡鱼的60回合自然限招规则，不会把每方超过10次将军的着法计入限招，并且对应的应将步也不会计入，但当局面产生吃子后，将军次数会重新计算。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;big&amp;gt;Rule60MaxPly&amp;lt;/big&amp;gt;&lt;br /&gt;
&lt;br /&gt;
60回合自然限招的步数设置，设置范围0到120，默认为120，且120为最佳设置。0则为取消自然限招。&lt;br /&gt;
注意120就是60回合。&lt;br /&gt;
&lt;br /&gt;
设置120时引擎将会考虑到60回合不吃子判和，把60回合不吃子视为0分，设置0引擎将会取消自然限招的考虑。&lt;br /&gt;
&lt;br /&gt;
如果你想分析局面同时不考虑60回合、或者不喜欢、或者平台和界面不兼容皮卡鱼60规则，则可以关闭设置为0或修改。如果发现莫名其妙的送子问题，很可能就是此选项导致，说明和你下棋平台或界面的60规则不兼容，可以选择设置0。（其实送子并非问题，因为60回合不吃子时引擎已经视为0分，任何不会马上造成吃子的着法都是0分）&lt;br /&gt;
&lt;br /&gt;
'''除特殊情况拆棋以外不建议调整该选项'''，因为引擎的搜索参数耦合120步自然限招，如果小太多，很可能导致棋力还不如直接关闭自然限招。并且一旦引擎走到了认为的限招步数，后续着法将会随机。只适合特殊情况拆棋使用，例如大优胜势残局局面想要找到可能的迅速简化或吃子路线，并且使用完后要随时记得要调回120，否则可能导致未来拆棋或引擎联赛测试出现荒谬的结果。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;big&amp;gt;Mate Threat Depth&amp;lt;/big&amp;gt;&lt;br /&gt;
&lt;br /&gt;
判断中规里“杀”的回合数。'''该选项只在Repetition Rule里设定为ChineseRule时才会生效'''，设置范围0~10，设置0则引擎不会判断“杀”。设置1~10，则引擎会在搜索中判断循环招法是不是1~10回合内的“杀”，而“杀”在中规里可能导致循环违规。设置得越高棋力下降越严重。适合纯人在中规环境下拆棋。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;big&amp;gt;Repetition Rule&amp;lt;/big&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
AsianRule是亚洲规则（作者团队所理解的一个亚规版本），违规严重级:长将&amp;gt;长捉同一子&amp;gt;其他，属于2fold(相同局面一旦重复出现直接进行判决，也就是一个局面出现第2次时判决)&lt;br /&gt;
'''大部分网络规则都是亚规。'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SkyRule是某些网络规则。请注意，某些网络规则是亚规基础上稍作修改的，并不是中规。用于线上对弈后拆棋，供纯人更好地在平台适应规则。请自行辨别网络规则是不是亚规。(该棋规代码作者为skystarspython)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
ComputerRule是基于皮卡鱼作者《中国象棋程序竞赛规则》的规则，和AsianRule有些许出入，也'''和所有网络平台规则都有较大差异'''，但是是唯一一个全部符合所有亚规图例结果裁决的规则，更严格按照亚规文字定义，并且属于3fold（一个局面出现第3次时判决）。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
ChineseRule是极简化的中国规则，本质上是亚规改版。注意目前几乎没有网络平台使用中规！且目前中规由于定义过度模糊且复杂，复杂棋例比较依赖裁判的主观想法，绝无任何可能程序化。违规严重级：长将&amp;gt;长捉、长杀、将杀循环、将捉循环、杀捉循环&amp;gt;其他。开启此选项后，Mate Threat Depth不为0时，引擎才会判断“杀”。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
AllowChase是只禁止长将，允许其他一切循环着法。因为象棋的规则不统一，所以在一些优势胜势局可以在此规则下进行拆棋分析，以求找到不涉及循环棋规的路线，避开可能存在的循环棋规问题。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
YitianRule适合在弈天平台使用。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
上述规则要随时记得调整。例如使用其他规则拆棋后，如果之后要进行引擎联赛测试要调回AsianRule，否则可能出现荒谬的测试结果。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;big&amp;gt;ScoreType&amp;lt;/big&amp;gt;&lt;br /&gt;
引擎显示的分数种类，不影响棋力。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Elo：胜率分数。根据胜率模型，将原始分数转换而成的分数。&lt;br /&gt;
皮卡鱼(自2024年开始)的elo分是'''和ELO挂钩'''，200分代表76%胜率(象棋圈常说的胜率，也就是胜局加上一半的和局，例如4胜4和2负就是60%胜率，3胜4和3负就是50%胜率)，elo和胜率的关系如右图所示。[[文件:Ca5083ed-8215-4faa-9b12-e394b109edae.png|缩略图|“胜率(胜局+一半的和局)”与elo分数的对应曲线图]]&lt;br /&gt;
[[文件:2ebfe70f-bb33-4146-9cf2-a9de2060c7f0.png|缩略图|“获胜概率(仅胜局)”与elo分数的近似对应曲线图]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PawnValueNormalized：原始分数除以一个兵价值的常数。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Raw：原始分数。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;big&amp;gt;LU_Output&amp;lt;/big&amp;gt;&lt;br /&gt;
&lt;br /&gt;
默认开启。开启会显示lowerbound和upperbound的信息，通俗说同一层可能会输出多次结果，比如某一层正在上层的时候会输出不完整的思考细节。若关闭，一层只会输出一次。&lt;br /&gt;
&lt;br /&gt;
该选项只有输出区别，不影响棋力。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;big&amp;gt;DrawRule&amp;lt;/big&amp;gt;&lt;br /&gt;
&lt;br /&gt;
None是正常，默认None。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
DrawAsBlackWin是和棋黑胜，引擎会把和棋循环与自然限招和棋算作黑胜。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
DrawAsRedWin是和棋红胜。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
DrawRepAsBlackWin是和棋循环黑胜，引擎只会把和棋的循环算作黑胜。拆棋分析的时候，不想短期内走出和棋循环可以利用这个。&lt;br /&gt;
&lt;br /&gt;
DrawRepAsRedWin是和棋循环红胜。&lt;br /&gt;
&lt;br /&gt;
皮卡鱼引擎并不保证这些规则的棋力，'''尤其是分数评估很不可信'''，只有着法可能会有参考性。&lt;br /&gt;
&lt;br /&gt;
上述规则如果调整过，要随时记得调整。例如使用和棋黑胜拆棋后，如果之后要进行引擎联赛测试要调回None，否则会出现荒谬的测试结果。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;big&amp;gt;EvalFile&amp;lt;/big&amp;gt;&lt;br /&gt;
&lt;br /&gt;
引擎要读取的NNUE权重文件名称与路径。引擎默认读取同一文件路径下名为pikafish.nnue的文件，可以将NNUE文件改名，并且通过此选项使其仍可以被读取。一般人不需要，手机优步可通过此选项达到配置多引擎的效果。&lt;br /&gt;
&lt;br /&gt;
部分界面（特别是自制界面，如T界面）在加载引擎的时候不会将工作目录切换到引擎目录下，导致引擎无法读取NNUE文件。可以通过该选项设置NNUE的完整路径解决问题，'''或者直接把pikafish.nnue放在界面目录下（如果引擎没有提供设置选项功能可以尝试）'''。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;big&amp;gt;NumaPolicy&amp;lt;/big&amp;gt;&lt;br /&gt;
默认auto  一般不用管此选项。&lt;br /&gt;
&lt;br /&gt;
但2022年以前Windows系统的多核心服务器注意，auto很可能会吃不满线程，可改为 hardware 或者关闭超线程，前者多开引擎时会有分配问题，后者有概率正常。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
其余选项一般人基本用不到（例如Threads和Hash，界面会提供相应的设置），可以无视，如果想要知道，可以进入[https://github.com/official-pikafish/Pikafish/wiki 皮卡鱼官方GitHub中的Wiki]查看。&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%EF%BC%88%E5%A6%82ChatGPT%E3%80%81DeepSeek%EF%BC%89%E4%BC%9A%E4%B8%8B%E8%B1%A1%E6%A3%8B%E5%90%97%EF%BC%9F&amp;diff=575</id>
		<title>大语言模型（如ChatGPT、DeepSeek）会下象棋吗？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%EF%BC%88%E5%A6%82ChatGPT%E3%80%81DeepSeek%EF%BC%89%E4%BC%9A%E4%B8%8B%E8%B1%A1%E6%A3%8B%E5%90%97%EF%BC%9F&amp;diff=575"/>
		<updated>2025-06-05T12:20:26Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
大语言模型（LLM），顾名思义，主要是用来专业处理语言的，'''不会下象棋'''，类似于部分人类不懂专业知识，依然能侃侃而谈错误的东西。&lt;br /&gt;
&lt;br /&gt;
术业有专攻，如果要下象棋，应使用专业化的的象棋引擎。&lt;br /&gt;
&lt;br /&gt;
LLM由于没有像专业象棋引擎那样输入棋类规则，且LLM本身就具有幻觉（Hallucination），所以并不会严格遵守规则，'''非常容易下出非法着法乱走棋'''。&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%EF%BC%88%E5%A6%82ChatGPT%E3%80%81DeepSeek%EF%BC%89%E4%BC%9A%E4%B8%8B%E8%B1%A1%E6%A3%8B%E5%90%97%EF%BC%9F&amp;diff=574</id>
		<title>大语言模型（如ChatGPT、DeepSeek）会下象棋吗？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%EF%BC%88%E5%A6%82ChatGPT%E3%80%81DeepSeek%EF%BC%89%E4%BC%9A%E4%B8%8B%E8%B1%A1%E6%A3%8B%E5%90%97%EF%BC%9F&amp;diff=574"/>
		<updated>2025-06-05T12:19:43Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
大语言模型（LLM），顾名思义，主要是用来专业处理语言的，'''不会下象棋'''，类似于部分人类不懂专业知识，依然能侃侃而谈错误的东西。&lt;br /&gt;
&lt;br /&gt;
术业有专攻，如果要下象棋，应使用专业化的的象棋引擎。&lt;br /&gt;
&lt;br /&gt;
LLM由于没有像专业象棋引擎那样输入棋类规则，且LLM本身就具有幻觉，所以并不会严格遵守规则，'''非常容易下出非法着法乱走棋'''。&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=573</id>
		<title>首页</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=573"/>
		<updated>2025-06-05T12:15:03Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;皮卡鱼（Pikafish）是一个免费、开源的象棋引擎，用于分析象棋局面并计算最优的走法。目前皮卡鱼是象棋棋力最强的引擎。&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
皮卡鱼没有界面（GUI），需要自行下载。可见→ [[不会用皮卡鱼，怎么使用？|新手如何使用皮卡鱼]]&lt;br /&gt;
&lt;br /&gt;
另外皮卡鱼不支持Windows 7（使用gcc自行编译的可以支持）。&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
皮卡鱼源自国际象棋引擎鳕鱼（Stockfish），并继承GPL-3.0开源协议。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''棋类软件的一些常识，可点击→[[棋软知识]]'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''关于皮卡鱼的引擎选项设置，可点击→[[UCI选项]]'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''关于皮卡鱼引擎的选择（avx2 bmi2 vnni512等等）可点击→[[指令集（如bmi2 avx2) 是什么？]]'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''皮卡鱼网页版说明 → [[皮卡鱼网页版说明]]'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''想了解引擎搜索算法，可点击→[[搜索算法]]'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''关于皮卡鱼的UCI协议，可点击→[[UCI协议]]'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''想了解比如象棋界面的具体使用方法或其他疑问，可进入皮卡鱼官方群询问→ 1019666453 (群可能满员，可根据群介绍寻找其他皮卡鱼官方群进入)'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
想要更深入地使用皮卡鱼，可查看Pikafish的官方GitHub。&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E7%9A%AE%E5%8D%A1%E9%B1%BC%E5%92%8C%E5%95%86%E4%B8%9A%E4%BB%98%E8%B4%B9%E5%BC%95%E6%93%8E%E8%B0%81%E6%9B%B4%E5%BC%BA%3F&amp;diff=544</id>
		<title>皮卡鱼和商业付费引擎谁更强?</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E7%9A%AE%E5%8D%A1%E9%B1%BC%E5%92%8C%E5%95%86%E4%B8%9A%E4%BB%98%E8%B4%B9%E5%BC%95%E6%93%8E%E8%B0%81%E6%9B%B4%E5%BC%BA%3F&amp;diff=544"/>
		<updated>2025-02-06T15:15:00Z</updated>

		<summary type="html">&lt;p&gt;New：​更新清晰图片&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
对于部分言论“付费的一定比免费更强”，首先，象棋的强引擎(不论免费付费)全都是改自国际象棋的最强引擎“鳕鱼”，而鳕鱼是开源免费的，也是国际象棋毫无疑问的第一引擎，所以该言论从根本上就不成立。&lt;br /&gt;
&lt;br /&gt;
另外，象棋商业引擎“象棋旋风”于2025年初非法使用皮卡鱼的nnue。&lt;br /&gt;
&lt;br /&gt;
截止2025年初，象棋引擎中棋力最强的是皮卡鱼，测试结果如下&lt;br /&gt;
[[文件:象棋引擎天梯图.png|缩略图|居中|注：2025年后象棋旋风为皮卡鱼的复制品，后续所谓的旋风版本将不予上榜|800px]]&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E6%96%87%E4%BB%B6:%E8%B1%A1%E6%A3%8B%E5%BC%95%E6%93%8E%E5%A4%A9%E6%A2%AF%E5%9B%BE.png&amp;diff=543</id>
		<title>文件:象棋引擎天梯图.png</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E6%96%87%E4%BB%B6:%E8%B1%A1%E6%A3%8B%E5%BC%95%E6%93%8E%E5%A4%A9%E6%A2%AF%E5%9B%BE.png&amp;diff=543"/>
		<updated>2025-02-06T15:11:23Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%EF%BC%88%E5%A6%82ChatGPT%E3%80%81DeepSeek%EF%BC%89%E4%BC%9A%E4%B8%8B%E8%B1%A1%E6%A3%8B%E5%90%97%EF%BC%9F&amp;diff=542</id>
		<title>大语言模型（如ChatGPT、DeepSeek）会下象棋吗？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%EF%BC%88%E5%A6%82ChatGPT%E3%80%81DeepSeek%EF%BC%89%E4%BC%9A%E4%B8%8B%E8%B1%A1%E6%A3%8B%E5%90%97%EF%BC%9F&amp;diff=542"/>
		<updated>2025-02-06T12:14:18Z</updated>

		<summary type="html">&lt;p&gt;New：​创建页面，内容为“大语言模型（LLM），顾名思义，主要是用来处理语言的，不适合用来下象棋。  如果要下象棋，应使用专门的象棋引擎。  LLM并不会严格遵守规则，'''容易下出非法着法'''。”&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;大语言模型（LLM），顾名思义，主要是用来处理语言的，不适合用来下象棋。&lt;br /&gt;
&lt;br /&gt;
如果要下象棋，应使用专门的象棋引擎。&lt;br /&gt;
&lt;br /&gt;
LLM并不会严格遵守规则，'''容易下出非法着法'''。&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E6%A3%8B%E8%BD%AF%E7%9F%A5%E8%AF%86&amp;diff=541</id>
		<title>棋软知识</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E6%A3%8B%E8%BD%AF%E7%9F%A5%E8%AF%86&amp;diff=541"/>
		<updated>2025-02-06T11:41:45Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[https://pikafish.org/wiki 返回首页]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[不会用皮卡鱼，怎么使用？]]&lt;br /&gt;
&lt;br /&gt;
[[皮卡鱼和商业付费引擎谁更强?]]&lt;br /&gt;
&lt;br /&gt;
[[象棋顶级引擎目前的和棋率有多少?]]&lt;br /&gt;
&lt;br /&gt;
[[核心（例如128核256核）是什么？]]&lt;br /&gt;
&lt;br /&gt;
[[指令集（如bmi2 avx2）是什么？]]&lt;br /&gt;
&lt;br /&gt;
[[ELO]]（等级分）是什么？&lt;br /&gt;
&lt;br /&gt;
[[什么是“AI”（人工智能）？]]&lt;br /&gt;
&lt;br /&gt;
[[象棋被穷尽了吗？]]&lt;br /&gt;
&lt;br /&gt;
[[引擎计算是使用“穷举法”吗？]]&lt;br /&gt;
&lt;br /&gt;
[[UCI选项]]（引擎的各个选项设置）&lt;br /&gt;
&lt;br /&gt;
[[什么是“引擎”？]]&lt;br /&gt;
&lt;br /&gt;
[[什么是“界面”？]]&lt;br /&gt;
&lt;br /&gt;
[[什么是“开局库”？]]&lt;br /&gt;
&lt;br /&gt;
[[什么是“云库”？]]&lt;br /&gt;
&lt;br /&gt;
[[什么是“残局库”？]]&lt;br /&gt;
&lt;br /&gt;
[[什么是“审局库”？]]&lt;br /&gt;
&lt;br /&gt;
[[什么是“NNUE”？]]（权重）&lt;br /&gt;
&lt;br /&gt;
[[什么是“传统引擎”？]]&lt;br /&gt;
&lt;br /&gt;
[[什么是“评估”？]]&lt;br /&gt;
&lt;br /&gt;
[[为什么引擎解不开某些局面？]]&lt;br /&gt;
&lt;br /&gt;
[[象棋有“阿尔法狗”吗？]]&lt;br /&gt;
&lt;br /&gt;
[[大语言模型（如ChatGPT、DeepSeek）会下象棋吗？]]&lt;br /&gt;
&lt;br /&gt;
[[神经网络的“自我学习”是什么？]]&lt;br /&gt;
&lt;br /&gt;
[[如何科学地测试引擎？]]&lt;br /&gt;
&lt;br /&gt;
[[深度（层数）是什么？上层速度代表棋力吗？]]&lt;br /&gt;
&lt;br /&gt;
[[NPS（K值）是什么？代表棋力吗？]]&lt;br /&gt;
&lt;br /&gt;
[[置换表（哈希）是什么？设置多少好？]]&lt;br /&gt;
&lt;br /&gt;
[[核心或线程越高的机器就越好吗？]]&lt;br /&gt;
&lt;br /&gt;
[[对象棋引擎来说，机器算力的因素大吗？]]&lt;br /&gt;
&lt;br /&gt;
[[各个引擎的打分为什么不一样？甚至相同引擎不同版本也不一样？]]&lt;br /&gt;
&lt;br /&gt;
[[软件对打只能和棋吗？]]&lt;br /&gt;
&lt;br /&gt;
[[引擎棋力越强，任何局面一定就比弱的引擎好吗？]]&lt;br /&gt;
&lt;br /&gt;
[[引擎多核多线程与单核的区别]]&lt;br /&gt;
&lt;br /&gt;
[[不进行引擎测试去“感觉”孰强孰弱可靠吗？]]&lt;br /&gt;
&lt;br /&gt;
[[FEN（局面码）是什么？]]&lt;br /&gt;
&lt;br /&gt;
[[什么是bench？bench的结果说明了什么？]]&lt;br /&gt;
&lt;br /&gt;
[[如何在UCCI界面（如象棋巫师）加载皮卡鱼？]]&lt;br /&gt;
&lt;br /&gt;
[[象棋棋规疑云]]&lt;br /&gt;
&lt;br /&gt;
部分有趣的[[皮卡鱼测试数据]]&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E5%A6%82%E4%BD%95%E5%9C%A8UCCI%E7%95%8C%E9%9D%A2%EF%BC%88%E5%A6%82%E8%B1%A1%E6%A3%8B%E5%B7%AB%E5%B8%88%EF%BC%89%E5%8A%A0%E8%BD%BD%E7%9A%AE%E5%8D%A1%E9%B1%BC%EF%BC%9F&amp;diff=392</id>
		<title>如何在UCCI界面（如象棋巫师）加载皮卡鱼？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E5%A6%82%E4%BD%95%E5%9C%A8UCCI%E7%95%8C%E9%9D%A2%EF%BC%88%E5%A6%82%E8%B1%A1%E6%A3%8B%E5%B7%AB%E5%B8%88%EF%BC%89%E5%8A%A0%E8%BD%BD%E7%9A%AE%E5%8D%A1%E9%B1%BC%EF%BC%9F&amp;diff=392"/>
		<updated>2024-11-08T14:44:36Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;皮卡鱼本身不支持UCCI协议（因为兼容了会出现一些问题），需要使用代理程序才能支持。代理程序将皮卡鱼的UCI协议转换为UCCI协议，还将界面发送的UCCI指令转换为对应的UCI指令。&lt;br /&gt;
&lt;br /&gt;
代理程序可从象棋巫师官网下载：[https://www.xqbase.com/xqwizard/pikafish.htm]（开源地址：[https://github.com/xqbase/eleeye/tree/master/pikafish-proxy]）&lt;br /&gt;
&lt;br /&gt;
pikafish-proxy.exe（位于安装目录下）即为代理程序，加载的时候应选择该文件进行加载。&lt;br /&gt;
&lt;br /&gt;
如果不想使用象棋巫师自带的开局库，可以将BOOK.DAT文件删除。&lt;br /&gt;
&lt;br /&gt;
如果需要使用皮卡鱼的其它版本，可以将pikafish-sse41-popcnt.exe替换成你想要的版本，同时要把NNUE文件一同复制过去。&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=NPS%EF%BC%88K%E5%80%BC%EF%BC%89%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F%E4%BB%A3%E8%A1%A8%E6%A3%8B%E5%8A%9B%E5%90%97%EF%BC%9F&amp;diff=391</id>
		<title>NPS（K值）是什么？代表棋力吗？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=NPS%EF%BC%88K%E5%80%BC%EF%BC%89%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F%E4%BB%A3%E8%A1%A8%E6%A3%8B%E5%8A%9B%E5%90%97%EF%BC%9F&amp;diff=391"/>
		<updated>2024-11-08T14:43:51Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;'''NPS'''（'''N'''odes '''P'''er '''S'''econd）是每秒平均搜索局面数，可以理解成搜索速度，因为常以K（千）为单位表示，又被称为'''K值'''。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
但速度快慢和棋力没什么大关系，例如nnue拖慢了不少nps，但是棋力仍然比传统评估引擎更强（当然其他条件相同的情况下，速度越快越好）。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''不同引擎不同版本的引擎nps不能互相比较'''，nps只适合比较'''机器'''的强弱：相同引擎的相同版本、相同设置，相同的局面的前提下，进行多次测速。测速出的nps越高，代表机器算力也就越高。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''nps不代表引擎本身的棋力。棋力的唯一指标是科学的测试数据。'''只有在引擎相同的情况下（如同一版本不同指令集、同一版本不同机器等），才能通过nps比较引擎的棋力。&lt;br /&gt;
&lt;br /&gt;
并且部分其他引擎或者远程云引擎会放大nps（一般也会放大节点数），使其更加好看，比如乘以6或乘以2.5。&lt;br /&gt;
&lt;br /&gt;
皮卡鱼的nps是真实的，如果皮卡鱼的nps只有几十k每秒(也就是几万每秒)，那么极有可能不正常，哪里出现了问题。&lt;br /&gt;
&lt;br /&gt;
[[文件:Hce_vs_nnue.png|500px|thumb|left|左边是皮卡鱼HCE，右边是皮卡鱼NNUE。可以看出皮卡鱼HCE的nps更高，但实际上皮卡鱼NNUE的棋力远超皮卡鱼HCE。]]&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=NPS%EF%BC%88K%E5%80%BC%EF%BC%89%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F%E4%BB%A3%E8%A1%A8%E6%A3%8B%E5%8A%9B%E5%90%97%EF%BC%9F&amp;diff=390</id>
		<title>NPS（K值）是什么？代表棋力吗？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=NPS%EF%BC%88K%E5%80%BC%EF%BC%89%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F%E4%BB%A3%E8%A1%A8%E6%A3%8B%E5%8A%9B%E5%90%97%EF%BC%9F&amp;diff=390"/>
		<updated>2024-11-08T14:43:14Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;'''NPS'''（'''N'''odes '''P'''er '''S'''econd）是每秒平均搜索局面数，可以理解成搜索速度，因为常以K（千）为单位表示，又被称为'''K值'''。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
但速度快慢和棋力没什么大关系，例如nnue拖慢了不少nps，但是棋力仍然比传统评估引擎更强（当然其他条件相同的情况下，速度越快越好）。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''不同引擎不同版本的引擎nps不能互相比较'''，nps只适合比较'''机器'''的强弱：相同引擎的相同版本、相同设置，相同的局面的前提下，进行多次测速。测速出的nps越高，代表机器算力也就越高。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''nps不代表引擎本身的棋力。棋力的唯一指标是科学的测试数据。'''只有在引擎相同的情况下，才能通过nps比较引擎的棋力。&lt;br /&gt;
&lt;br /&gt;
并且部分其他引擎或者远程云引擎会放大nps（一般也会放大节点数），使其更加好看，比如乘以6或乘以2.5。&lt;br /&gt;
&lt;br /&gt;
皮卡鱼的nps是真实的，如果皮卡鱼的nps只有几十k每秒(也就是几万每秒)，那么极有可能不正常，哪里出现了问题。&lt;br /&gt;
&lt;br /&gt;
[[文件:Hce_vs_nnue.png|500px|thumb|left|左边是皮卡鱼HCE，右边是皮卡鱼NNUE。可以看出皮卡鱼HCE的nps更高，但实际上皮卡鱼NNUE的棋力远超皮卡鱼HCE。]]&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E5%A6%82%E4%BD%95%E5%9C%A8UCCI%E7%95%8C%E9%9D%A2%EF%BC%88%E5%A6%82%E8%B1%A1%E6%A3%8B%E5%B7%AB%E5%B8%88%EF%BC%89%E5%8A%A0%E8%BD%BD%E7%9A%AE%E5%8D%A1%E9%B1%BC%EF%BC%9F&amp;diff=387</id>
		<title>如何在UCCI界面（如象棋巫师）加载皮卡鱼？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E5%A6%82%E4%BD%95%E5%9C%A8UCCI%E7%95%8C%E9%9D%A2%EF%BC%88%E5%A6%82%E8%B1%A1%E6%A3%8B%E5%B7%AB%E5%B8%88%EF%BC%89%E5%8A%A0%E8%BD%BD%E7%9A%AE%E5%8D%A1%E9%B1%BC%EF%BC%9F&amp;diff=387"/>
		<updated>2024-11-02T02:42:35Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;皮卡鱼本身不支持UCCI协议（因为兼容了会出现一些问题），需要使用代理程序才能支持。代理程序将皮卡鱼的UCI协议转换为UCCI协议，还将界面发送的UCCI指令转换为对应的UCCI指令。&lt;br /&gt;
&lt;br /&gt;
代理程序可从象棋巫师官网下载：[https://www.xqbase.com/xqwizard/pikafish.htm]（开源地址：[https://github.com/xqbase/eleeye/tree/master/pikafish-proxy]）&lt;br /&gt;
&lt;br /&gt;
pikafish-proxy.exe（位于安装目录下）即为代理程序，加载的时候应选择该文件进行加载。&lt;br /&gt;
&lt;br /&gt;
如果不想使用象棋巫师自带的开局库，可以将BOOK.DAT文件删除。&lt;br /&gt;
&lt;br /&gt;
如果需要使用皮卡鱼的其它版本，可以将pikafish-sse41-popcnt.exe替换成你想要的版本，同时要把NNUE文件一同复制过去。&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E5%A6%82%E4%BD%95%E5%9C%A8UCCI%E7%95%8C%E9%9D%A2%EF%BC%88%E5%A6%82%E8%B1%A1%E6%A3%8B%E5%B7%AB%E5%B8%88%EF%BC%89%E5%8A%A0%E8%BD%BD%E7%9A%AE%E5%8D%A1%E9%B1%BC%EF%BC%9F&amp;diff=386</id>
		<title>如何在UCCI界面（如象棋巫师）加载皮卡鱼？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E5%A6%82%E4%BD%95%E5%9C%A8UCCI%E7%95%8C%E9%9D%A2%EF%BC%88%E5%A6%82%E8%B1%A1%E6%A3%8B%E5%B7%AB%E5%B8%88%EF%BC%89%E5%8A%A0%E8%BD%BD%E7%9A%AE%E5%8D%A1%E9%B1%BC%EF%BC%9F&amp;diff=386"/>
		<updated>2024-11-02T02:41:17Z</updated>

		<summary type="html">&lt;p&gt;New：​创建页面，内容为“皮卡鱼本身不支持UCCI协议（因为兼容了会出现一些问题），需要使用代理程序才能支持。代理程序将皮卡鱼的UCI协议转换为UCCI协议，还将界面发送的UCCI指令转换为对应的UCCI指令。  代理程序可从象棋巫师官网下载：[https://www.xqbase.com/xqwizard/pikafish.htm]（开源地址：[https://github.com/xqbase/eleeye/tree/master/pikafish-proxy]）  pikafish-proxy.exe即为代理程序，加载的…”&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;皮卡鱼本身不支持UCCI协议（因为兼容了会出现一些问题），需要使用代理程序才能支持。代理程序将皮卡鱼的UCI协议转换为UCCI协议，还将界面发送的UCCI指令转换为对应的UCCI指令。&lt;br /&gt;
&lt;br /&gt;
代理程序可从象棋巫师官网下载：[https://www.xqbase.com/xqwizard/pikafish.htm]（开源地址：[https://github.com/xqbase/eleeye/tree/master/pikafish-proxy]）&lt;br /&gt;
&lt;br /&gt;
pikafish-proxy.exe即为代理程序，加载的时候应选择该文件进行加载。&lt;br /&gt;
&lt;br /&gt;
如果不想使用象棋巫师自带的开局库，可以将BOOK.DAT文件删除。&lt;br /&gt;
&lt;br /&gt;
如果需要使用皮卡鱼的其它版本，可以将pikafish-sse41-popcnt.exe替换成你想要的版本，同时要把NNUE文件一同复制过去。&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E6%A3%8B%E8%BD%AF%E7%9F%A5%E8%AF%86&amp;diff=385</id>
		<title>棋软知识</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E6%A3%8B%E8%BD%AF%E7%9F%A5%E8%AF%86&amp;diff=385"/>
		<updated>2024-11-02T02:31:04Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[https://pikafish.org/wiki 返回首页]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
[[指令集（如bmi2 avx2）是什么？]]&lt;br /&gt;
&lt;br /&gt;
[[各个引擎的打分为什么不一样？甚至相同引擎不同版本也不一样？]]&lt;br /&gt;
&lt;br /&gt;
[[UCI选项]]（引擎的各个选项设置）&lt;br /&gt;
&lt;br /&gt;
[[什么是“引擎”？]]&lt;br /&gt;
&lt;br /&gt;
[[什么是“界面”？]]&lt;br /&gt;
&lt;br /&gt;
[[什么是“开局库”？]]&lt;br /&gt;
&lt;br /&gt;
[[什么是“云库”？]]&lt;br /&gt;
&lt;br /&gt;
[[什么是“残局库”？]]&lt;br /&gt;
&lt;br /&gt;
[[什么是“审局库”？]]&lt;br /&gt;
&lt;br /&gt;
[[什么是“NNUE”？]]（权重）&lt;br /&gt;
&lt;br /&gt;
[[什么是“传统引擎”？]]&lt;br /&gt;
&lt;br /&gt;
[[什么是“评估”？]]&lt;br /&gt;
&lt;br /&gt;
[[ELO]]（等级分）是什么？&lt;br /&gt;
&lt;br /&gt;
[[什么是“AI”（人工智能）？]]&lt;br /&gt;
&lt;br /&gt;
[[为什么引擎解不开某些局面？]]&lt;br /&gt;
&lt;br /&gt;
[[象棋被穷尽了吗？]]&lt;br /&gt;
&lt;br /&gt;
[[引擎计算是使用“穷举法”吗？]]&lt;br /&gt;
&lt;br /&gt;
[[象棋有“阿尔法狗”吗？]]&lt;br /&gt;
&lt;br /&gt;
[[神经网络的“自我学习”是什么？]]&lt;br /&gt;
&lt;br /&gt;
[[如何科学地测试引擎？]]&lt;br /&gt;
&lt;br /&gt;
[[深度（层数）是什么？上层速度代表棋力吗？]]&lt;br /&gt;
&lt;br /&gt;
[[NPS（K值）是什么？代表棋力吗？]]&lt;br /&gt;
&lt;br /&gt;
[[置换表（哈希）是什么？设置多少好？]]&lt;br /&gt;
&lt;br /&gt;
[[核心或线程越高的机器就越好吗？]]&lt;br /&gt;
&lt;br /&gt;
[[对象棋引擎来说，机器算力的因素大吗？]]&lt;br /&gt;
&lt;br /&gt;
[[软件对打只能和棋吗？]]&lt;br /&gt;
&lt;br /&gt;
[[引擎棋力越强，任何局面一定就比弱的引擎好吗？]]&lt;br /&gt;
&lt;br /&gt;
[[引擎多核多线程与单核的区别]]&lt;br /&gt;
&lt;br /&gt;
[[不进行引擎测试去“感觉”孰强孰弱可靠吗？]]&lt;br /&gt;
&lt;br /&gt;
[[FEN（局面码）是什么？]]&lt;br /&gt;
&lt;br /&gt;
[[什么是bench？bench的结果说明了什么？]]&lt;br /&gt;
&lt;br /&gt;
[[如何在UCCI界面（如象棋巫师）加载皮卡鱼？]]&lt;br /&gt;
&lt;br /&gt;
[[象棋棋规疑云]]&lt;br /&gt;
&lt;br /&gt;
部分有趣的[[皮卡鱼测试数据]]&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=384</id>
		<title>首页</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=384"/>
		<updated>2024-10-06T16:40:48Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;皮卡鱼（Pikafish）是一个免费、开源的象棋引擎，用于分析象棋局面并计算最优的走法。&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
皮卡鱼没有界面（GUI），需要自行下载。另外皮卡鱼不支持Windows 7（使用gcc编译的可以支持）。&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
皮卡鱼源自国际象棋引擎鳕鱼（Stockfish），并继承GPL-3.0开源协议。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''关于部分引擎概念上的疑问，可点击→[[棋软知识]]'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''关于皮卡鱼的引擎选项设置，可点击→[[UCI选项]]'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''关于皮卡鱼引擎的选择（avx2 bmi2 vnni512等等）可点击→[[指令集（如bmi2 avx2) 是什么？]]'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''想了解引擎搜索算法，可点击→[[搜索算法]]'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''关于皮卡鱼的UCI协议，可点击→[[UCI协议]]'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''想了解比如象棋界面的具体使用方法或其他疑问，可进入皮卡鱼官方群询问→ 931125765 (群可能满员，可根据群介绍寻找其他皮卡鱼官方群进入)'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
想要更深入地使用皮卡鱼，可查看Pikafish的官方GitHub。&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C%E7%9A%84%E2%80%9C%E8%87%AA%E6%88%91%E5%AD%A6%E4%B9%A0%E2%80%9D%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&amp;diff=383</id>
		<title>神经网络的“自我学习”是什么？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C%E7%9A%84%E2%80%9C%E8%87%AA%E6%88%91%E5%AD%A6%E4%B9%A0%E2%80%9D%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&amp;diff=383"/>
		<updated>2024-10-02T15:38:37Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
在主流棋类引擎中，神经网络都属于离线监督学习。&lt;br /&gt;
&lt;br /&gt;
作者或者训练师会先让引擎自对弈，从而生成数据（棋谱）。&lt;br /&gt;
&lt;br /&gt;
以nnue跑谱为例，这些数据里面有每步的局面、分数、和这局游戏的结果等等信息，通常是以每步几层或者几千、几万节点自对弈生成。&lt;br /&gt;
&lt;br /&gt;
生成了足够多的数据后，便拿去训练，训练过程可以简单理解为去调整神经网络里的海量参数，使得网络的输出更接近数据。比如一个局面100分，训练就会改变神经网络参数让评估分数去接近这个100分。&lt;br /&gt;
&lt;br /&gt;
'''因为是离线学习，依赖作者训练发布，所以你使用引擎是无法让引擎“学习”的(但棋类也完全不适合在线学习)，而引擎有[[置换表（哈希）是什么？设置多少好？|哈希表]]这种暂时的信息储存，所以拆棋时会感觉引擎有记忆功能，但重新加载后便失去了记忆。'''&lt;br /&gt;
&lt;br /&gt;
'''如果有人声称引擎可以边使用边学习，并试图让人掏钱，就有极大概率是骗子。'''&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E6%90%9C%E7%B4%A2%E7%AE%97%E6%B3%95&amp;diff=382</id>
		<title>搜索算法</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E6%90%9C%E7%B4%A2%E7%AE%97%E6%B3%95&amp;diff=382"/>
		<updated>2024-10-02T07:48:04Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 简单Minimax搜索 ==&lt;br /&gt;
[[文件:Minimax.png|缩略图|Minimax搜索]]&lt;br /&gt;
&lt;br /&gt;
象棋属于零和博弈，有红方和黑方，双方的收益总和为0，一方的优势总是伴随着另一方的劣势。此时，我们可以使用Minimax进行搜索。&lt;br /&gt;
&lt;br /&gt;
为了方便理解，我们暂且把Maximizing Player称为“红方”，把Minimizing Player称为“黑方”（实际上则不一定），我们的评估函数暂时返回的是相对于红方的分数（分高说明红方优势，分低说明红方劣势）。&lt;br /&gt;
&lt;br /&gt;
博弈的双方都想让收益最大化，红方想让分数尽量高，黑方想让分数尽量低，这样就能最大程度上保证自己这方的优势，同时让对方占到的优势最小。&lt;br /&gt;
&lt;br /&gt;
举个例子，红方在一个局面有两种走法。一种走法可以吃掉对方的车，此时的分数是+500，另一种走法是送掉自己的车，此时的分数是-500。红方当然会选择对自己有利的走法，即第一种，所以该节点的分数就是+500。&lt;br /&gt;
&lt;br /&gt;
伪代码如下：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
int minimax(int depth, bool isMaximizingPlayer) { // 层数, 是否红方&lt;br /&gt;
    if (depth == 0 || isGameOver()) { // 搜索到叶子节点或游戏结束，直接返回评估&lt;br /&gt;
        return evaluate();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (isMaximizingPlayer) { // 如果当前是红方&lt;br /&gt;
        int maxEval = INT_MIN; // 初始化为负无穷，方便程序找出所有走法中分数的最大值&lt;br /&gt;
        for (Move move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
            makeMove(move); // 走子&lt;br /&gt;
            int eval = minimax(depth - 1, false); // 向下一层搜索，获得该走法对应的子节点的分数&lt;br /&gt;
            undoMove(move); // 恢复到走子前的局面&lt;br /&gt;
            maxEval = std::max(maxEval, eval);&lt;br /&gt;
        }&lt;br /&gt;
        return maxEval;&lt;br /&gt;
    } else { // 如果当前是黑方&lt;br /&gt;
        int minEval = INT_MAX;  // 初始化为正无穷，方便程序找出所有走法中分数的最小值&lt;br /&gt;
        for (Move move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
            makeMove(move); // 走子&lt;br /&gt;
            int eval = minimax(depth - 1, true); // 向下一层搜索，获得该走法对应的子节点的分数&lt;br /&gt;
            undoMove(move); // 恢复到走子前的局面&lt;br /&gt;
            minEval = std::min(minEval, eval); // 取最小值&lt;br /&gt;
        }&lt;br /&gt;
        return minEval;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Alpha-Beta剪枝的过程介绍 ==&lt;br /&gt;
上面实现的简单Minimax搜索，效率其实是很低的。我们这时就要引入一种新的搜索策略——Alpha-Beta剪枝，这种剪枝方法可以在不改变结果的前提下大大减少搜索的节点数。&lt;br /&gt;
&lt;br /&gt;
[[文件:1920px-AB_pruning-with-description.png|缩略图|Alpha-Beta剪枝（带注解）]]&lt;br /&gt;
&lt;br /&gt;
上文我们提到过，红方的目的是使得分尽量高，也就是说当前节点的分数是所有子节点分数的最大值，黑方则相反。&lt;br /&gt;
&lt;br /&gt;
参考右图，我们可以举个例子说明一下Alpha-Beta剪枝的原理。&lt;br /&gt;
&lt;br /&gt;
引擎搜完叶子节点的5和6，可以推断出这两个节点的父节点（图中ID：2）的分数是min(5,6)=5。这个节点的父节点在MAX层上（图中ID：1），是取其子节点分数的最大值，所以它的分数至少是5。子节点的分数只有&amp;gt;5才有搜下去的必要，否则不会比“ID：2”这一路线更优。&lt;br /&gt;
&lt;br /&gt;
接下来我们继续搜索“ID：3”这一路线，搜完叶子节点的“7”和“4”之后，由于“ID：3”处在MIN层，其分数是取其子节点分数的最小值，所以这个节点的分数必然≤4，不可能比“ID：2”这一条路线更优，没有再搜下去的必要了，我们可以把后续的子节点（即灰色的5）剪掉。&lt;br /&gt;
&lt;br /&gt;
其它灰色的部分也是同理，大家可自行按照上面的步骤推断。&lt;br /&gt;
&lt;br /&gt;
根据理论推断是会了，但这仍是纸上谈兵，如何编写相关代码实现该功能呢？且听下回分解。&lt;br /&gt;
&lt;br /&gt;
== Alpha-Beta剪枝的代码实现 ==&lt;br /&gt;
为了用代码实现Alpha-Beta剪枝，我们需要引入alpha和beta两个量，alpha是红方能保证至少获得的分数，beta是黑方能保证至多获得的分数（这里的分数都是从红方视角看的）。也就是说，最优走法的分数必然≥alpha而且≤beta。&lt;br /&gt;
&lt;br /&gt;
在最开始调用“alphaBeta”函数的时候，我们应该把alpha设为负无穷，把beta设为正无穷。&lt;br /&gt;
&lt;br /&gt;
当我们搜索一个节点的时候，我们需要接受从父节点传来的alpha和beta值，接着判断该节点处于MAX层还是MIN层。&lt;br /&gt;
&lt;br /&gt;
这里还是举一个例子方便读者理解，假设我们处于MAX层（即红方），父节点传递的alpha值是5，beta值是8。&lt;br /&gt;
&lt;br /&gt;
因为当前节点的分数是从子节点的分数中取最大值得到的，所以我们在搜索的时候可以保证当前节点的分数≥子节点的分数，应该根据子节点的分数更新alpha的值。如果我们此时搜到一个子节点，分数为10，此时当前节点的分数必定≥10。但我们不要忘了，上一层是MIN，取的是子节点中的最小分数。若当前节点的分数超过了beta，必然不会比其父节点之前搜过的某个子节点分数低，不会被父节点采纳；若当前节点的分数等于beta，必然不会比其父节点之前搜过的某个子节点更优。我们可以直接使用break跳出循环，进行Beta剪枝，同时返回目前搜过的子节点的最大分数（目前的搜索代码中，这里返回值不重要，只需要保证该节点不被父节点采纳即可）。&lt;br /&gt;
&lt;br /&gt;
判断当前节点的分数是否≥beta，可以等同于判断alpha≥beta，因为alpha就是按照子节点的分数通过取最大值进行更新的。&lt;br /&gt;
&lt;br /&gt;
MIN层同理，这里不再赘述。&lt;br /&gt;
&lt;br /&gt;
伪代码如下：&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
int alphaBeta(int depth, int alpha, int beta, bool isMaximizingPlayer) {&lt;br /&gt;
    if (depth == 0 || isGameOver()) {&lt;br /&gt;
        return evaluate();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (isMaximizingPlayer) { // 如果当前是红方&lt;br /&gt;
        int maxEval = INT_MIN; // 初始化为负无穷，方便程序找出所有走法中分数的最大值&lt;br /&gt;
        for (Move move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
            makeMove(move); // 走子&lt;br /&gt;
            int eval = alphaBeta(depth - 1, alpha, beta, false); // 向下一层搜索，获得该走法对应的子节点的分数&lt;br /&gt;
            undoMove(move); // 恢复到走子前的局面&lt;br /&gt;
            maxEval = std::max(maxEval, eval); // 取最大值&lt;br /&gt;
            alpha = std::max(alpha, eval);&lt;br /&gt;
            if (beta &amp;lt;= alpha)&lt;br /&gt;
                break; // Beta剪枝&lt;br /&gt;
        }&lt;br /&gt;
        return maxEval;&lt;br /&gt;
    } else { // 如果当前是黑方&lt;br /&gt;
        int minEval = INT_MAX;  // 初始化为正无穷，方便程序找出所有走法中分数的最小值&lt;br /&gt;
        for (Move move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
            makeMove(move); // 走子&lt;br /&gt;
            int eval = alphaBeta(depth - 1, alpha, beta, true); // 向下一层搜索，获得该走法对应的子节点的分数&lt;br /&gt;
            undoMove(move); // 恢复到走子前的局面&lt;br /&gt;
            minEval = std::min(minEval, eval); // 取最小值&lt;br /&gt;
            beta = std::min(beta, eval);&lt;br /&gt;
            if (beta &amp;lt;= alpha)&lt;br /&gt;
                break; // Alpha剪枝&lt;br /&gt;
        }&lt;br /&gt;
        return minEval;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 继续简化，引入Negamax ==&lt;br /&gt;
前面我们的分数一直是相对于红方视角的，需要将红方和黑方分类讨论，这显然使代码复杂化了。为了解决这个问题，我们需要引入Negamax。&lt;br /&gt;
&lt;br /&gt;
引入Negamax后，我们对双方可以使用同样的处理策略，省去了编写MIN方代码的必要性，但是评估函数需要改成相对于走子方的分数。&lt;br /&gt;
&lt;br /&gt;
如果当前走子方是红方，需要返回相对于红方的分数；如果当前走子方是黑方，需要返回相对于黑方的分数。&lt;br /&gt;
&lt;br /&gt;
由于我们把两种情况合并成了一种情况，判断红黑方的参数可以删去。&lt;br /&gt;
&lt;br /&gt;
在搜索子节点的时候，我们需要从MAX层切换到MIN层，或者从MIN层切换到MAX层，应该如何做呢？&lt;br /&gt;
&lt;br /&gt;
在这里，我们继续举一个例子来说明。假设当前节点从父节点传下来的alpha值为5，beta值为8，说明最优走法的分数一定在5和8之间，接下来我们把分数视角切换到对手，最优走法的分数一定在-8和-5之间，-8和-5就分别是传递给子节点的alpha值和beta值。&lt;br /&gt;
&lt;br /&gt;
所以，传给子节点的alpha和beta值应该是-beta和-alpha。&lt;br /&gt;
&lt;br /&gt;
下面是引入Negamax后的伪代码：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
int alphaBeta(int depth, int alpha, int beta) {&lt;br /&gt;
    if (depth == 0 || isGameOver()) {&lt;br /&gt;
        return evaluate();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    int maxEval = INT_MIN; // 初始化为负无穷，方便程序找出所有走法中分数的最大值&lt;br /&gt;
    for (Move move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
        makeMove(move); // 走子&lt;br /&gt;
        int eval = -alphaBeta(depth - 1, -beta, -alpha); // 向下一层搜索，获得该走法对应的子节点的分数&lt;br /&gt;
        undoMove(move); // 恢复到走子前的局面&lt;br /&gt;
        maxEval = std::max(maxEval, eval);&lt;br /&gt;
        alpha = std::max(alpha, eval);&lt;br /&gt;
        if (alpha &amp;gt;= beta)&lt;br /&gt;
            break; // Beta剪枝&lt;br /&gt;
    }&lt;br /&gt;
    return maxEval;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
有些资料上的代码可能是这样的：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
int alphaBeta(int depth, int alpha, int beta) {&lt;br /&gt;
    if (depth == 0 || isGameOver()) {&lt;br /&gt;
        return evaluate();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    for (Move move : getPossibleMoves()) {&lt;br /&gt;
        makeMove(move);&lt;br /&gt;
        int val = -alphaBeta(depth - 1, -beta, -alpha);&lt;br /&gt;
        undoMove(move);&lt;br /&gt;
&lt;br /&gt;
        if (val &amp;gt;= beta) {&lt;br /&gt;
            return beta; // Beta剪枝&lt;br /&gt;
        }&lt;br /&gt;
        if (val &amp;gt; alpha) {&lt;br /&gt;
            alpha = val; // 更新Alpha的值&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    return alpha;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
其实这两者是等价的，正常情况下，alpha&amp;lt;beta，所以alpha≥beta的情况只能是分数更新，导致alpha变大，发生Beta剪枝，所以直接和val进行比较也是一样的。&lt;br /&gt;
&lt;br /&gt;
== 空步裁剪（Null Move Pruning）==&lt;br /&gt;
一般来说，一个局面我方什么都不走（或者说走了一个“空步”），而是把走子权让给对方，我方是吃亏的。即最优走法的分数一般来说不会比走“空步”的分数低。&lt;br /&gt;
&lt;br /&gt;
我们可以利用这一点编写一个剪枝策略——空步裁剪。空步裁剪是一种冒险策略，就是基于上面假设进行的，把比“空步”分数低的招法剪掉。&lt;br /&gt;
&lt;br /&gt;
为了加快搜索，我们可以对“空步”搜索的层数额外减少R（一般是2-3）层。由于我们只关心“空步”的分数和beta相比是大还是小，所以应该用零窗口搜索（[beta - 1, beta]），使Alpha-Beta剪枝在搜索空步时发挥的作用最大。&lt;br /&gt;
&lt;br /&gt;
由于走子方互换，传递给子节点的alpha值和beta值也要按照之前的方法进行转换，也就是-beta和-beta + 1。&lt;br /&gt;
&lt;br /&gt;
基于空步的分数≤最优走法的分数这个假设，如果返回的评估≤beta-1，说明后续该节点的alpha值有可能达不到beta，不进行剪枝；如果返回的评估≥beta，说明最优走法的分数也一定≥beta，也就是说后续alpha在更新的时候也会≥beta，可以直接进行剪枝。&lt;br /&gt;
&lt;br /&gt;
然而一些局面并不满足之前我们提到过的假设，如国际象棋的迫移局面（Zguzwang），若使用空步裁剪，则会使引擎判断错误，于是我们可以设计一个“isNullMoveAllowed”函数判断是否满足空步裁剪的条件，尽量规避这种现象。&lt;br /&gt;
&lt;br /&gt;
伪代码如下：&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
// R是空步裁剪搜索中额外减去的层数&lt;br /&gt;
const int R = 3;&lt;br /&gt;
&lt;br /&gt;
int alphaBeta(int depth, int alpha, int beta) {&lt;br /&gt;
    if (depth == 0 || isGameOver()) {&lt;br /&gt;
        return evaluate();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Null Move Pruning&lt;br /&gt;
    if (depth &amp;gt;= R + 1 &amp;amp;&amp;amp; isNullMoveAllowed()) {&lt;br /&gt;
        makeNullMove(); // 走一步“空步”&lt;br /&gt;
        int nullMoveEval = -alphaBeta(depth - R - 1, -beta, -beta + 1); // 用[beta - 1, beta]进行搜索&lt;br /&gt;
        undoNullMove(); // 恢复&lt;br /&gt;
&lt;br /&gt;
        if (nullMoveEval &amp;gt;= beta) {&lt;br /&gt;
            return beta; // 如果beta的值还不如走“空步”的分数高，就直接剪掉&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    int value = INT_MIN;&lt;br /&gt;
    for (Move move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
        makeMove(move); // 走棋&lt;br /&gt;
        value = std::max(value, -alphaBeta(depth - 1, -beta, -alpha));&lt;br /&gt;
        undoMove(move); // 恢复&lt;br /&gt;
        alpha = std::max(alpha, value);&lt;br /&gt;
        if (alpha &amp;gt;= beta) {&lt;br /&gt;
            break; // Beta剪枝&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    return value;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== PVS（主要变例搜索） ==&lt;br /&gt;
PVS是对Alpha-Beta剪枝的增强，可以提高Alpha-Beta剪枝的效率。简单来说，PVS先对第一个子节点进行[alpha, beta]的全窗口搜索，再对剩余子节点使用[alpha, alpha+1]的零窗口搜索。零窗口搜索结束后，如果该节点搜索返回的评估在(alpha, beta)之间，说明该节点有希望，于是继续进行一次全窗口搜索。如果返回的评估超出了(alpha, beta)，说明这个节点必然会被剪枝。&lt;br /&gt;
&lt;br /&gt;
伪代码如下：&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
int alphaBeta(int depth, int alpha, int beta) {&lt;br /&gt;
    if (depth == 0 || isGameOver()) {&lt;br /&gt;
        return evaluate();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    int maxEval = INT_MIN; // 初始化为负无穷，方便程序找出所有走法中分数的最大值&lt;br /&gt;
    bool firstChild = true; // 标记是否为第一个子节点&lt;br /&gt;
&lt;br /&gt;
    for (const Move&amp;amp; move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
        makeMove(move); // 走子&lt;br /&gt;
        int eval;&lt;br /&gt;
&lt;br /&gt;
        if (firstChild) {&lt;br /&gt;
            // 对第一个子节点使用全窗口搜索&lt;br /&gt;
            eval = -alphaBeta(depth - 1, -beta, -alpha);&lt;br /&gt;
            firstChild = false;&lt;br /&gt;
        } else {&lt;br /&gt;
            // 对剩余子节点使用零窗口搜索&lt;br /&gt;
            eval = -alphaBeta(depth - 1, -alpha - 1, -alpha);&lt;br /&gt;
            &lt;br /&gt;
            if (eval &amp;gt; alpha &amp;amp;&amp;amp; eval &amp;lt; beta) {&lt;br /&gt;
                // 如果零窗口搜索未能确定性地剪枝，则进行全窗口重新搜索&lt;br /&gt;
                eval = -alphaBeta(depth - 1, -beta, -eval);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        undoMove(move); // 恢复到走子前的局面&lt;br /&gt;
        maxEval = std::max(maxEval, eval);&lt;br /&gt;
        alpha = std::max(alpha, eval);&lt;br /&gt;
        &lt;br /&gt;
        if (alpha &amp;gt;= beta) {&lt;br /&gt;
            break; // Beta 剪枝&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    return maxEval;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
注：为了简化代码，这里并未加入空步裁剪，后文也是单独介绍某种方法，而不是叠加在一起。&lt;br /&gt;
&lt;br /&gt;
== 静态搜索 ==&lt;br /&gt;
静态搜索（Quiescent Search）可以用来克服水平面效应。一般来说发生互相吃子，评估函数会发生剧烈变化，引入静态搜索，可以返回一个更加稳定的评估值。引入静态评估后，达到叶子节点时，我们不直接返回评估函数的值，而是进行一次静态搜索，继续探索吃子的走法。&lt;br /&gt;
&lt;br /&gt;
伪代码如下：&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
int qsearch(int alpha, int beta) {&lt;br /&gt;
    int standPat = evaluate(); // 当前局面不做任何动作的评估值&lt;br /&gt;
&lt;br /&gt;
    if (standPat &amp;gt;= beta) {&lt;br /&gt;
        return beta; // 剪枝&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (alpha &amp;lt; standPat) {&lt;br /&gt;
        alpha = standPat; // 更新 alpha&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    for (Move move : getPossibleCaptures()) { // 只考虑吃子的走法&lt;br /&gt;
        makeMove(move);&lt;br /&gt;
        int eval = -qsearch(-beta, -alpha); // 静态搜索下一层&lt;br /&gt;
        undoMove(move);&lt;br /&gt;
&lt;br /&gt;
        if (eval &amp;gt;= beta) {&lt;br /&gt;
            return beta; // 剪枝&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (eval &amp;gt; alpha) {&lt;br /&gt;
            alpha = eval; // 更新 alpha&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return alpha;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int alphaBeta(int depth, int alpha, int beta) {&lt;br /&gt;
    if (depth == 0 || isGameOver()) {&lt;br /&gt;
        return qsearch(alpha, beta); // 在深度为0或游戏结束时进入静态搜索&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    int maxEval = INT_MIN; // 初始化为负无穷&lt;br /&gt;
    for (Move move : getPossibleMoves()) { // 生成所有走法&lt;br /&gt;
        makeMove(move); // 执行走法&lt;br /&gt;
        int eval = -alphaBeta(depth - 1, -beta, -alpha); // 递归搜索&lt;br /&gt;
        undoMove(move); // 撤销走法&lt;br /&gt;
&lt;br /&gt;
        maxEval = std::max(maxEval, eval);&lt;br /&gt;
        alpha = std::max(alpha, eval);&lt;br /&gt;
        if (alpha &amp;gt;= beta) {&lt;br /&gt;
            break; // Beta剪枝&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return maxEval;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E6%90%9C%E7%B4%A2%E7%AE%97%E6%B3%95&amp;diff=381</id>
		<title>搜索算法</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E6%90%9C%E7%B4%A2%E7%AE%97%E6%B3%95&amp;diff=381"/>
		<updated>2024-10-02T07:30:36Z</updated>

		<summary type="html">&lt;p&gt;New：​/* PVS（主要变例搜索） */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 简单Minimax搜索 ==&lt;br /&gt;
[[文件:Minimax.png|缩略图|Minimax搜索]]&lt;br /&gt;
&lt;br /&gt;
象棋属于零和博弈，有红方和黑方，双方的收益总和为0，一方的优势总是伴随着另一方的劣势。此时，我们可以使用Minimax进行搜索。&lt;br /&gt;
&lt;br /&gt;
为了方便理解，我们暂且把Maximizing Player称为“红方”，把Minimizing Player称为“黑方”（实际上则不一定），我们的评估函数暂时返回的是相对于红方的分数（分高说明红方优势，分低说明红方劣势）。&lt;br /&gt;
&lt;br /&gt;
博弈的双方都想让收益最大化，红方想让分数尽量高，黑方想让分数尽量低，这样就能最大程度上保证自己这方的优势，同时让对方占到的优势最小。&lt;br /&gt;
&lt;br /&gt;
举个例子，红方在一个局面有两种走法。一种走法可以吃掉对方的车，此时的分数是+500，另一种走法是送掉自己的车，此时的分数是-500。红方当然会选择对自己有利的走法，即第一种，所以该节点的分数就是+500。&lt;br /&gt;
&lt;br /&gt;
伪代码如下：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
int minimax(int depth, bool isMaximizingPlayer) { // 层数, 是否红方&lt;br /&gt;
    if (depth == 0 || isGameOver()) { // 搜索到叶子节点或游戏结束，直接返回评估&lt;br /&gt;
        return evaluate();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (isMaximizingPlayer) { // 如果当前是红方&lt;br /&gt;
        int maxEval = INT_MIN; // 初始化为负无穷，方便程序找出所有走法中分数的最大值&lt;br /&gt;
        for (Move move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
            makeMove(move); // 走子&lt;br /&gt;
            int eval = minimax(depth - 1, false); // 向下一层搜索，获得该走法对应的子节点的分数&lt;br /&gt;
            undoMove(move); // 恢复到走子前的局面&lt;br /&gt;
            maxEval = std::max(maxEval, eval);&lt;br /&gt;
        }&lt;br /&gt;
        return maxEval;&lt;br /&gt;
    } else { // 如果当前是黑方&lt;br /&gt;
        int minEval = INT_MAX;  // 初始化为正无穷，方便程序找出所有走法中分数的最小值&lt;br /&gt;
        for (Move move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
            makeMove(move); // 走子&lt;br /&gt;
            int eval = minimax(depth - 1, true); // 向下一层搜索，获得该走法对应的子节点的分数&lt;br /&gt;
            undoMove(move); // 恢复到走子前的局面&lt;br /&gt;
            minEval = std::min(minEval, eval); // 取最小值&lt;br /&gt;
        }&lt;br /&gt;
        return minEval;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Alpha-Beta剪枝的过程介绍 ==&lt;br /&gt;
上面实现的简单Minimax搜索，效率其实是很低的。我们这时就要引入一种新的搜索策略——Alpha-Beta剪枝，这种剪枝方法可以在不改变结果的前提下大大减少搜索的节点数。&lt;br /&gt;
&lt;br /&gt;
[[文件:1920px-AB_pruning-with-description.png|缩略图|Alpha-Beta剪枝（带注解）]]&lt;br /&gt;
&lt;br /&gt;
上文我们提到过，红方的目的是使得分尽量高，也就是说当前节点的分数是所有子节点分数的最大值，黑方则相反。&lt;br /&gt;
&lt;br /&gt;
参考右图，我们可以举个例子说明一下Alpha-Beta剪枝的原理。&lt;br /&gt;
&lt;br /&gt;
引擎搜完叶子节点的5和6，可以推断出这两个节点的父节点（图中ID：2）的分数是min(5,6)=5。这个节点的父节点在MAX层上（图中ID：1），是取其子节点分数的最大值，所以它的分数至少是5。子节点的分数只有&amp;gt;5才有搜下去的必要，否则不会比“ID：2”这一路线更优。&lt;br /&gt;
&lt;br /&gt;
接下来我们继续搜索“ID：3”这一路线，搜完叶子节点的“7”和“4”之后，由于“ID：3”处在MIN层，其分数是取其子节点分数的最小值，所以这个节点的分数必然≤4，不可能比“ID：2”这一条路线更优，没有再搜下去的必要了，我们可以把后续的子节点（即灰色的5）剪掉。&lt;br /&gt;
&lt;br /&gt;
其它灰色的部分也是同理，大家可自行按照上面的步骤推断。&lt;br /&gt;
&lt;br /&gt;
根据理论推断是会了，但这仍是纸上谈兵，如何编写相关代码实现该功能呢？且听下回分解。&lt;br /&gt;
&lt;br /&gt;
== Alpha-Beta剪枝的代码实现 ==&lt;br /&gt;
为了用代码实现Alpha-Beta剪枝，我们需要引入alpha和beta两个量，alpha是红方能保证至少获得的分数，beta是黑方能保证至多获得的分数（这里的分数都是从红方视角看的）。也就是说，最优走法的分数必然≥alpha而且≤beta。&lt;br /&gt;
&lt;br /&gt;
在最开始调用“alphaBeta”函数的时候，我们应该把alpha设为负无穷，把beta设为正无穷。&lt;br /&gt;
&lt;br /&gt;
当我们搜索一个节点的时候，我们需要接受从父节点传来的alpha和beta值，接着判断该节点处于MAX层还是MIN层。&lt;br /&gt;
&lt;br /&gt;
这里还是举一个例子方便读者理解，假设我们处于MAX层（即红方），父节点传递的alpha值是5，beta值是8。&lt;br /&gt;
&lt;br /&gt;
因为当前节点的分数是从子节点的分数中取最大值得到的，所以我们在搜索的时候可以保证当前节点的分数≥子节点的分数，应该根据子节点的分数更新alpha的值。如果我们此时搜到一个子节点，分数为10，此时当前节点的分数必定≥10。但我们不要忘了，上一层是MIN，取的是子节点中的最小分数。若当前节点的分数超过了beta，必然不会比其父节点之前搜过的某个子节点分数低，不会被父节点采纳；若当前节点的分数等于beta，必然不会比其父节点之前搜过的某个子节点更优。我们可以直接使用break跳出循环，进行Beta剪枝，同时返回目前搜过的子节点的最大分数（目前的搜索代码中，这里返回值不重要，只需要保证该节点不被父节点采纳即可）。&lt;br /&gt;
&lt;br /&gt;
判断当前节点的分数是否≥beta，可以等同于判断alpha≥beta，因为alpha就是按照子节点的分数通过取最大值进行更新的。&lt;br /&gt;
&lt;br /&gt;
MIN层同理，这里不再赘述。&lt;br /&gt;
&lt;br /&gt;
伪代码如下：&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
int alphaBeta(int depth, int alpha, int beta, bool isMaximizingPlayer) {&lt;br /&gt;
    if (depth == 0 || isGameOver()) {&lt;br /&gt;
        return evaluate();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (isMaximizingPlayer) { // 如果当前是红方&lt;br /&gt;
        int maxEval = INT_MIN; // 初始化为负无穷，方便程序找出所有走法中分数的最大值&lt;br /&gt;
        for (Move move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
            makeMove(move); // 走子&lt;br /&gt;
            int eval = alphaBeta(depth - 1, alpha, beta, false); // 向下一层搜索，获得该走法对应的子节点的分数&lt;br /&gt;
            undoMove(move); // 恢复到走子前的局面&lt;br /&gt;
            maxEval = std::max(maxEval, eval); // 取最大值&lt;br /&gt;
            alpha = std::max(alpha, eval);&lt;br /&gt;
            if (beta &amp;lt;= alpha)&lt;br /&gt;
                break; // Beta剪枝&lt;br /&gt;
        }&lt;br /&gt;
        return maxEval;&lt;br /&gt;
    } else { // 如果当前是黑方&lt;br /&gt;
        int minEval = INT_MAX;  // 初始化为正无穷，方便程序找出所有走法中分数的最小值&lt;br /&gt;
        for (Move move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
            makeMove(move); // 走子&lt;br /&gt;
            int eval = alphaBeta(depth - 1, alpha, beta, true); // 向下一层搜索，获得该走法对应的子节点的分数&lt;br /&gt;
            undoMove(move); // 恢复到走子前的局面&lt;br /&gt;
            minEval = std::min(minEval, eval); // 取最小值&lt;br /&gt;
            beta = std::min(beta, eval);&lt;br /&gt;
            if (beta &amp;lt;= alpha)&lt;br /&gt;
                break; // Alpha剪枝&lt;br /&gt;
        }&lt;br /&gt;
        return minEval;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 继续简化，引入Negamax ==&lt;br /&gt;
前面我们的分数一直是相对于红方视角的，需要将红方和黑方分类讨论，这显然使代码复杂化了。为了解决这个问题，我们需要引入Negamax。&lt;br /&gt;
&lt;br /&gt;
引入Negamax后，我们对双方可以使用同样的处理策略，省去了编写MIN方代码的必要性，但是评估函数需要改成相对于走子方的分数。&lt;br /&gt;
&lt;br /&gt;
如果当前走子方是红方，需要返回相对于红方的分数；如果当前走子方是黑方，需要返回相对于黑方的分数。&lt;br /&gt;
&lt;br /&gt;
由于我们把两种情况合并成了一种情况，判断红黑方的参数可以删去。&lt;br /&gt;
&lt;br /&gt;
在搜索子节点的时候，我们需要从MAX层切换到MIN层，或者从MIN层切换到MAX层，应该如何做呢？&lt;br /&gt;
&lt;br /&gt;
在这里，我们继续举一个例子来说明。假设当前节点从父节点传下来的alpha值为5，beta值为8，说明最优走法的分数一定在5和8之间，接下来我们把分数视角切换到对手，最优走法的分数一定在-8和-5之间，-8和-5就分别是传递给子节点的alpha值和beta值。&lt;br /&gt;
&lt;br /&gt;
所以，传给子节点的alpha和beta值应该是-beta和-alpha。&lt;br /&gt;
&lt;br /&gt;
下面是引入Negamax后的伪代码：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
int alphaBeta(int depth, int alpha, int beta) {&lt;br /&gt;
    if (depth == 0 || isGameOver()) {&lt;br /&gt;
        return evaluate();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    int maxEval = INT_MIN; // 初始化为负无穷，方便程序找出所有走法中分数的最大值&lt;br /&gt;
    for (Move move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
        makeMove(move); // 走子&lt;br /&gt;
        int eval = -alphaBeta(depth - 1, -beta, -alpha); // 向下一层搜索，获得该走法对应的子节点的分数&lt;br /&gt;
        undoMove(move); // 恢复到走子前的局面&lt;br /&gt;
        maxEval = std::max(maxEval, eval);&lt;br /&gt;
        alpha = std::max(alpha, eval);&lt;br /&gt;
        if (alpha &amp;gt;= beta)&lt;br /&gt;
            break; // Beta剪枝&lt;br /&gt;
    }&lt;br /&gt;
    return maxEval;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
有些资料上的代码可能是这样的：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
int alphaBeta(int depth, int alpha, int beta) {&lt;br /&gt;
    if (depth == 0 || isGameOver()) {&lt;br /&gt;
        return evaluate();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    for (Move move : getPossibleMoves()) {&lt;br /&gt;
        makeMove(move);&lt;br /&gt;
        int val = -alphaBeta(depth - 1, -beta, -alpha);&lt;br /&gt;
        undoMove(move);&lt;br /&gt;
&lt;br /&gt;
        if (val &amp;gt;= beta) {&lt;br /&gt;
            return beta; // Beta剪枝&lt;br /&gt;
        }&lt;br /&gt;
        if (val &amp;gt; alpha) {&lt;br /&gt;
            alpha = val; // 更新Alpha的值&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    return alpha;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
其实这两者是等价的，正常情况下，alpha&amp;lt;beta，所以alpha≥beta的情况只能是分数更新，导致alpha变大，发生Beta剪枝，所以直接和val进行比较也是一样的。&lt;br /&gt;
&lt;br /&gt;
== 空步裁剪（Null Move Pruning）==&lt;br /&gt;
一般来说，一个局面我方什么都不走（或者说走了一个“空步”），而是把走子权让给对方，我方是吃亏的。即最优走法的分数一般来说不会比走“空步”的分数低。&lt;br /&gt;
&lt;br /&gt;
我们可以利用这一点编写一个剪枝策略——空步裁剪。空步裁剪是一种冒险策略，就是基于上面假设进行的，把比“空步”分数低的招法剪掉。&lt;br /&gt;
&lt;br /&gt;
为了加快搜索，我们可以对“空步”搜索的层数额外减少R（一般是2-3）层。由于我们只关心“空步”的分数和beta相比是大还是小，所以应该用零窗口搜索（[beta - 1, beta]），使Alpha-Beta剪枝在搜索空步时发挥的作用最大。&lt;br /&gt;
&lt;br /&gt;
由于走子方互换，传递给子节点的alpha值和beta值也要按照之前的方法进行转换，也就是-beta和-beta + 1。&lt;br /&gt;
&lt;br /&gt;
基于空步的分数≤最优走法的分数这个假设，如果返回的评估≤beta-1，说明后续该节点的alpha值有可能达不到beta，不进行剪枝；如果返回的评估≥beta，说明最优走法的分数也一定≥beta，也就是说后续alpha在更新的时候也会≥beta，可以直接进行剪枝。&lt;br /&gt;
&lt;br /&gt;
然而一些局面并不满足之前我们提到过的假设，如国际象棋的迫移局面（Zguzwang），若使用空步裁剪，则会使引擎判断错误，于是我们需要使用“isNullMoveAllowed”函数判断是否满足空步裁剪的条件，尽量规避这种现象。&lt;br /&gt;
&lt;br /&gt;
伪代码如下：&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
// R是空步裁剪搜索中额外减去的层数&lt;br /&gt;
const int R = 3;&lt;br /&gt;
&lt;br /&gt;
int alphaBeta(int depth, int alpha, int beta) {&lt;br /&gt;
    if (depth == 0 || isGameOver()) {&lt;br /&gt;
        return evaluate();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Null Move Pruning&lt;br /&gt;
    if (depth &amp;gt;= R + 1 &amp;amp;&amp;amp; isNullMoveAllowed()) {&lt;br /&gt;
        makeNullMove(); // 走一步“空步”&lt;br /&gt;
        int nullMoveEval = -alphaBeta(depth - R - 1, -beta, -beta + 1); // 用[beta - 1, beta]进行搜索&lt;br /&gt;
        undoNullMove(); // 恢复&lt;br /&gt;
&lt;br /&gt;
        if (nullMoveEval &amp;gt;= beta) {&lt;br /&gt;
            return beta; // 如果beta的值还不如走“空步”的分数高，就直接剪掉&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    int value = INT_MIN;&lt;br /&gt;
    for (Move move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
        makeMove(move); // 走棋&lt;br /&gt;
        value = std::max(value, -alphaBeta(depth - 1, -beta, -alpha));&lt;br /&gt;
        undoMove(move); // 恢复&lt;br /&gt;
        alpha = std::max(alpha, value);&lt;br /&gt;
        if (alpha &amp;gt;= beta) {&lt;br /&gt;
            break; // Beta剪枝&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    return value;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== PVS（主要变例搜索） ==&lt;br /&gt;
PVS是对Alpha-Beta剪枝的增强，可以提高Alpha-Beta剪枝的效率。简单来说，PVS先对第一个子节点进行[alpha, beta]的全窗口搜索，再对剩余子节点使用[alpha, alpha+1]的零窗口搜索。零窗口搜索结束后，如果该节点搜索返回的评估在(alpha, beta)之间，说明该节点有希望，于是继续进行一次全窗口搜索。如果返回的评估超出了(alpha, beta)，说明这个节点必然会被剪枝。&lt;br /&gt;
&lt;br /&gt;
伪代码如下：&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
int alphaBeta(int depth, int alpha, int beta) {&lt;br /&gt;
    if (depth == 0 || isGameOver()) {&lt;br /&gt;
        return evaluate();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    int maxEval = INT_MIN; // 初始化为负无穷，方便程序找出所有走法中分数的最大值&lt;br /&gt;
    bool firstChild = true; // 标记是否为第一个子节点&lt;br /&gt;
&lt;br /&gt;
    for (const Move&amp;amp; move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
        makeMove(move); // 走子&lt;br /&gt;
        int eval;&lt;br /&gt;
&lt;br /&gt;
        if (firstChild) {&lt;br /&gt;
            // 对第一个子节点使用全窗口搜索&lt;br /&gt;
            eval = -alphaBeta(depth - 1, -beta, -alpha);&lt;br /&gt;
            firstChild = false;&lt;br /&gt;
        } else {&lt;br /&gt;
            // 对剩余子节点使用零窗口搜索&lt;br /&gt;
            eval = -alphaBeta(depth - 1, -alpha - 1, -alpha);&lt;br /&gt;
            &lt;br /&gt;
            if (eval &amp;gt; alpha &amp;amp;&amp;amp; eval &amp;lt; beta) {&lt;br /&gt;
                // 如果零窗口搜索未能确定性地剪枝，则进行全窗口重新搜索&lt;br /&gt;
                eval = -alphaBeta(depth - 1, -beta, -eval);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        undoMove(move); // 恢复到走子前的局面&lt;br /&gt;
        maxEval = std::max(maxEval, eval);&lt;br /&gt;
        alpha = std::max(alpha, eval);&lt;br /&gt;
        &lt;br /&gt;
        if (alpha &amp;gt;= beta) {&lt;br /&gt;
            break; // Beta 剪枝&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    return maxEval;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E6%90%9C%E7%B4%A2%E7%AE%97%E6%B3%95&amp;diff=380</id>
		<title>搜索算法</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E6%90%9C%E7%B4%A2%E7%AE%97%E6%B3%95&amp;diff=380"/>
		<updated>2024-10-02T07:28:22Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 简单Minimax搜索 ==&lt;br /&gt;
[[文件:Minimax.png|缩略图|Minimax搜索]]&lt;br /&gt;
&lt;br /&gt;
象棋属于零和博弈，有红方和黑方，双方的收益总和为0，一方的优势总是伴随着另一方的劣势。此时，我们可以使用Minimax进行搜索。&lt;br /&gt;
&lt;br /&gt;
为了方便理解，我们暂且把Maximizing Player称为“红方”，把Minimizing Player称为“黑方”（实际上则不一定），我们的评估函数暂时返回的是相对于红方的分数（分高说明红方优势，分低说明红方劣势）。&lt;br /&gt;
&lt;br /&gt;
博弈的双方都想让收益最大化，红方想让分数尽量高，黑方想让分数尽量低，这样就能最大程度上保证自己这方的优势，同时让对方占到的优势最小。&lt;br /&gt;
&lt;br /&gt;
举个例子，红方在一个局面有两种走法。一种走法可以吃掉对方的车，此时的分数是+500，另一种走法是送掉自己的车，此时的分数是-500。红方当然会选择对自己有利的走法，即第一种，所以该节点的分数就是+500。&lt;br /&gt;
&lt;br /&gt;
伪代码如下：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
int minimax(int depth, bool isMaximizingPlayer) { // 层数, 是否红方&lt;br /&gt;
    if (depth == 0 || isGameOver()) { // 搜索到叶子节点或游戏结束，直接返回评估&lt;br /&gt;
        return evaluate();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (isMaximizingPlayer) { // 如果当前是红方&lt;br /&gt;
        int maxEval = INT_MIN; // 初始化为负无穷，方便程序找出所有走法中分数的最大值&lt;br /&gt;
        for (Move move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
            makeMove(move); // 走子&lt;br /&gt;
            int eval = minimax(depth - 1, false); // 向下一层搜索，获得该走法对应的子节点的分数&lt;br /&gt;
            undoMove(move); // 恢复到走子前的局面&lt;br /&gt;
            maxEval = std::max(maxEval, eval);&lt;br /&gt;
        }&lt;br /&gt;
        return maxEval;&lt;br /&gt;
    } else { // 如果当前是黑方&lt;br /&gt;
        int minEval = INT_MAX;  // 初始化为正无穷，方便程序找出所有走法中分数的最小值&lt;br /&gt;
        for (Move move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
            makeMove(move); // 走子&lt;br /&gt;
            int eval = minimax(depth - 1, true); // 向下一层搜索，获得该走法对应的子节点的分数&lt;br /&gt;
            undoMove(move); // 恢复到走子前的局面&lt;br /&gt;
            minEval = std::min(minEval, eval); // 取最小值&lt;br /&gt;
        }&lt;br /&gt;
        return minEval;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Alpha-Beta剪枝的过程介绍 ==&lt;br /&gt;
上面实现的简单Minimax搜索，效率其实是很低的。我们这时就要引入一种新的搜索策略——Alpha-Beta剪枝，这种剪枝方法可以在不改变结果的前提下大大减少搜索的节点数。&lt;br /&gt;
&lt;br /&gt;
[[文件:1920px-AB_pruning-with-description.png|缩略图|Alpha-Beta剪枝（带注解）]]&lt;br /&gt;
&lt;br /&gt;
上文我们提到过，红方的目的是使得分尽量高，也就是说当前节点的分数是所有子节点分数的最大值，黑方则相反。&lt;br /&gt;
&lt;br /&gt;
参考右图，我们可以举个例子说明一下Alpha-Beta剪枝的原理。&lt;br /&gt;
&lt;br /&gt;
引擎搜完叶子节点的5和6，可以推断出这两个节点的父节点（图中ID：2）的分数是min(5,6)=5。这个节点的父节点在MAX层上（图中ID：1），是取其子节点分数的最大值，所以它的分数至少是5。子节点的分数只有&amp;gt;5才有搜下去的必要，否则不会比“ID：2”这一路线更优。&lt;br /&gt;
&lt;br /&gt;
接下来我们继续搜索“ID：3”这一路线，搜完叶子节点的“7”和“4”之后，由于“ID：3”处在MIN层，其分数是取其子节点分数的最小值，所以这个节点的分数必然≤4，不可能比“ID：2”这一条路线更优，没有再搜下去的必要了，我们可以把后续的子节点（即灰色的5）剪掉。&lt;br /&gt;
&lt;br /&gt;
其它灰色的部分也是同理，大家可自行按照上面的步骤推断。&lt;br /&gt;
&lt;br /&gt;
根据理论推断是会了，但这仍是纸上谈兵，如何编写相关代码实现该功能呢？且听下回分解。&lt;br /&gt;
&lt;br /&gt;
== Alpha-Beta剪枝的代码实现 ==&lt;br /&gt;
为了用代码实现Alpha-Beta剪枝，我们需要引入alpha和beta两个量，alpha是红方能保证至少获得的分数，beta是黑方能保证至多获得的分数（这里的分数都是从红方视角看的）。也就是说，最优走法的分数必然≥alpha而且≤beta。&lt;br /&gt;
&lt;br /&gt;
在最开始调用“alphaBeta”函数的时候，我们应该把alpha设为负无穷，把beta设为正无穷。&lt;br /&gt;
&lt;br /&gt;
当我们搜索一个节点的时候，我们需要接受从父节点传来的alpha和beta值，接着判断该节点处于MAX层还是MIN层。&lt;br /&gt;
&lt;br /&gt;
这里还是举一个例子方便读者理解，假设我们处于MAX层（即红方），父节点传递的alpha值是5，beta值是8。&lt;br /&gt;
&lt;br /&gt;
因为当前节点的分数是从子节点的分数中取最大值得到的，所以我们在搜索的时候可以保证当前节点的分数≥子节点的分数，应该根据子节点的分数更新alpha的值。如果我们此时搜到一个子节点，分数为10，此时当前节点的分数必定≥10。但我们不要忘了，上一层是MIN，取的是子节点中的最小分数。若当前节点的分数超过了beta，必然不会比其父节点之前搜过的某个子节点分数低，不会被父节点采纳；若当前节点的分数等于beta，必然不会比其父节点之前搜过的某个子节点更优。我们可以直接使用break跳出循环，进行Beta剪枝，同时返回目前搜过的子节点的最大分数（目前的搜索代码中，这里返回值不重要，只需要保证该节点不被父节点采纳即可）。&lt;br /&gt;
&lt;br /&gt;
判断当前节点的分数是否≥beta，可以等同于判断alpha≥beta，因为alpha就是按照子节点的分数通过取最大值进行更新的。&lt;br /&gt;
&lt;br /&gt;
MIN层同理，这里不再赘述。&lt;br /&gt;
&lt;br /&gt;
伪代码如下：&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
int alphaBeta(int depth, int alpha, int beta, bool isMaximizingPlayer) {&lt;br /&gt;
    if (depth == 0 || isGameOver()) {&lt;br /&gt;
        return evaluate();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (isMaximizingPlayer) { // 如果当前是红方&lt;br /&gt;
        int maxEval = INT_MIN; // 初始化为负无穷，方便程序找出所有走法中分数的最大值&lt;br /&gt;
        for (Move move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
            makeMove(move); // 走子&lt;br /&gt;
            int eval = alphaBeta(depth - 1, alpha, beta, false); // 向下一层搜索，获得该走法对应的子节点的分数&lt;br /&gt;
            undoMove(move); // 恢复到走子前的局面&lt;br /&gt;
            maxEval = std::max(maxEval, eval); // 取最大值&lt;br /&gt;
            alpha = std::max(alpha, eval);&lt;br /&gt;
            if (beta &amp;lt;= alpha)&lt;br /&gt;
                break; // Beta剪枝&lt;br /&gt;
        }&lt;br /&gt;
        return maxEval;&lt;br /&gt;
    } else { // 如果当前是黑方&lt;br /&gt;
        int minEval = INT_MAX;  // 初始化为正无穷，方便程序找出所有走法中分数的最小值&lt;br /&gt;
        for (Move move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
            makeMove(move); // 走子&lt;br /&gt;
            int eval = alphaBeta(depth - 1, alpha, beta, true); // 向下一层搜索，获得该走法对应的子节点的分数&lt;br /&gt;
            undoMove(move); // 恢复到走子前的局面&lt;br /&gt;
            minEval = std::min(minEval, eval); // 取最小值&lt;br /&gt;
            beta = std::min(beta, eval);&lt;br /&gt;
            if (beta &amp;lt;= alpha)&lt;br /&gt;
                break; // Alpha剪枝&lt;br /&gt;
        }&lt;br /&gt;
        return minEval;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 继续简化，引入Negamax ==&lt;br /&gt;
前面我们的分数一直是相对于红方视角的，需要将红方和黑方分类讨论，这显然使代码复杂化了。为了解决这个问题，我们需要引入Negamax。&lt;br /&gt;
&lt;br /&gt;
引入Negamax后，我们对双方可以使用同样的处理策略，省去了编写MIN方代码的必要性，但是评估函数需要改成相对于走子方的分数。&lt;br /&gt;
&lt;br /&gt;
如果当前走子方是红方，需要返回相对于红方的分数；如果当前走子方是黑方，需要返回相对于黑方的分数。&lt;br /&gt;
&lt;br /&gt;
由于我们把两种情况合并成了一种情况，判断红黑方的参数可以删去。&lt;br /&gt;
&lt;br /&gt;
在搜索子节点的时候，我们需要从MAX层切换到MIN层，或者从MIN层切换到MAX层，应该如何做呢？&lt;br /&gt;
&lt;br /&gt;
在这里，我们继续举一个例子来说明。假设当前节点从父节点传下来的alpha值为5，beta值为8，说明最优走法的分数一定在5和8之间，接下来我们把分数视角切换到对手，最优走法的分数一定在-8和-5之间，-8和-5就分别是传递给子节点的alpha值和beta值。&lt;br /&gt;
&lt;br /&gt;
所以，传给子节点的alpha和beta值应该是-beta和-alpha。&lt;br /&gt;
&lt;br /&gt;
下面是引入Negamax后的伪代码：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
int alphaBeta(int depth, int alpha, int beta) {&lt;br /&gt;
    if (depth == 0 || isGameOver()) {&lt;br /&gt;
        return evaluate();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    int maxEval = INT_MIN; // 初始化为负无穷，方便程序找出所有走法中分数的最大值&lt;br /&gt;
    for (Move move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
        makeMove(move); // 走子&lt;br /&gt;
        int eval = -alphaBeta(depth - 1, -beta, -alpha); // 向下一层搜索，获得该走法对应的子节点的分数&lt;br /&gt;
        undoMove(move); // 恢复到走子前的局面&lt;br /&gt;
        maxEval = std::max(maxEval, eval);&lt;br /&gt;
        alpha = std::max(alpha, eval);&lt;br /&gt;
        if (alpha &amp;gt;= beta)&lt;br /&gt;
            break; // Beta剪枝&lt;br /&gt;
    }&lt;br /&gt;
    return maxEval;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
有些资料上的代码可能是这样的：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
int alphaBeta(int depth, int alpha, int beta) {&lt;br /&gt;
    if (depth == 0 || isGameOver()) {&lt;br /&gt;
        return evaluate();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    for (Move move : getPossibleMoves()) {&lt;br /&gt;
        makeMove(move);&lt;br /&gt;
        int val = -alphaBeta(depth - 1, -beta, -alpha);&lt;br /&gt;
        undoMove(move);&lt;br /&gt;
&lt;br /&gt;
        if (val &amp;gt;= beta) {&lt;br /&gt;
            return beta; // Beta剪枝&lt;br /&gt;
        }&lt;br /&gt;
        if (val &amp;gt; alpha) {&lt;br /&gt;
            alpha = val; // 更新Alpha的值&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    return alpha;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
其实这两者是等价的，正常情况下，alpha&amp;lt;beta，所以alpha≥beta的情况只能是分数更新，导致alpha变大，发生Beta剪枝，所以直接和val进行比较也是一样的。&lt;br /&gt;
&lt;br /&gt;
== 空步裁剪（Null Move Pruning）==&lt;br /&gt;
一般来说，一个局面我方什么都不走（或者说走了一个“空步”），而是把走子权让给对方，我方是吃亏的。即最优走法的分数一般来说不会比走“空步”的分数低。&lt;br /&gt;
&lt;br /&gt;
我们可以利用这一点编写一个剪枝策略——空步裁剪。空步裁剪是一种冒险策略，就是基于上面假设进行的，把比“空步”分数低的招法剪掉。&lt;br /&gt;
&lt;br /&gt;
为了加快搜索，我们可以对“空步”搜索的层数额外减少R（一般是2-3）层。由于我们只关心“空步”的分数和beta相比是大还是小，所以应该用零窗口搜索（[beta - 1, beta]），使Alpha-Beta剪枝在搜索空步时发挥的作用最大。&lt;br /&gt;
&lt;br /&gt;
由于走子方互换，传递给子节点的alpha值和beta值也要按照之前的方法进行转换，也就是-beta和-beta + 1。&lt;br /&gt;
&lt;br /&gt;
基于空步的分数≤最优走法的分数这个假设，如果返回的评估≤beta-1，说明后续该节点的alpha值有可能达不到beta，不进行剪枝；如果返回的评估≥beta，说明最优走法的分数也一定≥beta，也就是说后续alpha在更新的时候也会≥beta，可以直接进行剪枝。&lt;br /&gt;
&lt;br /&gt;
然而一些局面并不满足之前我们提到过的假设，如国际象棋的迫移局面（Zguzwang），若使用空步裁剪，则会使引擎判断错误，于是我们需要使用“isNullMoveAllowed”函数判断是否满足空步裁剪的条件，尽量规避这种现象。&lt;br /&gt;
&lt;br /&gt;
伪代码如下：&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
// R是空步裁剪搜索中额外减去的层数&lt;br /&gt;
const int R = 3;&lt;br /&gt;
&lt;br /&gt;
int alphaBeta(int depth, int alpha, int beta) {&lt;br /&gt;
    if (depth == 0 || isGameOver()) {&lt;br /&gt;
        return evaluate();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Null Move Pruning&lt;br /&gt;
    if (depth &amp;gt;= R + 1 &amp;amp;&amp;amp; isNullMoveAllowed()) {&lt;br /&gt;
        makeNullMove(); // 走一步“空步”&lt;br /&gt;
        int nullMoveEval = -alphaBeta(depth - R - 1, -beta, -beta + 1); // 用[beta - 1, beta]进行搜索&lt;br /&gt;
        undoNullMove(); // 恢复&lt;br /&gt;
&lt;br /&gt;
        if (nullMoveEval &amp;gt;= beta) {&lt;br /&gt;
            return beta; // 如果beta的值还不如走“空步”的分数高，就直接剪掉&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    int value = INT_MIN;&lt;br /&gt;
    for (Move move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
        makeMove(move); // 走棋&lt;br /&gt;
        value = std::max(value, -alphaBeta(depth - 1, -beta, -alpha));&lt;br /&gt;
        undoMove(move); // 恢复&lt;br /&gt;
        alpha = std::max(alpha, value);&lt;br /&gt;
        if (alpha &amp;gt;= beta) {&lt;br /&gt;
            break; // Beta剪枝&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    return value;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== PVS（主要变例搜索） ==&lt;br /&gt;
PVS是对Alpha-Beta剪枝的增强，可以提高Alpha-Beta剪枝的效率。简单来说，PVS先对第一个子节点进行[alpha, beta]的全窗口搜索，再对剩余子节点使用[alpha, alpha+1]的零窗口搜索。零窗口搜索结束后，如果该节点搜索返回的评估在(alpha, beta)之间，说明该节点有希望，于是继续进行一次全窗口搜索。&lt;br /&gt;
伪代码如下：&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
int alphaBeta(int depth, int alpha, int beta) {&lt;br /&gt;
    if (depth == 0 || isGameOver()) {&lt;br /&gt;
        return evaluate();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    int maxEval = INT_MIN; // 初始化为负无穷，方便程序找出所有走法中分数的最大值&lt;br /&gt;
    bool firstChild = true; // 标记是否为第一个子节点&lt;br /&gt;
&lt;br /&gt;
    for (const Move&amp;amp; move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
        makeMove(move); // 走子&lt;br /&gt;
        int eval;&lt;br /&gt;
&lt;br /&gt;
        if (firstChild) {&lt;br /&gt;
            // 对第一个子节点使用全窗口搜索&lt;br /&gt;
            eval = -alphaBeta(depth - 1, -beta, -alpha);&lt;br /&gt;
            firstChild = false;&lt;br /&gt;
        } else {&lt;br /&gt;
            // 对剩余子节点使用零窗口搜索&lt;br /&gt;
            eval = -alphaBeta(depth - 1, -alpha - 1, -alpha);&lt;br /&gt;
            &lt;br /&gt;
            if (eval &amp;gt; alpha &amp;amp;&amp;amp; eval &amp;lt; beta) {&lt;br /&gt;
                // 如果零窗口搜索未能确定性地剪枝，则进行全窗口重新搜索&lt;br /&gt;
                eval = -alphaBeta(depth - 1, -beta, -eval);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        undoMove(move); // 恢复到走子前的局面&lt;br /&gt;
        maxEval = std::max(maxEval, eval);&lt;br /&gt;
        alpha = std::max(alpha, eval);&lt;br /&gt;
        &lt;br /&gt;
        if (alpha &amp;gt;= beta) {&lt;br /&gt;
            break; // Beta 剪枝&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    return maxEval;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E6%90%9C%E7%B4%A2%E7%AE%97%E6%B3%95&amp;diff=379</id>
		<title>搜索算法</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E6%90%9C%E7%B4%A2%E7%AE%97%E6%B3%95&amp;diff=379"/>
		<updated>2024-10-02T07:13:06Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 简单Minimax搜索 ==&lt;br /&gt;
[[文件:Minimax.png|缩略图|Minimax搜索]]&lt;br /&gt;
&lt;br /&gt;
象棋属于零和博弈，有红方和黑方，双方的收益总和为0，一方的优势总是伴随着另一方的劣势。此时，我们可以使用Minimax进行搜索。&lt;br /&gt;
&lt;br /&gt;
为了方便理解，我们暂且把Maximizing Player称为“红方”，把Minimizing Player称为“黑方”（实际上则不一定），我们的评估函数暂时返回的是相对于红方的分数（分高说明红方优势，分低说明红方劣势）。&lt;br /&gt;
&lt;br /&gt;
博弈的双方都想让收益最大化，红方想让分数尽量高，黑方想让分数尽量低，这样就能最大程度上保证自己这方的优势，同时让对方占到的优势最小。&lt;br /&gt;
&lt;br /&gt;
举个例子，红方在一个局面有两种走法。一种走法可以吃掉对方的车，此时的分数是+500，另一种走法是送掉自己的车，此时的分数是-500。红方当然会选择对自己有利的走法，即第一种，所以该节点的分数就是+500。&lt;br /&gt;
&lt;br /&gt;
伪代码如下：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
int minimax(int depth, bool isMaximizingPlayer) { // 层数, 是否红方&lt;br /&gt;
    if (depth == 0 || isGameOver()) { // 搜索到叶子节点或游戏结束，直接返回评估&lt;br /&gt;
        return evaluate();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (isMaximizingPlayer) { // 如果当前是红方&lt;br /&gt;
        int maxEval = INT_MIN; // 初始化为负无穷，方便程序找出所有走法中分数的最大值&lt;br /&gt;
        for (Move move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
            makeMove(move); // 走子&lt;br /&gt;
            int eval = minimax(depth - 1, false); // 向下一层搜索，获得该走法对应的子节点的分数&lt;br /&gt;
            undoMove(move); // 恢复到走子前的局面&lt;br /&gt;
            maxEval = std::max(maxEval, eval);&lt;br /&gt;
        }&lt;br /&gt;
        return maxEval;&lt;br /&gt;
    } else { // 如果当前是黑方&lt;br /&gt;
        int minEval = INT_MAX;  // 初始化为正无穷，方便程序找出所有走法中分数的最小值&lt;br /&gt;
        for (Move move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
            makeMove(move); // 走子&lt;br /&gt;
            int eval = minimax(depth - 1, true); // 向下一层搜索，获得该走法对应的子节点的分数&lt;br /&gt;
            undoMove(move); // 恢复到走子前的局面&lt;br /&gt;
            minEval = std::min(minEval, eval); // 取最小值&lt;br /&gt;
        }&lt;br /&gt;
        return minEval;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Alpha-Beta剪枝的过程介绍 ==&lt;br /&gt;
上面实现的简单Minimax搜索，效率其实是很低的。我们这时就要引入一种新的搜索策略——Alpha-Beta剪枝，这种剪枝方法可以在不改变结果的前提下大大减少搜索的节点数。&lt;br /&gt;
&lt;br /&gt;
[[文件:1920px-AB_pruning-with-description.png|缩略图|Alpha-Beta剪枝（带注解）]]&lt;br /&gt;
&lt;br /&gt;
上文我们提到过，红方的目的是使得分尽量高，也就是说当前节点的分数是所有子节点分数的最大值，黑方则相反。&lt;br /&gt;
&lt;br /&gt;
参考右图，我们可以举个例子说明一下Alpha-Beta剪枝的原理。&lt;br /&gt;
&lt;br /&gt;
引擎搜完叶子节点的5和6，可以推断出这两个节点的父节点（图中ID：2）的分数是min(5,6)=5。这个节点的父节点在MAX层上（图中ID：1），是取其子节点分数的最大值，所以它的分数至少是5。子节点的分数只有&amp;gt;5才有搜下去的必要，否则不会比“ID：2”这一路线更优。&lt;br /&gt;
&lt;br /&gt;
接下来我们继续搜索“ID：3”这一路线，搜完叶子节点的“7”和“4”之后，由于“ID：3”处在MIN层，其分数是取其子节点分数的最小值，所以这个节点的分数必然≤4，不可能比“ID：2”这一条路线更优，没有再搜下去的必要了，我们可以把后续的子节点（即灰色的5）剪掉。&lt;br /&gt;
&lt;br /&gt;
其它灰色的部分也是同理，大家可自行按照上面的步骤推断。&lt;br /&gt;
&lt;br /&gt;
根据理论推断是会了，但这仍是纸上谈兵，如何编写相关代码实现该功能呢？且听下回分解。&lt;br /&gt;
&lt;br /&gt;
== Alpha-Beta剪枝的代码实现 ==&lt;br /&gt;
为了用代码实现Alpha-Beta剪枝，我们需要引入alpha和beta两个量，alpha是红方能保证至少获得的分数，beta是黑方能保证至多获得的分数（这里的分数都是从红方视角看的）。也就是说，最优走法的分数必然≥alpha而且≤beta。&lt;br /&gt;
&lt;br /&gt;
在最开始调用“alphaBeta”函数的时候，我们应该把alpha设为负无穷，把beta设为正无穷。&lt;br /&gt;
&lt;br /&gt;
当我们搜索一个节点的时候，我们需要接受从父节点传来的alpha和beta值，接着判断该节点处于MAX层还是MIN层。&lt;br /&gt;
&lt;br /&gt;
这里还是举一个例子方便读者理解，假设我们处于MAX层（即红方），父节点传递的alpha值是5，beta值是8。&lt;br /&gt;
&lt;br /&gt;
因为当前节点的分数是从子节点的分数中取最大值得到的，所以我们在搜索的时候可以保证当前节点的分数≥子节点的分数，应该根据子节点的分数更新alpha的值。如果我们此时搜到一个子节点，分数为10，此时当前节点的分数必定≥10。但我们不要忘了，上一层是MIN，取的是子节点中的最小分数。若当前节点的分数超过了beta，必然不会比其父节点之前搜过的某个子节点分数低，不会被父节点采纳；若当前节点的分数等于beta，必然不会比其父节点之前搜过的某个子节点更优。我们可以直接使用break跳出循环，进行Beta剪枝，同时返回目前搜过的子节点的最大分数（目前的搜索代码中，这里返回值不重要，只需要保证该节点不被父节点采纳即可）。&lt;br /&gt;
&lt;br /&gt;
判断当前节点的分数是否≥beta，可以等同于判断alpha≥beta，因为alpha就是按照子节点的分数通过取最大值进行更新的。&lt;br /&gt;
&lt;br /&gt;
MIN层同理，这里不再赘述。&lt;br /&gt;
&lt;br /&gt;
伪代码如下：&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
int alphaBeta(int depth, int alpha, int beta, bool isMaximizingPlayer) {&lt;br /&gt;
    if (depth == 0 || isGameOver()) {&lt;br /&gt;
        return evaluate();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (isMaximizingPlayer) { // 如果当前是红方&lt;br /&gt;
        int maxEval = INT_MIN; // 初始化为负无穷，方便程序找出所有走法中分数的最大值&lt;br /&gt;
        for (Move move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
            makeMove(move); // 走子&lt;br /&gt;
            int eval = alphaBeta(depth - 1, alpha, beta, false); // 向下一层搜索，获得该走法对应的子节点的分数&lt;br /&gt;
            undoMove(move); // 恢复到走子前的局面&lt;br /&gt;
            maxEval = std::max(maxEval, eval); // 取最大值&lt;br /&gt;
            alpha = std::max(alpha, eval);&lt;br /&gt;
            if (beta &amp;lt;= alpha)&lt;br /&gt;
                break; // Beta剪枝&lt;br /&gt;
        }&lt;br /&gt;
        return maxEval;&lt;br /&gt;
    } else { // 如果当前是黑方&lt;br /&gt;
        int minEval = INT_MAX;  // 初始化为正无穷，方便程序找出所有走法中分数的最小值&lt;br /&gt;
        for (Move move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
            makeMove(move); // 走子&lt;br /&gt;
            int eval = alphaBeta(depth - 1, alpha, beta, true); // 向下一层搜索，获得该走法对应的子节点的分数&lt;br /&gt;
            undoMove(move); // 恢复到走子前的局面&lt;br /&gt;
            minEval = std::min(minEval, eval); // 取最小值&lt;br /&gt;
            beta = std::min(beta, eval);&lt;br /&gt;
            if (beta &amp;lt;= alpha)&lt;br /&gt;
                break; // Alpha剪枝&lt;br /&gt;
        }&lt;br /&gt;
        return minEval;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 继续简化，引入Negamax ==&lt;br /&gt;
前面我们的分数一直是相对于红方视角的，需要将红方和黑方分类讨论，这显然使代码复杂化了。为了解决这个问题，我们需要引入Negamax。&lt;br /&gt;
&lt;br /&gt;
引入Negamax后，我们对双方可以使用同样的处理策略，省去了编写MIN方代码的必要性，但是评估函数需要改成相对于走子方的分数。&lt;br /&gt;
&lt;br /&gt;
如果当前走子方是红方，需要返回相对于红方的分数；如果当前走子方是黑方，需要返回相对于黑方的分数。&lt;br /&gt;
&lt;br /&gt;
由于我们把两种情况合并成了一种情况，判断红黑方的参数可以删去。&lt;br /&gt;
&lt;br /&gt;
在搜索子节点的时候，我们需要从MAX层切换到MIN层，或者从MIN层切换到MAX层，应该如何做呢？&lt;br /&gt;
&lt;br /&gt;
在这里，我们继续举一个例子来说明。假设当前节点从父节点传下来的alpha值为5，beta值为8，说明最优走法的分数一定在5和8之间，接下来我们把分数视角切换到对手，最优走法的分数一定在-8和-5之间，-8和-5就分别是传递给子节点的alpha值和beta值。&lt;br /&gt;
&lt;br /&gt;
所以，传给子节点的alpha和beta值应该是-beta和-alpha。&lt;br /&gt;
&lt;br /&gt;
下面是引入Negamax后的伪代码：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
int alphaBeta(int depth, int alpha, int beta) {&lt;br /&gt;
    if (depth == 0 || isGameOver()) {&lt;br /&gt;
        return evaluate();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    int maxEval = INT_MIN; // 初始化为负无穷，方便程序找出所有走法中分数的最大值&lt;br /&gt;
    for (Move move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
        makeMove(move); // 走子&lt;br /&gt;
        int eval = -alphaBeta(depth - 1, -beta, -alpha); // 向下一层搜索，获得该走法对应的子节点的分数&lt;br /&gt;
        undoMove(move); // 恢复到走子前的局面&lt;br /&gt;
        maxEval = std::max(maxEval, eval);&lt;br /&gt;
        alpha = std::max(alpha, eval);&lt;br /&gt;
        if (alpha &amp;gt;= beta)&lt;br /&gt;
            break; // Beta剪枝&lt;br /&gt;
    }&lt;br /&gt;
    return maxEval;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
有些资料上的代码可能是这样的：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
int alphaBeta(int depth, int alpha, int beta) {&lt;br /&gt;
    if (depth == 0 || isGameOver()) {&lt;br /&gt;
        return evaluate();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    for (Move move : getPossibleMoves()) {&lt;br /&gt;
        makeMove(move);&lt;br /&gt;
        int val = -alphaBeta(depth - 1, -beta, -alpha);&lt;br /&gt;
        undoMove(move);&lt;br /&gt;
&lt;br /&gt;
        if (val &amp;gt;= beta) {&lt;br /&gt;
            return beta; // Beta剪枝&lt;br /&gt;
        }&lt;br /&gt;
        if (val &amp;gt; alpha) {&lt;br /&gt;
            alpha = val; // 更新Alpha的值&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    return alpha;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
其实这两者是等价的，正常情况下，alpha&amp;lt;beta，所以alpha≥beta的情况只能是分数更新，导致alpha变大，发生Beta剪枝，所以直接和val进行比较也是一样的。&lt;br /&gt;
&lt;br /&gt;
== 空步裁剪（Null Move Pruning）==&lt;br /&gt;
一般来说，一个局面我方什么都不走（或者说走了一个“空步”），而是把走子权让给对方，我方是吃亏的。即最优走法的分数一般来说不会比走“空步”的分数低。&lt;br /&gt;
&lt;br /&gt;
我们可以利用这一点编写一个剪枝策略——空步裁剪。空步裁剪是一种冒险策略，就是基于上面假设进行的，把比“空步”分数低的招法剪掉。&lt;br /&gt;
&lt;br /&gt;
为了加快搜索，我们可以对“空步”搜索的层数额外减少R（一般是2-3）层。由于我们只关心“空步”的分数和beta相比是大还是小，所以应该用零窗口搜索（[beta - 1, beta]），使Alpha-Beta剪枝在搜索空步时发挥的作用最大。&lt;br /&gt;
&lt;br /&gt;
由于走子方互换，传递给子节点的alpha值和beta值也要按照之前的方法进行转换，也就是-beta和-beta + 1。&lt;br /&gt;
&lt;br /&gt;
基于空步的分数≤最优走法的分数这个假设，如果返回的评估≤beta-1，说明后续该节点的alpha值有可能达不到beta，不进行剪枝；如果返回的评估≥beta，说明最优走法的分数也一定≥beta，也就是说后续alpha在更新的时候也会≥beta，可以直接进行剪枝。&lt;br /&gt;
&lt;br /&gt;
然而一些局面并不满足之前我们提到过的假设，如国际象棋的迫移局面（Zguzwang），若使用空步裁剪，则会使引擎判断错误，于是我们需要使用“isNullMoveAllowed”函数判断是否满足空步裁剪的条件，尽量规避这种现象。&lt;br /&gt;
&lt;br /&gt;
伪代码如下：&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
// R是空步裁剪搜索中额外减去的层数&lt;br /&gt;
const int R = 3;&lt;br /&gt;
&lt;br /&gt;
int alphaBeta(int depth, int alpha, int beta) {&lt;br /&gt;
    if (depth == 0 || isGameOver()) {&lt;br /&gt;
        return evaluate();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Null Move Pruning&lt;br /&gt;
    if (depth &amp;gt;= R + 1 &amp;amp;&amp;amp; isNullMoveAllowed()) {&lt;br /&gt;
        makeNullMove(); // 走一步“空步”&lt;br /&gt;
        int nullMoveEval = -alphaBeta(depth - R - 1, -beta, -beta + 1); // 用[beta - 1, beta]进行搜索&lt;br /&gt;
        undoNullMove(); // 恢复&lt;br /&gt;
&lt;br /&gt;
        if (nullMoveEval &amp;gt;= beta) {&lt;br /&gt;
            return beta; // 如果beta的值还不如走“空步”的分数高，就直接剪掉&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    int value = INT_MIN;&lt;br /&gt;
    for (Move move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
        makeMove(move); // 走棋&lt;br /&gt;
        value = std::max(value, -alphaBeta(depth - 1, -beta, -alpha));&lt;br /&gt;
        undoMove(move); // 恢复&lt;br /&gt;
        alpha = std::max(alpha, value);&lt;br /&gt;
        if (alpha &amp;gt;= beta) {&lt;br /&gt;
            break; // Beta剪枝&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    return value;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== PVS（主要变例搜索） ==&lt;br /&gt;
PVS是对Alpha-Beta剪枝的增强，可以显著提高Alpha-Beta剪枝的效率。简单来说，PVS先对第一个子节点进行[alpha, beta]的全窗口搜索，再对剩余子节点使用[alpha, alpha+1]的零窗口搜索。零窗口搜索结束后，如果该节点搜索返回的评估在(alpha, beta)之间，说明该节点有希望，于是继续进行一次全窗口搜索。&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
int alphaBeta(int depth, int alpha, int beta) {&lt;br /&gt;
    if (depth == 0 || isGameOver()) {&lt;br /&gt;
        return evaluate();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    int maxEval = INT_MIN; // 初始化为负无穷，方便程序找出所有走法中分数的最大值&lt;br /&gt;
    bool firstChild = true; // 标记是否为第一个子节点&lt;br /&gt;
&lt;br /&gt;
    for (const Move&amp;amp; move : getPossibleMoves()) { // 走法生成&lt;br /&gt;
        makeMove(move); // 走子&lt;br /&gt;
        int eval;&lt;br /&gt;
&lt;br /&gt;
        if (firstChild) {&lt;br /&gt;
            // 对第一个子节点使用全窗口搜索&lt;br /&gt;
            eval = -alphaBeta(depth - 1, -beta, -alpha);&lt;br /&gt;
            firstChild = false;&lt;br /&gt;
        } else {&lt;br /&gt;
            // 对剩余子节点使用零窗口搜索&lt;br /&gt;
            eval = -alphaBeta(depth - 1, -alpha - 1, -alpha);&lt;br /&gt;
            &lt;br /&gt;
            if (eval &amp;gt; alpha &amp;amp;&amp;amp; eval &amp;lt; beta) {&lt;br /&gt;
                // 如果零窗口搜索未能确定性地剪枝，则进行全窗口重新搜索&lt;br /&gt;
                eval = -alphaBeta(depth - 1, -beta, -eval);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        undoMove(move); // 恢复到走子前的局面&lt;br /&gt;
        maxEval = std::max(maxEval, eval);&lt;br /&gt;
        alpha = std::max(alpha, eval);&lt;br /&gt;
        &lt;br /&gt;
        if (alpha &amp;gt;= beta) {&lt;br /&gt;
            break; // Beta 剪枝&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    return maxEval;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E6%A3%8B%E8%BD%AF%E7%9F%A5%E8%AF%86&amp;diff=378</id>
		<title>棋软知识</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E6%A3%8B%E8%BD%AF%E7%9F%A5%E8%AF%86&amp;diff=378"/>
		<updated>2024-09-28T15:53:15Z</updated>

		<summary type="html">&lt;p&gt;New：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[https://pikafish.org/wiki 返回首页]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
[[指令集（如bmi2 avx2）是什么？]]&lt;br /&gt;
&lt;br /&gt;
[[各个引擎的打分为什么不一样？甚至相同引擎不同版本也不一样？]]&lt;br /&gt;
&lt;br /&gt;
[[UCI选项]]（引擎的各个选项设置）&lt;br /&gt;
&lt;br /&gt;
[[什么是“引擎”？]]&lt;br /&gt;
&lt;br /&gt;
[[什么是“界面”？]]&lt;br /&gt;
&lt;br /&gt;
[[什么是“开局库”？]]&lt;br /&gt;
&lt;br /&gt;
[[什么是“云库”？]]&lt;br /&gt;
&lt;br /&gt;
[[什么是“残局库”？]]&lt;br /&gt;
&lt;br /&gt;
[[什么是“审局库”？]]&lt;br /&gt;
&lt;br /&gt;
[[什么是“NNUE”？]]（权重）&lt;br /&gt;
&lt;br /&gt;
[[什么是“传统引擎”？]]&lt;br /&gt;
&lt;br /&gt;
[[什么是“评估”？]]&lt;br /&gt;
&lt;br /&gt;
[[ELO]]（等级分）是什么？&lt;br /&gt;
&lt;br /&gt;
[[什么是“AI”（人工智能）？]]&lt;br /&gt;
&lt;br /&gt;
[[为什么引擎解不开某些局面？]]&lt;br /&gt;
&lt;br /&gt;
[[象棋被穷尽了吗？]]&lt;br /&gt;
&lt;br /&gt;
[[引擎计算是使用“穷举法”吗？]]&lt;br /&gt;
&lt;br /&gt;
[[象棋有“阿尔法狗”吗？]]&lt;br /&gt;
&lt;br /&gt;
[[神经网络的“自我学习”是什么？]]&lt;br /&gt;
&lt;br /&gt;
[[如何科学地测试引擎？]]&lt;br /&gt;
&lt;br /&gt;
[[深度（层数）是什么？上层速度代表棋力吗？]]&lt;br /&gt;
&lt;br /&gt;
[[NPS（K值）是什么？代表棋力吗？]]&lt;br /&gt;
&lt;br /&gt;
[[置换表（哈希）是什么？设置多少好？]]&lt;br /&gt;
&lt;br /&gt;
[[核心或线程越高的机器就越好吗？]]&lt;br /&gt;
&lt;br /&gt;
[[对象棋引擎来说，机器算力的因素大吗？]]&lt;br /&gt;
&lt;br /&gt;
[[软件对打只能和棋吗？]]&lt;br /&gt;
&lt;br /&gt;
[[引擎棋力越强，任何局面一定就比弱的引擎好吗？]]&lt;br /&gt;
&lt;br /&gt;
[[引擎多核多线程与单核的区别]]&lt;br /&gt;
&lt;br /&gt;
[[不进行引擎测试去“感觉”孰强孰弱可靠吗？]]&lt;br /&gt;
&lt;br /&gt;
[[FEN（局面码）是什么？]]&lt;br /&gt;
&lt;br /&gt;
[[什么是bench？bench的结果说明了什么？]]&lt;br /&gt;
&lt;br /&gt;
[[象棋棋规疑云]]&lt;br /&gt;
&lt;br /&gt;
部分有趣的[[皮卡鱼测试数据]]&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E6%8C%87%E4%BB%A4%E9%9B%86%EF%BC%88%E5%A6%82bmi2_avx2)_%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&amp;diff=377</id>
		<title>指令集（如bmi2 avx2) 是什么？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E6%8C%87%E4%BB%A4%E9%9B%86%EF%BC%88%E5%A6%82bmi2_avx2)_%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&amp;diff=377"/>
		<updated>2024-09-28T15:52:56Z</updated>

		<summary type="html">&lt;p&gt;New：​New移动页面指令集（如bmi2 avx2) 是什么？至指令集（如bmi2 avx2）是什么？&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#重定向 [[指令集（如bmi2 avx2）是什么？]]&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E6%8C%87%E4%BB%A4%E9%9B%86%EF%BC%88%E5%A6%82bmi2_avx2%EF%BC%89%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&amp;diff=376</id>
		<title>指令集（如bmi2 avx2）是什么？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E6%8C%87%E4%BB%A4%E9%9B%86%EF%BC%88%E5%A6%82bmi2_avx2%EF%BC%89%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&amp;diff=376"/>
		<updated>2024-09-28T15:52:56Z</updated>

		<summary type="html">&lt;p&gt;New：​New移动页面指令集（如bmi2 avx2) 是什么？至指令集（如bmi2 avx2）是什么？&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
CPU引擎的工作依赖CPU，需要和CPU“沟通”，指令集就相当于“沟通方法”，沟通越高效，引擎计算得就越快。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
不同指令集只有速度的区别(类似硬件的区别)。指令集一定程度上决定了引擎搜索的速度。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
对于nnue引擎来说，vnni512 avx512 avx2 bmi2的速度比其余指令集快，具体哪个快得看你的CPU，可以进行bench测速比较一下哪个最快。&lt;br /&gt;
&lt;br /&gt;
大多数CPU不支持vnni和avx512指令集，部分老CPU不支持avx2和bmi2，如果用不了就是用不了。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''bench测速：'''&lt;br /&gt;
&lt;br /&gt;
'''如果想要选择速度最快的引擎，则可以bench测速，步骤如下：'''&lt;br /&gt;
&lt;br /&gt;
    直接双击点开那些引擎的exe，会出现如图所示[[文件:8D(5YS(ZMSLNQV8EJRP82BO(1).png|缩略图|点开引擎后]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
   然后输入 bench 如图所示[[文件:5(V~EAF@O~J0XZW5G(YKV69(1).png|缩略图|输入bench后]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    然后回车，会出现如图所示情况，看最下面的那个数字，越大说明速度越快[[文件:`~2%B%0(UF0(7V)I3F)0$)9(1).png|缩略图|结果]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
一次两次bench测速，速度可能会有误差 可以多次尝试。&lt;br /&gt;
&lt;br /&gt;
  注意，部分引擎无法成功运行很正常，正说明你的机器不支持那个指令集的引擎。&lt;br /&gt;
  不同指令集的引擎只影响速度，棋力差异仅体现在速度上。&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E6%8C%87%E4%BB%A4%E9%9B%86(%E5%A6%82bmi2_avx2)_%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&amp;diff=375</id>
		<title>指令集(如bmi2 avx2) 是什么？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E6%8C%87%E4%BB%A4%E9%9B%86(%E5%A6%82bmi2_avx2)_%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&amp;diff=375"/>
		<updated>2024-09-28T15:52:37Z</updated>

		<summary type="html">&lt;p&gt;New：​New移动页面指令集(如bmi2 avx2) 是什么？至指令集（如bmi2 avx2) 是什么？&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#重定向 [[指令集（如bmi2 avx2) 是什么？]]&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
	<entry>
		<id>http://www.pikafish.com/wiki/index.php?title=%E6%8C%87%E4%BB%A4%E9%9B%86%EF%BC%88%E5%A6%82bmi2_avx2%EF%BC%89%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&amp;diff=374</id>
		<title>指令集（如bmi2 avx2）是什么？</title>
		<link rel="alternate" type="text/html" href="http://www.pikafish.com/wiki/index.php?title=%E6%8C%87%E4%BB%A4%E9%9B%86%EF%BC%88%E5%A6%82bmi2_avx2%EF%BC%89%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F&amp;diff=374"/>
		<updated>2024-09-28T15:52:37Z</updated>

		<summary type="html">&lt;p&gt;New：​New移动页面指令集(如bmi2 avx2) 是什么？至指令集（如bmi2 avx2) 是什么？&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;small&amp;gt;[[棋软知识|返回“棋软知识”]]&amp;lt;/small&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
CPU引擎的工作依赖CPU，需要和CPU“沟通”，指令集就相当于“沟通方法”，沟通越高效，引擎计算得就越快。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
不同指令集只有速度的区别(类似硬件的区别)。指令集一定程度上决定了引擎搜索的速度。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
对于nnue引擎来说，vnni512 avx512 avx2 bmi2的速度比其余指令集快，具体哪个快得看你的CPU，可以进行bench测速比较一下哪个最快。&lt;br /&gt;
&lt;br /&gt;
大多数CPU不支持vnni和avx512指令集，部分老CPU不支持avx2和bmi2，如果用不了就是用不了。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''bench测速：'''&lt;br /&gt;
&lt;br /&gt;
'''如果想要选择速度最快的引擎，则可以bench测速，步骤如下：'''&lt;br /&gt;
&lt;br /&gt;
    直接双击点开那些引擎的exe，会出现如图所示[[文件:8D(5YS(ZMSLNQV8EJRP82BO(1).png|缩略图|点开引擎后]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
   然后输入 bench 如图所示[[文件:5(V~EAF@O~J0XZW5G(YKV69(1).png|缩略图|输入bench后]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    然后回车，会出现如图所示情况，看最下面的那个数字，越大说明速度越快[[文件:`~2%B%0(UF0(7V)I3F)0$)9(1).png|缩略图|结果]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
一次两次bench测速，速度可能会有误差 可以多次尝试。&lt;br /&gt;
&lt;br /&gt;
  注意，部分引擎无法成功运行很正常，正说明你的机器不支持那个指令集的引擎。&lt;br /&gt;
  不同指令集的引擎只影响速度，棋力差异仅体现在速度上。&lt;/div&gt;</summary>
		<author><name>New</name></author>
	</entry>
</feed>