为什么chess.engine在同一个脚本中多次分析同一个位置会有不同的分数?

Why are there different scores when chess.engine analyses a position multiple times in the same script?

此处为初学者,正在尝试制作一款可检测错误的应用,并希望了解有关 chess.engine 库的更多信息。

我假设使用分析函数是一个独立的、独立的过程,不依赖于引擎之前调用的缓存或内存或类似的东西。

如果是这样,为什么我在脚本中多次调用分析时得到多个不同的评估:

import chess
import chess.engine
import os

# Loads board and engine
board = chess.Board("3r3k/pp5p/n1p2rp1/2P2p1n/1P2p3/PBNqP2P/5PP1/R2RB1K1 w - - 0 26")
engine = chess.engine.SimpleEngine.popen_uci(os.getcwd()+'/static/'+'stockfish')

#Sets engine depth constant
engine_depth = 10

# I'm assuming this is how to find the best move - arbitrary as I could have chosen any move in board.legal_moves
best_move = engine.play(board, chess.engine.Limit(depth=engine_depth)).move

# I'm assuming this is how to evaluate the score after a specific move from this position
info = engine.analyse(board, chess.engine.Limit(depth=engine_depth), root_moves=[best_move])
print(info["score"])

# Repeating the analysis call and printing the score 3 more times
info = engine.analyse(board, chess.engine.Limit(depth=engine_depth), root_moves=[best_move])
print(info["score"])

info = engine.analyse(board, chess.engine.Limit(depth=engine_depth), root_moves=[best_move])
print(info["score"])

info = engine.analyse(board, chess.engine.Limit(depth=engine_depth), root_moves=[best_move])
print(info["score"])

engine.quit()

输出:

PovScore(Cp(-21), WHITE)
PovScore(Cp(+2), WHITE)
PovScore(Cp(+19), WHITE)
PovScore(Cp(+63), WHITE)

对于超过一定水平的各种引擎深度,它会发生,但不会在深度较低时发生,例如最多 3.
它以时间而不是深度为限制发生。
它发生在多个不同的起始位置。
它发生在多个不同的动作中。
即使我在每次调用之间有 engine.quit()engine = chess.engine.SimpleEngine.popen_uci(os.getcwd()+'/static/'+'stockfish'),它也会发生。

分析函数不能有随机元素,因为当我再次 运行 整个脚本时,我得到完全相同的分数。只是当它在同一个脚本中多次调用同一个位置时,它每次都会给出不同的分数,就好像它在使用某种缓存或者每次看起来更深。

那么我对它的工作原理的理解哪里出错了?

编辑:

如果我删除 root_moves 参数(只是为了简化事情)然后替换:

print(info["score"])

与:

for k, v in info.items():
    print(k, v)

我得到以下输出:

string NNUE evaluation using nn-82215d0fd0df.nnue enabled
depth 10
seldepth 13
multipv 1
score PovScore(Cp(+25), WHITE)
nodes 4396
nps 439600
tbhits 0
time 0.01
pv [Move.from_uci('d1d3'), Move.from_uci('e4d3'), Move.from_uci('a1d1'), Move.from_uci('a6c7'), Move.from_uci('c3a4'), Move.from_uci('f6f8'), Move.from_uci('a4b2'), Move.from_uci('h5f6'), Move.from_uci('b2d3'), Move.from_uci('d8d7')]

string NNUE evaluation using nn-82215d0fd0df.nnue enabled
depth 10
seldepth 15
multipv 1
score PovScore(Cp(+55), WHITE)
nodes 4072
nps 290857
tbhits 0
time 0.014
pv [Move.from_uci('d1d3'), Move.from_uci('e4d3'), Move.from_uci('a1d1'), Move.from_uci('a6c7'), Move.from_uci('c3a4'), Move.from_uci('c7b5'), Move.from_uci('a4b2'), Move.from_uci('b5a3'), Move.from_uci('b2d3'), Move.from_uci('a3b5')]

string NNUE evaluation using nn-82215d0fd0df.nnue enabled
depth 10
seldepth 16
multipv 1
score PovScore(Cp(+26), WHITE)
nodes 4514
nps 282125
tbhits 0
time 0.016
pv [Move.from_uci('d1d3'), Move.from_uci('e4d3'), Move.from_uci('a1d1'), Move.from_uci('a6c7'), Move.from_uci('c3a4'), Move.from_uci('c7b5'), Move.from_uci('a4b2'), Move.from_uci('h8g7'), Move.from_uci('b2d3'), Move.from_uci('f6f8')]

string NNUE evaluation using nn-82215d0fd0df.nnue enabled
depth 10
seldepth 12
multipv 1
score PovScore(Cp(+164), WHITE)
nodes 2018
nps 252250
tbhits 0
time 0.008
pv [Move.from_uci('d1d3'), Move.from_uci('d8d3'), Move.from_uci('b3c4'), Move.from_uci('d3d8'), Move.from_uci('c3e2'), Move.from_uci('h8g7'), Move.from_uci('e1c3')]

看来我每次都变得不同 'seldepths'。 seldepth 到底是什么?我在文档中找不到足够的信息。

Stockfish 对于 单线程 分析具有确定性 nodedepth 限制,状态和选项相等。

引擎状态主要是 哈希表(以及可能加载的 Syzygy 表库)。因此,您的第二个分析将利用第一个 运行 的哈希表。第三个 运行 将重用第二个 运行 的哈希表结果,依此类推。

通常重用哈希表结果是可取的并提高强度,但有一种方法可以重置引擎状态,清除哈希表。

在 UCI 协议中,这是通过发送提示来完成的:

ucinewgame

在 python-chess 中,这是使用 game 键自动管理的。

engine.analyse(..., game="key1")
engine.analyse(..., game="key1")
engine.analyse(..., game="key2")  # will clear previous state
engine.play(..., game="key2")
engine.analyse(..., game="key1")  # will clear previous state

键可以是任何东西,特别是 object() 不等于任何其他对象,因此总是会清除哈希表。


import chess
import chess.engine

board = chess.Board("3r3k/pp5p/n1p2rp1/2P2p1n/1P2p3/PBNqP2P/5PP1/R2RB1K1 w - - 0 26")
engine = chess.engine.SimpleEngine.popen_uci("stockfish")
engine_depth = 10

best_move = engine.play(board, chess.engine.Limit(depth=engine_depth), game=object()).move

info = engine.analyse(board, chess.engine.Limit(depth=engine_depth), game=object(), root_moves=[best_move])
print(info["score"])

info = engine.analyse(board, chess.engine.Limit(depth=engine_depth), game=object(), root_moves=[best_move])
print(info["score"])

info = engine.analyse(board, chess.engine.Limit(depth=engine_depth), game=object(), root_moves=[best_move])
print(info["score"])

info = engine.analyse(board, chess.engine.Limit(depth=engine_depth), game=object(), root_moves=[best_move])
print(info["score"])

engine.quit()
PovScore(Cp(+35), WHITE)
PovScore(Cp(+35), WHITE)
PovScore(Cp(+35), WHITE)
PovScore(Cp(+35), WHITE)