self deepening: an improved implementation of IID
Posted: 24 Apr 2006, 16:36
I just tried a very simple implementation of Internal Iterative Deepening, that seems superior to the usual implementation:
The main incentive for doing IID is to discover as quickly as possible (i.e. at low search depth) if a node is a beta node. As soon as a move that causes a beta cutoff is found, it is sorted in front, and each iteration consists of only searching that single move. If the cutoff survives all the way to the requested dept, we are done.
But if the beta cutoff was only an illusion due to insufficient search depth, and disappears at a later iteration, we might not have any idea which was the second-best move: it was never searched, or it was a move that was searched at an early iteration and did not give a cutoff there. The whole philosophy behind IID, of hunting for potential cutoff moves at low depth, and only search them at full depth when we are pretty sure we have one, is subverted by allowing the examination of only a single nmove that proves a bust to effectively skip the iterations that might have found the true cutoff move.
What we really would like is to resume the deepening at the point where we first started skipping the moves because of the (false) cutoff. This can easily be achieved by moving the deepening of a cutoff move to the daughter node, so that the node itself remembers upto where it got with cutoff-free iterations (where really all moves were considered). I implemented this by passing not only a ('requested_depth' to the search routine, the depth that it should minimally search, but also a second depth parameter, that tells it how much deeper we like it to search if the search move causes a fail high (i.e. if the replies fail low).
As soon as in the course of the IID we encounter a move that fails high, it deepens itself to the full depth to be done with this node. After that the IID in the parent node is just a formality: all subsequent iterations just get the move score from the value that is now in the hash table, and don't have to search any other moves.
But if the self deepening does not reach the full requested depth, search() will not return a fail-high score. The only way a fail-high score can be returned is where this fail high is valid for all subsequent iterations, and we are done with this node. Except from such a final cutoff, no cutoffs can occur. We are either done with the node at some point, or we continue doing deepening iterations that do not skip any move at any depth to the very end.
If a false cutoff is encountered, that at the depth where it first not failed high is still a pretty good move, it might still be the best moe for the iterations to come, and provide an automatic aspiration to the search of the other moves, by being 'searched' first (i.e. just retrieved from the hash table).
This implementation of IID seems superior to the regular one: in 'self play' of micro-Max, where the only difference between the versions was this why of IID, the new version scored 57.5-42.5 (57.5%) at (each side) 15 sec per game (it used 3.6% mor time, though), and 26.5-23.5 (53%) at 60 sec per game (where it iused 2.8% less time). I am now testing at longer time controls.
- Code: Select all
/* elementary IID */
search(int requested_depth, int alpha, int beta)
{ int iter_depth
iter_depth = HASH->depth; /* gives zero on hash miss */
while(iter_depth++ < requested_depth)
{
best_score = -INF;
for(ALL_MOVES)
{
make(next_move);
score = -search(iter_depth - 1, -beta, -max(alpha,best_score));
unmake(next_move);
best_score = max(score, best_score);
if(best_score >= beta) break; /* beta cutoff */
}
STORE_HASH(iter_depth);
}
return(best_score)
}
The main incentive for doing IID is to discover as quickly as possible (i.e. at low search depth) if a node is a beta node. As soon as a move that causes a beta cutoff is found, it is sorted in front, and each iteration consists of only searching that single move. If the cutoff survives all the way to the requested dept, we are done.
But if the beta cutoff was only an illusion due to insufficient search depth, and disappears at a later iteration, we might not have any idea which was the second-best move: it was never searched, or it was a move that was searched at an early iteration and did not give a cutoff there. The whole philosophy behind IID, of hunting for potential cutoff moves at low depth, and only search them at full depth when we are pretty sure we have one, is subverted by allowing the examination of only a single nmove that proves a bust to effectively skip the iterations that might have found the true cutoff move.
What we really would like is to resume the deepening at the point where we first started skipping the moves because of the (false) cutoff. This can easily be achieved by moving the deepening of a cutoff move to the daughter node, so that the node itself remembers upto where it got with cutoff-free iterations (where really all moves were considered). I implemented this by passing not only a ('requested_depth' to the search routine, the depth that it should minimally search, but also a second depth parameter, that tells it how much deeper we like it to search if the search move causes a fail high (i.e. if the replies fail low).
- Code: Select all
/* self-deepening IID */
search(int requested_depth, int ultimate_depth, int alpha, int beta)
{ int iter_depth
iter_depth = HASH->depth; /* gives zero on hash miss */
best_score = HASH->score; /* -INF on hash miss */
while(iter_depth++ < requested_depth ||
iter_depth <= utimate_depth && best_score <= alpha)
{
best_score = -INF;
for(ALL_MOVES)
{
make(next_move);
score = -search(iter_depth-1, requested_depth-1, -beta, -max(alpha,best_score));
unmake(next_move);
best_score = max(score, best_score);
if(best_score >= beta) break; /* beta cutoff */
}
STORE_HASH(iter_depth);
}
return(best_score)
}
As soon as in the course of the IID we encounter a move that fails high, it deepens itself to the full depth to be done with this node. After that the IID in the parent node is just a formality: all subsequent iterations just get the move score from the value that is now in the hash table, and don't have to search any other moves.
But if the self deepening does not reach the full requested depth, search() will not return a fail-high score. The only way a fail-high score can be returned is where this fail high is valid for all subsequent iterations, and we are done with this node. Except from such a final cutoff, no cutoffs can occur. We are either done with the node at some point, or we continue doing deepening iterations that do not skip any move at any depth to the very end.
If a false cutoff is encountered, that at the depth where it first not failed high is still a pretty good move, it might still be the best moe for the iterations to come, and provide an automatic aspiration to the search of the other moves, by being 'searched' first (i.e. just retrieved from the hash table).
This implementation of IID seems superior to the regular one: in 'self play' of micro-Max, where the only difference between the versions was this why of IID, the new version scored 57.5-42.5 (57.5%) at (each side) 15 sec per game (it used 3.6% mor time, though), and 26.5-23.5 (53%) at 60 sec per game (where it iused 2.8% less time). I am now testing at longer time controls.